ESLint blanket-exempted every *.spec.ts from the any ban, and no gate type-checked spec files at all (ng test is transpile-only), so a wrong cast in a test could never fail the build. 76 `as any` + 12 `as Extract<>` state-narrowing casts in the three biggest wizard specs read one variant's fields off a whole-union value: if the reducer returned the wrong variant, the assertion silently read undefined instead of failing. expectTag(state, tag) (libs/shared/src/testing/expect-tag.ts) asserts and narrows in one call, replacing every one of those casts. Removes the spec-file any exemption, adds `npm run typecheck` (tsc --noEmit over each project's tsconfig.spec.json) to CI, and forbids production code from importing libs/shared/src/testing via dependency-cruiser. Backend: AanvraagBuilder now models ZaakUrl (closing the last post-Build() mutation) and guards AtStep; null-forgiving `!` on endpoint assertions replaced with Assert.NotNull so a null DTO fails by name, not NullReferenceException. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
164 lines
5.1 KiB
TypeScript
164 lines
5.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { expectTag } from '@shared/testing/expect-tag';
|
|
import { OrgTemplate, OrgTemplateAdminView } from './org-template';
|
|
import { OrgTemplateState, reduce } from './org-template.machine';
|
|
import { DocumentCategory } from '@shared/upload/upload.machine';
|
|
|
|
const template: OrgTemplate = {
|
|
subOrgId: 'cibg-registers',
|
|
orgName: 'CIBG',
|
|
returnAddress: 'Postbus 1\n2500 AA Den Haag',
|
|
footerContact: 'info@cibg.nl',
|
|
footerLegal: 'CIBG is onderdeel van VWS',
|
|
signatureName: 'A. de Vries',
|
|
signatureRole: 'Hoofd Registratie',
|
|
signatureClosing: 'Met vriendelijke groet,',
|
|
margins: { topMm: 25, rightMm: 20, bottomMm: 25, leftMm: 20 },
|
|
version: 3,
|
|
};
|
|
|
|
const view = (over: Partial<OrgTemplateAdminView> = {}): OrgTemplateAdminView => ({
|
|
draft: template,
|
|
publishedVersion: 3,
|
|
history: [],
|
|
unsentBriefs: 2,
|
|
...over,
|
|
});
|
|
|
|
const loaded = (): OrgTemplateState =>
|
|
reduce({ tag: 'loading' }, { tag: 'DraftLoaded', view: view() });
|
|
|
|
const logoCategory: DocumentCategory = {
|
|
categoryId: 'org-logo',
|
|
label: 'Logo',
|
|
description: '',
|
|
required: false,
|
|
acceptedTypes: ['image/png'],
|
|
maxSizeMb: 2,
|
|
multiple: false,
|
|
allowPostDelivery: false,
|
|
};
|
|
|
|
describe('org-template.machine', () => {
|
|
it('DraftLoaded moves to loaded with the draft, clean', () => {
|
|
const s = expectTag(loaded(), 'loaded');
|
|
expect(s.draft.orgName).toBe('CIBG');
|
|
expect(s.subOrgId).toBe('cibg-registers');
|
|
expect(s.unsentBriefs).toBe(2);
|
|
expect(s.dirty).toBe(false);
|
|
});
|
|
|
|
it('LoadFailed carries the reason', () => {
|
|
const s = reduce({ tag: 'loading' }, { tag: 'LoadFailed', reason: 'boom' });
|
|
expect(s).toEqual({ tag: 'failed', reason: 'boom' });
|
|
});
|
|
|
|
it('FieldEdited edits the draft and marks dirty', () => {
|
|
const s = expectTag(
|
|
reduce(loaded(), { tag: 'FieldEdited', field: 'orgName', value: 'CIBG Nieuw' }),
|
|
'loaded',
|
|
);
|
|
expect(s.draft.orgName).toBe('CIBG Nieuw');
|
|
expect(s.dirty).toBe(true);
|
|
});
|
|
|
|
it('MarginEdited edits one edge and marks dirty', () => {
|
|
const s = expectTag(
|
|
reduce(loaded(), { tag: 'MarginEdited', edge: 'topMm', value: 40 }),
|
|
'loaded',
|
|
);
|
|
expect(s.draft.margins.topMm).toBe(40);
|
|
expect(s.draft.margins.leftMm).toBe(20);
|
|
expect(s.dirty).toBe(true);
|
|
});
|
|
|
|
it('DraftSaved clears dirty when the saved draft is the current one', () => {
|
|
const edited = expectTag(
|
|
reduce(loaded(), { tag: 'FieldEdited', field: 'orgName', value: 'X' }),
|
|
'loaded',
|
|
);
|
|
const s = expectTag(reduce(edited, { tag: 'DraftSaved', savedDraft: edited.draft }), 'loaded');
|
|
expect(s.dirty).toBe(false);
|
|
expect(s.draft.orgName).toBe('X');
|
|
});
|
|
|
|
it('DraftSaved keeps dirty when an edit landed during the save round-trip', () => {
|
|
const editing = expectTag(
|
|
reduce(loaded(), { tag: 'FieldEdited', field: 'orgName', value: 'X' }),
|
|
'loaded',
|
|
);
|
|
const savedDraft = editing.draft;
|
|
// a further edit changes the draft reference before the save resolves
|
|
const raced = reduce(editing, { tag: 'FieldEdited', field: 'orgName', value: 'Y' });
|
|
const s = expectTag(reduce(raced, { tag: 'DraftSaved', savedDraft }), 'loaded');
|
|
expect(s.dirty).toBe(true);
|
|
});
|
|
|
|
it('edits are no-ops in non-loaded states', () => {
|
|
expect(
|
|
reduce({ tag: 'loading' }, { tag: 'FieldEdited', field: 'orgName', value: 'x' }),
|
|
).toEqual({
|
|
tag: 'loading',
|
|
});
|
|
});
|
|
|
|
it('a completed logo upload sets logoDocumentId + dirty', () => {
|
|
const withCat = reduce(loaded(), {
|
|
tag: 'Upload',
|
|
msg: { type: 'CategoriesLoaded', categories: [logoCategory] },
|
|
});
|
|
const selected = reduce(withCat, {
|
|
tag: 'Upload',
|
|
msg: {
|
|
type: 'FileSelected',
|
|
categoryId: 'org-logo',
|
|
localId: 'a',
|
|
fileName: 'l.png',
|
|
fileSizeMb: 0.1,
|
|
},
|
|
});
|
|
const done = expectTag(
|
|
reduce(selected, {
|
|
tag: 'Upload',
|
|
msg: { type: 'UploadComplete', localId: 'a', documentId: 'doc-1' },
|
|
}),
|
|
'loaded',
|
|
);
|
|
expect(done.draft.logoDocumentId).toBe('doc-1');
|
|
expect(done.dirty).toBe(true);
|
|
});
|
|
|
|
it('removing the logo clears logoDocumentId + dirty', () => {
|
|
const withLogo = reduce(loaded(), {
|
|
tag: 'Upload',
|
|
msg: { type: 'UploadComplete', localId: 'a', documentId: 'doc-1' },
|
|
});
|
|
const removed = expectTag(
|
|
reduce(withLogo, {
|
|
tag: 'Upload',
|
|
msg: { type: 'UploadRemoved', localId: 'a' },
|
|
}),
|
|
'loaded',
|
|
);
|
|
expect(removed.draft.logoDocumentId).toBeUndefined();
|
|
expect(removed.dirty).toBe(true);
|
|
});
|
|
|
|
it('DraftLoaded (sub-org switch) keeps the loaded logo category, drops uploads', () => {
|
|
const withCat = reduce(loaded(), {
|
|
tag: 'Upload',
|
|
msg: { type: 'CategoriesLoaded', categories: [logoCategory] },
|
|
});
|
|
const switched = expectTag(
|
|
reduce(withCat, {
|
|
tag: 'DraftLoaded',
|
|
view: view({ draft: { ...template, subOrgId: 'cibg-vakbekwaamheid' } }),
|
|
}),
|
|
'loaded',
|
|
);
|
|
expect(switched.upload.categories).toHaveLength(1);
|
|
expect(switched.upload.uploads).toHaveLength(0);
|
|
expect(switched.subOrgId).toBe('cibg-vakbekwaamheid');
|
|
});
|
|
});
|