77 lines
2.6 KiB
Markdown
77 lines
2.6 KiB
Markdown
# CLAUDE.md — test/
|
|
|
|
## Skill precedence
|
|
|
|
When writing or modifying tests, load the **nuxt-testing** skill first:
|
|
```
|
|
/nuxt-testing
|
|
```
|
|
It takes precedence over other testing skills (vue-testing-best-practices, vitest, etc.) because it covers the exact `@nuxt/test-utils` setup used here.
|
|
|
|
## Test structure
|
|
|
|
| Directory | Project | Environment | Purpose |
|
|
|-----------|---------|-------------|---------|
|
|
| `test/unit/` | `unit` | node | Pure logic, Zod schemas, utilities |
|
|
| `test/nuxt/` | `nuxt` | nuxt (happy-dom) | Components and composables that need Nuxt runtime |
|
|
| `test/e2e/` | `e2e` | node | Browser tests against the running dev server |
|
|
|
|
Run a single project: `pnpm test --project unit` / `nuxt` / `e2e`
|
|
|
|
## Known gotchas
|
|
|
|
### Nuxt component tests — mock stores in `vi.hoisted`, not in `it()`
|
|
|
|
Global middleware (e.g. `00.fetchUser.global.ts`) runs during `mountSuspended` **before** the test body executes. If a mocked store returns `undefined` at that point, Nuxt throws and the test crashes.
|
|
|
|
**Wrong** — mock is set up too late:
|
|
```ts
|
|
const useUserMock = vi.fn()
|
|
mockNuxtImport('useUser', () => useUserMock)
|
|
|
|
it('...', async () => {
|
|
useUserMock.mockReturnValue({ isAuthenticated: false, authRefresh: vi.fn() })
|
|
// ❌ middleware already ran with undefined
|
|
})
|
|
```
|
|
|
|
**Correct** — default implementation lives in `vi.hoisted`:
|
|
```ts
|
|
const { useUserMock } = vi.hoisted(() => ({
|
|
useUserMock: vi.fn(() => ({ isAuthenticated: false, user: null, authRefresh: vi.fn() }))
|
|
}))
|
|
mockNuxtImport('useUser', () => useUserMock)
|
|
```
|
|
|
|
### E2E tests — do not pass a path to `createPage()` on this SPA
|
|
|
|
`createPage('/some-path')` internally calls `waitForHydration`, which polls
|
|
`window.useNuxtApp?.().isHydrating === false`. Because `useNuxtApp` is not
|
|
exposed on `window` in SPA mode, this never resolves and the test times out.
|
|
|
|
**Wrong:**
|
|
```ts
|
|
const page = await createPage('/login') // ❌ hangs forever
|
|
```
|
|
|
|
**Correct** — call without a path, then navigate manually:
|
|
```ts
|
|
const page = await createPage()
|
|
await page.goto('http://localhost:3000/login', { waitUntil: 'networkidle' })
|
|
```
|
|
|
|
### E2E tests — use `isVisible()` + Vitest `expect`, not Playwright matchers
|
|
|
|
`@playwright/test` is not installed (only `playwright-core`). Playwright's
|
|
`expect(locator).toBeVisible()` is not available.
|
|
|
|
```ts
|
|
// ❌ crashes — @playwright/test not installed
|
|
import { expect } from '@playwright/test'
|
|
await expect(page.locator('input')).toBeVisible()
|
|
|
|
// ✅ use the async boolean method with Vitest's expect
|
|
import { expect } from 'vitest'
|
|
expect(await page.locator('input').isVisible()).toBe(true)
|
|
```
|