import { describe, it, expect } from 'vitest'; import { RichTextBlock } from '@shared/kernel/rich-text'; import { PlaceholderDef, lintPlaceholders, severityOf } from './placeholders'; const valid: PlaceholderDef[] = [ { key: 'naam', label: 'Naam', autoResolvable: true }, // clean when used { key: 'reden', label: 'Reden', autoResolvable: false }, // manual → unresolved-at-send { key: 'oud_veld', label: 'Oud veld', autoResolvable: true, deprecated: true }, { key: 'niet_invulbaar', label: 'Niet invulbaar', autoResolvable: true, fillable: false }, ]; const withPlaceholder = (key: string): RichTextBlock => ({ paragraphs: [{ nodes: [{ type: 'placeholder', key }] }], }); const withText = (text: string): RichTextBlock => ({ paragraphs: [{ nodes: [{ type: 'text', text }] }], }); describe('lintPlaceholders', () => { it('clean content (auto-resolvable, fillable, current key) yields no diagnostics', () => { expect(lintPlaceholders(withPlaceholder('naam'), valid, 'b1')).toEqual([]); expect(lintPlaceholders(withText('gewone tekst'), valid, 'b1')).toEqual([]); }); it('flags an unknown key as an error', () => { const [d] = lintPlaceholders(withPlaceholder('onbekend'), valid, 'b1'); expect(d.code).toBe('unknown-placeholder'); expect(d.severity).toBe('error'); expect(d.placeholderKey).toBe('onbekend'); expect(d.location).toEqual({ blockId: 'b1', paragraphIndex: 0, nodeIndex: 0 }); }); it('flags a not-fillable key as an error', () => { const [d] = lintPlaceholders(withPlaceholder('niet_invulbaar'), valid, 'b1'); expect(d.code).toBe('not-fillable'); expect(d.severity).toBe('error'); }); it('flags a deprecated key as a warning', () => { const [d] = lintPlaceholders(withPlaceholder('oud_veld'), valid, 'b1'); expect(d.code).toBe('deprecated'); expect(d.severity).toBe('warning'); }); it('flags a manual placeholder as unresolved-at-send (warning)', () => { const [d] = lintPlaceholders(withPlaceholder('reden'), valid, 'b1'); expect(d.code).toBe('unresolved-at-send'); expect(d.severity).toBe('warning'); }); it('flags raw braces in text as malformed (paste safety net)', () => { const [d] = lintPlaceholders(withText('Beste {{naam'), valid, 'b1'); expect(d.code).toBe('malformed'); expect(d.severity).toBe('error'); expect(d.placeholderKey).toBeUndefined(); }); it('returns diagnostics in document order across paragraphs/nodes', () => { const content: RichTextBlock = { paragraphs: [ { nodes: [{ type: 'placeholder', key: 'onbekend' }] }, { nodes: [ { type: 'text', text: 'ok' }, { type: 'placeholder', key: 'reden' }, ], }, ], }; const codes = lintPlaceholders(content, valid, 'b1').map( (d) => `${d.code}@${d.location.paragraphIndex}.${d.location.nodeIndex}`, ); expect(codes).toEqual(['unknown-placeholder@0.0', 'unresolved-at-send@1.1']); }); it('severityOf maps each code to its policy', () => { expect(severityOf('malformed')).toBe('error'); expect(severityOf('unknown-placeholder')).toBe('error'); expect(severityOf('not-fillable')).toBe('error'); expect(severityOf('deprecated')).toBe('warning'); expect(severityOf('unresolved-at-send')).toBe('warning'); }); });