# 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) ```