import { inflateSync } from 'node:zlib'; import { describe, expect, it } from 'vitest'; import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { validateProject } from '@/domain/validation'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { toWinAnsi } from '@/render/pdfSurface'; /* * Befund 42: Die Erklaerung "Barrierefreiheit dieser Datei" beschrieb * Zeichnung und Signalzeitentabelle auch dann, wenn der Abschnitt * "Signalzeitenplan" abgewaehlt war. drawBarrierefreiheit bekam * options.includePlan nicht uebergeben. * * Was der Anwender sonst erlebt: Er waehlt in der Ansicht "Ausgabe" das * Kaestchen "Signalzeitenplan" ab und erzeugt die Unterlage. Die Uebersicht * der Bestandteile weist den Abschnitt zu Recht als abgewaehlt aus - wenige * Seiten spaeter sagt die Erklaerung zur Barrierefreiheit trotzdem, die * Signalbilder der Zeichnung staenden vollstaendig in der Tabelle * "Signalzeiten je Signalgruppe" und "ein Absatz unter der Zeichnung verweist * auf beides". Beides enthaelt die Datei nicht. Eine oeffentliche Stelle * stuetzt auf diesen Abschnitt ihre Erklaerung nach Paragraf 12b BGG. */ const STICHTAG = new Date('2026-01-01T12:00:00Z'); function gedruckt(text: string): string { return toWinAnsi(text); } function entpacke(roh: string, von: number): string | null { const anfang = roh.indexOf('stream', von); if (anfang < 0) return null; const start = anfang + (roh.startsWith('stream\r\n', anfang) ? 8 : 7); const ende = roh.indexOf('endstream', start); if (ende < 0) return null; try { return inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); } catch { return null; } } function volltext(bytes: Uint8Array): string { const roh = new TextDecoder('latin1').decode(bytes); const stuecke: string[] = []; for (const treffer of roh.matchAll(/\/Type \/Page\b[^>]*?\/Contents (\d+) 0 R/g)) { const objekt = roh.indexOf(`\n${treffer[1]!} 0 obj`); const inhalt = objekt < 0 ? null : entpacke(roh, objekt); if (inhalt === null) continue; for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) { stuecke.push(t[1]!.replace(/\\([()\\])/g, '$1')); } } return stuecke.join(' ').replace(/\s+/g, ' '); } function ausdruck(mitPlan: boolean): string { const projekt = createStandardIntersectionProject('Musterkreuzung', STICHTAG); const plan = buildSignalPlan(projekt); return volltext( buildProjectPdf( projekt, plan, validateProject(projekt, plan, STICHTAG), { ...DEFAULT_PDF_OPTIONS, includePlan: mitPlan }, STICHTAG, ), ); } describe('Lueckenausweis bei abgewaehltem Signalzeitenplan', () => { it('verspricht keine Textfassung der Zeichnung, wenn die Ausgabe keine Zeichnung enthaelt', () => { const ohnePlan = ausdruck(false); // Vorbedingung: Die Uebersicht der Bestandteile weist den Abschnitt - und // damit Zeichnung und Signalzeitentabelle - als abgewaehlt aus. expect(ohnePlan).toContain(gedruckt('Signalzeitenplan (Grafik und Signalzeiten)')); expect(ohnePlan).toContain(gedruckt('abgewählt')); expect(ohnePlan).not.toContain( gedruckt('Die Signalbilder der Zeichnung stehen vollständig in Textform'), ); expect(ohnePlan).not.toContain(gedruckt('Ein Absatz unter der Zeichnung verweist auf beides')); }); it('nennt in der Merkmalstabelle keine Textfassung einer fehlenden Zeichnung', () => { const ohnePlan = ausdruck(false); expect(ohnePlan).toContain(gedruckt('Alternativtext der Zeichnung')); expect(ohnePlan).not.toContain(gedruckt('gleichwertige Textfassung: Tabelle')); expect(ohnePlan).not.toContain(gedruckt('Textfassung der Signalbilder: Tabelle')); }); it('sagt nicht, wie eine Zeichnung vorgelesen wird, die es nicht gibt', () => { const ohnePlan = ausdruck(false); expect(ohnePlan).not.toContain( gedruckt('Die Zeichnung des Signalzeitenplans erscheint als lose Folge von Zahlen'), ); expect(ohnePlan).not.toContain(gedruckt('genügt die Signalzeitentabelle als Textfassung')); }); it('benennt die Luecke, statt sie zu verschweigen', () => { const ohnePlan = ausdruck(false); expect(ohnePlan).toContain( gedruckt('Abschnitt „Signalzeitenplan" wurde für diese Ausgabe abgewählt'), ); }); it('haelt die Zusagen unveraendert, solange der Signalzeitenplan enthalten ist', () => { const mitPlan = ausdruck(true); expect(mitPlan).toContain( gedruckt('Die Signalbilder der Zeichnung stehen vollständig in Textform'), ); expect(mitPlan).toContain(gedruckt('Ein Absatz unter der Zeichnung verweist auf beides')); expect(mitPlan).toContain( gedruckt('Die Zeichnung des Signalzeitenplans erscheint als lose Folge von Zahlen'), ); }); });