lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests export wegherkunftOhneDatum.test.ts

5,1 KB Rohdatei
tests/export/wegherkunftOhneDatum.test.ts — 127 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { wegherkunftZelle } from '@/services/export/wegherkunft';
4 import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf';
5 import { buildSignalPlan } from '@/domain/plan/signalPlan';
6 import { validateProject } from '@/domain/validation';
7 import { createStandardIntersectionProject } from '@/domain/model/factory';
8 import type { Conflict, Project, Wegnachweis } from '@/domain/model/project';
9 import * as fmt from '@/ui/format';
10
11 /*
12 * Herkunft der Wege ohne ueberlieferten Zeitpunkt (Befund 52).
13 *
14 * `wegherkunftZelle` gab bei herkunft = 'lageplan' und uebernommenAm = null die
15 * blanke Beschriftung "Aus dem Lageplan vermessen" aus. Der Erlaeuterungsabsatz
16 * unter der Tabelle (src/services/export/pdf.ts) sagt daneben unbedingt zu, die
17 * Angabe nenne den Zeitpunkt der Uebernahme - fuer diese Zeile eine falsche
18 * Aussage der Planunterlage ueber ihre eigene Tabelle. Der Zustand entsteht
19 * beim Einlesen einer fremd erzeugten Datei und ist dort ausdruecklich
20 * vorgesehen (parseWegnachweis in src/domain/model/schema.ts sammelt nur eine
21 * Warnung und laesst den Nachweis stehen).
22 *
23 * Der Zeitstempel hat einen benannten Zweck: Er soll gegen
24 * `lageplan.bild.geladenAm` gehalten werden. Fehlt er wortlos, kann ein Pruefer
25 * nicht unterscheiden, ob er nie ueberliefert wurde oder ob der Druck ihn
26 * verschluckt hat. Die Zelle sagt es deshalb selbst.
27 */
28
29 const DATUM = new Date('2026-01-01T12:00:00Z');
30
31 function beziehung(wegnachweis: Wegnachweis): Conflict {
32 const basis = createStandardIntersectionProject('Testknoten', DATUM).conflicts[0];
33 expect(basis, 'Vorbedingung: der Regelknotenpunkt fuehrt Beziehungen').toBeDefined();
34 return { ...basis!, wegnachweis };
35 }
36
37 /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */
38 function pdfText(bytes: Uint8Array): string {
39 const roh = new TextDecoder('latin1').decode(bytes);
40 const teile: string[] = [];
41 const suche = /stream\r?\n/g;
42 let treffer: RegExpExecArray | null;
43
44 while ((treffer = suche.exec(roh)) !== null) {
45 const start = treffer.index + treffer[0].length;
46 const ende = roh.indexOf('endstream', start);
47 if (ende < 0) continue;
48 let inhalt: string;
49 try {
50 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
51 } catch {
52 continue;
53 }
54 for (const stueck of inhalt.matchAll(/\((?:\\.|[^\\()])*\)\s*Tj/g)) {
55 teile.push(stueck[0].slice(1, stueck[0].lastIndexOf(')')));
56 }
57 }
58 // Lange Absaetze und Tabellenzellen sind auf Zeilen umbrochen; fuer die Suche
59 // wieder zusammenziehen. Klammern und Schraegstriche stehen im Inhaltsstrom
60 // maskiert - die Zelle traegt Klammern, also wird die Maskierung entfernt.
61 return teile
62 .join(' ')
63 .replace(/\\([()\\])/g, '$1')
64 .replace(/\s+/g, ' ');
65 }
66
67 function ausdruck(project: Project): string {
68 const plan = buildSignalPlan(project);
69 const report = validateProject(project, plan, DATUM);
70 return pdfText(
71 buildProjectPdf(
72 project,
73 plan,
74 report,
75 { ...DEFAULT_PDF_OPTIONS, includeCalculationDetails: true },
76 DATUM,
77 ),
78 );
79 }
80
81 describe('Herkunft der Wege: Vermessung ohne ueberlieferten Zeitpunkt', () => {
82 it('vermerkt in der Zelle, dass der Zeitpunkt der Uebernahme nicht ueberliefert ist', () => {
83 const zelle = wegherkunftZelle(beziehung({ herkunft: 'lageplan', uebernommenAm: null }));
84
85 expect(zelle).toBe('Aus dem Lageplan vermessen (Zeitpunkt nicht überliefert)');
86 // Zuvor stand hier die blanke Beschriftung - und der Absatz daneben
87 // behauptete, sie nenne den Zeitpunkt.
88 expect(zelle).not.toBe('Aus dem Lageplan vermessen');
89 });
90
91 it('nennt den Zeitpunkt unveraendert, wenn er ueberliefert ist', () => {
92 const am = '2026-03-04T09:15:00.000Z';
93 const zelle = wegherkunftZelle(beziehung({ herkunft: 'lageplan', uebernommenAm: am }));
94
95 expect(zelle).toBe(`Aus dem Lageplan vermessen (${fmt.dateTime(am)})`);
96 });
97
98 it('laesst die beiden anderen Herkunftsstufen unberuehrt', () => {
99 expect(wegherkunftZelle(beziehung({ herkunft: 'handeingabe', uebernommenAm: null }))).toBe(
100 'Von Hand eingetragen',
101 );
102 expect(wegherkunftZelle(beziehung({ herkunft: 'unbestimmt', uebernommenAm: null }))).toBe(
103 'Herkunft nicht nachgewiesen',
104 );
105 });
106
107 it('traegt den Vermerk bis auf das Papier, wo der Erlaeuterungsabsatz den Zeitpunkt zusagt', () => {
108 const basis = createStandardIntersectionProject('Testknoten', DATUM);
109 const erste = basis.conflicts[0];
110 expect(erste).toBeDefined();
111 const project: Project = {
112 ...basis,
113 conflicts: basis.conflicts.map((c) =>
114 c.id === erste!.id
115 ? { ...c, wegnachweis: { herkunft: 'lageplan', uebernommenAm: null } }
116 : c,
117 ),
118 };
119
120 const text = ausdruck(project);
121
122 // Der Absatz sagt den Zeitpunkt weiterhin unbedingt zu ...
123 expect(text).toContain('"Aus dem Lageplan vermessen" nennt den Zeitpunkt der Übernahme');
124 // ... die Zeile, fuer die er nicht ueberliefert ist, sagt es selbst.
125 expect(text).toContain('(Zeitpunkt nicht überliefert)');
126 });
127 });