Testing
When to Use
Writing or augmenting tests: unit/interaction tests, accessibility assertions, end-to-end flows, Storybook stories, test setup and mocking. Also when a component is delivered without coverage — every shipped component needs a test.
Stack
Vitest + jsdom · @testing-library/react + user-event · jest-axe · Playwright (e2e) · Storybook 8 CSF3
Core Rules
- Untested UI is incomplete UI. A component is done when a test asserts the behaviour a user depends on — not when it renders.
- Test the public contract, never implementation. Assert on rendered roles and text, never on state variable names or class strings.
- Query priority:
getByRole > getByLabelText > getByText > getByTestId. If you can't find it by role, neither can a screen reader — that's a finding, not a test problem.
userEvent, not fireEvent, for flows — it fires the full focus/keydown/keyup sequence a real user produces.
- Every component gets three tests minimum: renders (asserting real DOM), one role-based interaction, one
jest-axe pass.
- Mock the dependency, never the component under test. Typed stubs only — no
any.
- No arbitrary waits.
await screen.findBy… or waitFor, never setTimeout.
- No placeholder assertions.
expect(true).toBe(true) is not a test.
- Per component type: forms assert error wiring via
aria-describedby; data components assert the empty state; overlays assert focus return and aria-hidden on siblings; interactive components assert keyboard activation.
Patterns
- Mock policy —
motion/react → passthrough proxy; next/navigation → stub router; R3F/drei/three → Canvas becomes a <div> (no WebGL in jsdom); TanStack Query → real provider, mocked fetch; recharts → sized <div>.
- axe pass —
expect(await axe(container)).toHaveNoViolations() on mounted DOM; open overlays first.
- Play functions — Storybook CSF3
play with userEvent doubles as an interaction test.
- e2e — Playwright with role selectors,
checkA11y, network mocking, keyboard/focus assertions.
Examples
Every gold example across all skills ships a colocated *.test.tsx — read any of them as a reference implementation. examples/good-playwright.tsx demonstrates an e2e dashboard surface.
Reference Index
Load only for the specific task:
| Task |
Load |
| Doctrine, stack, per-type requirements, mock policy, anti-patterns |
references/testing.md |
| Playwright: role selectors, visual regression, axe, CI config |
references/playwright.md |
| Storybook 8 CSF3, argTypes, decorators, autodocs, Chromatic |
references/storybook.md |
Constraints
Zero any in test files · zero placeholder assertions · role-based queries first · typed mocks · every gold has a colocated .test.tsx (release Gate 7) · tests compile under tsc --noEmit --strict.
1---2name: testing3description: Component testing — Vitest, Testing Library, jest-axe, Playwright e2e, Storybook stories, mock policy. Use when writing or augmenting tests — unit and interaction tests, accessibility assertions, end-to-end flows, Storybook stories, test setup and mocking — or when a component has been delivered without coverage.4---56# Testing78## When to Use9Writing or augmenting tests: unit/interaction tests, accessibility assertions, end-to-end flows, Storybook stories, test setup and mocking. Also when a component is delivered without coverage — every shipped component needs a test.1011## Stack12Vitest + jsdom · `@testing-library/react` + `user-event` · `jest-axe` · Playwright (e2e) · Storybook 8 CSF31314## Core Rules151. **Untested UI is incomplete UI.** A component is done when a test asserts the behaviour a user depends on — not when it renders.162. **Test the public contract**, never implementation. Assert on rendered roles and text, never on state variable names or class strings.173. **Query priority:** `getByRole` > `getByLabelText` > `getByText` > `getByTestId`. If you can't find it by role, neither can a screen reader — that's a finding, not a test problem.184. **`userEvent`, not `fireEvent`,** for flows — it fires the full focus/keydown/keyup sequence a real user produces.195. **Every component gets three tests minimum:** renders (asserting real DOM), one role-based interaction, one `jest-axe` pass.206. **Mock the dependency, never the component under test.** Typed stubs only — no `any`.217. **No arbitrary waits.** `await screen.findBy…` or `waitFor`, never `setTimeout`.228. **No placeholder assertions.** `expect(true).toBe(true)` is not a test.239. **Per component type:** forms assert error wiring via `aria-describedby`; data components assert the empty state; overlays assert focus return and `aria-hidden` on siblings; interactive components assert keyboard activation.2425## Patterns26- **Mock policy** — `motion/react` → passthrough proxy; `next/navigation` → stub router; R3F/drei/`three` → `Canvas` becomes a `<div>` (no WebGL in jsdom); TanStack Query → real provider, mocked fetch; recharts → sized `<div>`.27- **axe pass** — `expect(await axe(container)).toHaveNoViolations()` on mounted DOM; open overlays first.28- **Play functions** — Storybook CSF3 `play` with `userEvent` doubles as an interaction test.29- **e2e** — Playwright with role selectors, `checkA11y`, network mocking, keyboard/focus assertions.3031## Examples32Every gold example across all skills ships a colocated `*.test.tsx` — read any of them as a reference implementation. `examples/good-playwright.tsx` demonstrates an e2e dashboard surface.3334## Reference Index35Load only for the specific task:3637| Task | Load |38|---|---|39| Doctrine, stack, per-type requirements, mock policy, anti-patterns | `references/testing.md` |40| Playwright: role selectors, visual regression, axe, CI config | `references/playwright.md` |41| Storybook 8 CSF3, argTypes, decorators, autodocs, Chromatic | `references/storybook.md` |4243## Constraints44Zero `any` in test files · zero placeholder assertions · role-based queries first · typed mocks · every gold has a colocated `.test.tsx` (release Gate 7) · tests compile under `tsc --noEmit --strict`.