lsa-planer

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

/ tests export pdfUnterlagen.test.ts

21,0 KB Rohdatei
tests/export/pdfUnterlagen.test.ts — 485 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 { ANLAGENART_GRENZEN, type SchrankenHerkunft } from '@/domain/rilsa/constants';
8 import {
9 PRUEFSTAND_LABELS,
10 RECHTSGRUNDLAGEN,
11 RECHTSGRUNDLAGEN_EINSTREIFIG,
12 rechtsgrundlagenFuer,
13 } from '@/domain/rilsa/quellen';
14 import { toWinAnsi } from '@/render/pdfSurface';
15 import type { Anlagenart, Project } from '@/domain/model/project';
16 import * as fmt from '@/ui/format';
17
18 /*
19 * Unterlagen und Rechtsgrundlagen im Ausdruck - Befunde B8 und C13 bis C16.
20 *
21 * B8: Die Tabelle "Schranken der Anlagenart" schrieb in jede Zeile dieselbe
22 * Quelle der Anlagenart - "RiLSA 2015" auch neben die Wartezeitschwellen,
23 * "RSA 21 - Richtlinien fuer die Sicherung von Arbeitsstellen an Strassen" auch
24 * neben die Umlaufzeit von 30 bis 300 s. Keine dieser Zahlen steht im jeweils
25 * genannten Regelwerk.
26 * C13: Die Rechtsgrundlagen waren fuer jede Anlagenart dieselben; einer
27 * Baustellenampel fehlten RSA 21, TL und ZTV transportable LSA 2023.
28 * C14: Der Ausdruck wies nicht aus, was ihm fehlt - Einsatzzeiten,
29 * Betriebsverantwortlicher und vor allem der bemasste Signallageplan mit den
30 * Signalgeberstandorten, den dieses Programm gar nicht zeichnen kann.
31 * C15: Der Zwischenzeitennachweis war standardmaessig abgewaehlt.
32 * C16: Die Kopfzeile behauptete auf jeder Seite "Planung nach RiLSA 2015" -
33 * auch dort, wo derselbe Ausdruck sagt, die Grenzen stammten NICHT aus der
34 * RiLSA.
35 */
36
37 const STICHTAG = new Date('2026-01-01T12:00:00Z');
38
39 const ANLAGENARTEN: readonly Anlagenart[] = [
40 'knotenpunkt',
41 'fussgaengerschutzanlage',
42 'einstreifig',
43 ];
44
45 /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */
46 function pdfText(bytes: Uint8Array): string {
47 const roh = new TextDecoder('latin1').decode(bytes);
48 const teile: string[] = [];
49 const suche = /stream\r?\n/g;
50 let treffer: RegExpExecArray | null;
51
52 while ((treffer = suche.exec(roh)) !== null) {
53 const start = treffer.index + treffer[0].length;
54 const ende = roh.indexOf('endstream', start);
55 if (ende < 0) continue;
56 let inhalt: string;
57 try {
58 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
59 } catch {
60 continue;
61 }
62 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
63 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
64 }
65 }
66 // Der Ausdruck bricht Saetze und Tabellenzellen fuer den Satzspiegel um; fuer
67 // die Suche wieder zusammenziehen.
68 return teile.join(' ').replace(/\s+/g, ' ');
69 }
70
71 function projekt(anlagenart: Anlagenart): Project {
72 const basis = createStandardIntersectionProject('Hauptstrasse / Bahnhofstrasse', STICHTAG);
73 return { ...basis, anlagenart };
74 }
75
76 function ausdruck(project: Project, optionen: PdfOptions = DEFAULT_PDF_OPTIONS): string {
77 const plan = buildSignalPlan(project);
78 const bericht = validateProject(project, plan, STICHTAG);
79 return pdfText(buildProjectPdf(project, plan, bericht, optionen, STICHTAG));
80 }
81
82 /**
83 * Die Zeichen des Ausdrucks: Gedankenstrich und deutsche Anfuehrungszeichen
84 * gibt es im Zeichenvorrat der PDF-Standardschriften nicht. Erwartungen laufen
85 * deshalb durch dieselbe Umsetzung wie der Ausdruck selbst.
86 */
87 const gedruckt = (text: string): string => toWinAnsi(text);
88
89 describe('B8 - Schranken der Anlagenart: Herkunft und Pruefstand je Zeile', () => {
90 it('druckt zu jeder Schranke die Herkunft des Zahlenwerts samt eigenem Pruefstand', () => {
91 // Gegen den Altstand: Dort stand in allen vier Zeilen dieselbe Quelle der
92 // Anlagenart mit demselben Pruefstand. Erwartet wird hier die Folge
93 // "Angesetzt - Grundlage - Pruefstand" in einer Zeile; sie faellt schon
94 // dann, wenn nur der Pruefstand einer Zeile falsch danebensteht.
95 for (const art of ANLAGENARTEN) {
96 const grenzen = ANLAGENART_GRENZEN[art];
97 const text = ausdruck(projekt(art));
98
99 const zeilen: readonly { angesetzt: string; herkunft: SchrankenHerkunft }[] = [
100 {
101 angesetzt: `${fmt.seconds(grenzen.cycleTime.min)} bis ${fmt.seconds(grenzen.cycleTime.max)}`,
102 herkunft: grenzen.herkunft.umlaufzeit,
103 },
104 {
105 angesetzt:
106 `${fmt.seconds(grenzen.cycleTime.recommendedMin)} bis ` +
107 `${fmt.seconds(grenzen.cycleTime.recommendedMax)}`,
108 herkunft: grenzen.herkunft.regelbereich,
109 },
110 {
111 angesetzt: fmt.meters(grenzen.maxClearingDistance, 0),
112 herkunft: grenzen.herkunft.raeumweg,
113 },
114 {
115 angesetzt: `ab ${fmt.seconds(grenzen.warteZeitWarnung)} / ab ${fmt.seconds(grenzen.warteZeitFehler)}`,
116 herkunft: grenzen.herkunft.wartezeit,
117 },
118 ];
119
120 for (const zeile of zeilen) {
121 const erwartet = gedruckt(
122 `${zeile.angesetzt} ${zeile.herkunft.text} ${PRUEFSTAND_LABELS[zeile.herkunft.pruefstand]}`,
123 );
124 expect(text, `${art}: ${erwartet}`).toContain(erwartet);
125 }
126 }
127 });
128
129 it('nennt neben den Wartezeitschwellen kein Regelwerk mehr', () => {
130 // Frage E7 (Fassung 5.4.0): Bezifferte Wartezeitschwellen nennt weder die
131 // RiLSA noch die RSA in einer oeffentlich belegten Fassung. Der Altstand
132 // druckte hier am Knotenpunkt "RiLSA 2015 / gesichert", bei einstreifiger
133 // Fuehrung den vollen Namen der RSA.
134 for (const art of ANLAGENARTEN) {
135 const grenzen = ANLAGENART_GRENZEN[art];
136 const text = ausdruck(projekt(art));
137 const wartezeit = `ab ${fmt.seconds(grenzen.warteZeitWarnung)} / ab ${fmt.seconds(grenzen.warteZeitFehler)}`;
138
139 expect(text, art).toContain(
140 gedruckt(`${wartezeit} Schwelle dieses Programms, kein Regelwerkswert`),
141 );
142 expect(text, art).not.toContain(gedruckt(`${wartezeit} RiLSA 2015 gesichert`));
143 expect(text, art).not.toContain(
144 gedruckt(
145 `${wartezeit} RSA 21 – Richtlinien für die Sicherung von Arbeitsstellen an Straßen`,
146 ),
147 );
148 // Der Pruefstand der Wartezeitzeile ist in jeder Anlagenart "abzugleichen".
149 expect(grenzen.herkunft.wartezeit.pruefstand, art).toBe('abzugleichen');
150 }
151 });
152
153 it('weist die Umlaufzeit der Arbeitsstelle als hergeleitet aus, die des Knotenpunkts als RiLSA-Wert', () => {
154 const einstreifig = ausdruck(projekt('einstreifig'));
155 expect(einstreifig).toContain(
156 gedruckt('30 s bis 300 s Schwelle dieses Programms, aus den übrigen Schranken hergeleitet'),
157 );
158 expect(einstreifig).toContain(gedruckt('kein RSA-Wert'));
159 // Nur die belegte Zeile darf "gesichert" tragen - bei einstreifiger
160 // Fuehrung gibt es keine.
161 expect(einstreifig).toContain(gedruckt('30 s bis 300 s'));
162 expect(einstreifig).not.toContain(gedruckt('30 s bis 300 s RSA 21'));
163
164 const knoten = ausdruck(projekt('knotenpunkt'));
165 expect(knoten).toContain(
166 gedruckt('30 s bis 120 s RiLSA 2015 – Umlaufzeit am Knotenpunkt (30 bis 120 s) gesichert'),
167 );
168 /*
169 * NACHGEZOGEN (5.27.0): Der Regelbereich am Knotenpunkt ist belegt und
170 * traegt "gesichert".
171 *
172 * Die Zeile hat zweimal gewechselt. Zuerst stand dort "RiLSA 2015 -
173 * Umlaufzeit (90 s als Obergrenze belegt; 60 s als Untergrenze
174 * abzugleichen)". Die Klaerung der Frage E4 (Fassung 5.4.0) hat den
175 * RiLSA-Bezug der 60 s gestrichen und sie als Zahl dieses Programms
176 * ausgewiesen - richtig, soweit es ohne Druckwerk ging. Die gekaufte
177 * Ausgabe zeigt, was nicht zu sehen war: Die RiLSA nennt 30 s als
178 * Richtwert, und gestrichen ist nur der frueher "normal" genannte
179 * MITTELbereich von 50 bis 75 s.
180 */
181 const regelbereich = ANLAGENART_GRENZEN.knotenpunkt.herkunft.regelbereich;
182 expect(regelbereich.text).toContain('Abschnitt 2.6');
183 expect(regelbereich.text).not.toContain('setzt dieses Programm');
184 expect(regelbereich.pruefstand).toBe('gesichert');
185 expect(knoten).toContain(
186 gedruckt(`30 s bis 90 s ${regelbereich.text} ${PRUEFSTAND_LABELS[regelbereich.pruefstand]}`),
187 );
188 });
189
190 it('sagt im Einleitungssatz, was die Spalte "Grundlage" bedeutet', () => {
191 const text = ausdruck(projekt('knotenpunkt'));
192 expect(text).toContain(gedruckt('sagt je Zeile, woher der Zahlenwert stammt'));
193 expect(text).toContain(gedruckt('Mehrere dieser Schwellen setzt dieses Programm selbst'));
194 });
195 });
196
197 describe('C13 - Rechtsgrundlagen nach Anlagenart', () => {
198 it('fuehrt bei einstreifiger Fuehrung RSA 21, TL und ZTV transportable LSA 2023 auf', () => {
199 const text = ausdruck(projekt('einstreifig'));
200 expect(rechtsgrundlagenFuer('einstreifig')).toHaveLength(
201 RECHTSGRUNDLAGEN.length + RECHTSGRUNDLAGEN_EINSTREIFIG.length,
202 );
203 for (const grundlage of rechtsgrundlagenFuer('einstreifig')) {
204 expect(text, grundlage.bezeichnung).toContain(gedruckt(grundlage.bezeichnung));
205 }
206 // Die Aussagen, auf die es ankommt - und der Pruefstand dazu.
207 expect(text).toContain(
208 gedruckt(
209 'Signalgeregelte einstreifige Führungen sind nach RiLSA zu berechnen (Teil A 3.2 Abs. 3)',
210 ),
211 );
212 expect(text).toContain(gedruckt('eingeführt mit ARS Nr. 07/2024'));
213 expect(text).toContain(
214 gedruckt('Typklassen A bis D (Typ D bei kreuzenden Strömen und bei Fußgängerschutzanlagen)'),
215 );
216 for (const grundlage of RECHTSGRUNDLAGEN_EINSTREIFIG) {
217 expect(grundlage.pruefstand, grundlage.bezeichnung).toBe('abzugleichen');
218 }
219 });
220
221 it('nennt sie an Knotenpunkt und Fussgaengerschutzanlage nicht', () => {
222 // Eine Unterlage, die am Knotenpunkt die RSA 21 als Rechtsgrundlage
223 // auffuehrt, behauptet einen Bezug, den es nicht gibt.
224 for (const art of ['knotenpunkt', 'fussgaengerschutzanlage'] as const) {
225 const text = ausdruck(projekt(art));
226 expect(rechtsgrundlagenFuer(art)).toEqual(RECHTSGRUNDLAGEN);
227 for (const grundlage of RECHTSGRUNDLAGEN_EINSTREIFIG) {
228 expect(text, `${art}: ${grundlage.bezeichnung}`).not.toContain(
229 gedruckt(grundlage.bezeichnung),
230 );
231 }
232 expect(text, art).not.toContain(gedruckt('ARS Nr. 07/2024'));
233 // Der Grundstock steht weiterhin da.
234 for (const grundlage of RECHTSGRUNDLAGEN) {
235 expect(text, `${art}: ${grundlage.bezeichnung}`).toContain(gedruckt(grundlage.bezeichnung));
236 }
237 }
238 });
239
240 it('druckt den VwV-StVO-Eintrag mit den Werten, die sie selbst vorgibt (C17)', () => {
241 // Der Eintrag sagte bis Fassung 5.4.0 nur, die VwV-StVO "verweise auf die
242 // Regelwerke der FGSV" - dabei gibt sie Gelb- und Rot-Gelb-Zeit selbst vor,
243 // und genau darauf stuetzt das Programm zwei harte Schranken.
244 const text = ausdruck(projekt('knotenpunkt'));
245 expect(text).toContain(gedruckt('Gibt zu § 37 die Gelbzeiten (3/4/5 s bei 50/60/70 km/h)'));
246 expect(text).toContain(gedruckt('die Rot-Gelb-Zeit (1 s, höchstens 2 s) selbst vor (Rn. 17)'));
247 expect(text).toContain(gedruckt('sieht Lichtsignalanlagen über 70 km/h nicht vor (Rn. 10)'));
248 });
249 });
250
251 describe('C14 - Bestandteile der Anordnungsunterlage', () => {
252 it('weist den bemassten Signallageplan ausdruecklich als NICHT enthalten aus', () => {
253 // Der Kern des Befunds: Das Programm zeichnet Fahrlinien und keine
254 // Signalgeberstandorte. Eine Unterlage, die das verschweigt, sieht
255 // vollstaendig aus.
256 for (const art of ANLAGENARTEN) {
257 const text = ausdruck(projekt(art));
258 expect(text, art).toContain(gedruckt('Bestandteile der Anordnungsunterlage'));
259 expect(text, art).toContain(
260 gedruckt(
261 'Bemaßter Signallageplan mit Signalgeberstandorten NICHT Bestandteil dieser Unterlage – gesondert beizubringen',
262 ),
263 );
264 expect(text, art).toContain(
265 gedruckt('Verkehrszeichenplan NICHT Bestandteil dieser Unterlage – gesondert beizubringen'),
266 );
267 expect(text, art).toContain(
268 gedruckt(
269 'wo die Signalgeber stehen, welche Bauform sie haben und wie sie bemaßt sind, geht daraus nicht hervor',
270 ),
271 );
272 }
273 });
274
275 it('nennt fehlende Einsatzzeiten und den fehlenden Betriebsverantwortlichen beim Namen', () => {
276 const text = ausdruck(projekt('einstreifig'));
277 expect(text).toContain(gedruckt('Einsatzzeiten der Anordnung nicht angegeben'));
278 expect(text).toContain(
279 gedruckt('Betriebsverantwortlicher (Name, Firma, Erreichbarkeit) nicht angegeben'),
280 );
281 });
282
283 it('druckt die eingetragenen Angaben statt des Lueckenausweises', () => {
284 const basis = projekt('einstreifig');
285 const gefuellt: Project = {
286 ...basis,
287 meta: {
288 ...basis.meta,
289 einsatzzeiten: '12.05.-30.06.2027, ganztägig',
290 betriebsverantwortlicher: 'Muster Bau GmbH, Herr Roth, 0170 1234567',
291 },
292 };
293 const text = ausdruck(gefuellt);
294 expect(text).toContain(gedruckt('Einsatzzeiten der Anordnung 12.05.-30.06.2027, ganztägig'));
295 expect(text).toContain(
296 gedruckt(
297 'Betriebsverantwortlicher (Name, Firma, Erreichbarkeit) Muster Bau GmbH, Herr Roth, 0170 1234567',
298 ),
299 );
300 expect(text).not.toContain(gedruckt('Einsatzzeiten der Anordnung nicht angegeben'));
301 });
302
303 it('benennt abgewaehlte Abschnitte als abgewaehlt', () => {
304 const ohne: PdfOptions = {
305 ...DEFAULT_PDF_OPTIONS,
306 includeIntergreenMatrix: false,
307 includeReport: false,
308 };
309 const text = ausdruck(projekt('knotenpunkt'), ohne);
310 expect(text).toContain(
311 gedruckt('Zwischenzeitenmatrix abgewählt – in dieser Ausgabe nicht enthalten'),
312 );
313 expect(text).toContain(gedruckt('Prüfbericht abgewählt – in dieser Ausgabe nicht enthalten'));
314 // Was enthalten ist, nennt seinen Abschnitt.
315 expect(text).toContain(
316 gedruckt('Phasen und Phasenübergänge ja – Abschnitt „Phasen und Phasenübergänge"'),
317 );
318 });
319
320 it('nennt bei einstreifiger Fuehrung die RSA-Fundstelle, sonst § 45 StVO ohne Regelwerkswert', () => {
321 const einstreifig = ausdruck(projekt('einstreifig'));
322 expect(einstreifig).toContain(gedruckt('Grundlage: RSA 21 Teil A 1.4/1.5'));
323 expect(einstreifig).toContain(gedruckt('ZTV transportable Lichtsignalanlagen 2023'));
324 expect(einstreifig).toContain(gedruckt('der Prüfbericht führt fehlende Angaben als Warnung'));
325
326 const knoten = ausdruck(projekt('knotenpunkt'));
327 expect(knoten).toContain(
328 gedruckt(
329 'üblicher Bestandteil einer verkehrsbehördlichen Anordnung nach § 45 StVO; ein bezifferter Regelwerkswert steht dahinter nicht',
330 ),
331 );
332 expect(knoten).not.toContain(gedruckt('Grundlage: RSA 21 Teil A 1.4/1.5'));
333 });
334 });
335
336 describe('C15 - Rechenweg der Zwischenzeiten und der Wartezeit', () => {
337 it('ist voreingestellt enthalten', () => {
338 expect(DEFAULT_PDF_OPTIONS.includeCalculationDetails).toBe(true);
339 const text = ausdruck(projekt('knotenpunkt'));
340 expect(text).toContain(gedruckt('Zwischenzeiten – Rechenweg'));
341 expect(text).toContain(gedruckt('Leistungsfähigkeit – Rechenweg Wartezeit'));
342 expect(text).not.toContain(gedruckt('ist in dieser Ausgabe abgewählt'));
343 });
344
345 it('vermerkt die Abwahl an der Stelle des Abschnitts - genau einmal', () => {
346 const satz = gedruckt(
347 'Der Rechenweg der Zwischenzeiten und der Wartezeit ist in dieser Ausgabe abgewählt; die ' +
348 'Werte der Zwischenzeitenmatrix und der Leistungsfähigkeit sind ohne ihn nicht ' +
349 'nachrechenbar.',
350 );
351 const text = ausdruck(projekt('knotenpunkt'), {
352 ...DEFAULT_PDF_OPTIONS,
353 includeCalculationDetails: false,
354 });
355 expect(text).toContain(satz);
356 expect(text.split(satz).length - 1, 'der Satz steht genau einmal').toBe(1);
357 expect(text).not.toContain(gedruckt('Zwischenzeiten – Rechenweg'));
358 // Die Uebersicht der Bestandteile sagt dasselbe mit derselben Folge.
359 expect(text).toContain(
360 gedruckt(
361 'Rechenweg der Zwischenzeiten und der Wartezeit abgewählt – die Werte der Matrix sind ohne ihn nicht nachrechenbar',
362 ),
363 );
364 });
365
366 it('vermerkt die Abwahl auch dann, wenn die Zwischenzeitenmatrix selbst abgewaehlt ist', () => {
367 // Sonst stuende der Vermerk nirgends: Er haengt an der Stelle des
368 // Rechenwegs, und die erste davon faellt mit der Matrix weg.
369 const text = ausdruck(projekt('knotenpunkt'), {
370 ...DEFAULT_PDF_OPTIONS,
371 includeIntergreenMatrix: false,
372 includeCalculationDetails: false,
373 });
374 expect(text).toContain(gedruckt('ist in dieser Ausgabe abgewählt'));
375 });
376
377 /*
378 * Befunde 1, 2 und B-5 (Fassung 5.4.0): Der Rechenweg haengt an zwei
379 * Bedingungen - am eigenen Schalter UND daran, ob der Abschnitt gedruckt
380 * wird, in dem er steht. Geprueft wurde bis dahin nur
381 * `includeCalculationDetails: false` bei sonst voller Ausgabe.
382 */
383 it('nennt in der Uebersicht nur Abschnitte, die es in dieser Ausgabe gibt', () => {
384 const ohneMatrix = ausdruck(projekt('knotenpunkt'), {
385 ...DEFAULT_PDF_OPTIONS,
386 includeIntergreenMatrix: false,
387 });
388 // Der Rechenweg ist angefordert, aber nur einer der beiden Abschnitte
389 // existiert - die Uebersicht darf nicht "ja - Abschnitte ... und ..." sagen.
390 expect(ohneMatrix).not.toContain(
391 gedruckt(
392 'ja – Abschnitte „Zwischenzeiten – Rechenweg" und „Leistungsfähigkeit – Rechenweg Wartezeit"',
393 ),
394 );
395 expect(ohneMatrix).toContain(
396 gedruckt('teilweise – Abschnitt „Leistungsfähigkeit – Rechenweg Wartezeit"'),
397 );
398 expect(ohneMatrix).toContain(
399 gedruckt(
400 'der Rechenweg der Zwischenzeiten entfällt mit der abgewählten Zwischenzeitenmatrix',
401 ),
402 );
403 expect(ohneMatrix).not.toContain(gedruckt('Zwischenzeiten – Rechenweg'));
404
405 const ohneLeistung = ausdruck(projekt('knotenpunkt'), {
406 ...DEFAULT_PDF_OPTIONS,
407 includePerformance: false,
408 });
409 expect(ohneLeistung).toContain(gedruckt('teilweise – Abschnitt „Zwischenzeiten – Rechenweg"'));
410 expect(ohneLeistung).not.toContain(gedruckt('Leistungsfähigkeit – Rechenweg Wartezeit'));
411 });
412
413 it('sagt es, wenn der angeforderte Rechenweg mit beiden Abschnitten entfaellt', () => {
414 const text = ausdruck(projekt('knotenpunkt'), {
415 ...DEFAULT_PDF_OPTIONS,
416 includeIntergreenMatrix: false,
417 includePerformance: false,
418 });
419 expect(text).toContain(
420 gedruckt(
421 'entfällt – angefordert, aber beide Abschnitte, in denen er steht (Zwischenzeitenmatrix, Leistungsfähigkeit), sind abgewählt',
422 ),
423 );
424 expect(text).not.toContain(gedruckt('Zwischenzeiten – Rechenweg'));
425 expect(text).not.toContain(gedruckt('Leistungsfähigkeit – Rechenweg Wartezeit'));
426 });
427
428 it('haelt den Abwahlvermerk auch dann, wenn beide Abschnitte abgewaehlt sind', () => {
429 // Der Fall, in dem der Vermerk vor der Fassung 5.4.0 NIRGENDS stand: Er
430 // wurde nur innerhalb der beiden Abschnitte gedruckt.
431 const text = ausdruck(projekt('knotenpunkt'), {
432 ...DEFAULT_PDF_OPTIONS,
433 includeIntergreenMatrix: false,
434 includePerformance: false,
435 includeCalculationDetails: false,
436 });
437 const satz = gedruckt(
438 'Der Rechenweg der Zwischenzeiten und der Wartezeit ist in dieser Ausgabe abgewählt; die ' +
439 'Abschnitte, in denen er gestanden hätte - Zwischenzeitenmatrix und Leistungsfähigkeit -, ' +
440 'sind ebenfalls abgewählt.',
441 );
442 expect(text).toContain(satz);
443 expect(text.split(satz).length - 1, 'der Satz steht genau einmal').toBe(1);
444 });
445
446 it('spricht bei abgewaehlter Matrix nicht von "den Werten der Matrix"', () => {
447 // Befund B-5, zweiter Teil: Der Vermerk behauptete eine Matrix, die die
448 // Unterlage gar nicht enthaelt.
449 const text = ausdruck(projekt('knotenpunkt'), {
450 ...DEFAULT_PDF_OPTIONS,
451 includeIntergreenMatrix: false,
452 includeCalculationDetails: false,
453 });
454 expect(text).toContain(
455 gedruckt('die Werte der Leistungsfähigkeit sind ohne ihn nicht nachrechenbar'),
456 );
457 expect(text).not.toContain(gedruckt('die Werte der Matrix sind ohne ihn nicht nachrechenbar.'));
458 });
459 });
460
461 describe('C16 - Kopfzeile nennt die Grundlage der Anlagenart', () => {
462 it('sagt an der Arbeitsstelle RiLSA und RSA, sonst nur RiLSA', () => {
463 const einstreifig = ausdruck(projekt('einstreifig'));
464 expect(einstreifig).toContain(
465 gedruckt('LSA-Planer Professional · Signalzeitenplan nach RiLSA 2015 (RSA 21, Teil A 3.2)'),
466 );
467
468 for (const art of ['knotenpunkt', 'fussgaengerschutzanlage'] as const) {
469 const text = ausdruck(projekt(art));
470 expect(text, art).toContain(
471 gedruckt('LSA-Planer Professional · Signalzeitenplan nach RiLSA 2015'),
472 );
473 expect(text, art).not.toContain(gedruckt('(RSA 21, Teil A 3.2)'));
474 }
475 });
476
477 it('behauptet nirgends mehr pauschal "Planung nach RiLSA 2015"', () => {
478 // Die alte Kopfzeile stand auf JEDER Seite - auch neben dem Satz "Die
479 // Umlaufzeitgrenzen dieses Plans stammen NICHT aus der RiLSA".
480 for (const art of ANLAGENARTEN) {
481 expect(ausdruck(projekt(art)), art).not.toContain(gedruckt('Planung nach RiLSA 2015'));
482 }
483 expect(ausdruck(projekt('einstreifig'))).toContain(gedruckt('stammen NICHT aus der RiLSA'));
484 });
485 });