// @vitest-environment node import { describe, expect, it } from 'vitest' import { createElement } from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { Sheet } from '@/components/documents/Sheet' import type { RenderedSection } from '@/lib/sections/render' import type { TemplateField } from '@/lib/templates/fields' /** * Das Blatt wirklich rendern, nicht nur seine Vorlage prüfen. * * Es ist ein Formular, das gedruckt weitergereicht wird: dass ein Feld in der * falschen Zelle landet oder der Klassifizierungsbalken grau bleibt, fällt auf * Papier auf und hier vorher. */ function abschnitt( field: TemplateField, value: unknown, options: { visible?: boolean; requiredLevel?: number } = {}, ): RenderedSection { const gemeinsam = { fieldKey: field.key, field, version: 1, updatedAt: null, updatedById: null, } if (options.visible === false) { return { ...gemeinsam, visible: false, value: { redacted: true, requiredLevel: options.requiredLevel ?? 4 }, } } return { ...gemeinsam, visible: true, value, segments: null } } const STUFEN = [ { level: 1, name: 'Vertraulich', colorHex: '#1e3a5f' }, { level: 4, name: 'Streng Geheim', colorHex: '#450a0a' }, ] function blatt(sections: RenderedSection[]): string { return renderToStaticMarkup( createElement(Sheet, { sections, title: 'SCP Foundation', subtitle: 'Personalakte // Personalabteilung', documentNumber: 'PA-2026-0042', issuedAt: new Date('2026-03-14T10:00:00Z'), foundationLevels: STUFEN, footerNote: 'Nur für autorisiertes Personal.', }), ) } describe('Aktenblatt, gerendert', () => { const felder: TemplateField[] = [ { key: 'foundation_klassifikation', label: 'Klassifikation', type: 'select', slot: 'classbar' }, { key: 'lichtbild', label: 'Lichtbild', type: 'photo', rows: 2 }, { key: 'name', label: 'Name', type: 'shorttext', span: 2 }, { key: 'vorname', label: 'Vorname', type: 'shorttext', span: 2 }, { key: 'alter', label: 'Alter', type: 'shorttext', span: 4 }, { key: 'wissensfreigabe', label: 'Wissensfreigabe', type: 'shorttext', span: 5, emphasis: true }, { key: 'standortleiter', label: 'Standortleiter', type: 'signature', span: 5, dateKey: 'standortleiter_datum', }, { key: 'standortleiter_datum', label: 'Datum', type: 'date' }, ] const gefuellt = [ abschnitt(felder[0]!, '4'), abschnitt(felder[1]!, null), abschnitt(felder[2]!, 'Vogt'), abschnitt(felder[3]!, 'Marlene'), abschnitt(felder[4]!, '38'), abschnitt(felder[5]!, 'Stufe 3 / Projekt Nachtblende'), abschnitt(felder[6]!, 'M. Rutkowski'), abschnitt(felder[7]!, '14.03.2026'), ] it('setzt die Werte in das Raster und zeichnet den Balken in der Stufenfarbe', () => { const html = blatt(gefuellt) expect(html).toContain('PA-2026-0042') expect(html).toContain('Vogt') expect(html).toContain('Marlene') // Der Balken trägt die Farbe und den Namen der gewählten Stufe. expect(html).toContain('#450a0a') expect(html).toContain('Streng Geheim') expect(html).toContain('Level 4') // Das Lichtbild spannt über die angegebene Zeilenzahl. React schreibt das // Attribut in gemischter Schreibweise; HTML wertet sie ohnehin nicht. expect(html.toLowerCase()).toContain('rowspan="2"') // Die Unterschrift trägt das Datum mit, das Datum hat keine eigene Zelle. expect(html).toContain('M. Rutkowski') // Zweistellig wie auf dem Formular, und nur einmal: das Datum der // Unterschrift hat keine eigene Zelle. expect(html.match(/14\.03\.2026/g)).toHaveLength(2) expect(html).not.toContain('14.3.2026') }) it('lässt den Balken neutral, wenn keine Klassifikation gesetzt ist', () => { const ohne = [abschnitt(felder[0]!, null), ...gefuellt.slice(1)] const html = blatt(ohne) // Kein erfundener Anschein von Einstufung. expect(html).not.toContain('#450a0a') expect(html).toContain('#333333') expect(html).toContain('Ohne Klassifikation') }) it('zeigt statt eines gesperrten Feldes einen Balken, nie den Wert', () => { const gesperrt = gefuellt.map((section) => section.fieldKey === 'wissensfreigabe' ? abschnitt(felder[5]!, null, { visible: false, requiredLevel: 4 }) : section, ) const html = blatt(gesperrt) expect(html).not.toContain('Nachtblende') expect(html).toContain('redaction') // Die Beschriftung bleibt: dass es das Feld gibt, ist kein Geheimnis. expect(html).toContain('Wissensfreigabe') }) })