В Vue 3 появился компонент Teleport, позволяющий
рендерить дочерние элементы вне текущей иерархии DOM, например, в
отдельный контейнер в body. Это особенно полезно для
модальных окон, уведомлений и всплывающих подсказок. Тестирование
компонентов с Teleport требует особого подхода, так как стандартные
методы рендеринга в Vue Test Utils (mount,
shallowMount) по умолчанию не учитывают перемещённые в DOM
элементы.
Для корректного тестирования необходимо учитывать два момента:
Целевой элемент (to) Teleport должен
существовать в DOM. Если этого не сделать, Teleport не сможет
разместить дочерние элементы, и они не появятся в тестируемом
дереве.
Использование attachTo в Vue Test
Utils. При монтировании компонента можно прикрепить его к
реальному элементу DOM, чтобы Teleport корректно переместил
контент.
Пример базовой настройки:
import { mount } from '@vue/test-utils'
import Modal from '@/components/Modal.vue'
describe('Modal.vue с Teleport', () => {
let container
beforeEach(() => {
container = document.createElement('div')
container.setAttribute('id', 'modal-root')
document.body.appendChild(container)
})
afterEach(() => {
document.body.removeChild(container)
})
it('рендерит содержимое в target контейнер', () => {
const wrapper = mount(Modal, {
attachTo: document.body
})
expect(container.innerHTML).toContain('Контент модального окна')
})
})
Ключевые моменты:
document.createElement создаёт целевой контейнер для
Teleport.attachTo: document.body позволяет компоненту корректно
работать с Teleport.Teleport переносит элементы вне иерархии компонента, поэтому методы
Vue Test Utils вроде wrapper.find() не всегда найдут
перемещённый контент. Для проверки можно использовать стандартные
DOM-методы:
it('проверяет видимость содержимого Teleport', () => {
mount(Modal, { attachTo: document.body })
const teleportedContent = document.querySelector('#modal-root')
expect(teleportedContent).not.toBeNull()
expect(teleportedContent.textContent).toContain('Контент модального окна')
})
Особенности:
querySelector позволяет получать элементы, находящиеся
вне компонента.Teleport не меняет способ обработки событий Vue. Все события внутри Teleport-компонента работают стандартным образом. Для тестирования взаимодействия можно использовать Vue Test Utils:
it('закрывает модальное окно по клику на кнопку', async () => {
const wrapper = mount(Modal, { attachTo: document.body })
const button = document.querySelector('#modal-root button.close')
await button.click()
expect(document.querySelector('#modal-root').textContent).not.toContain('Контент модального окна')
})
Важно:
await используется для корректного ожидания асинхронных
изменений состояния.shallowMount с TeleportshallowMount игнорирует дочерние компоненты, включая
Teleport, поэтому тестирование поведения Teleport-контента через
shallowMount не подходит. Для проверки реального рендеринга
необходимо использовать mount.
import { shallowMount } from '@vue/test-utils'
const wrapper = shallowMount(Modal)
// Teleport-контент не будет смонтирован
expect(document.querySelector('#modal-root')).toBeNull()
Это показывает, что shallow-рендеринг Teleport полностью пропускается.
При множественных тестах важно создавать и удалять контейнер для
Teleport в beforeEach и afterEach, чтобы
избежать конфликта тестов.
beforeEach(() => {
const container = document.createElement('div')
container.id = 'modal-root'
document.body.appendChild(container)
})
afterEach(() => {
const container = document.querySelector('#modal-root')
if (container) document.body.removeChild(container)
})
Такой подход гарантирует изоляцию тестов и предотвращает “утечки” элементов в DOM, которые могут влиять на последующие тесты.
attachTo: document.body для правильного
перемещения контента.querySelector, textContent).shallowMount для проверки рендеринга
Teleport.Этот подход позволяет тестировать модальные окна, всплывающие уведомления и любые компоненты с Teleport так же надёжно, как и обычные компоненты Vue, с полной проверкой видимого DOM и реакции на события.