4.2.6 Story tests — bulk/create service + selection/drag/create components + at-scale backlog-grooming E2E
Estimate: 24m · Depends on: 4.2.2, 4.2.3, 4.2.4, 4.2.5
The closing test subtask — the same split Stories 4.1.5 / 4.5.4 used: this Story's OWN service/component tests + a focused at-scale backlog E2E, against the real Postgres (the project convention: no mocks except getSession; tests/helpers/db.ts truncation). Story 4.1's single-issue association/rank + bounded reads are already proven in 4.1.5; this proves the bulk composition + the grooming UI on top.
Service (vitest, real Postgres). 4.2.2: bulkAssignToSprint / bulkMoveToBacklog move the whole batch in ONE transaction (a forced mid-batch failure rolls back — assert NONE moved, not a partial set), the same-project guard rejects a batch with a cross-project member (atomic), the batch bound rejects oversize, an empty itemIds is a guarded no-op, and each move records a 1.4.6 revision; createBacklogIssue creates + appends a backlog_rank + optionally assigns in one tx and records the revision.
Component. The container/list render + bounded count + virtualized window (DOM stays bounded on a large seed); the selection model (click / shift-range / ⌘-toggle, survives lazy-load); drag reorder (single-row rank write) + drag-into-sprint (assign + counts) + snap-back on failure; inline create into backlog + sprint; the ⋯ menu actions; the empty/loading/error states.
E2E (Playwright) tests/e2e/backlog.spec.ts. A real grooming session against a seeded project with sprints + a backlog:
- Render —
/backlogshows the sprint container(s) over the ranked backlog; the Backlog nav item leads here. - Rank — drag a row between two neighbours; its order changes and survives reload (single-row rank).
- Assign — drag a backlog row into a sprint; it leaves the backlog, the sprint count increments; drag it back; it restores in rank order.
- Bulk — multi-select two rows and Move to sprint; both move in one action; (a forced failure path leaves none moved).
- Create — inline-create an issue into the backlog and into a sprint.
- Scale (finding #57) — against
pnpm db:seed:large, the backlog renders a bounded first page + count, lazy-loads on scroll (DOM bounded), and drag still works out of the virtualized list.
Defers nothing to a later test story (4.2 is self-contained); the Scrum-board + combined at-scale Scrum journey is Stories 4.5.4 / 4.7, not here.
Acceptance criteria
pnpm test(real Postgres) covers 4.2.2 (atomic bulk move + rollback-on-failure, same-project batch guard, batch bound, empty-input no-op, create-into-sprint, 1.4.6 revisions) and the components (container/list/bounded-count/virtualized window, selection model incl. shift-range + survive-lazy-load, drag reorder/assign/snap-back, inline create,⋯menu, states).pnpm test:e2e --grep backlogruns green over the real stack: render + nav, drag-reorder (survives reload), drag-assign + count update + restore, atomic multi-select bulk move, inline create into backlog + sprint, and the at-scale bounded/virtualized/lazy-load grooming (DOM bounded, drag works) ondb:seed:large.pnpm test:coveragekeeps the Story-4.2 service/route + component files ≥90% branch/fn/line (the CI coverage gate); the suite uses the real-Postgres harness + the single allowedgetSessionmock.
Context refs
tests/e2e/board-ui.spec.ts(3.2.7) +tests/e2e/board-swimlanes.spec.ts(3.3.7) — the dnd-kit + virtualized-list E2E patterns (drag, drop, scroll) to build the backlog E2E on;tests/helpers/db.ts(real-Postgres truncation + large-seed fixture)- Story 4.2.2 (bulk/create service) + 4.2.3/4.2.4/4.2.5 (the UI under test); Story 4.1.5 — the single-issue association/rank/bounded-read tests this builds atop (no duplication)
prodect-core-coverage-gate(≥90% per-file; empty-input guards need a direct test) +prodect-core-local-postgres(sandbox PG@5433 + Playwright) +motir-core/CLAUDE.md(real-Postgres, no mocks, singlegetSessionmock) + theprodect-e2e-selector-gotchas/prodect-e2e-run-harness-oomlessons