lsa-planer

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

/ tests export pdfStauraum.test.ts

4,3 KB Rohdatei
tests/export/pdfStauraum.test.ts — 107 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { buildProjectPdf, DEFAULT_PDF_OPTIONS } 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 { STAURAUMBEDARF_JE_FAHRZEUG } from '@/domain/rilsa/constants';
8 import { toWinAnsi } from '@/render/pdfSurface';
9 import * as fmt from '@/ui/format';
10 import type { Project } from '@/domain/model/project';
11
12 /**
13 * Der Rueckstau in der Planunterlage.
14 *
15 * Die Unterlage druckte die Fahrzeugzahl NGE seit jeher. Was fehlte, war die
16 * Laenge - und damit die Antwort auf die Frage, an der eine Abwaegung nach
17 * Paragraf 45 StVO haengt: Laeuft der Aufstellstreifen ueber?
18 *
19 * DIE FAELLE BEWACHEN VOR ALLEM DIE EHRLICHKEIT: dass der Ausdruck sagt,
20 * welche Groesse verglichen wurde, und dass die Laenge je Fahrzeug als
21 * Praxiswert ausgewiesen ist.
22 */
23
24 const STICHTAG = new Date('2026-01-01T12:00:00Z');
25
26 /** Der sichtbare Text der Datei, in einer Zeile. */
27 function pdfText(bytes: Uint8Array): string {
28 const roh = new TextDecoder('latin1').decode(bytes);
29 const teile: string[] = [];
30 const suche = /stream\r?\n/g;
31 let treffer: RegExpExecArray | null;
32 while ((treffer = suche.exec(roh)) !== null) {
33 const start = treffer.index + treffer[0].length;
34 const ende = roh.indexOf('endstream', start);
35 if (ende < 0) continue;
36 let inhalt: string;
37 try {
38 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
39 } catch {
40 continue;
41 }
42 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
43 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
44 }
45 }
46 return teile.join(' ').replace(/\s+/g, ' ');
47 }
48
49 function mitStauraum(stauraum: number | null): Project {
50 const roh = createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG);
51 return {
52 ...roh,
53 signalGroups: roh.signalGroups.map((gruppe) =>
54 gruppe.mode === 'kfz' && stauraum !== null ? { ...gruppe, stauraumlaenge: stauraum } : gruppe,
55 ),
56 demands: roh.signalGroups
57 .filter((g) => g.mode === 'kfz')
58 .map((g) => ({ signalGroupId: g.id, volume: 700, heavyVehicleShare: 0.05 })),
59 };
60 }
61
62 function ausdruck(project: Project): string {
63 const plan = buildSignalPlan(project);
64 const bericht = validateProject(project, plan, STICHTAG);
65 return pdfText(buildProjectPdf(project, plan, bericht, DEFAULT_PDF_OPTIONS, STICHTAG));
66 }
67
68 describe('Planunterlage - Rückstau und Stauraum', () => {
69 it('druckt einen eigenen Abschnitt mit der Länge', () => {
70 const text = ausdruck(mitStauraum(45));
71 expect(text).toContain(toWinAnsi('Rückstau und Stauraum'));
72 expect(text).toContain(toWinAnsi('Rückstau je Fahrstreifen'));
73 expect(text).toContain(toWinAnsi(fmt.meters(45, 1)));
74 });
75
76 it('sagt, dass ein Mittelwert verglichen wurde', () => {
77 /*
78 * DER WICHTIGSTE FALL DIESER DATEI. Ohne den Satz liest eine Behörde
79 * "Auslastung 80 %" und schließt, der Stauraum reiche – während er in
80 * jedem fünften Umlauf überläuft.
81 */
82 const text = ausdruck(mitStauraum(45));
83 expect(text).toContain(toWinAnsi('MITTLERE RÜCKSTAU'));
84 expect(text).toContain(toWinAnsi('Hälfte der Umläufe steht mehr'));
85 expect(text).toContain(toWinAnsi('95-Prozent-Wert'));
86 });
87
88 it('weist die Länge je Fahrzeug als Praxiswert aus', () => {
89 const text = ausdruck(mitStauraum(45));
90 expect(text).toContain(toWinAnsi(`NGE · ${fmt.meters(STAURAUMBEDARF_JE_FAHRZEUG, 0)}`));
91 expect(text).toContain(toWinAnsi('weder die RiLSA 2015 noch das HBS 2015 nennen einen'));
92 // Und im Fundstellenverzeichnis, mit Prüfstand.
93 expect(text).toContain(toWinAnsi('Rückstaulänge aus dem mittleren Rückstau'));
94 });
95
96 it('druckt den Abschnitt auch ohne erfassten Stauraum', () => {
97 // Die Länge hilft auch ohne Vergleich weiter; nur die Spalte bleibt leer.
98 const text = ausdruck(mitStauraum(null));
99 expect(text).toContain(toWinAnsi('Rückstau und Stauraum'));
100 expect(text).toContain(toWinAnsi('nicht erfasst'));
101 });
102
103 it('sagt, dass der Stauraum in keine Zahl der Bemessung eingeht', () => {
104 const text = ausdruck(mitStauraum(45));
105 expect(text).toContain(toWinAnsi('geht in keine Zahl der Bemessung ein'));
106 });
107 });