lsa-planer

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

/ tests tools pruefstueck.test.ts

8,3 KB Rohdatei
tests/tools/pruefstueck.test.ts — 177 Zeilen
1 import { readFileSync } from 'node:fs';
2 import { describe, expect, it } from 'vitest';
3
4 /*
5 * Befunde 33, 34, 61 und 62 zur Fassung 5.10.0.
6 *
7 * Vier Aussagen des Pruefstuecks, die nicht stimmten. Drei davon stehen in der
8 * ausgelieferten Projektdatei bzw. in der ausgegebenen Planunterlage, eine im
9 * Quelltext des Werkzeugs.
10 *
11 * WARUM DIESE DATEI DAS WERKZEUG NICHT AUSFUEHRT: `tools/vierarmigerKnotenpunkt.ts`
12 * schreibt beim blossen Einbinden `beispiele/vierarmiger-knotenpunkt.svg` und
13 * `beispiele/vierarmiger-knotenpunkt.lsap`. Geprueft wird deshalb der Quelltext
14 * gegen die eingecheckten Erzeugnisse - dieselbe Vorsicht, aus der
15 * tests/tools/winkelangabe.test.ts `node:fs` stellt.
16 *
17 * DIE PROJEKTDATEI TRAEGT WEITERHIN schemaVersion 7. Sie ist zugleich
18 * Pruefstueck fuer den Altdatei-Pfad (bewusst so); ein Neuerzeugen
19 * haette sie auf 13 gehoben. Berichtigt sind an ihr nur die beiden Angaben, um
20 * die es hier geht.
21 *
22 * GEGEN DEN ALTSTAND schlagen alle Faelle bis auf die drei Vorbedingungen fehl.
23 */
24
25 const WERKZEUG = readFileSync('tools/vierarmigerKnotenpunkt.ts', 'utf8');
26 const PROJEKTDATEI = JSON.parse(readFileSync('beispiele/vierarmiger-knotenpunkt.lsap', 'utf8')) as {
27 schemaVersion: number;
28 meta: { description: string };
29 lageplan: {
30 bild: { herkunft: string };
31 kalibrierung: {
32 von: { x: number; y: number };
33 bis: { x: number; y: number };
34 laengeMeter: number;
35 herkunft: string;
36 };
37 haltlinien: { id: string; von: { x: number; y: number }; bis: { x: number; y: number } }[];
38 };
39 };
40 const SVG = readFileSync('beispiele/vierarmiger-knotenpunkt.svg', 'utf8');
41
42 /** Eine Zahl aus dem Werkzeug, damit die Wache nicht selbst rechnet. */
43 function konstante(name: string): number {
44 const treffer = new RegExp(`const ${name} = (-?[0-9.]+);`).exec(WERKZEUG);
45 expect(treffer, `Konstante ${name} im Werkzeug nicht gefunden`).not.toBeNull();
46 return Number(treffer![1]);
47 }
48
49 describe('Befund 33 - die Projektbeschreibung nennt die Lage der Haltlinie', () => {
50 const HALTLINIE_VOR = konstante('HALTLINIE_VOR');
51 const FURT_VOR = konstante('FURT_VOR');
52 const PX_JE_METER = konstante('PX_JE_METER');
53
54 it('ist die Haltlinie im Lageplan wirklich HALTLINIE_VOR Meter vor der Mitte', () => {
55 // Vorbedingung: Sie stimmt schon im Altstand - nur der Satz wich ab.
56 const nord = PROJEKTDATEI.lageplan.haltlinien.find((h) => h.id === 'hl-arm-nord');
57 expect(nord, 'Haltlinie "hl-arm-nord"').toBeDefined();
58 const mitteY = konstante('KANTE') * PX_JE_METER * 0.5;
59 expect((mitteY - nord!.von.y) / PX_JE_METER).toBe(HALTLINIE_VOR);
60 });
61
62 it('nennt die Beschreibung der Projektdatei dieselbe Zahl', () => {
63 const treffer = /Haltlinie ([0-9]+) m vor Knotenpunktmitte/.exec(PROJEKTDATEI.meta.description);
64 expect(treffer, 'Satz zur Haltlinienlage in meta.description').not.toBeNull();
65 expect(Number(treffer![1]), 'Haltlinienlage in der Projektbeschreibung').toBe(HALTLINIE_VOR);
66 });
67
68 it('nennt die Beschreibung die Furt gesondert, statt ihre Zahl der Haltlinie zu geben', () => {
69 expect(PROJEKTDATEI.meta.description).toContain(`Furt ${FURT_VOR} m davor`);
70 });
71
72 it('bildet das Werkzeug den Satz aus den Konstanten', () => {
73 // Sonst laufen Text und Geometrie beim naechsten Massaenderung erneut
74 // auseinander - genau so ist der Fehler entstanden.
75 const satz = /description:\s*((?:`[^`]*`\s*\+\s*)*`[^`]*`),/.exec(WERKZEUG);
76 expect(satz, 'Beschreibung als Schablone (Backtick) im Werkzeug').not.toBeNull();
77 expect(satz![1]).toContain('${HALTLINIE_VOR}');
78 expect(satz![1]).toContain('${FURT_VOR}');
79 expect(satz![1], 'die Haltlinienlage steht nicht mehr als Zahl im Satz').not.toMatch(
80 /Haltlinie [0-9]/,
81 );
82 });
83 });
84
85 describe('Befund 34 - die Kalibrierung des Pruefstuecks behauptet keinen Kartendienst', () => {
86 it('zeichnet das Werkzeug sein Hintergrundbild selbst', () => {
87 // Vorbedingung: Kein Kartendienst ist beteiligt - das einzige `fetch` des
88 // Vorhabens liegt in electron/karte.ts und wird hier nie beruehrt.
89 expect(PROJEKTDATEI.lageplan.bild.herkunft).toBe(
90 'Schematischer vierarmiger Knotenpunkt (Pruefstueck)',
91 );
92 expect(WERKZEUG).toContain('datenUrl: baueBild()');
93 });
94
95 it('weist die Projektdatei den Massstab als gemessen aus', () => {
96 // 'georeferenziert' heisst: Das Programm hat den Ausschnitt selbst
97 // angefordert und kennt seine Kantenlaenge deshalb exakt.
98 // Der Ausdruck druckt daraus "aus dem Kartendienst übernommen ..., nicht
99 // nachgemessen", die Seitenleiste des Lageplans "Er muss nicht nachgemessen
100 // werden" - beides falsch fuer ein selbst gezeichnetes Bild.
101 expect(PROJEKTDATEI.lageplan.kalibrierung.herkunft).toBe('gemessen');
102 });
103
104 it('setzt das Werkzeug denselben Wert', () => {
105 const block = /kalibrierung: \{[\s\S]*?herkunft: '([a-z]+)'/.exec(WERKZEUG);
106 expect(block, 'Kalibrierungsblock im Werkzeug').not.toBeNull();
107 expect(block![1], 'herkunft der Kalibrierung im Werkzeug').toBe('gemessen');
108 });
109
110 it('ist die Strecke bekannter Laenge nachpruefbar', () => {
111 // Der Zweig 'gemessen' druckt "an einer Strecke bekannter Länge
112 // abgegriffen: 90,0 m auf 900 Bildpunkten". Beide Zahlen muessen in der
113 // Datei stehen, sonst waere auch diese Aussage nicht belegt.
114 const k = PROJEKTDATEI.lageplan.kalibrierung;
115 const laengeBildpunkte = Math.hypot(k.bis.x - k.von.x, k.bis.y - k.von.y);
116 expect(laengeBildpunkte).toBeGreaterThan(0);
117 expect(k.laengeMeter).toBe(konstante('KANTE'));
118 expect(laengeBildpunkte).toBe(konstante('KANTE') * konstante('PX_JE_METER'));
119 });
120 });
121
122 describe('Befund 61 - keine angekuendigte Darstellung ohne Ausgabe', () => {
123 it('kuendigt das Werkzeug im SVG nichts zu feindlichen Beruehrungen an', () => {
124 // Der Abschnitt lief ueber alle feindlichen Paare, suchte beide Planlinien
125 // heraus und erzeugte danach nichts - kein `teile.push`. Entweder zeichnen
126 // oder die Ankuendigung fallen lassen; gewaehlt ist das Zweite, weil der
127 // Kopfkommentar von `baueSvg` nur zusagt, die Punkte sichtbar zu machen.
128 // Gesucht wird die Abschnittsueberschrift im Rumpf, nicht das Wort: Der
129 // Kopfkommentar von `baueSvg` nennt den behobenen Befund und darf das.
130 const ueberschrift = WERKZEUG.split('\n').filter(
131 (z) => z.trim() === '// Feindliche Beruehrungen',
132 );
133 expect(ueberschrift, 'Abschnittsueberschrift im Rumpf von baueSvg').toHaveLength(0);
134 expect(WERKZEUG).not.toContain('vertraeglichkeit.filter((v) => v.feindlich)');
135 });
136
137 it('rechnet das Werkzeug die Ableitung nicht ein drittes Mal', () => {
138 // Der dritte Aufruf haengt allein an der leeren Schleife: Der zweite
139 // Parameter von `baueSvg` wurde nirgends sonst gebraucht.
140 const aufrufe = WERKZEUG.match(/\bleiteAb\(/g) ?? [];
141 expect(aufrufe, 'Aufrufe von leiteAb im Werkzeug').toHaveLength(2);
142 expect(WERKZEUG).toContain('baueSvg(fertig)');
143 });
144
145 it('traegt das erzeugte Bild folgerichtig kein Merkmal zu Konflikten', () => {
146 // Gegenprobe zur Ankuendigung: Was im SVG steht, sind Fahrbahnen,
147 // Korridore, Mittellinien, Haltlinien und deren Beschriftung.
148 expect(SVG).toContain('<svg');
149 expect(SVG.match(/<polyline/g) ?? []).not.toHaveLength(0);
150 expect(SVG.toLowerCase()).not.toContain('feindlich');
151 expect(SVG.toLowerCase()).not.toContain('konflikt');
152 });
153 });
154
155 describe('Befund 62 - eine SVG-Einheit ist ein Meter', () => {
156 it('spannt die Zeichenflaeche die Kantenlaenge in Metern auf', () => {
157 // Vorbedingung und zugleich der Beweis: KANTE ist eine Laenge in Metern,
158 // die viewBox fuehrt dieselbe Zahl. Waere eine Einheit ein Dezimeter,
159 // stuenden dort 900.
160 const kante = konstante('KANTE');
161 expect(SVG).toContain(`viewBox="0 0 ${kante} ${kante}"`);
162 });
163
164 it('liegt die Haltlinie Nord HALTLINIE_VOR Einheiten vor der Mitte', () => {
165 const mitte = konstante('KANTE') / 2;
166 const treffer = /<line x1="([0-9.]+)" y1="([0-9.]+)"/.exec(SVG);
167 expect(treffer, 'erste Haltlinie im SVG').not.toBeNull();
168 expect(mitte - Number(treffer![2])).toBe(konstante('HALTLINIE_VOR'));
169 });
170
171 it('sagt der Kommentar am Massstab dasselbe', () => {
172 const zeile = WERKZEUG.split('\n').find((z) => z.includes('const s = (KANTE * PX_JE_METER)'));
173 expect(zeile, 'Zeile mit dem SVG-Massstab').toBeDefined();
174 expect(zeile).toContain('1 Einheit = 1 m');
175 expect(zeile).not.toContain('dm');
176 });
177 });