docs(test): make Given/When/Then the default BDD structure (WP-71)

bdd.mdx previously banned "Given/When/Then ceremony" outright, which
directly contradicted WP-70's own acceptance tests (Acceptance/
BesluitLifecycleTests.cs already used // Given/When/Then comments) and
the backend's organically-evolved PascalCase_snake_sentence convention,
which the doc gave zero guidance for. Reverses that rule: every test is
now structured Given -> When -> Then, with a genuinely empty phase
omitted rather than faked; present-tense declarative naming and the
one-behaviour-per-test rule are unchanged. ADR-0006 gets a cross-reference
so both documents agree everywhere, not just in acceptance tests.

Also closes out the doc's other named-but-unenforced rules found by the
audit: fixes the 5 files asserting rendered $localize copy instead of
the underlying tag/message-id (the compliant pattern already existed in
werkvoorraad-item-view.spec.ts), splits the multi-behaviour titles the
doc itself calls a smell (";", "and", "/"), and fixes bdd.mdx's own false
citation of registratie-wizard.machine.spec.ts as "one transition per
test" by actually splitting that test into one-transition-per-test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-18 20:25:30 +02:00
co-authored by Claude Sonnet 5
parent 306d002221
commit 3652ff8d3f
9 changed files with 247 additions and 34 deletions
@@ -27,32 +27,49 @@ describe('statusLabel', () => {
describe('detailRows', () => {
it('lists soort/status/referentie/eigenaar/ingediend', () => {
// Given a case InBehandeling.
// When its detail rows are derived...
const rows = detailRows({
...base,
status: { tag: 'InBehandeling', referentie: 'R1', manual: false },
});
const values = rows.map((r) => r.value);
// Then the type label (via TYPE_LABELS, not a literal), the reference, and the
// owner all appear as rows.
expect(values).toContain(TYPE_LABELS.herregistratie);
expect(values).toContain('R1');
expect(values).toContain(base.owner);
expect(rows.length).toBe(5);
});
it('adds a reden row for Afgewezen and MeerInfoGevraagd only', () => {
// One behaviour ("a reden row is added exactly for the two statuses that carry a
// reden") checked as a truth table over three statuses — kept together per
// bdd.mdx's "truth-table of one rule" exception, rather than split apart.
//
// `reden` itself is raw domain data (a free-text field on the status union, not an
// enum), passed through `detailRows` unchanged and never wrapped by `$localize` —
// there is no reason-code/tag to assert on instead; the value under test IS the
// string the Given supplied, so checking it reappears in the Then is a
// pass-through check, not a translated-copy assertion.
it('a reden row is present only for Afgewezen and MeerInfoGevraagd', () => {
// Given three cases: rejected, more-info-requested, and approved.
// When their detail rows are derived...
const afgewezen = detailRows({
...base,
status: { tag: 'Afgewezen', referentie: 'R1', reden: 'Onvoldoende uren' },
});
expect(afgewezen.length).toBe(6);
expect(afgewezen.map((r) => r.value)).toContain('Onvoldoende uren');
const meerInfo = detailRows({
...base,
status: { tag: 'MeerInfoGevraagd', referentie: 'R1', reden: 'Diploma ontbreekt' },
});
expect(meerInfo.length).toBe(6);
const goedgekeurd = detailRows({ ...base, status: { tag: 'Goedgekeurd', referentie: 'R1' } });
// Then only the rejected and more-info-requested cases gain a reden row (carrying
// the reason through unchanged); the approved case does not.
expect(afgewezen.length).toBe(6);
expect(afgewezen.map((r) => r.value)).toContain('Onvoldoende uren');
expect(meerInfo.length).toBe(6);
expect(goedgekeurd.length).toBe(5);
});
});
@@ -31,7 +31,13 @@ describe('parseBeoordelingStatus', () => {
);
});
it('rejects a missing status, unknown tag, and wrong-typed fields', () => {
// One behaviour ("rejects a malformed status") checked over several malformed
// shapes — a loop asserting one rule over many inputs, kept together per bdd.mdx.
it('rejects a malformed status', () => {
// Given a status that is missing entirely, has an unknown tag, or is missing a
// required field for its tag.
// When each is parsed...
// Then all are rejected.
expect(parseBeoordelingStatus(undefined).ok).toBe(false);
expect(parseBeoordelingStatus({ tag: 'Concept' } as never).ok).toBe(false);
expect(parseBeoordelingStatus({ tag: 'InBehandeling', referentie: 'BIG-1' }).ok).toBe(false);
@@ -50,7 +56,13 @@ describe('parseBeoordelingView', () => {
expect(r.value.canBesluiten).toBe(true);
});
it('rejects a missing owner, bad type, missing decisions, and non-objects', () => {
// One behaviour ("rejects a malformed view") checked over several malformed
// shapes — a loop asserting one rule over many inputs, kept together per bdd.mdx.
it('rejects a malformed view', () => {
// Given a view that is a non-object, missing the owner, has an unknown aanvraag
// type, or is missing decisions.
// When each is parsed...
// Then all are rejected.
expect(parseBeoordelingView(null).ok).toBe(false);
expect(
parseBeoordelingView({ ...view, aanvraag: { ...view.aanvraag, owner: undefined } }).ok,