При тестировании 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.position: absolute; top: -1000px) остаётся видимым для Vue
Test Utils, так как визуальная прокрутка не симулируется.Ключевой момент: тесты на наличие элемента и его видимость —
разные вещи. Для проверки существования используется
find() или findAll(), для проверки видимости —
isVisible().
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, но всё равно будет найден.
isVisible() Проверяет видимость
элемента на основе CSS-свойств display,
visibility и размеров. Для элементов вне viewport с
обычными стилями позиционирования метод вернёт true, даже
если пользователь не видит элемент на экране.
trigger Метод для имитации событий
работает с элементами, даже если они невидимы, за исключением
случаев, когда JSDOM не позволяет взаимодействовать с элементами с
display: none. В этом случае может быть выброшено
исключение.
Пример:
const wrapper = mount(Component)
const hiddenInput = wrapper.find('input[type="hidden"]')
expect(() => hiddenInput.trigger('focus')).toThrow()
Элемент с display: none не может получить фокус, и это
отражает поведение браузера.
В компонентах с виртуальной прокруткой или ленивой загрузкой контента элементы изначально находятся вне 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 перед
проверкой видимости новых элементов.overflow: hidden или position: absolute вне
видимой области будут считаться видимыми.find()/findAll(), а не только видимость.isVisible()
для элементов с display: none или
visibility: hidden.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 до события прокрутки и становится видимым после него.