Тестирование компонентов с Teleport

В Vue 3 появился компонент Teleport, позволяющий рендерить дочерние элементы вне текущей иерархии DOM, например, в отдельный контейнер в body. Это особенно полезно для модальных окон, уведомлений и всплывающих подсказок. Тестирование компонентов с Teleport требует особого подхода, так как стандартные методы рендеринга в Vue Test Utils (mount, shallowMount) по умолчанию не учитывают перемещённые в DOM элементы.


Монтирование компонентов с Teleport

Для корректного тестирования необходимо учитывать два момента:

  1. Целевой элемент (to) Teleport должен существовать в DOM. Если этого не сделать, Teleport не сможет разместить дочерние элементы, и они не появятся в тестируемом дереве.

  2. Использование 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.
  • После теста контейнер удаляется для предотвращения загрязнения DOM.

Проверка видимости 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 позволяет получать элементы, находящиеся вне компонента.
  • Тестирование состояния компонента не ограничивается деревом Vue — нужно проверять реальный DOM.

Эмуляция событий внутри Teleport

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 используется для корректного ожидания асинхронных изменений состояния.
  • События внутри Teleport корректно вызывают реактивные изменения компонента.

Использование shallowMount с Teleport

shallowMount игнорирует дочерние компоненты, включая Teleport, поэтому тестирование поведения Teleport-контента через shallowMount не подходит. Для проверки реального рендеринга необходимо использовать mount.

import { shallowMount } from '@vue/test-utils'

const wrapper = shallowMount(Modal)
// Teleport-контент не будет смонтирован
expect(document.querySelector('#modal-root')).toBeNull()

Это показывает, что shallow-рендеринг Teleport полностью пропускается.


Сборка и очистка тестового DOM

При множественных тестах важно создавать и удалять контейнер для 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, которые могут влиять на последующие тесты.


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

  1. Всегда создавать реальный контейнер в DOM перед монтированием компонента.
  2. Использовать attachTo: document.body для правильного перемещения контента.
  3. Проверять Teleport-контент через стандартные DOM-методы (querySelector, textContent).
  4. Не использовать shallowMount для проверки рендеринга Teleport.
  5. Очистка контейнера после каждого теста обязательна для предотвращения побочных эффектов.

Этот подход позволяет тестировать модальные окна, всплывающие уведомления и любые компоненты с Teleport так же надёжно, как и обычные компоненты Vue, с полной проверкой видимого DOM и реакции на события.