test(frontend): replay real messages instead of hand-built state literals (WP-70)

Every machine spec redefined its own throwaway fixture helper (editing1/2/3,
editingWith), hardcoding fields like errors: {} that assert against shapes
the reducer may never actually produce. given(reduce, initial)(...msgs)
(libs/shared/src/testing/machine.ts) replaces them by replaying real Msgs
through the real reduce, so a fixture is provably reachable. Adds the same
idiom for value objects (unwrapOk) and RemoteData (loading/success/failure),
plus intake.acceptance.spec.ts as a worked full-journey example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-18 15:31:06 +02:00
co-authored by Claude Sonnet 5
parent 2eea860efe
commit a73a1c6f1e
12 changed files with 264 additions and 56 deletions
+15
View File
@@ -0,0 +1,15 @@
/**
* Test-only DSL for the Elm-store idiom (CLAUDE.md #3). A fixture is built by
* replaying real `Msg`s through the real `reduce` — never by hand-assembling a
* state object field-by-field. That closes off illegal states the reducer would
* never actually produce: if a spec can't reach a state via messages, it can't
* assert on it either.
*
* `given(reduce, initial)` partially applies a machine's reducer + starting
* state; the result is a variadic replay function a spec calls with the exact
* message sequence a real user/flow would send.
*/
export const given =
<S, M>(reduce: (s: S, m: M) => S, initial: S) =>
(...msgs: M[]): S =>
msgs.reduce(reduce, initial);
+13
View File
@@ -0,0 +1,13 @@
import { RemoteData } from '@shared/application/remote-data';
/**
* Test-only constructors for `RemoteData` — one line per variant, so a spec
* builds fixtures through the same tagged-union shape production code renders
* (`foldRemote`/`<app-async>`), never a hand-rolled literal that could drift
* from the real type.
*/
export const loading = <E = never, T = never>(): RemoteData<E, T> => ({ tag: 'Loading' });
export const success = <T, E = never>(value: T): RemoteData<E, T> => ({ tag: 'Success', value });
export const failure = <E, T = never>(error: E): RemoteData<E, T> => ({ tag: 'Failure', error });
+14
View File
@@ -0,0 +1,14 @@
import { Result } from '@shared/kernel/fp';
/**
* Unwrap a `Result` produced by a REAL `parse*` value-object parser, throwing
* if it isn't `ok`. This is the only sanctioned way for a spec to obtain a
* branded value-object type — it closes off the `'garbage' as Postcode` cast
* route, since the only door to the branded type is the parser itself.
*/
export function unwrapOk<E, T>(result: Result<E, T>): T {
if (!result.ok) {
throw new Error(`unwrapOk: expected ok, got error: ${JSON.stringify(result.error)}`);
}
return result.value;
}