v2 — Testing strategy
What gets tested, by what, and what to lift from v1.
Principle: behavior tests are the spec
Per PRINCIPLES.md #4: tests describe what the product DOES, in user-observable terms. Strict assertions. No relaxation for convenience. The test is the contract.
Test pyramid
┌──────────────┐
│ e2e (~25) │ Full user journeys, real browser
│ strict │
┌───┴──────────────┴───┐
│ integration (~50) │ Yjs ops, broker, REST endpoints
│ bounded contracts │
┌───┴──────────────────────┴───┐
│ unit tests (~887) │ Pure logic, schemas, helpers
│ framework-agnostic │
└───────────────────────────────┘
What lifts from v1 unchanged
Most of v1's testing infra and content carries over.
Unit tests (~887 tests in tests/unit/)
All tests in v1 tests/unit/ are framework-agnostic logic tests. They lift directly into lib/domain/tests/.
Coverage areas:
- Variant lifecycle (state machine semantics) —
variants.test.ts,variant-invariants.test.ts,variant-ops.test.ts,variants.exhaustive.test.ts,variants.property.test.ts - Scoring algorithm — multiple variants
- Convention resolution — locale, opening, closing
- Cell origin —
getOrigin,setOrigin,isHuman - Singleflight locks —
lock.ts - Order state mobility —
order.ts - Format / number / commute helpers —
format.ts - ID slug generation —
ids.test.ts - Tailor pipeline integration —
sync-tailor.test.ts,tailor-variant-sync.test.ts - Realtime sync invariants —
sync-realtime.test.ts - Salary parsing — sites in
jobs.ts - Industry normalization
- Job CRUD round-trip — bullet text round-trips
- Variants exhaustive — every state × every op pair
- Property-based — fuzz-style invariant testing
Action: Move directory tests/unit/ → lib/domain/tests/. Update imports. Run npm test.
Behavior tests (e2e — Playwright, ~25 tests across 10 spec files)
All in v1 e2e/. Most lift directly because they're behavior-level (operations + assertions, not framework internals).
Spec files to lift:
e2e/cvl-reorder.spec.ts— section reorder, expand state, preview rendere2e/journeys.spec.ts— main demo pipeline + many sub-journeyse2e/jobs-and-scoring.spec.ts— job CRUDe2e/byok.spec.ts— masked key visiblee2e/origin-tracking.spec.ts— provider origin tintse2e/views.spec.ts— view rule-based groupinge2e/agent-api-parity.spec.ts— REST + GUI paritye2e/extra-journeys*.spec.ts— additional journeyse2e/layout.spec.ts+layout-zoom.spec.ts— viewport matrixe2e/tailoring.spec.ts— tailor flow with FIXED restoratione2e/known-bugs/*.spec.ts— pinned regressions
Action: Lift e2e/ directory to v2 root. Update test setup (URL targets, possibly testid renames if Svelte components rename). Run npx playwright test.
Helpers
e2e/helpers/api.ts— HTTP helpers (createWorkspace, fetchJobs, persistEvaluation, etc.)e2e/helpers/cvl.ts— CV editor helpers (gotoCV, gotoDashboard, dragSectionToSection, waitForPreview)e2e/helpers/demo.ts— demo journey setup (configureDemoWorkspace, installDemoNetworkMocks, addJobViaUI, setupDemoWorkspace)
Action: Lift to v2. Update URL conventions (SvelteKit conventions vs Astro paths). Network mock patterns may need updating for new server endpoints.
What's NEW in v2 (tests to write)
Yjs broker behavior
Tests for the DO Yjs broker:
- Two clients connect, one writes → other receives delta
- Client disconnects → reconnects → vector clock sync delivers missed ops
- Concurrent same-field edits converge deterministically
- DO hibernates → wakes → state restored from snapshot + ops log
- Snapshot compaction: ops log truncates correctly
Test layer: integration (real Worker + DO emulator via miniflare or similar).
Yjs schema migration (v1 → v2)
One-shot script. Test:
- v1 D1 JSON blobs migrate to Y.Doc cleanly
- All sections preserve variantGroup IDs (already canonical post-v1 sweep)
- groupState entries migrate
- Round-trip: load Y.Doc, serialize, compare to original
REST adapters
Tests for the legacy REST endpoints that translate to Yjs ops:
- PATCH workspace settings via REST — confirm Yjs op applied to broker
- POST job — confirm Y.Doc updated
- DELETE job — confirm soft delete or removal
MCP server
Tests for the MCP Worker:
- Tool listing returns expected tools
add_jobcreates a jobtailor_cvtriggers tailoring (mocked LLM)- Idempotency: same call twice produces same result
- Error responses follow MCP error format
Service Worker / OPFS Typst caching
- First load fetches Typst WASM, caches in OPFS
- Reload skips network fetch
- Cache invalidation when Typst version pin changes
Tests to DELETE from v1 (don't lift)
Per LEARNINGS.md lesson "migration tests outlive the migration":
- Tests verifying
normalizeStack"fills in missing variantGroups" — that code is GONE in v2 - Tests asserting position-derived gid format (
g-s-${idx}) — Yjs handles identity now - Tests testing the v1 → v2 migration BEHAVIOR ongoing — migration is one-off
If tests describe the v2 contract correctly, keep them. If they describe v1 internals, delete.
Test coverage targets
| Surface | Target | Strategy |
|---|---|---|
lib/domain (lifted code) |
Existing 887 tests pass | No change |
| Yjs broker | New: ~30 tests | Integration via miniflare |
| REST adapters | New: ~15 tests | Integration |
| MCP tools | New: ~25 tests | Per-tool happy-path + 1-2 error paths |
| Components (Svelte) | Light unit tests + e2e | Svelte components don't need unit tests for visual behavior; e2e covers it |
| E2E full journeys | Existing ~25 + a few new | Lift, possibly add MCP-driven journey |
Test runner / infrastructure
- Vitest — unit + integration. Same as v1.
- Playwright — e2e. Same as v1.
- Miniflare or Wrangler dev — for Worker / DO integration tests.
- Codex review — every commit. Same v1 discipline.
CI / CD
- Run unit + integration tests on every push (under 1 minute total).
- Run e2e suite nightly + on every release branch.
- Codex review remains the default for any non-trivial commit.
- Deploy via
wrangler deployfor Workers,wrangler pages deploy(or equivalent) for the SvelteKit app.
Test data / fixtures
- v1 has
e2e/helpers/demo.tswithDEMO_IDENTITY,DEMO_JOB_URL, demo provider mocks. Lift unchanged. - Demo workspace creation logic carries over (
setupDemoWorkspace). - LLM mock provider URLs (
api.groq.com,api.cerebras.ai,api.mistral.ai) — same patterns work; just hook into Yjs ops where applicable.
What "ready to ship" means for v2
The v2 build is "ready" when:
- All ~887 unit tests pass (lifted from v1)
- All ~25 behavior tests pass (lifted from v1, possibly with selector updates)
- New Yjs broker tests pass (integration layer)
- New MCP tools tests pass (per-tool happy-path)
- Migration script tested against real workspace data dump (one-time, but verified)
- Manual smoke test on dev: create workspace, add job, edit CV, view PDF, share link with second tab, edit concurrently, see convergence
- Codex review on every meaningful commit through the rewrite