# Sprint 14 Verification — Vitest for `useOnboarding` (Q4) **Date:** 2026-06-05. **Owner:** this agent. **Status:** code complete, 7/7 tests green, `npm run build` green, awaiting commit + push. ## What Sprint 14 does Adds Vitest + @testing-library/react + happy-dom to the frontend devDeps and locks the `useOnboarding` state-transition contract with 7 unit tests. The S9 bug class (wiring `onComplete` to a call that flips `isComplete` to `false`, e.g. `onboarding.reset()`) is now caught at `npm test` time. Sprint 9 (F1 Onboarding Tour) shipped a hand-rolled ~420-line component; the bug `1562929` shipped a post-deploy fix the same day. Sprint 14 prevents recurrence. ## Why this sprint, why this scope - **Q4** (open question from Sprint 9): "Add Vitest to lock `useOnboarding` state transitions." Cost: ~1 hr. Value: prevents the S9 bug class. Decision: lift the "no new npm deps" rule for **testing-only** (devDeps). - **Hook-only, not component-level.** The S9 bug was at the App.tsx call-site. Sprint 14 includes one integration test (Case 7) that statically reads `App.tsx` and asserts the wiring. Component-level tests (focus, arrow keys, dialog a11y) are a future sprint. ## Files added - `frontend/vitest.config.ts` — happy-dom env, setup file, `src/**/*.test.{ts,tsx}` glob. - `frontend/vitest-setup.ts` — `@testing-library/jest-dom/vitest` matchers. - `frontend/src/components/OnboardingTour.test.tsx` — 7 cases. ## Files modified - `frontend/package.json` — 4 new devDeps (`vitest`, `happy-dom`, `@testing-library/react`, `@testing-library/jest-dom`) + 1 devDep for tsc (`@types/node`) + 2 scripts (`test`, `test:watch`). - `.agent/plan.md` — S14 sections (S14.1-S14.4 + Done when + Out of scope). - `.agent/context.md` — S14 decisions (D1-D7), open question (Q1), file:line references. ## Test cases (S14.3) | # | Case | What it locks | |---|------|---------------| | 1 | clean init | `isComplete === false` when localStorage is empty | | 2 | persisted init | `isComplete === true` when `localStorage.getItem(KEY) === '1'` | | 3 | `markComplete` | state → true, localStorage **stays** at `'1'` (locks one direction of the S9 bug) | | 4 | `reset` | localStorage cleared, state → false | | 5 | `show` | identical to `reset` (intentional mirror) | | 6 | localStorage throw on read | silently swallowed, `isComplete === false`, no crash | | 7 | App.tsx wiring | static check: `onComplete` calls `markComplete`, `onReset` calls `reset`; neither inverts (locks the original S9 bug at the call site) | ## Verification commands ```bash ssh docker-willester cd /home/peter/MealPlanner/frontend # 1. install (already done on host; rerun if pulling to a fresh checkout) npm install # 2. run tests npm test # → 7 passed in ~25 ms # 3. build (still green; vitest is devDeps only) npm run build # → tsc 0 errors, vite built in ~2.6 s, bundle 503.82 kB unchanged # 4. prove Case 7 catches the bug (manual) sed -i 's|onboarding.markComplete()|onboarding.reset()|' src/App.tsx npm test # → 1 failed (Case 7: onCompleteBody does not match /markComplete/) sed -i 's|onboarding.reset()|onboarding.markComplete()|' src/App.tsx # restore npm test # → 7 passed ``` ## Deploy No backend change. No migration. No new runtime dep. The 5 new devDeps land on `npm install` on the host. ```bash ssh docker-willester cd /home/peter/MealPlanner git pull cd frontend && npm install && npm test # confirm 7/7 in CI cd .. && docker compose up -d --build frontend ``` If the user wants Vitest in CI (currently not wired), that is a follow-up — outside Sprint 14 scope. ## What Sprint 14 does NOT do - **No component-level tests for ``.** Focus, arrow keys, dialog a11y, portal positioning. Future sprint. Would add `@testing-library/user-event` for keyboard simulation. - **No backend tests.** Venv on `docker-willester` is broken (Nix symlinks to `/run/current-system/sw/bin/python`). Frontend-only sprint. - **No CI integration.** Tests are local-only. Wiring them into a CI runner is a follow-up. ## Risk table | Risk | Mitigation | Status | |------|------------|--------| | Vitest conflicts with Vite 5 config | vitest.config.ts uses `defineConfig` from `vitest/config` which extends Vite's; tested, no conflict | Resolved | | `node:*` imports in tests break tsc | Added `@types/node` as devDep | Resolved | | Tests slow CI | 7 cases run in 25 ms; not a concern | N/A | | Devs forget to run `npm test` before commit | Follow-up: pre-commit hook or CI; not in Sprint 14 | Open | | Future refactor adds a hook dependency that breaks Case 7's regex | Case 7 uses a single-shot regex match; if `App.tsx` gains a `markComplete` call in unrelated code (e.g. button), the regex still works because the `onComplete` arrow body is the captured group | Resolved (verified) | ## Open question for follow-up **Q1 — Should Sprint 15 add component-level tests for `` (focus, arrow keys, dialog a11y)?** Default: yes, future sprint. Adds `@testing-library/user-event`. ~1.5 hr.