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>
4.8 KiB
4.8 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Use flushPromises for Testing Async Components | HIGH | Without awaiting async operations, tests make assertions before the component has rendered, causing false negatives | gotcha |
|
Use flushPromises for Testing Async Components
Impact: HIGH - When testing async components created with defineAsyncComponent, you must use await flushPromises() to ensure the component has loaded before making assertions. Vue updates asynchronously, so tests that don't account for this will make assertions before the component has rendered.
Task Checklist
- Use
async/awaitin test functions for async components - Call
await flushPromises()after mounting async components - Test loading states by making assertions before
flushPromises() - Test error states using rejected promises in
defineAsyncComponent - Use
trigger()withawaitas it returns a Promise
Incorrect:
import { mount } from '@vue/test-utils'
import { defineAsyncComponent } from 'vue'
const AsyncWidget = defineAsyncComponent(() =>
import('./Widget.vue')
)
test('renders async component', () => {
const wrapper = mount(AsyncWidget)
// FAILS: Component hasn't loaded yet
expect(wrapper.text()).toContain('Widget Content')
})
Correct:
import { mount, flushPromises } from '@vue/test-utils'
import { defineAsyncComponent, nextTick } from 'vue'
const AsyncWidget = defineAsyncComponent(() =>
import('./Widget.vue')
)
test('renders async component', async () => {
const wrapper = mount(AsyncWidget)
// Wait for async component to load
await flushPromises()
expect(wrapper.text()).toContain('Widget Content')
})
test('shows loading state initially', async () => {
const AsyncWithLoading = defineAsyncComponent({
loader: () => import('./Widget.vue'),
loadingComponent: { template: '<div>Loading...</div>' },
delay: 0
})
const wrapper = mount(AsyncWithLoading)
// Check loading state immediately
expect(wrapper.text()).toContain('Loading...')
// Wait for component to load
await flushPromises()
// Check final state
expect(wrapper.text()).toContain('Widget Content')
})
Testing with Suspense
import { mount, flushPromises } from '@vue/test-utils'
import { Suspense, defineAsyncComponent, h } from 'vue'
const AsyncWidget = defineAsyncComponent(() =>
import('./Widget.vue')
)
test('renders async component with Suspense', async () => {
const wrapper = mount({
components: { AsyncWidget },
template: `
<Suspense>
<AsyncWidget />
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
`
})
// Initially shows fallback
expect(wrapper.text()).toContain('Loading...')
// Wait for async resolution
await flushPromises()
// Now shows actual content
expect(wrapper.text()).toContain('Widget Content')
})
Testing Error States
import { mount, flushPromises } from '@vue/test-utils'
import { defineAsyncComponent } from 'vue'
test('shows error component on load failure', async () => {
const AsyncWithError = defineAsyncComponent({
loader: () => Promise.reject(new Error('Failed to load')),
errorComponent: { template: '<div>Error loading component</div>' }
})
const wrapper = mount(AsyncWithError)
await flushPromises()
expect(wrapper.text()).toContain('Error loading component')
})
Utilities Reference
| Utility | Purpose |
|---|---|
await flushPromises() |
Resolves all pending promises |
await nextTick() |
Waits for Vue's next DOM update cycle |
await wrapper.trigger('click') |
Triggers event and waits for update |
Dynamic Import Handling
Note: Dynamic imports (import('./File.vue')) may require additional handling beyond flushPromises() in test environments. Test runners like Vitest handle module resolution differently than runtime bundlers, which can cause timing issues with dynamic imports. If flushPromises() alone doesn't resolve the component, consider:
- Mocking the dynamic import to return the component synchronously
- Using multiple
await flushPromises()calls in sequence - Wrapping assertions in
waitFor()or retry utilities - Configuring your test runner's module resolution settings
// If flushPromises() isn't sufficient, mock the import
vi.mock('./Widget.vue', () => ({
default: { template: '<div>Widget Content</div>' }
}))
// Or use multiple flush calls for nested async operations
await flushPromises()
await flushPromises()