import { afterEach, describe, expect, it } from 'vitest'; import { el } from '@/ui/dom'; import { mitBewahrtemFokus } from '@/ui/fokus'; /* * Der Weg ueber den Fokusschluessel prueft die Bedienbarkeit nicht. * * `mitBewahrtemFokus` kennt zwei Wege zurueck zum Bedienelement: den * Fokusschluessel und den Strukturweg. Der Strukturweg fragt `istBedienbar()`, * bevor er den Fokus setzt - "Nur auf ein Bedienelement zurueckspringen", * begruendet im Quelltext damit, dass ein Fokus auf etwas Unbedienbarem * schlechter ist als gar keiner. Der Schluesselweg fragte nicht. * * Der Unterschied trifft jedes Feld, das der Neuaufbau sperrt oder aus der * Tabreihenfolge nimmt: Es traegt seinen Schluessel weiter, der Fokus landet * darauf, und der Anwender sieht nicht mehr, wohin er geraten ist - die naechste * Tabulatortaste setzt irgendwo neu an. * * Dass die Fokusrettung auf ein gesperrtes Element bewusst NICHT zurueckspringt, * setzt `src/ui/views/exportView.ts` bereits voraus (Befund 49, Merkposten fuer * den PDF-Knopf). */ let aufraeumen: (() => void) | null = null; afterEach(() => { aufraeumen?.(); aufraeumen = null; document.body.replaceChildren(); }); /** Wurzel mit einem Knopf, der seinen Fokusschluessel ueber den Neuaufbau haelt. */ function bauePlatz(): { wurzel: HTMLElement; zeichne: (ausserReihe: boolean) => void } { const wurzel = document.createElement('div'); document.body.append(wurzel); aufraeumen = () => wurzel.remove(); const zeichne = (ausserReihe: boolean): void => { wurzel.replaceChildren( el('button', { type: 'button', text: 'Ansehen', 'data-fokus': 'Werkzeug|Ansehen|BUTTON', ...(ausserReihe ? { tabindex: '-1' } : {}), }), ); }; zeichne(false); return { wurzel, zeichne }; } const knopf = (wurzel: HTMLElement): HTMLButtonElement => wurzel.querySelector('button') as HTMLButtonElement; describe('Fokusrettung ueber den Schluessel', () => { it('springt nicht auf ein Element, das der Neuaufbau aus der Tabreihenfolge genommen hat', () => { const { wurzel, zeichne } = bauePlatz(); const vorher = knopf(wurzel); vorher.focus(); expect(document.activeElement).toBe(vorher); mitBewahrtemFokus(wurzel, () => { zeichne(true); }); const nachher = knopf(wurzel); expect(nachher.tabIndex, 'der Knopf muss ausserhalb der Tabreihenfolge stehen').toBe(-1); expect( document.activeElement, 'ein Fokus auf etwas Unbedienbarem ist schlechter als gar keiner', ).not.toBe(nachher); }); it('faellt auf den Strukturweg zurueck, statt den Fokus fallen zu lassen', () => { /* * Die zweite Haelfte der Behebung: Die Pruefung darf nicht frueh * zurueckkehren. "Traegt der Schluessel nicht, bleibt der Strukturweg" sagt * der Quelltext zu - eine Fassung mit `if (nachher !== undefined) { ...; * return; }` besaesse den Schluesselweg und verschluckte den Strukturweg. * * Aufbau: Der Schluessel wandert beim Neuaufbau an eine ANDERE Stelle und * sitzt dort auf einem Knopf ausserhalb der Tabreihenfolge; an der * urspruenglichen Strukturstelle steht ein bedienbarer Knopf. */ const wurzel = document.createElement('div'); document.body.append(wurzel); aufraeumen = () => wurzel.remove(); wurzel.replaceChildren( el('button', { type: 'button', text: 'Bearbeiten', 'data-fokus': 'Werkzeug|K1|BUTTON' }), el('button', { type: 'button', text: 'Verwerfen' }), ); const vorher = wurzel.children[0] as HTMLButtonElement; vorher.focus(); expect(document.activeElement).toBe(vorher); mitBewahrtemFokus(wurzel, () => { wurzel.replaceChildren( el('button', { type: 'button', text: 'Bearbeiten' }), el('button', { type: 'button', text: 'Verwerfen', 'data-fokus': 'Werkzeug|K1|BUTTON', tabindex: '-1', }), ); }); const anDerStrukturstelle = wurzel.children[0] as HTMLButtonElement; expect(anDerStrukturstelle.tabIndex, 'die Strukturstelle muss bedienbar sein').toBe(0); expect( document.activeElement, 'traegt der Schluessel nicht, muss der Strukturweg einspringen', ).toBe(anDerStrukturstelle); }); it('setzt den Fokus weiterhin, solange das Element bedienbar bleibt', () => { // Gegenprobe: Die Pruefung darf den Schluesselweg nicht stilllegen. const { wurzel, zeichne } = bauePlatz(); const vorher = knopf(wurzel); vorher.focus(); mitBewahrtemFokus(wurzel, () => { zeichne(false); }); const nachher = knopf(wurzel); expect(nachher).not.toBe(vorher); expect(document.activeElement).toBe(nachher); }); });