Sprint 14 (commit28f3212) adds Vitest + happy-dom + @testing-library/react + @testing-library/jest-dom as devDeps and locks the useOnboarding state-transition contract with 7 unit tests in 25 ms. Lifts the 'no new npm deps' rule for testing-only. Runtime bundle unchanged (503.82 kB). No pre-existing WIP files touched. Sprint 9's bug1562929(onComplete wired to .reset, the inverse op) shipped a post-deploy fix the same day. Sprint 14 prevents recurrence at npm test time. This commit updates the 6 running docs that track sprints: - .agent/plan.md — header changed to 'Active sprint: Sprint 14'; Sprint 14 section (S14.1-S14.4 + Done when + Out of scope) added after the Sprint 13 section. - .agent/context.md — Sprint 14 decisions (D1-D7), open Q1, and file:line references added. - Review/sprint14-verification.md — NEW: deploy + 7-case test table + verification commands + Case 7 S9-bug-catch proof + 5-risk table + open Q1 (component-level tests). - Review/ui-nielsen-audit.md — Sprint 14 status block (T7.1-T7.3) added after the Sprint 13 block. - fix-ui-audit.md — Sprint 14 section (T7.1-T7.5) added after the Sprint 13 section. - Review/handoff-ui-audit.md — Batch J line in deploy list, Sprint 14 section after Sprint 13, TL;DR Sprint 14 line, Last-updated footer updated. (Did NOT replace the Sprint 7/8/TL;DR/environment-quirks/active-risks content that was at the bottom of the file — that material is preserved unchanged.) - docs/HANDOFF.md — Sprint 14 section added after Sprint 13, Last-updated footer updated. All 6 docs now reflect Sprint 14. §Future backlog remaining: F9-full (local Ollama model pull on the host). Q4 (Vitest) is now closed. Sprint 14 is the smallest sprint in the cycle (devDeps + 1 test file + 2 config files + 2 scripts) and the first that adds a test infrastructure layer.
4.9 KiB
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
useOnboardingstate 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.tsxand 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/vitestmatchers.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
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.
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
<OnboardingTour/>. Focus, arrow keys, dialog a11y, portal positioning. Future sprint. Would add@testing-library/user-eventfor keyboard simulation. - No backend tests. Venv on
docker-willesteris 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 <OnboardingTour/> (focus, arrow keys, dialog a11y)? Default: yes, future sprint. Adds @testing-library/user-event. ~1.5 hr.