Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
159 lines
4.0 KiB
Markdown
159 lines
4.0 KiB
Markdown
---
|
|
title: Teleported Content Requires Special Testing Approach
|
|
impact: MEDIUM
|
|
impactDescription: Vue Test Utils cannot find teleported content using standard wrapper.find() methods
|
|
type: gotcha
|
|
tags: [vue3, teleport, testing, vue-test-utils]
|
|
---
|
|
|
|
# Teleported Content Requires Special Testing Approach
|
|
|
|
**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.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Stub Teleport in unit tests to keep content in component tree
|
|
- [ ] Use `document.body` queries for integration tests with real Teleport
|
|
- [ ] Consider using `getComponent()` instead of DOM queries for teleported components
|
|
|
|
**Problem - Standard Testing Fails:**
|
|
```vue
|
|
<!-- Modal.vue -->
|
|
<template>
|
|
<button @click="open = true">Open</button>
|
|
<Teleport to="body">
|
|
<div v-if="open" class="modal" data-testid="modal">
|
|
<input type="text" data-testid="modal-input" />
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
```
|
|
|
|
```ts
|
|
// Modal.spec.ts - BROKEN
|
|
import { mount } from '@vue/test-utils'
|
|
import Modal from './Modal.vue'
|
|
|
|
test('modal input exists', async () => {
|
|
const wrapper = mount(Modal)
|
|
await wrapper.find('button').trigger('click')
|
|
|
|
// FAILS: Teleported content is not in wrapper's DOM tree
|
|
expect(wrapper.find('[data-testid="modal-input"]').exists()).toBe(true)
|
|
})
|
|
```
|
|
|
|
**Solution 1 - Stub Teleport:**
|
|
```ts
|
|
import { mount } from '@vue/test-utils'
|
|
import Modal from './Modal.vue'
|
|
|
|
test('modal input exists', async () => {
|
|
const wrapper = mount(Modal, {
|
|
global: {
|
|
stubs: {
|
|
// Stub teleport to render content inline
|
|
Teleport: true
|
|
}
|
|
}
|
|
})
|
|
|
|
await wrapper.find('button').trigger('click')
|
|
|
|
// Works: Content renders inside wrapper
|
|
expect(wrapper.find('[data-testid="modal-input"]').exists()).toBe(true)
|
|
})
|
|
```
|
|
|
|
**Solution 2 - Query Document Body:**
|
|
```ts
|
|
import { mount } from '@vue/test-utils'
|
|
import Modal from './Modal.vue'
|
|
|
|
test('modal renders to body', async () => {
|
|
const wrapper = mount(Modal, {
|
|
attachTo: document.body // Required for Teleport to work
|
|
})
|
|
|
|
await wrapper.find('button').trigger('click')
|
|
|
|
// Query the actual DOM
|
|
const modal = document.querySelector('[data-testid="modal"]')
|
|
expect(modal).toBeTruthy()
|
|
|
|
const input = document.querySelector('[data-testid="modal-input"]')
|
|
expect(input).toBeTruthy()
|
|
|
|
// Cleanup
|
|
wrapper.unmount()
|
|
})
|
|
```
|
|
|
|
**Solution 3 - Custom Teleport Stub with Content Access:**
|
|
```ts
|
|
import { mount, config } from '@vue/test-utils'
|
|
import { h, Teleport } from 'vue'
|
|
import Modal from './Modal.vue'
|
|
|
|
// Custom stub that renders content in a testable way
|
|
const TeleportStub = {
|
|
setup(props, { slots }) {
|
|
return () => h('div', { class: 'teleport-stub' }, slots.default?.())
|
|
}
|
|
}
|
|
|
|
test('modal with custom stub', async () => {
|
|
const wrapper = mount(Modal, {
|
|
global: {
|
|
stubs: {
|
|
Teleport: TeleportStub
|
|
}
|
|
}
|
|
})
|
|
|
|
await wrapper.find('button').trigger('click')
|
|
|
|
// Content is inside .teleport-stub
|
|
expect(wrapper.find('.teleport-stub [data-testid="modal-input"]').exists()).toBe(true)
|
|
})
|
|
```
|
|
|
|
## Testing Vue Final Modal and UI Libraries
|
|
|
|
Libraries like Vue Final Modal use Teleport internally, causing test failures:
|
|
|
|
```ts
|
|
// Problem: Vue Final Modal teleports to body
|
|
import { VueFinalModal } from 'vue-final-modal'
|
|
|
|
test('modal content', async () => {
|
|
const wrapper = mount(MyComponent, {
|
|
global: {
|
|
stubs: {
|
|
// Stub the modal component to avoid teleport issues
|
|
VueFinalModal: true
|
|
}
|
|
}
|
|
})
|
|
})
|
|
```
|
|
|
|
## E2E Testing (Cypress, Playwright)
|
|
|
|
E2E tests query the real DOM, so Teleport works naturally:
|
|
|
|
```ts
|
|
// Cypress
|
|
it('opens modal', () => {
|
|
cy.visit('/page-with-modal')
|
|
cy.get('button').click()
|
|
|
|
// Works: Cypress queries the real DOM
|
|
cy.get('[data-testid="modal"]').should('be.visible')
|
|
})
|
|
```
|
|
|
|
## Reference
|
|
- [Vue Test Utils - Teleport](https://test-utils.vuejs.org/guide/advanced/teleport)
|
|
- [Vue Test Utils - Stubs](https://test-utils.vuejs.org/guide/advanced/stubs-shallow-mount)
|