4.6.7 Story tests — burndown series (revision-trail derivation, scope changes, bounded), velocity (last-N + average + low-history), chart primitive a11y + render; focused E2E
Estimate: 26m · Depends on: 4.6.2, 4.6.3, 4.6.4, 4.6.5, 4.6.6
The closing test subtask — Vitest over the real Postgres (the project convention: no mocks except getSession; tests/helpers/db.ts truncation) for the two report aggregates + the chart primitive, plus a focused Playwright E2E. The at-scale combined Scrum journey (drag + WIP + swimlanes + these charts on a large active sprint) is Story 4.7's, not duplicated here; this story's scale proof is the bounded aggregates asserted in 4.6.3 / 4.6.4 against db:seed:large.
Service / unit (vitest, real Postgres). Burndown: seed a sprint with a known window + committed baseline + issues whose 1.4.6 revisions move them to a done status on known days (and one added after start, one reopened) → getBurndownSeries returns the guideline (committed → 0), the stepped actual dropping on completion days + rising on the scope-add day + the reopened add-back, the scope-change events, and an end-point remaining EQUAL to rollupForSprint().remaining; assert the day buckets come from the grouped aggregate (not a full-revision load) and that an unestimated sprint degrades to the issue-count series (no NaN) and an empty sprint is a flat guideline. Velocity: seed several completed sprints (varied committed/completed, one unestimated) → getVelocity returns the last N oldest→newest with committed (baseline) vs completed (rollupForSprint) + the average; assert the LIMIT N bound, the 0-sprint + 1-sprint low-history states, and the cross-workspace denial (finding #26).
Component (vitest/jsdom). The 4.6.2 primitives render given sample series (the step line, the guideline, the grouped bars, the average line) and expose the a11y fallback — assert the data-table / aria summary conveys every series as text+number (finding #35) and that no charting lib is imported.
E2E (Playwright) tests/e2e/charts.spec.ts. Against a seeded project with an active scrum sprint: open /boards → the scrum header shows the in-sprint burndown beside the numeric remaining; move issues to done → the actual line reflects the burn; complete the sprint → the sprint report shows the completed-sprint burndown + the velocity bars + the average; assert the low-history (a fresh project) + unestimated states render without errors.
Acceptance criteria
pnpm test(real Postgres) covers: the burndown derivation (guideline; stepped actual from seeded done-transitions + scope add + reopen; scope-change events; end-point ==rollupForSprint().remaining; unestimated → issue-count/no-NaN; empty → flat; planned-sprint guard; bounded grouped aggregate), the velocity aggregate (last-N oldest→newest; committed vs completed; average;LIMIT Nbound; 0/1-sprint low-history; unestimated 0s), and the cross-workspace denial on both endpoints.- Component tests assert the chart primitives render each series (step / guideline / grouped bars / average) and expose the a11y data-table/
ariafallback (finding #35);package.jsongains no charting dependency. pnpm test:e2e --grep chartsruns green over the real stack: live burndown in the scrum header, completed-sprint burndown + velocity in the report, and the low-history + unestimated states.pnpm test:coveragekeeps the Story-4.6 service/repo/component files ≥90% branch/fn/line (the CI coverage gate); the suite uses the real-Postgres harness + the single allowedgetSessionmock; it does NOT duplicate the at-scale combined Scrum journey (Story 4.7).
Context refs
tests/helpers/db.ts— real-Postgres truncation harness + the fixture pattern (a sprint with a window + committed baseline + issues with seeded 1.4.6 revisions at known dates; several completed sprints for velocity)- Stories 4.6.3 / 4.6.4 (the aggregates under test) + 4.6.2 (the primitive) + 4.6.5 / 4.6.6 (the mounted UI) — the units under test; Story 4.7 — the test story the at-scale combined Scrum journey defers to
tests/e2e/board-scrum.spec.ts(4.5.4) +tests/e2e/sprint-lifecycle.spec.ts(4.4.7) — the sibling E2Es this composes the chart assertions on top ofmotir-core-coverage-gate(≥90% per-file; empty-input guards need a direct test) +motir-core-local-postgres(sandbox PG@5433 + Playwright chromium) +motir-core/CLAUDE.md(real Postgres, no mocks, singlegetSessionmock)