- fix: wire remark-gfm into addon-docs so GFM pipe tables in *.mdx render (previously raw text in cibg-gaps/layers/atomic-design docs) - add src/docs/i18n.mdx (Foundations/Internationalization): the $localize locale seam + how to test languages without coupling to copy - add src/docs/testing.mdx (Foundations/Testing strategy): per-layer spec matrix, house style, Storybook a11y gate, GREEN gate - add .claude/skills/test-strategy skill Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.6 KiB
2.6 KiB
name, description
| name | description |
|---|---|
| test-strategy | Place tests the house way — Vitest specs co-located by layer (pure domain, no TestBed; parse* trust boundaries; thin UI via Storybook a11y). Use whenever adding a spec or deciding what to test. |
Test strategy (test where it's pure)
Push logic down to where it's pure, test it there directly, keep the layers above thin.
No TestBed for domain. Never assert on user-facing copy.
Rules
domain/+ any pure logic → required spec. Reducers, combinators,visibleSteps, policies, parsers. Import the function and call it — no Angular, noTestBed.- Value-object parser → happy path + normalisation + each rejection. Assert on the
Resultdiscriminant (.ok) and the parsed value, not the error message. infrastructure/parse*(trust boundary) → required spec. Accept a valid DTO; rejectnull,{}, and malformed shapes. Name itdescribe('… (trust boundary)').application/stores/commands → spec the pure reduce + optimistic begin→confirm/rollback + the commandResult.ui/→ Storybook story, not a component test. Axe runs on every story; add aplayonly for wiring axe can't see.- Never assert on
$localizecopy. It changes per locale/edit — assert on theResult, the value object, or the message id.
Skeleton
Co-locate *.spec.ts next to the unit, in the same layer folder:
<context>/domain/<thing>.spec.ts # pure — no TestBed
<context>/domain/value-objects/<vo>.spec.ts # parser: ok + normalise + each reject
<context>/infrastructure/<x>.adapter.spec.ts# parse* trust boundary
<context>/application/<store|command>.spec.ts
Minimal parser spec:
import { describe, it, expect } from 'vitest';
import { parseThing } from './thing';
describe('parseThing', () => {
it('accepts + normalises', () => {
const r = parseThing(' raw ');
expect(r.ok).toBe(true);
if (r.ok) expect(r.value).toBe('RAW');
});
it('rejects malformed', () => {
expect(parseThing('').ok).toBe(false); // asserts the tag, not the copy
});
});
Worked examples
src/app/registratie/domain/value-objects/postcode.spec.ts— parser style.src/app/registratie/infrastructure/brp.adapter.spec.ts— trust boundary (null/{}).src/app/registratie/domain/registratie-wizard.machine.spec.ts— pure reducer.
Verify
npm test # Vitest (ng test — no vitest.config)
npm run test-storybook # axe over every story (UI a11y gate)
cd backend && dotnet test # backend rule + endpoint + golden tests