# 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.
