lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests export pdfUnterlagen.test.ts
| 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 | }); |