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 render
  • e2e/journeys.spec.ts — main demo pipeline + many sub-journeys
  • e2e/jobs-and-scoring.spec.ts — job CRUD
  • e2e/byok.spec.ts — masked key visible
  • e2e/origin-tracking.spec.ts — provider origin tints
  • e2e/views.spec.ts — view rule-based grouping
  • e2e/agent-api-parity.spec.ts — REST + GUI parity
  • e2e/extra-journeys*.spec.ts — additional journeys
  • e2e/layout.spec.ts + layout-zoom.spec.ts — viewport matrix
  • e2e/tailoring.spec.ts — tailor flow with FIXED restoration
  • e2e/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_job creates a job
  • tailor_cv triggers 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 deploy for Workers, wrangler pages deploy (or equivalent) for the SvelteKit app.

Test data / fixtures

  • v1 has e2e/helpers/demo.ts with DEMO_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:

  1. All ~887 unit tests pass (lifted from v1)
  2. All ~25 behavior tests pass (lifted from v1, possibly with selector updates)
  3. New Yjs broker tests pass (integration layer)
  4. New MCP tools tests pass (per-tool happy-path)
  5. Migration script tested against real workspace data dump (one-time, but verified)
  6. Manual smoke test on dev: create workspace, add job, edit CV, view PDF, share link with second tab, edit concurrently, see convergence
  7. Codex review on every meaningful commit through the rewrite
Source: wiki/content/archive/2026-05/TESTING-PLAN-v1-to-v2.md