lsa-planer

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

/ tests ui bedienbarkeit.test.ts

4,6 KB Rohdatei
tests/ui/bedienbarkeit.test.ts — 131 Zeilen
1 import { afterEach, describe, expect, it } from 'vitest';
2 import { el } from '@/ui/dom';
3 import { mitBewahrtemFokus } from '@/ui/fokus';
4
5 /*
6 * Der Weg ueber den Fokusschluessel prueft die Bedienbarkeit nicht.
7 *
8 * `mitBewahrtemFokus` kennt zwei Wege zurueck zum Bedienelement: den
9 * Fokusschluessel und den Strukturweg. Der Strukturweg fragt `istBedienbar()`,
10 * bevor er den Fokus setzt - "Nur auf ein Bedienelement zurueckspringen",
11 * begruendet im Quelltext damit, dass ein Fokus auf etwas Unbedienbarem
12 * schlechter ist als gar keiner. Der Schluesselweg fragte nicht.
13 *
14 * Der Unterschied trifft jedes Feld, das der Neuaufbau sperrt oder aus der
15 * Tabreihenfolge nimmt: Es traegt seinen Schluessel weiter, der Fokus landet
16 * darauf, und der Anwender sieht nicht mehr, wohin er geraten ist - die naechste
17 * Tabulatortaste setzt irgendwo neu an.
18 *
19 * Dass die Fokusrettung auf ein gesperrtes Element bewusst NICHT zurueckspringt,
20 * setzt `src/ui/views/exportView.ts` bereits voraus (Befund 49, Merkposten fuer
21 * den PDF-Knopf).
22 */
23
24 let aufraeumen: (() => void) | null = null;
25 afterEach(() => {
26 aufraeumen?.();
27 aufraeumen = null;
28 document.body.replaceChildren();
29 });
30
31 /** Wurzel mit einem Knopf, der seinen Fokusschluessel ueber den Neuaufbau haelt. */
32 function bauePlatz(): { wurzel: HTMLElement; zeichne: (ausserReihe: boolean) => void } {
33 const wurzel = document.createElement('div');
34 document.body.append(wurzel);
35 aufraeumen = () => wurzel.remove();
36
37 const zeichne = (ausserReihe: boolean): void => {
38 wurzel.replaceChildren(
39 el('button', {
40 type: 'button',
41 text: 'Ansehen',
42 'data-fokus': 'Werkzeug|Ansehen|BUTTON',
43 ...(ausserReihe ? { tabindex: '-1' } : {}),
44 }),
45 );
46 };
47
48 zeichne(false);
49 return { wurzel, zeichne };
50 }
51
52 const knopf = (wurzel: HTMLElement): HTMLButtonElement =>
53 wurzel.querySelector<HTMLButtonElement>('button') as HTMLButtonElement;
54
55 describe('Fokusrettung ueber den Schluessel', () => {
56 it('springt nicht auf ein Element, das der Neuaufbau aus der Tabreihenfolge genommen hat', () => {
57 const { wurzel, zeichne } = bauePlatz();
58 const vorher = knopf(wurzel);
59 vorher.focus();
60 expect(document.activeElement).toBe(vorher);
61
62 mitBewahrtemFokus(wurzel, () => {
63 zeichne(true);
64 });
65
66 const nachher = knopf(wurzel);
67 expect(nachher.tabIndex, 'der Knopf muss ausserhalb der Tabreihenfolge stehen').toBe(-1);
68 expect(
69 document.activeElement,
70 'ein Fokus auf etwas Unbedienbarem ist schlechter als gar keiner',
71 ).not.toBe(nachher);
72 });
73
74 it('faellt auf den Strukturweg zurueck, statt den Fokus fallen zu lassen', () => {
75 /*
76 * Die zweite Haelfte der Behebung: Die Pruefung darf nicht frueh
77 * zurueckkehren. "Traegt der Schluessel nicht, bleibt der Strukturweg" sagt
78 * der Quelltext zu - eine Fassung mit `if (nachher !== undefined) { ...;
79 * return; }` besaesse den Schluesselweg und verschluckte den Strukturweg.
80 *
81 * Aufbau: Der Schluessel wandert beim Neuaufbau an eine ANDERE Stelle und
82 * sitzt dort auf einem Knopf ausserhalb der Tabreihenfolge; an der
83 * urspruenglichen Strukturstelle steht ein bedienbarer Knopf.
84 */
85 const wurzel = document.createElement('div');
86 document.body.append(wurzel);
87 aufraeumen = () => wurzel.remove();
88
89 wurzel.replaceChildren(
90 el('button', { type: 'button', text: 'Bearbeiten', 'data-fokus': 'Werkzeug|K1|BUTTON' }),
91 el('button', { type: 'button', text: 'Verwerfen' }),
92 );
93 const vorher = wurzel.children[0] as HTMLButtonElement;
94 vorher.focus();
95 expect(document.activeElement).toBe(vorher);
96
97 mitBewahrtemFokus(wurzel, () => {
98 wurzel.replaceChildren(
99 el('button', { type: 'button', text: 'Bearbeiten' }),
100 el('button', {
101 type: 'button',
102 text: 'Verwerfen',
103 'data-fokus': 'Werkzeug|K1|BUTTON',
104 tabindex: '-1',
105 }),
106 );
107 });
108
109 const anDerStrukturstelle = wurzel.children[0] as HTMLButtonElement;
110 expect(anDerStrukturstelle.tabIndex, 'die Strukturstelle muss bedienbar sein').toBe(0);
111 expect(
112 document.activeElement,
113 'traegt der Schluessel nicht, muss der Strukturweg einspringen',
114 ).toBe(anDerStrukturstelle);
115 });
116
117 it('setzt den Fokus weiterhin, solange das Element bedienbar bleibt', () => {
118 // Gegenprobe: Die Pruefung darf den Schluesselweg nicht stilllegen.
119 const { wurzel, zeichne } = bauePlatz();
120 const vorher = knopf(wurzel);
121 vorher.focus();
122
123 mitBewahrtemFokus(wurzel, () => {
124 zeichne(false);
125 });
126
127 const nachher = knopf(wurzel);
128 expect(nachher).not.toBe(vorher);
129 expect(document.activeElement).toBe(nachher);
130 });
131 });