# Testing ## Quick Reference ```bash npm test # Vitest watch mode npm run test:run # Vitest single run (used by pre-push hook) npm run test:e2e # Playwright E2E tests npm run test:e2e:ui # Playwright with interactive UI npm run test:a11y # Accessibility scans (axe-core) npm run test:visual # Visual regression screenshots npm run test:all # Vitest + Playwright together ``` ## Vitest (Unit / Handler Tests) Tests live in `tests/` mirroring the source structure. Two vitest projects: - **server** (`tests/server/`) — Node environment, `setup.js` stubs Nitro auto-imports - **client** (`tests/client/`) — jsdom environment for composables ### Test patterns **Behavioral tests** mock models/services with `vi.mock()`, import the handler, and call it with `createMockEvent()`: ```js vi.mock('../../../server/models/member.js', () => ({ default: { findOne: vi.fn() } })) import handler from '../../../server/api/some/route.js' import { createMockEvent } from '../helpers/createMockEvent.js' ``` **Source inspection tests** use `readFileSync` to verify structural properties (auth guards, import order) without executing handlers. ### Nitro auto-imports in tests Handlers use Nitro auto-imports for `requireAuth`, `requireAdmin`, `validateBody`, and schemas. These are stubbed as globals in `tests/server/setup.js`. Individual tests can configure their behavior with `.mockResolvedValue()` etc. ## Playwright (E2E Tests) Tests live in `e2e/`. Requires a running dev server and MongoDB. ### Auth helpers `e2e/helpers/auth.js` provides `loginAsAdmin(page)` and `loginAsMember(page, email)` using the dev login endpoints. These set real JWT cookies. `e2e/helpers/fixtures.js` extends Playwright's `test` with `adminPage` and `memberPage` fixtures. ### Running locally ```bash # Start dev server (Playwright config does this automatically) npm run dev # Run tests npm run test:e2e # Interactive mode npm run test:e2e:ui ``` ### Visual regression Baselines stored in `e2e/__screenshots__/`. Generate or update: ```bash npm run test:visual:update ``` Note: Linux CI and macOS local produce different renders. Generate baselines in CI with `--update-snapshots`, then commit. ## Pre-Push Hook Husky runs `npm run test:run` (Vitest only) before `git push`. All mocked, runs in ~1s. Playwright is not included — too slow and requires MongoDB. ## CI (GitHub Actions) `.github/workflows/test.yml` defines three jobs: 1. **vitest** — runs on every push/PR to main 2. **playwright** — runs after vitest passes, uses MongoDB service container 3. **visual** — runs after vitest, `continue-on-error: true` during stabilization CI uses dummy secrets (`JWT_SECRET: ci-test-jwt-secret`). No real Helcim/Resend/Slack tokens — those paths are mocked at Vitest level, and E2E skips the Helcim iframe. ## Known Limitations - **Helcim iframe** — cannot be tested in E2E (cross-origin). Payment flows are tested at the Vitest handler level. E2E tests stop before the iframe opens. - **Email delivery** — mocked at Vitest level. E2E verifies form submission, not actual email. - **Slack invitations** — mocked at Vitest level. - **Visual baseline OS drift** — generate baselines in CI, not locally.