test(admin): Activities + app/nav — fill gaps + lifecycle test

Fill coverage gaps and lock in behavior for the Activities screen and
app/nav (Sidebar) domain (plan task B-D6). All four domain src files
(screens/Activities.tsx, api/activities.ts, App.tsx, components/Sidebar.tsx)
now at 100% stmts/branch/funcs/lines.

- Activities.test.tsx: insole-type toggle select/deselect (both branches),
  reduced-subset create, add disabled when no types, edit insole types,
  cancel-without-save, empty-types-defaults-all editor, empty state,
  error-falls-back-to-empty, and the create->edit types->reorder->delete
  lifecycle test.
- Sidebar.test.tsx (new): brand + nav links + email, active-route highlight,
  end-match keeps Live inactive off-root, nav click switches screen + moves
  highlight, Uitloggen calls signOut, email hidden without useMe data.
- Activities.tsx: three /* c8 ignore next */ annotations on the defensive
  early-return guards in handleAdd/handleSave/handleMove (each mirrored by a
  disabled button, so unreachable via the UI). Comment-only, no behavior change.

No bugs found — the domain code was already correct; the tests lock it in.
This commit is contained in:
Bas van Rossem
2026-07-23 15:18:59 +02:00
parent 8a6bc9ea7d
commit 75c5a9f350
4 changed files with 369 additions and 1 deletions

View File

@@ -516,3 +516,45 @@ Guaranteed behaviors:
- Lifecycle (`src/screens/Users.test.tsx`): create a worker via the form → it appears in the refreshed
list as `Werker` → promote it via `Maak admin` → the pill flips to `Beheerder` after the refetch →
reset its password inline (`Opslaan`) → the POST fires and the inline field closes.
## Activities + app/nav — admin client
Src covered (admin `apps/admin`): `src/screens/Activities.tsx`, `src/api/activities.ts`,
`src/App.tsx`, and `src/components/Sidebar.tsx` — all at 100% stmts/branch/funcs/lines. Three
`c8 ignore`s with reasons in `Activities.tsx`: the early-return guards in `handleAdd`, `handleSave`,
and `handleMove` are unreachable via the UI because the button that triggers each is disabled in
exactly that state (`Stap toevoegen` when `!addEnabled`, `Opslaan` when the name/types are empty,
the ↑/↓ move buttons at the list bounds).
Guaranteed behaviors:
- Activities renders the Dutch header (`Handelingen` / `Beheer handelingen per zooltype`), the
add-handeling card, and the current-steps list with a live count (`Huidige stappen (<n>)`).
- The insole-type toggles select/deselect a type (both the remove and re-add branches), reflected via
`aria-pressed`; a new handeling defaults to all three types (`Kurk`, `Berk`, `3D`).
- Creating a handeling POSTs `/api/activities` with the trimmed name and the selected `insole_types`
subset (a deselected type is dropped from the payload); `Stap toevoegen` is disabled while the name
is blank or every type is deselected.
- Editing a handeling opens an inline editor prefilled from the row and PUTs `/api/activities/:id` with
the updated name + `insole_types`; an activity stored with no insole types pre-selects all three in the
editor; `Annuleren` exits edit mode without issuing a PUT.
- Deleting a handeling confirms first (`window.confirm`); DELETE `/api/activities/:id` fires only when
confirmed, and is skipped when cancelled.
- Reorder: ↑/↓ move a row and PUT `/api/activities/reorder` with the swapped id order; ↑ is disabled on
the first row and ↓ on the last.
- The list shows the loading placeholder (`Laden...`) while the query is pending and the empty state
(`Nog geen stappen. Voeg er een toe hierboven.`) when there are none; a query error falls back to the
same empty state (there is no dedicated error UI).
- `api/activities.ts` hooks hit the right path/method — `useActivities` → GET `/api/activities`;
`useCreateActivity` → POST; `useUpdateActivity` → PUT `/:id`; `useDeleteActivity` → DELETE `/:id`;
`useReorderActivities` → PUT `/reorder` — and every mutation invalidates the `['activities']` query on
success.
- App/nav: `App` gates on auth (no token → `Login`; a token → the authed shell with `Sidebar` + routed
screens). `Sidebar` renders the brand, the five nav links, and the signed-in email (hidden when
`useMe` has no data), and `Uitloggen` calls `signOut`.
- `Sidebar` highlights the active route via `nav-link-active` and, with `end` matching on `/`, keeps the
Live link inactive on any non-root route; clicking a nav link switches the rendered screen and moves
the highlight.
- Lifecycle (`src/screens/Activities.test.tsx`): create a handeling → edit its insole types down to a
single type → reorder it above another → delete it, with each step reflected in the refetched list;
the Sidebar test (`src/components/Sidebar.test.tsx`) covers navigation switching screens.