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
@@ -1,4 +1,5 @@
import { describe, it, expect } from 'vitest';
import { formatDatumNl } from '@shared/kernel/datum';
import { tasksFromProfile } from './tasks';
import { Registration } from './registration';
@@ -13,34 +14,56 @@ const base: Registration = {
describe('tasksFromProfile', () => {
it('offers herregistratie when the server says eligible, with the formatted deadline', () => {
// Given a registration whose deadline is 2026-12-31.
// When the server says the professional is eligible for herregistratie...
const tasks = tasksFromProfile(base, true);
// Then one task is offered, routed to herregistratie, whose copy carries the
// deadline through the same date formatter the rest of the app uses (this test's
// point IS the date formatting, so the expectation is derived from `formatDatumNl`
// rather than a hardcoded Dutch date literal).
expect(tasks).toHaveLength(1);
expect(tasks[0].to).toBe('/herregistratie');
expect(tasks[0].description).toContain('31 december 2026');
expect(tasks[0].description).toContain(formatDatumNl('2026-12-31'));
});
it('offers nothing when the server says not eligible', () => {
// Given the same registration.
// When the server says the professional is not eligible for herregistratie...
// Then no task is offered.
expect(tasksFromProfile(base, false)).toHaveLength(0);
});
it('surfaces a notice for a suspended registration (independent of eligibility)', () => {
// Given a suspended ("Geschorst") registration, ineligible for herregistratie.
const reg: Registration = {
...base,
status: { tag: 'Geschorst', geschorstTot: '2027-01-01', reden: 'Onderzoek' },
};
// When the tasks are derived...
const tasks = tasksFromProfile(reg, false);
// Then exactly one notice is surfaced, routed to the registration page, carrying
// the raw suspension reason through unchanged (not translated copy — `reden` is
// domain data, passed through as-is).
expect(tasks).toHaveLength(1);
expect(tasks[0].title).toContain('geschorst');
expect(tasks[0].to).toBe('/registratie');
expect(tasks[0].description).toBe('Onderzoek');
});
it('surfaces a notice for a struck-off registration', () => {
// Given a struck-off ("Doorgehaald") registration.
const reg: Registration = {
...base,
status: { tag: 'Doorgehaald', doorgehaaldOp: '2025-01-01', reden: 'Op eigen verzoek' },
};
// When the tasks are derived...
const tasks = tasksFromProfile(reg, false);
// Then exactly one notice is surfaced, routed to the registration page.
expect(tasks).toHaveLength(1);
expect(tasks[0].title).toContain('doorgehaald');
expect(tasks[0].to).toBe('/registratie');
});
});