test(admin): Sessions + SessionForm — fill gaps + lifecycle test

Drives Sessions.tsx and SessionForm.tsx to 100% stmts/branch/funcs/lines.

Added gap tests: sessions error state, in-filter empty message, row
placeholders (missing worker/activity/type/date), frozen worked-time for a
paused active session, edit-form open, and form cancel; SessionForm create/edit
validation paths, non-400 error surfacing, null serialization (empty zooltype +
notitie, status=active clears end_time), pair/pause floors, and the defensive
no-session edit guard.

Lifecycle test (Sessions.test.tsx): open create form -> fill a valid manual
session -> it appears in the list -> edit the row -> the change persists, driven
by a stateful apiFetch mock.

Annotated one unreachable branch: localToIso's Number.isNaN guard is c8-ignored
(a datetime-local input only yields a valid datetime or empty).

No bugs found; no behavior changes.
This commit is contained in:
Bas van Rossem
2026-07-23 14:44:32 +02:00
parent 77db308e41
commit e7eefe8f62
4 changed files with 388 additions and 1 deletions

View File

@@ -407,3 +407,46 @@ Guaranteed behaviors:
- Lifecycle (`src/screens/Live.test.tsx`): active-sessions data renders a running card and a paused
card side by side, with exactly the paused session showing the `Gepauzeerd` badge and its
`Pauze 00:01:30` total.
## Sessions + SessionForm — admin client
Src covered (admin `apps/admin`): `src/screens/Sessions.tsx` and `src/components/SessionForm.tsx` —
both at 100% stmts/branch/funcs/lines. One `c8 ignore` with a reason: `localToIso`'s
`Number.isNaN` guard is unreachable because a `datetime-local` input only ever yields a valid
datetime string or `''`.
Guaranteed behaviors:
- Sessions renders one `.sessions-row` per session (worker, activity + insole-type chip, worked
time, start date, status badge). Row worked-time is server-authoritative: completed/discarded rows
use `duration_seconds`; an active row counts `(base − start) − paused_seconds` where `base` is the
frozen `paused_at` moment while paused (else `now`), floored at 0.
- The `Status` filter narrows the list to `all`/`active`/`completed`/`discarded`; the empty message is
`Nog geen sessies.` when there are truly none and `Geen sessies in dit filter.` when the filter
hides every row.
- The query's error state renders `Kon sessies niet laden.` (no list); the loading state renders
`Laden…`; a non-array payload is treated as zero sessions.
- Row placeholders: a missing `user_name` renders `Onbekend` (and the edit aria-label uses
`onbekend`), a missing `activity_name` renders `Onbekende handeling`, a null `insole_type` omits the
type chip, and an unparseable `start_time` renders an empty date cell; the `Pauze <time>` line shows
only when `paused_seconds > 0`.
- `Stop` and `Annuleer` appear only on `active` rows and call `POST /api/admin/sessions/:id/stop` and
`.../discard`; the `✎` edit button appears on every row and opens the shared form in edit mode.
- `+ Nieuwe registratie` opens the shared form in create mode; cancelling (`Annuleren`) closes it and
returns to the list.
- SessionForm create mode shows the medewerker picker (hidden in edit mode) and POSTs a
`CreateManualSessionInput`; edit mode hides the picker, shows a `Status` select, prefills from the
session, and PUTs an `AdminUpdateSessionInput`.
- Create validation: an empty handeling/starttijd shows `Vul handeling en starttijd in.` and blocks the
POST; a filled handeling+starttijd but missing medewerker/zooltype/eindtijd shows
`Vul medewerker, zooltype, start- en eindtijd in.`.
- Serialization: the notitie is trimmed (empty → `null`); in edit mode an empty zooltype serializes to
`insole_type: null` and status `active` serializes `end_time: null`; `Aantal paren` floors at 1 and
`Pauze (minuten)` floors at 0 on invalid/empty input.
- The gewerkt preview renders `end − start − paused` (`—` when the span is indeterminate); a
prefilled unparseable date falls back to an empty field; a 400 from the API surfaces
`Controleer de ingevoerde gegevens …` while any other failure surfaces `Opslaan mislukt …`.
- Edit mode without a session is a defensive no-op on submit (no PUT is issued).
- Lifecycle (`src/screens/Sessions.test.tsx`): open the create form → fill a valid manual session →
submit → it appears in the list; then edit that row (change the handeling) → the change persists in
the refreshed list.