Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 testing best practices with Vitest, Vue Test Utils, component testing, mocking, and Playwright E2E.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/teleport-testing-complexity.md
1---2title: Teleported Content Requires Special Testing Approach3impact: MEDIUM4impactDescription: Vue Test Utils cannot find teleported content using standard wrapper.find() methods5type: gotcha6tags: [vue3, teleport, testing, vue-test-utils]7---89# Teleported Content Requires Special Testing Approach1011**Impact: MEDIUM** - Vue Test Utils scopes queries to the mounted component. Teleported content renders outside the component's DOM tree, so `wrapper.find()` cannot locate it. This leads to failing tests and confusion.1213## Task Checklist1415- [ ] Stub Teleport in unit tests to keep content in component tree16- [ ] Use `document.body` queries for integration tests with real Teleport17- [ ] Consider using `getComponent()` instead of DOM queries for teleported components1819**Problem - Standard Testing Fails:**20```vue21<!-- Modal.vue -->22<template>23<button @click="open = true">Open</button>24<Teleport to="body">25<div v-if="open" class="modal" data-testid="modal">26<input type="text" data-testid="modal-input" />27</div>28</Teleport>29</template>30```3132```ts33// Modal.spec.ts - BROKEN34import { mount } from '@vue/test-utils'35import Modal from './Modal.vue'3637test('modal input exists', async () => {38const wrapper = mount(Modal)39await wrapper.find('button').trigger('click')4041// FAILS: Teleported content is not in wrapper's DOM tree42expect(wrapper.find('[data-testid="modal-input"]').exists()).toBe(true)43})44```4546**Solution 1 - Stub Teleport:**47```ts48import { mount } from '@vue/test-utils'49import Modal from './Modal.vue'5051test('modal input exists', async () => {52const wrapper = mount(Modal, {53global: {54stubs: {55// Stub teleport to render content inline56Teleport: true57}58}59})6061await wrapper.find('button').trigger('click')6263// Works: Content renders inside wrapper64expect(wrapper.find('[data-testid="modal-input"]').exists()).toBe(true)65})66```6768**Solution 2 - Query Document Body:**69```ts70import { mount } from '@vue/test-utils'71import Modal from './Modal.vue'7273test('modal renders to body', async () => {74const wrapper = mount(Modal, {75attachTo: document.body // Required for Teleport to work76})7778await wrapper.find('button').trigger('click')7980// Query the actual DOM81const modal = document.querySelector('[data-testid="modal"]')82expect(modal).toBeTruthy()8384const input = document.querySelector('[data-testid="modal-input"]')85expect(input).toBeTruthy()8687// Cleanup88wrapper.unmount()89})90```9192**Solution 3 - Custom Teleport Stub with Content Access:**93```ts94import { mount, config } from '@vue/test-utils'95import { h, Teleport } from 'vue'96import Modal from './Modal.vue'9798// Custom stub that renders content in a testable way99const TeleportStub = {100setup(props, { slots }) {101return () => h('div', { class: 'teleport-stub' }, slots.default?.())102}103}104105test('modal with custom stub', async () => {106const wrapper = mount(Modal, {107global: {108stubs: {109Teleport: TeleportStub110}111}112})113114await wrapper.find('button').trigger('click')115116// Content is inside .teleport-stub117expect(wrapper.find('.teleport-stub [data-testid="modal-input"]').exists()).toBe(true)118})119```120121## Testing Vue Final Modal and UI Libraries122123Libraries like Vue Final Modal use Teleport internally, causing test failures:124125```ts126// Problem: Vue Final Modal teleports to body127import { VueFinalModal } from 'vue-final-modal'128129test('modal content', async () => {130const wrapper = mount(MyComponent, {131global: {132stubs: {133// Stub the modal component to avoid teleport issues134VueFinalModal: true135}136}137})138})139```140141## E2E Testing (Cypress, Playwright)142143E2E tests query the real DOM, so Teleport works naturally:144145```ts146// Cypress147it('opens modal', () => {148cy.visit('/page-with-modal')149cy.get('button').click()150151// Works: Cypress queries the real DOM152cy.get('[data-testid="modal"]').should('be.visible')153})154```155156## Reference157- [Vue Test Utils - Teleport](https://test-utils.vuejs.org/guide/advanced/teleport)158- [Vue Test Utils - Stubs](https://test-utils.vuejs.org/guide/advanced/stubs-shallow-mount)159