Элементы вне viewport

При тестировании Vue-компонентов часто возникает необходимость взаимодействовать с элементами, которые не находятся в видимой области страницы (viewport). Vue Test Utils предоставляет инструменты для работы с DOM, однако стандартные методы могут вести себя иначе при попытке тестировать невидимые элементы. Понимание этого поведения критично для создания корректных и стабильных тестов.

Рендеринг и видимость элементов

В Vue Test Utils компонент рендерится в изолированном DOM, созданном JSDOM или аналогичной средой для тестирования. В этом случае визуальная область (viewport) существует только виртуально, поэтому CSS-свойства display: none и visibility: hidden работают иначе, чем в реальном браузере:

  • Элемент с display: none не создаёт DOM-ноды в рендере при использовании методов вроде find()? Нет — DOM-нода существует, но wrapper.isVisible() вернёт false.
  • Элемент с visibility: hidden остаётся видимым для методов поиска, но wrapper.isVisible() также вернёт false.
  • Элемент за пределами виртуального viewport (например, position: absolute; top: -1000px) остаётся видимым для Vue Test Utils, так как визуальная прокрутка не симулируется.

Ключевой момент: тесты на наличие элемента и его видимость — разные вещи. Для проверки существования используется find() или findAll(), для проверки видимости — isVisible().

Методы для работы с невидимыми элементами

  1. find и findAll Эти методы ищут элементы независимо от того, находятся ли они в viewport. Например:

    import { mount } from '@vue/test-utils'
    import Component from '@/components/MyComponent.vue'
    
    const wrapper = mount(Component)
    const hiddenButton = wrapper.find('.hidden-button')
    console.log(hiddenButton.exists()) // true
    console.log(hiddenButton.isVisible()) // false

    Здесь .hidden-button может иметь display: none, но всё равно будет найден.

  2. isVisible() Проверяет видимость элемента на основе CSS-свойств display, visibility и размеров. Для элементов вне viewport с обычными стилями позиционирования метод вернёт true, даже если пользователь не видит элемент на экране.

  3. trigger Метод для имитации событий работает с элементами, даже если они невидимы, за исключением случаев, когда JSDOM не позволяет взаимодействовать с элементами с display: none. В этом случае может быть выброшено исключение.

    Пример:

    const wrapper = mount(Component)
    const hiddenInput = wrapper.find('input[type="hidden"]')
    expect(() => hiddenInput.trigger('focus')).toThrow()

    Элемент с display: none не может получить фокус, и это отражает поведение браузера.

Тестирование прокрутки и lazy-load

В компонентах с виртуальной прокруткой или ленивой загрузкой контента элементы изначально находятся вне viewport. В тестах необходимо эмулировать события прокрутки и рендеринг:

const wrapper = mount(VirtualListComponent, {
  attachTo: document.body
})

// Имитируем прокрутку
wrapper.element.scrollTop = 500
await wrapper.vm.$nextTick()

// Проверяем появление новых элементов
const items = wrapper.findAll('.list-item')
expect(items.length).toBeGreaterThan(0)
  • attachTo используется для привязки компонента к реальному DOM, что необходимо для корректной работы размеров и событий прокрутки.
  • nextTick обеспечивает обновление виртуального DOM перед проверкой видимости новых элементов.

Ограничения и особенности JSDOM

  • JSDOM не рендерит фактическую визуальную область, поэтому методы определения видимости ограничены CSS. Элементы с overflow: hidden или position: absolute вне видимой области будут считаться видимыми.
  • Невозможно полностью эмулировать пользовательскую прокрутку и наблюдение за IntersectionObserver без специальных библиотек или моков.
  • Для тестирования взаимодействий с реальным viewport рекомендуется использовать end-to-end тестирование (Cypress, Playwright).

Рекомендации по тестированию элементов вне viewport

  • Проверка существования всегда должна использовать find()/findAll(), а не только видимость.
  • Проверка видимости через isVisible() для элементов с display: none или visibility: hidden.
  • Для компонентов с ленивой загрузкой необходимо эмулировать события прокрутки и обновление DOM, иначе тесты будут проверять только начальное состояние.
  • Фокус и события пользовательского ввода не могут быть вызваны на скрытых элементах.

Пример комплексного теста

import { mount } from '@vue/test-utils'
import LazyImage from '@/components/LazyImage.vue'

test('загружаются изображения при прокрутке', async () => {
  const wrapper = mount(LazyImage, { attachTo: document.body })
  
  // Изначально изображение скрыто
  const img = wrapper.find('img')
  expect(img.isVisible()).toBe(false)

  // Эмулируем прокрутку
  wrapper.element.scrollTop = 300
  await wrapper.vm.$nextTick()

  // Проверяем, что изображение стало видимым
  expect(img.isVisible()).toBe(true)
})

Этот пример демонстрирует полный цикл работы с элементом, который находится вне viewport до события прокрутки и становится видимым после него.