lsa-planer

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

/ tests export barrierefreiheitZeichnung.test.ts

4,8 KB Rohdatei
tests/export/barrierefreiheitZeichnung.test.ts — 123 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 { toWinAnsi } from '@/render/pdfSurface';
8
9 /*
10 * Befund 42: Die Erklaerung "Barrierefreiheit dieser Datei" beschrieb
11 * Zeichnung und Signalzeitentabelle auch dann, wenn der Abschnitt
12 * "Signalzeitenplan" abgewaehlt war. drawBarrierefreiheit bekam
13 * options.includePlan nicht uebergeben.
14 *
15 * Was der Anwender sonst erlebt: Er waehlt in der Ansicht "Ausgabe" das
16 * Kaestchen "Signalzeitenplan" ab und erzeugt die Unterlage. Die Uebersicht
17 * der Bestandteile weist den Abschnitt zu Recht als abgewaehlt aus - wenige
18 * Seiten spaeter sagt die Erklaerung zur Barrierefreiheit trotzdem, die
19 * Signalbilder der Zeichnung staenden vollstaendig in der Tabelle
20 * "Signalzeiten je Signalgruppe" und "ein Absatz unter der Zeichnung verweist
21 * auf beides". Beides enthaelt die Datei nicht. Eine oeffentliche Stelle
22 * stuetzt auf diesen Abschnitt ihre Erklaerung nach Paragraf 12b BGG.
23 */
24
25 const STICHTAG = new Date('2026-01-01T12:00:00Z');
26
27 function gedruckt(text: string): string {
28 return toWinAnsi(text);
29 }
30
31 function entpacke(roh: string, von: number): string | null {
32 const anfang = roh.indexOf('stream', von);
33 if (anfang < 0) return null;
34 const start = anfang + (roh.startsWith('stream\r\n', anfang) ? 8 : 7);
35 const ende = roh.indexOf('endstream', start);
36 if (ende < 0) return null;
37 try {
38 return inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
39 } catch {
40 return null;
41 }
42 }
43
44 function volltext(bytes: Uint8Array): string {
45 const roh = new TextDecoder('latin1').decode(bytes);
46 const stuecke: string[] = [];
47 for (const treffer of roh.matchAll(/\/Type \/Page\b[^>]*?\/Contents (\d+) 0 R/g)) {
48 const objekt = roh.indexOf(`\n${treffer[1]!} 0 obj`);
49 const inhalt = objekt < 0 ? null : entpacke(roh, objekt);
50 if (inhalt === null) continue;
51 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
52 stuecke.push(t[1]!.replace(/\\([()\\])/g, '$1'));
53 }
54 }
55 return stuecke.join(' ').replace(/\s+/g, ' ');
56 }
57
58 function ausdruck(mitPlan: boolean): string {
59 const projekt = createStandardIntersectionProject('Musterkreuzung', STICHTAG);
60 const plan = buildSignalPlan(projekt);
61 return volltext(
62 buildProjectPdf(
63 projekt,
64 plan,
65 validateProject(projekt, plan, STICHTAG),
66 { ...DEFAULT_PDF_OPTIONS, includePlan: mitPlan },
67 STICHTAG,
68 ),
69 );
70 }
71
72 describe('Lueckenausweis bei abgewaehltem Signalzeitenplan', () => {
73 it('verspricht keine Textfassung der Zeichnung, wenn die Ausgabe keine Zeichnung enthaelt', () => {
74 const ohnePlan = ausdruck(false);
75
76 // Vorbedingung: Die Uebersicht der Bestandteile weist den Abschnitt - und
77 // damit Zeichnung und Signalzeitentabelle - als abgewaehlt aus.
78 expect(ohnePlan).toContain(gedruckt('Signalzeitenplan (Grafik und Signalzeiten)'));
79 expect(ohnePlan).toContain(gedruckt('abgewählt'));
80
81 expect(ohnePlan).not.toContain(
82 gedruckt('Die Signalbilder der Zeichnung stehen vollständig in Textform'),
83 );
84 expect(ohnePlan).not.toContain(gedruckt('Ein Absatz unter der Zeichnung verweist auf beides'));
85 });
86
87 it('nennt in der Merkmalstabelle keine Textfassung einer fehlenden Zeichnung', () => {
88 const ohnePlan = ausdruck(false);
89
90 expect(ohnePlan).toContain(gedruckt('Alternativtext der Zeichnung'));
91 expect(ohnePlan).not.toContain(gedruckt('gleichwertige Textfassung: Tabelle'));
92 expect(ohnePlan).not.toContain(gedruckt('Textfassung der Signalbilder: Tabelle'));
93 });
94
95 it('sagt nicht, wie eine Zeichnung vorgelesen wird, die es nicht gibt', () => {
96 const ohnePlan = ausdruck(false);
97
98 expect(ohnePlan).not.toContain(
99 gedruckt('Die Zeichnung des Signalzeitenplans erscheint als lose Folge von Zahlen'),
100 );
101 expect(ohnePlan).not.toContain(gedruckt('genügt die Signalzeitentabelle als Textfassung'));
102 });
103
104 it('benennt die Luecke, statt sie zu verschweigen', () => {
105 const ohnePlan = ausdruck(false);
106
107 expect(ohnePlan).toContain(
108 gedruckt('Abschnitt „Signalzeitenplan" wurde für diese Ausgabe abgewählt'),
109 );
110 });
111
112 it('haelt die Zusagen unveraendert, solange der Signalzeitenplan enthalten ist', () => {
113 const mitPlan = ausdruck(true);
114
115 expect(mitPlan).toContain(
116 gedruckt('Die Signalbilder der Zeichnung stehen vollständig in Textform'),
117 );
118 expect(mitPlan).toContain(gedruckt('Ein Absatz unter der Zeichnung verweist auf beides'));
119 expect(mitPlan).toContain(
120 gedruckt('Die Zeichnung des Signalzeitenplans erscheint als lose Folge von Zahlen'),
121 );
122 });
123 });