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