lsa-planer

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

/ tests ui zustandUndWertebereich.test.ts

4,9 KB Rohdatei
tests/ui/zustandUndWertebereich.test.ts — 124 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { ProjectStore } from '@/app/store';
3 import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent';
4 import type { Project } from '@/domain/model/project';
5 import type { ViewDefinition } from '@/ui/shell';
6 import { compatibilityView } from '@/ui/views/compatibilityView';
7 import { phasesView } from '@/ui/views/phasesView';
8 import { zugaenglicherName } from './zugaenglicherName';
9
10 /**
11 * Zwei Zusagen, die eine Sprachausgabe betreffen und sich still verlieren.
12 *
13 * Beide Faelle sind schon einmal eingetreten und beide Male war der Schaden
14 * derselbe: Eine Angabe, die ueber die Sicherheit der Schaltung entscheidet,
15 * kam beim Anwender falsch oder gar nicht an.
16 */
17
18 const FESTES_DATUM = '2026-01-01T00:00:00.000Z';
19
20 function gefuelltesProjekt(): Project {
21 return projektAusAngaben(
22 { ...ASSISTENT_VORGABE, fussgaenger: true, rad: true, linksabbieger: true },
23 FESTES_DATUM,
24 );
25 }
26
27 function zeichne(
28 view: ViewDefinition,
29 project: Project,
30 ): { wurzel: HTMLElement; aufraeumen: () => void } {
31 const wurzel = document.createElement('div');
32 document.body.append(wurzel);
33 const aufraeumen = view.render(wurzel, {
34 store: new ProjectStore(project),
35 refresh: () => {},
36 navigate: () => {},
37 target: null,
38 });
39 return {
40 wurzel,
41 aufraeumen: () => {
42 aufraeumen();
43 wurzel.remove();
44 },
45 };
46 }
47
48 describe('Vertraeglichkeitsmatrix meldet ihren Zustand genau einmal', () => {
49 it('nennt den Zustand in der Beschriftung und nicht zusaetzlich als aria-pressed', () => {
50 // Beides zusammen kippt gemeinsam: "vertraeglich, Umschaltflaeche, nicht
51 // gedrueckt". Das "nicht" laesst sich auf den Namen beziehen, und damit
52 // klingt ein vertraegliches Paar wie ein feindliches - in dieser Matrix
53 // die Entscheidung darueber, ob zwei kreuzende Stroeme gemeinsam Freigabe
54 // bekommen duerfen.
55 const { wurzel, aufraeumen } = zeichne(compatibilityView, gefuelltesProjekt());
56 try {
57 const zellen = [...wurzel.querySelectorAll<HTMLButtonElement>('.vertraeglichkeit-zelle')];
58 expect(zellen.length, 'Die Matrix hat keine bedienbaren Zellen').toBeGreaterThan(0);
59
60 for (const zelle of zellen) {
61 const zustand = (zelle.textContent ?? '').trim();
62 expect(['feindlich', 'verträglich']).toContain(zustand);
63 expect(
64 zelle.hasAttribute('aria-pressed'),
65 `"${zustand}" traegt zusaetzlich aria-pressed`,
66 ).toBe(false);
67 /*
68 * Der vorgelesene Name enthaelt den sichtbaren Zustand UND das Paar,
69 * um das es geht (Rest zu Befund M3).
70 *
71 * Zuvor stand hier `toBe(zustand)` - der Name war der blosse Zustand.
72 * Gemessen hiessen damit in einer Matrix mit sechzehn Signalgruppen
73 * 64 Knoepfe "feindlich" und 56 "verträglich": eine Folge gleicher
74 * Woerter, in der niemand weiss, welches Paar er gerade umschaltet.
75 *
76 * Der sichtbare Text muss im Namen VORKOMMEN (WCAG 2.5.3): Wer die
77 * Anwendung mit der Stimme bedient, spricht, was er liest.
78 */
79 const name = zugaenglicherName(zelle);
80 expect(name, 'Der sichtbare Zustand fehlt im Namen').toContain(zustand);
81 expect(name, 'Der Name nennt das Paar nicht').toMatch(/^\S+ und \S+: /);
82 }
83 } finally {
84 aufraeumen();
85 }
86 });
87 });
88
89 describe('Wertebereich der Umlaufzeit', () => {
90 it('steht sichtbar unter dem Feld und nennt dieselben Grenzen wie das Feld', () => {
91 // Nur vorgelesen erfuehre ein Sehender die Grenzen erst, nachdem seine
92 // Eingabe stillschweigend auf die naechste Schranke zurechtgestutzt wurde.
93 const basis = gefuelltesProjekt();
94 const projekt: Project = { ...basis, program: { ...basis.program, method: 'manuell' } };
95
96 const { wurzel, aufraeumen } = zeichne(phasesView, projekt);
97 try {
98 const beschriftung = [...wurzel.querySelectorAll('label')].find(
99 (l) => (l.textContent ?? '').trim() === 'Umlaufzeit',
100 );
101 expect(beschriftung, 'Kein Feld "Umlaufzeit" in der Ansicht').toBeDefined();
102
103 const feld = wurzel.querySelector<HTMLInputElement>(`#${beschriftung?.htmlFor ?? ''}`);
104 expect(feld, 'Die Beschriftung "Umlaufzeit" verweist auf kein Feld').not.toBeNull();
105
106 const hinweisId = feld?.getAttribute('aria-describedby') ?? '';
107 const hinweis = wurzel.querySelector(`#${hinweisId}`);
108 expect(hinweis, 'Das Feld "Umlaufzeit" hat keinen Hinweis').not.toBeNull();
109
110 expect(
111 hinweis?.classList.contains('nur-vorlesen'),
112 'Der Wertebereich der Umlaufzeit ist nur noch hoerbar',
113 ).toBe(false);
114
115 // Dieselben Grenzen wie das Feld selbst - ein von Hand danebengesetzter
116 // Satz liefe beim naechsten Grenzwechsel auseinander und loege.
117 const text = hinweis?.textContent ?? '';
118 expect(text).toContain(feld?.getAttribute('min') ?? '');
119 expect(text).toContain(feld?.getAttribute('max') ?? '');
120 } finally {
121 aufraeumen();
122 }
123 });
124 });