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'; import type { Lageplan } from '@/domain/geometrie/lageplan'; import type { Project } from '@/domain/model/project'; /** * Das Lageplanblatt in der Unterlage. * * Die Zwischenzeiten dieser Anlage beruhen auf gezeichneten Fahrlinien, und * von diesen Linien stand bis hierher keine einzige im Ausdruck. Der Abschnitt * "Planungsgrundlage" wies die Unterlage nach, auf der gezeichnet wurde - die * Zeichnung selbst fehlte. */ const STICHTAG = new Date('2026-01-01T12:00:00Z'); function pdfText(bytes: Uint8Array): string { const roh = new TextDecoder('latin1').decode(bytes); const teile: string[] = []; const suche = /stream\r?\n/g; let treffer: RegExpExecArray | null; while ((treffer = suche.exec(roh)) !== null) { const start = treffer.index + treffer[0].length; const ende = roh.indexOf('endstream', start); if (ende < 0) continue; let inhalt: string; try { inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); } catch { continue; } for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) { teile.push(t[1]!.replace(/\\([()\\])/g, '$1')); } } return teile.join(' ').replace(/\s+/g, ' '); } const LAGEPLAN: Lageplan = { arbeitsbereiche: [], signalgeber: [], bild: { datenUrl: 'data:image/png;base64,AA==', breite: 400, hoehe: 300, herkunft: 'Luftbild Hessen', geladenAm: '2026-01-01T10:00:00.000Z', }, kalibrierung: { von: { x: 0, y: 0 }, bis: { x: 100, y: 0 }, laengeMeter: 20, herkunft: 'gemessen', }, haltlinien: [ { id: 'h1', name: 'Nord', art: 'zufahrt', armId: null, von: { x: 0, y: 0 }, bis: { x: 0, y: 100 }, }, ], linien: [ { id: 'l1', name: 'K1', mode: 'kfz', movement: 'geradeaus', breiteMeter: 3, punkte: [ { x: 0, y: 50 }, { x: 200, y: 50 }, ], haltlinieId: 'h1', startT: 0.5, signalGroupId: null, }, ], }; function projekt(mitLageplan: boolean): Project { const roh = createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG); return mitLageplan ? { ...roh, lageplan: LAGEPLAN } : roh; } function ausdruck(mitLageplan: boolean): string { const p = projekt(mitLageplan); const plan = buildSignalPlan(p); return pdfText( buildProjectPdf(p, plan, validateProject(p, plan, STICHTAG), DEFAULT_PDF_OPTIONS, STICHTAG), ); } describe('Planunterlage - Lageplan der Fahrlinien', () => { it('druckt das Blatt, wo ein maßstäblicher Lageplan vorliegt', () => { const text = ausdruck(true); expect(text).toContain(toWinAnsi('Lageplan der Fahrlinien')); // Der Maßstabsbalken gehört zum Blatt und wird mitgezeichnet. expect(text).toMatch(/\d+ m/u); }); it('nennt den Maßstabsbalken als maßgebende Angabe', () => { /* * DER WICHTIGSTE SATZ. Ein Blatt, das verkleinert kopiert wird, trägt ein * falsches Verhältnis - und wer daraus einen Räumweg abgreift, bekommt * eine zu kurze Zwischenzeit. Das ist die gefährliche Richtung. */ const text = ausdruck(true); expect(text).toContain(toWinAnsi('MASSGEBEND IST DER MASSSTABSBALKEN')); expect(text).toContain(toWinAnsi('zu kurze Zwischenzeit')); }); it('sagt, dass das Blatt kein Signallageplan ist', () => { const text = ausdruck(true); expect(text).toContain(toWinAnsi('KEIN Signallageplan')); // Und die Bestandteileübersicht führt den bemaßten Signallageplan // unverändert als nicht enthalten - das Blatt tritt nicht an seine Stelle. expect(text).toContain( toWinAnsi('Bemaßter Signallageplan mit Signalgeberstandorten NICHT Bestandteil'), ); }); it('führt eine eigene Zeile in der Bestandteileübersicht', () => { const mit = ausdruck(true); expect(mit).toContain(toWinAnsi('Lageplan der Fahrlinien (maßstäblich, ohne Signalgeber)')); // Und ohne auswertbaren Lageplan sagt dieselbe Zeile, dass es ihn nicht // gibt - eine fehlende Angabe ist kein Nachweis, dass es sie nicht gibt. const ohne = ausdruck(false); expect(ohne).toContain(toWinAnsi('kein maßstäblich auswertbarer Lageplan im Projekt')); expect(ohne).not.toContain(toWinAnsi('MASSGEBEND IST DER MASSSTABSBALKEN')); }); });