Set up frontend test tooling and cover the review workspace #11
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The frontend has no test tooling configured. The review workspace holds the most
intricate client logic in the app — conflict handling, optimistic updates, the
front/back toggle, and tab state — and it is entirely unverified.
Scope
Vitest + React Testing Library setup, plus component tests for the highest-risk UI.
Implementation notes
testscript and make sure itruns under the same Node version CI uses.
QueryClientper test — a shared client leaks cache between tests and producesfalse passes.
block further submission until reload. This is the single most important client
behaviour to pin down, because failing open here means silent data loss.
Done when
npm testruns green locally and in CIReferences
frontend/src/pages/ReviewWorkspace,PhotoBrowserfrontend/src/api/,frontend/src/hooks/useAuth10.1.1.14perAGENTS.mdDone in
baac843. 34 new tests; 50 passing across 6 files.Tooling was already in place
Vitest + RTL + jsdom,
npm testwired and green in CI,src/test/render.tsxgiving a freshQueryClientper test with retries off, andsetup.tsrunningcleanup()+vi.restoreAllMocks()after each. So the work went entirely into the client logic that decides whether a reviewer's work survives.Two defects, both the same shape
1.
patchNotesandpatchFlagshad noonErrorat all.A 409 on either left the reviewer with the editor still open, their text still on screen, and nothing anywhere to say the save had not happened. Notes are where a reviewer records what a relative told them about a photograph — frequently the only surviving record of who is in it — so a reviewer who types it, saves, sees no error and navigates away has lost it. This is exactly the "failing open here means silent data loss" the issue names, and it was on the notes panel rather than the decision form.
2. A conflict did not block further submission.
The issue asks for this explicitly and nothing did it. Every button stayed live, carrying the same
review_versionthe server had already rejected, failing the same way each time — with the reason two panels away in a banner.Conflict handling is now one shared shape (
conflictVersion()+ aConflictPropspair) across the decision form, notes and the rescan flag. The reruns stay enabled: they carry noreview_versionand cannot conflict, so disabling them would let a conflict in one panel silently remove a capability in another for no reason.Three accessibility defects, found by asserting on roles
The issue asks for assertions on accessible roles rather than implementation details, and doing that surfaced:
<label>s were associated with no control at all —getByLabelText("Precision")simply fails, and so does a screen reader<button>s, so which tab is selected was invisible to a screen reader — and the Notes panel has its own History control, makinggetByRole("button", {name: "History"})genuinely ambiguous. Nowrole="tablist"/role="tab"/aria-selected.Coverage
ReviewWorkspacePage.test.tsx(20)null, never-1— #75), blocking, reload restores, ordinary errors stay inline, decision payload/version/disabled-in-flight/rationale cleared, notes dirty-cancel-save + conflict + inline failure, rescan conflict, front/back toggle incl. no-back-scan, tabs, photo-not-found, and evidence-fetch-failure ≠ no-evidence (#78)PhotoBrowserPage.test.tsx(10)Alldrops the parameter rather than sendingstatus=(which the typed enum from #8 would now 422), initial state read from the URL so a filtered view is linkable, active marking, the three empty states kept distinct, grid rendering, click and keyboard navigationApp.test.tsx(4)isLoadingas unauthenticated flashes the sign-in page at every signed-in reviewer on every reload; and that a 401 is not retried, since each retry delays the sign-in page and spends the auth rate limit (#68) on a request the user did not makeAuthGateis now exported so it can be tested without mounting a secondBrowserRouterinside the test's router.Verified load-bearing
Mutation: setting
blocked: falseand deleting the notesonErrorfails exactly the two tests written for them, and nothing else.Done when
npm testruns green locally and in CIQueryClientper test,fetchreset after eachfetchtsc --noEmitclean,npm run buildclean. Run locally in anode:22-slimcontainer matching CI's image, since this machine has no Node.