lsa-planer

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

/ tests export verweiseAbgewaehlt.test.ts

6,4 KB Rohdatei
tests/export/verweiseAbgewaehlt.test.ts — 178 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { buildProjectPdf, DEFAULT_PDF_OPTIONS, type PdfOptions } from '@/services/export/pdf';
4 import { buildSignalPlan } from '@/domain/plan/signalPlan';
5 import { validateProject } from '@/domain/validation';
6 import { createStandardIntersectionProject } from '@/domain/model/factory';
7 import { toWinAnsi } from '@/render/pdfSurface';
8 import type { Lageplan, Planlinie } from '@/domain/geometrie/lageplan';
9 import type { Project } from '@/domain/model/project';
10
11 /*
12 * BEFUNDE 21 UND 22: Zwei Verweise standen unbedingt da und schickten den
13 * Pruefer in Abschnitte, die die Ausgabe nicht enthaelt.
14 *
15 * Befund 21 - "Grundlagen der Kennwerte": Der Satz "Dieses Projekt rechnet und
16 * bewertet nach dem HBS 2015 (Verfahren siehe Abschnitt Leistungsfaehigkeit)"
17 * wurde auch dann gedruckt, wenn der Abschnitt "Leistungsfaehigkeit" abgewaehlt
18 * war. Auf Blatt 2 derselben Datei weist die Uebersicht "Bestandteile der
19 * Anordnungsunterlage" ihn zugleich als "abgewählt - in dieser Ausgabe nicht
20 * enthalten" aus.
21 *
22 * Befund 22 - "Planungsgrundlage": Der Satz verwies auf die Spalte "Herkunft
23 * der Wege" im Rechenweg der Zwischenzeiten. Die Spalte entsteht nur unter
24 * `includeCalculationDetails && includeIntergreenMatrix`; ohne sie blieb
25 * "Herkunft der Wege" genau einmal im Dokument stehen - naemlich im Verweis
26 * selbst.
27 *
28 * Beides ist dasselbe Muster, das dieselbe Datei an sechs anderen Stellen
29 * bereits bedingt fuehrt ("Ein Verweis auf einen abgewaehlten Abschnitt waere
30 * ein Verweis ins Leere", pdf.ts zu planVerweisRumpf).
31 */
32
33 const FEST = new Date('2026-01-01T12:00:00Z');
34
35 /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */
36 function pdfText(bytes: Uint8Array): string {
37 const roh = new TextDecoder('latin1').decode(bytes);
38 const teile: string[] = [];
39 const suche = /stream\r?\n/g;
40 let treffer: RegExpExecArray | null;
41
42 while ((treffer = suche.exec(roh)) !== null) {
43 const start = treffer.index + treffer[0].length;
44 const ende = roh.indexOf('endstream', start);
45 if (ende < 0) continue;
46 let inhalt: string;
47 try {
48 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
49 } catch {
50 continue;
51 }
52 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
53 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
54 }
55 }
56 return teile.join(' ').replace(/\s+/g, ' ');
57 }
58
59 const gedruckt = (text: string): string => toWinAnsi(text).replace(/\s+/g, ' ');
60
61 function fahrlinie(): Planlinie {
62 return {
63 id: 'l1',
64 name: 'l1',
65 mode: 'kfz',
66 movement: 'geradeaus',
67 breiteMeter: 3.25,
68 punkte: [
69 { x: 10, y: 10 },
70 { x: 10, y: 90 },
71 ],
72 haltlinieId: null,
73 startT: 0.5,
74 signalGroupId: null,
75 };
76 }
77
78 /** Ein masstaeblich auswertbarer Lageplan - sonst greift der andere Satzzweig. */
79 const AUSWERTBAR: Lageplan = {
80 arbeitsbereiche: [],
81 signalgeber: [],
82 bild: {
83 datenUrl: 'data:image/png;base64,AAAA',
84 breite: 1000,
85 hoehe: 1000,
86 herkunft: 'Luftbild NRW - Kante 100 m',
87 geladenAm: '2026-03-04T09:15:00.000Z',
88 },
89 kalibrierung: {
90 von: { x: 0, y: 0 },
91 bis: { x: 100, y: 0 },
92 laengeMeter: 10,
93 herkunft: 'georeferenziert',
94 },
95 haltlinien: [],
96 linien: [fahrlinie()],
97 };
98
99 function ausdruck(optionen: Partial<PdfOptions>, mitLageplan = false): string {
100 const basis = createStandardIntersectionProject('Hauptstrasse / Bahnhofstrasse', FEST);
101 const project: Project = mitLageplan ? { ...basis, lageplan: AUSWERTBAR } : basis;
102 const plan = buildSignalPlan(project);
103 return pdfText(
104 buildProjectPdf(
105 project,
106 plan,
107 validateProject(project, plan, FEST),
108 { ...DEFAULT_PDF_OPTIONS, ...optionen },
109 FEST,
110 ),
111 );
112 }
113
114 describe('Befund 21 - Verweis der Grundlagen auf die Leistungsfaehigkeit', () => {
115 it('verweist bei abgewaehlter Leistungsfaehigkeit nicht auf deren Abschnitt', () => {
116 const text = ausdruck({ includePerformance: false, includeSources: true });
117
118 expect(text, 'Vorbedingung: der Satz selbst steht weiter da').toContain(
119 gedruckt('Dieses Projekt rechnet und bewertet nach dem HBS 2015'),
120 );
121 expect(text, 'Vorbedingung: die Bestandteileuebersicht weist die Abwahl aus').toContain(
122 gedruckt('abgewählt'),
123 );
124 expect(text, 'der Verweis geht ins Leere').not.toContain(
125 gedruckt('Verfahren siehe Abschnitt Leistungsfähigkeit'),
126 );
127 expect(text, 'stattdessen wird die Abwahl an der Stelle benannt').toContain(
128 gedruckt(
129 'der Abschnitt "Leistungsfähigkeit", der das Verfahren zeigt, ist für diese Ausgabe abgewählt',
130 ),
131 );
132 });
133
134 it('behaelt den Verweis, solange die Leistungsfaehigkeit im Dokument steht', () => {
135 const text = ausdruck({ includePerformance: true, includeSources: true });
136 expect(text).toContain(
137 gedruckt('Dieses Projekt rechnet und bewertet nach dem HBS 2015 (Verfahren siehe Abschnitt'),
138 );
139 });
140 });
141
142 describe('Befund 22 - Verweis der Planungsgrundlage auf "Herkunft der Wege"', () => {
143 for (const [name, optionen] of [
144 ['Rechenweg abgewaehlt', { includeCalculationDetails: false }],
145 ['Zwischenzeitenmatrix abgewaehlt', { includeIntergreenMatrix: false }],
146 ] as const) {
147 it(`verweist bei "${name}" nicht auf die Spalte "Herkunft der Wege"`, () => {
148 const text = ausdruck(optionen, true);
149
150 expect(text, 'Vorbedingung: der Lageplan ist auswertbar').toContain(
151 gedruckt('Der Lageplan ist maßstäblich auswertbar'),
152 );
153 expect(text, 'Vorbedingung: die Zusage zum Mitfuehren bleibt').toContain(
154 gedruckt('führt das Projekt je Beziehung mit'),
155 );
156 expect(text, 'der Verweis auf die Spalte geht ins Leere').not.toContain(
157 gedruckt(
158 'ausgewiesen wird es in der Spalte "Herkunft der Wege" im Rechenweg der Zwischenzeiten',
159 ),
160 );
161 expect(text, 'stattdessen wird die Luecke ausgewiesen').toContain(
162 gedruckt(
163 'der Rechenweg der Zwischenzeiten, der es in der Spalte "Herkunft der Wege" Beziehung ' +
164 'für Beziehung ausweist, ist für diese Ausgabe abgewählt',
165 ),
166 );
167 });
168 }
169
170 it('behaelt den Verweis, solange der Rechenweg der Zwischenzeiten im Dokument steht', () => {
171 const text = ausdruck({}, true);
172 expect(text).toContain(
173 gedruckt(
174 'ausgewiesen wird es in der Spalte "Herkunft der Wege" im Rechenweg der Zwischenzeiten',
175 ),
176 );
177 });
178 });