import { describe, it, expect } from 'vitest'; import { Besluit, LetterBlock, LibraryPassage } from './brief'; import { besluitGuidance, inferSelection, passagesForBesluit, redenenFor } from './besluit'; const block = (t: string): LibraryPassage['content'] => ({ paragraphs: [{ nodes: [{ type: 'text', text: t }] }], }); const p = (over: Partial): LibraryPassage => ({ passageId: over.passageId ?? 'x', scope: 'global', sectionKey: 'kern', label: over.label ?? 'x', content: block('x'), version: 1, ...over, }); const lib: LibraryPassage[] = [ p({ passageId: 'intro', besluit: undefined }), // shared, any besluit p({ passageId: 'pos', besluit: 'positief' }), p({ passageId: 'neg', besluit: 'negatief' }), p({ passageId: 'neg-scholing', besluit: 'negatief', reason: 'onvoldoende_scholing', label: 'Onvoldoende scholing', }), p({ passageId: 'neg-gegevens', besluit: 'negatief', reason: 'onjuiste_gegevens', label: 'Onjuiste gegevens', }), p({ passageId: 'slot-x', sectionKey: 'slot', besluit: undefined }), // not kern → never offered ]; describe('passagesForBesluit', () => { it('positief = shared intro + the positief passage, no negatief/reason passages', () => { const ids = passagesForBesluit(lib, 'positief', []).map((x) => x.passageId); expect(ids).toEqual(['intro', 'pos']); }); it('negatief without redenen = intro + negatief base, but no reason-specific passages', () => { const ids = passagesForBesluit(lib, 'negatief', []).map((x) => x.passageId); expect(ids).toEqual(['intro', 'neg']); }); it('negatief with a reden ticked includes that reason-specific passage only', () => { const ids = passagesForBesluit(lib, 'negatief', ['onvoldoende_scholing']).map( (x) => x.passageId, ); expect(ids).toEqual(['intro', 'neg', 'neg-scholing']); }); it('preserves library order (= reading order)', () => { const ids = passagesForBesluit(lib, 'negatief', [ 'onjuiste_gegevens', 'onvoldoende_scholing', ]).map((x) => x.passageId); expect(ids).toEqual(['intro', 'neg', 'neg-scholing', 'neg-gegevens']); }); it('never offers non-kern passages', () => { expect(passagesForBesluit(lib, 'positief', []).some((x) => x.sectionKey !== 'kern')).toBe( false, ); }); }); describe('redenenFor', () => { it('derives reason checkboxes (code + label) from the negatief reason passages', () => { expect(redenenFor(lib, 'negatief')).toEqual([ { code: 'onvoldoende_scholing', label: 'Onvoldoende scholing' }, { code: 'onjuiste_gegevens', label: 'Onjuiste gegevens' }, ]); }); it('positief has no reason-specific redenen', () => { expect(redenenFor(lib, 'positief')).toEqual([]); }); }); describe('inferSelection', () => { // Build the kern blocks a besluit would produce, then read the selection back off them. const kern = (besluit: Besluit, reasons: string[]): LetterBlock[] => passagesForBesluit(lib, besluit, reasons).map((p, i) => ({ type: 'passage', blockId: `local-${i + 1}`, sourcePassageId: p.passageId, sourceVersion: p.version, content: p.content, edited: false, })); it('round-trips a positief selection', () => { expect(inferSelection(kern('positief', []), lib)).toEqual({ besluit: 'positief', reasons: [] }); }); it('round-trips a negatief selection with redenen (in order)', () => { const blocks = kern('negatief', ['onjuiste_gegevens', 'onvoldoende_scholing']); expect(inferSelection(blocks, lib)).toEqual({ besluit: 'negatief', reasons: ['onvoldoende_scholing', 'onjuiste_gegevens'], // library order }); }); it('an empty kern (nothing chosen) infers no besluit', () => { expect(inferSelection([], lib)).toEqual({ besluit: null, reasons: [] }); }); it('ignores free-text blocks and unknown passage ids', () => { const blocks: LetterBlock[] = [ { type: 'freeText', blockId: 'local-9', content: block('vrij') }, ...kern('positief', []), ]; expect(inferSelection(blocks, lib)).toEqual({ besluit: 'positief', reasons: [] }); }); }); describe('besluitGuidance', () => { it('positief: counts inserted passages, no reden needed (positief has no redenen)', () => { expect(besluitGuidance(lib, 'positief', [])).toEqual({ insertedCount: 2, needsReason: false }); }); it('negatief without a reden: flags that a reden must be chosen', () => { expect(besluitGuidance(lib, 'negatief', [])).toEqual({ insertedCount: 2, needsReason: true }); }); it('negatief with a reden: no longer flags, and the reason passage is counted', () => { expect(besluitGuidance(lib, 'negatief', ['onvoldoende_scholing'])).toEqual({ insertedCount: 3, needsReason: false, }); }); });