lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests electron schriftgroesse.test.ts
| 1 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
| 2 | import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; |
| 3 | import { tmpdir } from 'node:os'; |
| 4 | import path from 'node:path'; |
| 5 | import { |
| 6 | SCHRIFT_STANDARD, |
| 7 | SCHRIFT_STUFEN, |
| 8 | gueltigeStufe, |
| 9 | liesSchriftgroesse, |
| 10 | nachbarstufe, |
| 11 | schreibeSchriftgroesse, |
| 12 | schriftsicherung, |
| 13 | } from '../../electron/schriftgroesse'; |
| 14 | |
| 15 | /** |
| 16 | * Befund L11 (EN 301 549 Kap. 11.7). |
| 17 | * |
| 18 | * Der Altbestand hatte weder Stufen noch eine Sicherung - dieses Modul gab es |
| 19 | * nicht. Geprueft wird deshalb nicht nur, dass es die Stufen gibt, sondern die |
| 20 | * beiden Eigenschaften, an denen der Befund haengt: Die Einstellung ueberlebt |
| 21 | * den Programmstart, und ein unbrauchbarer Dateiinhalt haelt die Anwendung nicht |
| 22 | * auf. |
| 23 | */ |
| 24 | |
| 25 | describe('Schriftgroesse: Stufen', () => { |
| 26 | it('enthält die von WCAG 1.4.4 geforderten 200 % und die im Befund genannten 175 %', () => { |
| 27 | expect(SCHRIFT_STUFEN).toContain(200); |
| 28 | expect(SCHRIFT_STUFEN).toContain(175); |
| 29 | expect(SCHRIFT_STUFEN).toContain(SCHRIFT_STANDARD); |
| 30 | }); |
| 31 | |
| 32 | it('steigt lückenlos an und lässt auch kleiner als 100 % zu', () => { |
| 33 | const sortiert = [...SCHRIFT_STUFEN].sort((a, b) => a - b); |
| 34 | expect(SCHRIFT_STUFEN).toEqual(sortiert); |
| 35 | expect(new Set(SCHRIFT_STUFEN).size).toBe(SCHRIFT_STUFEN.length); |
| 36 | expect(Math.min(...SCHRIFT_STUFEN)).toBeLessThan(SCHRIFT_STANDARD); |
| 37 | }); |
| 38 | |
| 39 | it('rundet Zwischenwerte auf die nächstgelegene Stufe', () => { |
| 40 | // Ein Zwischenwert entsteht durch Strg+Mausrad oder durch Handarbeit an der |
| 41 | // Datei. Er darf nicht bestehen bleiben - sonst zeigt die Auswahlmarke im |
| 42 | // Menue auf eine Stufe, die gar nicht gilt. |
| 43 | expect(gueltigeStufe(160)).toBe(150); |
| 44 | expect(gueltigeStufe(170)).toBe(175); |
| 45 | expect(gueltigeStufe(5)).toBe(Math.min(...SCHRIFT_STUFEN)); |
| 46 | expect(gueltigeStufe(5000)).toBe(Math.max(...SCHRIFT_STUFEN)); |
| 47 | }); |
| 48 | |
| 49 | it('weist alles zurück, was keine brauchbare Zahl ist', () => { |
| 50 | expect(gueltigeStufe('175')).toBe(SCHRIFT_STANDARD); |
| 51 | expect(gueltigeStufe(null)).toBe(SCHRIFT_STANDARD); |
| 52 | expect(gueltigeStufe(undefined)).toBe(SCHRIFT_STANDARD); |
| 53 | expect(gueltigeStufe(Number.NaN)).toBe(SCHRIFT_STANDARD); |
| 54 | expect(gueltigeStufe(Number.POSITIVE_INFINITY)).toBe(SCHRIFT_STANDARD); |
| 55 | expect(gueltigeStufe({ prozent: 175 })).toBe(SCHRIFT_STANDARD); |
| 56 | }); |
| 57 | |
| 58 | it('geht eine Stufe weiter und bleibt an den Enden stehen', () => { |
| 59 | const kleinste = Math.min(...SCHRIFT_STUFEN); |
| 60 | const groesste = Math.max(...SCHRIFT_STUFEN); |
| 61 | expect(nachbarstufe(100, 1)).toBe(125); |
| 62 | expect(nachbarstufe(125, -1)).toBe(100); |
| 63 | expect(nachbarstufe(groesste, 1)).toBe(groesste); |
| 64 | expect(nachbarstufe(kleinste, -1)).toBe(kleinste); |
| 65 | }); |
| 66 | |
| 67 | it('führt von 100 % in fünf Schritten mindestens auf 200 %', () => { |
| 68 | let stufe = SCHRIFT_STANDARD; |
| 69 | for (let i = 0; i < 5; i += 1) stufe = nachbarstufe(stufe, 1); |
| 70 | expect(stufe).toBeGreaterThanOrEqual(200); |
| 71 | }); |
| 72 | }); |
| 73 | |
| 74 | describe('Schriftgroesse: Sicherung', () => { |
| 75 | let ordner = ''; |
| 76 | let datei = ''; |
| 77 | |
| 78 | beforeEach(() => { |
| 79 | ordner = mkdtempSync(path.join(tmpdir(), 'lsa-ansicht-')); |
| 80 | datei = path.join(ordner, 'ansicht.json'); |
| 81 | }); |
| 82 | |
| 83 | afterEach(() => { |
| 84 | rmSync(ordner, { recursive: true, force: true }); |
| 85 | }); |
| 86 | |
| 87 | it('gibt die Vorgabe zurück, solange nichts gesichert wurde', () => { |
| 88 | expect(liesSchriftgroesse(datei)).toBe(SCHRIFT_STANDARD); |
| 89 | }); |
| 90 | |
| 91 | it('überlebt den Programmstart - das ist der Kern des Befunds', () => { |
| 92 | schreibeSchriftgroesse(datei, 175); |
| 93 | expect(liesSchriftgroesse(datei)).toBe(175); |
| 94 | }); |
| 95 | |
| 96 | it('behält fremde Schlüssel der Datei', () => { |
| 97 | // Fenstergroesse und Fensterlage gehoeren spaeter in dieselbe Datei; sie |
| 98 | // duerfen beim Sichern der Schriftgroesse nicht verlorengehen. |
| 99 | writeFileSync(datei, JSON.stringify({ fensterbreite: 1200 }), 'utf8'); |
| 100 | schreibeSchriftgroesse(datei, 150); |
| 101 | const gelesen: unknown = JSON.parse(readFileSync(datei, 'utf8')); |
| 102 | expect(gelesen).toMatchObject({ fensterbreite: 1200, schriftgroesseProzent: 150 }); |
| 103 | }); |
| 104 | |
| 105 | it('schreibt ohne Byte-Reihenfolge-Marke und mit abschließendem Zeilenende', () => { |
| 106 | schreibeSchriftgroesse(datei, 125); |
| 107 | const roh = readFileSync(datei); |
| 108 | expect(roh[0]).not.toBe(0xef); |
| 109 | expect(roh.toString('utf8').endsWith('\n')).toBe(true); |
| 110 | }); |
| 111 | |
| 112 | it('hält bei beschädigtem Inhalt an der Vorgabe fest, statt zu scheitern', () => { |
| 113 | writeFileSync(datei, '{ das ist kein JSON', 'utf8'); |
| 114 | expect(liesSchriftgroesse(datei)).toBe(SCHRIFT_STANDARD); |
| 115 | writeFileSync(datei, '[]', 'utf8'); |
| 116 | expect(liesSchriftgroesse(datei)).toBe(SCHRIFT_STANDARD); |
| 117 | writeFileSync(datei, JSON.stringify({ schriftgroesseProzent: 'sehr groß' }), 'utf8'); |
| 118 | expect(liesSchriftgroesse(datei)).toBe(SCHRIFT_STANDARD); |
| 119 | }); |
| 120 | |
| 121 | it('scheitert nicht, wenn der Ort nicht beschreibbar ist', () => { |
| 122 | const unerreichbar = path.join(ordner, 'gibt-es-nicht', 'ansicht.json'); |
| 123 | expect(() => { |
| 124 | schreibeSchriftgroesse(unerreichbar, 175); |
| 125 | }).not.toThrow(); |
| 126 | expect(liesSchriftgroesse(unerreichbar)).toBe(SCHRIFT_STANDARD); |
| 127 | }); |
| 128 | }); |
| 129 | |
| 130 | /** |
| 131 | * Verzoegerte Sicherung (Befund B5). |
| 132 | * |
| 133 | * `zoom-changed` feuert bei jeder Raste von Strg+Mausrad, und jede Raste lief |
| 134 | * in ein `writeFileSync` im HAUPTPROZESS - also in den Prozess, der die Fenster |
| 135 | * bedient. Auf einem servergespeicherten Benutzerprofil blockiert das die |
| 136 | * Oberflaeche pro Raste. Geschrieben wurde ausserdem auch dann, wenn sich gar |
| 137 | * nichts geaendert hatte: An beiden Enden der Stufenliste gibt `nachbarstufe` |
| 138 | * denselben Wert zurueck. |
| 139 | */ |
| 140 | describe('Schriftgroesse: Sicherung wartet die Ruhezeit ab', () => { |
| 141 | let ordner = ''; |
| 142 | let datei = ''; |
| 143 | |
| 144 | beforeEach(() => { |
| 145 | vi.useFakeTimers(); |
| 146 | ordner = mkdtempSync(path.join(tmpdir(), 'lsa-sicherung-')); |
| 147 | datei = path.join(ordner, 'ansicht.json'); |
| 148 | }); |
| 149 | |
| 150 | afterEach(() => { |
| 151 | vi.useRealTimers(); |
| 152 | rmSync(ordner, { recursive: true, force: true }); |
| 153 | }); |
| 154 | |
| 155 | /** Wie oft die Datei seit dem letzten Aufruf neu geschrieben wurde. */ |
| 156 | function schreibungen(): number { |
| 157 | try { |
| 158 | readFileSync(datei, 'utf8'); |
| 159 | return 1; |
| 160 | } catch { |
| 161 | return 0; |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | it('macht aus einem Dutzend Rasten eine einzige Schreibung', () => { |
| 166 | const sicherung = schriftsicherung(datei, 100, 750); |
| 167 | for (const stufe of [125, 150, 175, 200, 250]) sicherung.plane(stufe); |
| 168 | |
| 169 | expect(schreibungen(), 'schon vor der Ruhezeit geschrieben').toBe(0); |
| 170 | vi.advanceTimersByTime(800); |
| 171 | expect(liesSchriftgroesse(datei)).toBe(250); |
| 172 | }); |
| 173 | |
| 174 | it('schreibt gar nicht, wenn sich die Stufe nicht aendert', () => { |
| 175 | // An beiden Enden der Liste gibt `nachbarstufe` denselben Wert zurueck - |
| 176 | // wer am Anschlag weiterdreht, loeste bis hierher Schreibung um Schreibung |
| 177 | // aus. |
| 178 | const sicherung = schriftsicherung(datei, 250, 750); |
| 179 | for (let i = 0; i < 12; i += 1) sicherung.plane(nachbarstufe(250, 1)); |
| 180 | |
| 181 | vi.advanceTimersByTime(2000); |
| 182 | expect(schreibungen(), 'ohne Aenderung geschrieben').toBe(0); |
| 183 | }); |
| 184 | |
| 185 | it('verwirft eine Aenderung, die vor Ablauf wieder zurueckgenommen wird', () => { |
| 186 | const sicherung = schriftsicherung(datei, 100, 750); |
| 187 | sicherung.plane(150); |
| 188 | vi.advanceTimersByTime(300); |
| 189 | sicherung.plane(100); |
| 190 | vi.advanceTimersByTime(2000); |
| 191 | expect(schreibungen(), 'hin und zurueck ist keine Aenderung').toBe(0); |
| 192 | }); |
| 193 | |
| 194 | it('schreibt beim Programmende sofort, was noch aussteht', () => { |
| 195 | // Eine Einstellung, die eine halbe Sekunde vor dem Beenden getroffen wurde, |
| 196 | // darf nicht verlorengehen. |
| 197 | const sicherung = schriftsicherung(datei, 100, 750); |
| 198 | sicherung.plane(175); |
| 199 | sicherung.jetzt(); |
| 200 | expect(liesSchriftgroesse(datei)).toBe(175); |
| 201 | |
| 202 | // Und ein zweites `jetzt()` schreibt nicht noch einmal. |
| 203 | rmSync(datei); |
| 204 | sicherung.jetzt(); |
| 205 | expect(schreibungen()).toBe(0); |
| 206 | }); |
| 207 | |
| 208 | it('haelt die Buchfuehrung ueber mehrere Vorgaenge hinweg', () => { |
| 209 | const sicherung = schriftsicherung(datei, 100, 750); |
| 210 | sicherung.plane(150); |
| 211 | vi.advanceTimersByTime(800); |
| 212 | expect(liesSchriftgroesse(datei)).toBe(150); |
| 213 | |
| 214 | // Derselbe Wert noch einmal: keine zweite Schreibung. |
| 215 | rmSync(datei); |
| 216 | sicherung.plane(150); |
| 217 | vi.advanceTimersByTime(800); |
| 218 | expect(schreibungen()).toBe(0); |
| 219 | |
| 220 | sicherung.plane(200); |
| 221 | vi.advanceTimersByTime(800); |
| 222 | expect(liesSchriftgroesse(datei)).toBe(200); |
| 223 | }); |
| 224 | }); |