Testing
The test harnesses that ship with Valcraven — Vitest, Playwright, the Workers pool, the mobile node runner — what they cover, and the conventions for adding tests.
Valcraven ships real test infrastructure across the monorepo. Each workspace has a harness suited to what it runs, and each has a deliberate scope — knowing what a harness doesn't cover is as important as knowing what it does, so you push the uncovered cases into E2E or manual QA instead of assuming a green run proves everything.
What each workspace has
| Workspace | Harness | Location | Runs |
|---|---|---|---|
apps/web | Vitest (node env) — unit/integration | apps/web/tests/*.test.ts | npm test |
apps/web | Playwright — end-to-end | apps/web/e2e/*.spec.ts | npm run test:e2e |
apps/realtime | Vitest + @cloudflare/vitest-pool-workers | apps/realtime/test/*.test.ts | npm run test:realtime (from repo root) |
apps/mobile | Vitest (node env) — pure modules only | apps/mobile/src/**/*.test.ts | npm test -w apps/mobile |
apps/web — Vitest unit/integration
The web app's Vitest config (apps/web/vitest.config.ts) runs in the node environment with fileParallelism: false (test files run sequentially so parallel forks don't race on the shared SQLite test database). Tests mock auth.api.getSession() and exercise API-route handlers and lib/** modules directly against a real SQLite test DB.
npm test # Vitest run (unit + integration)
npm run test:watch # watch mode
npm run test:coverage # with V8 coverageThere is no client-React test harness in
apps/web. The Vitest environment isnode, with nojsdom/happy-domand no@testing-library/react— client-side React (hooks likeuse-*, components) is explicitly excluded from coverage because it isn't runnable in a node environment. Don't add jsdom for a small change and don't try to force arenderHooktest. Verify client behavior by typecheck + code review + a Playwright E2E test or a manual QA scenario instead.
Server, lib/, and API-route code, by contrast, has good coverage — that's where new unit tests belong.
apps/web — Playwright E2E
Playwright (apps/web/e2e/) drives the real app in a headless browser against a dev server. playwright.config.ts starts the server via its webServer block and points baseURL at localhost; a dedicated test database is wiped before each run so every run starts clean.
npm run test:e2e # headless
npm run test:e2e:ui # interactive UI modeConventions that keep the suite stable:
- Auth helpers live in
e2e/helpers.ts(signUp,signIn,ensureLoggedIn). They also dismiss the onboarding overlay and cookie-consent banner, so/appinteractions aren't blocked. If you navigate to/appwithout them, dismiss the onboarding overlay yourself. - Prefer role/label selectors (
getByRole,getByLabel,getByPlaceholder) over CSS — they survive markup changes. - Flows that depend on external services are tested at smoke level (OAuth buttons render, Stripe billing UI renders and routes enforce auth, chat composer renders, 2FA surfaces its QR) so CI stays deterministic without live credentials. Deepen any of these once you wire up real test credentials, but keep the smoke specs as the always-on baseline.
apps/realtime — Workers pool
The realtime Worker (which hosts the AI agent and realtime channels as Durable Objects) has its own harness using @cloudflare/vitest-pool-workers, which runs tests inside the actual Workers runtime so Durable Object and binding behavior is exercised for real. Run it from the repo root:
npm run test:realtimeSee Realtime worker for what this Worker does.
apps/mobile — pure modules only
The Expo app has a minimal node-env Vitest harness scoped deliberately to framework-free pure modules (src/**/*.test.ts — note .test.tsx is intentionally excluded). React Native components and Expo-dependent code are not unit-tested here, because they need Metro/Babel and a native-aware environment that a plain node runner can't provide.
Verify a mobile change with this convention instead:
npm run typecheck -w apps/mobile— the primary gate.npx expo export --platform ios(fromapps/mobile/) — catches Metro import/resolution errors that typecheck misses, especially when a new native module is added.- A manual QA scenario in Expo Go for anything device-dependent (auth-gated image loading, camera/QR scanning, push) — the real behavioral gate.
More in Expo app and Mobile parity.
The separate-test-author practice
When adding a non-trivial feature, the template's practice is that the person (or agent) who writes the implementation does not write its own unit tests. A separate author writes tests from the spec/acceptance criteria — not from the implementation — so the tests verify required behavior rather than mirroring the implementation's internal assumptions. If a test fails, fix the implementation, not the test. Observable E2E tests can be written by the implementer, since they assert behavior rather than internals.
A note on CI
The template's GitHub Actions workflows (.github/workflows/*.yml) run lint, typecheck, unit tests, build, and E2E. Depending on how the template was configured, these may be wired to run on push/PR or be workflow_dispatch-only (a cost-control choice when a repo has heavy active development and no users). Check the on: triggers in your .github/workflows/*.yml — if they're dispatch-only, a PR will show no checks and you must verify locally (npm run typecheck && npm test && npm run build) or dispatch a run manually. Don't wait for checks that will never fire on their own.