import { afterEach, describe, expect, it } from 'vitest'; import * as actions from '@/app/actions'; import { ProjectStore } from '@/app/store'; import type { Lageplan } from '@/domain/geometrie/lageplan'; import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent'; import { lageplanView } from '@/ui/views/lageplanView'; /** * Befund 54: Die Rueckschritttaste wirkte nur beim Werkzeug "Fahrlinie". * * WAS DER ANWENDER SONST ERLEBT * * Er waehlt "Massstab", setzt mit der Leertaste den ersten Punkt der * Bezugsstrecke und merkt, dass er danebengegriffen hat. Er drueckt * Rueckschritt - nichts geschieht, und nichts wird angesagt. Der naechste * Leertastendruck schliesst die Strecke zu genau dem Punkt, den er * zuruecknehmen wollte. Dasselbe beim Werkzeug "Haltlinie". * * Zugesagt wird die Taste ohne jede Einschraenkung: im vorgelesenen Hinweis der * Zeichenflaeche, in der aufklappbaren Tastenbelegung darunter und im * Hilfefenster. Uebrig blieb nur Escape - das verwirft aber die ganze * Messung UND schaltet auf "Ansehen", ist also kein Schritt zurueck. * * WCAG 2.1 Erfolgskriterium 4.1.3 "Statusmeldungen": Eine zurueckgenommene * Marke, die nicht angesagt wird, ist auf einer Zeichenflaeche nicht * wahrnehmbar. */ const FESTES_DATUM = '2026-01-01T00:00:00.000Z'; function planOhneMassstab(): Lageplan { return { arbeitsbereiche: [], signalgeber: [], bild: { datenUrl: 'data:image/png;base64,iVBORw0KGgo=', breite: 800, hoehe: 600, herkunft: 'Prüfbild', geladenAm: FESTES_DATUM, }, kalibrierung: null, haltlinien: [], linien: [], }; } /** Derselbe Plan, nachdem der Massstab steht: 100 Bildpunkte auf 20 m. */ function planMitMassstab(): Lageplan { return { ...planOhneMassstab(), kalibrierung: { von: { x: 0, y: 0 }, bis: { x: 100, y: 0 }, laengeMeter: 20, herkunft: 'gemessen', }, }; } interface Ansicht { readonly store: ProjectStore; readonly wurzel: HTMLElement; readonly leinwand: HTMLCanvasElement; readonly aufraeumen: () => void; } function baueAnsicht(lageplan: Lageplan): Ansicht { const store = new ProjectStore(projektAusAngaben({ ...ASSISTENT_VORGABE }, FESTES_DATUM)); store.update((p) => actions.setLageplan(p, lageplan), { label: 'Pruefplan' }); const wurzel = document.createElement('div'); document.body.append(wurzel); const schliessen = lageplanView.render(wurzel, { store, refresh: () => {}, navigate: () => {}, target: null, }); const leinwand = wurzel.querySelector('canvas'); if (leinwand === null) throw new Error('Die Ansicht hat keine Zeichenflaeche.'); return { store, wurzel, leinwand, aufraeumen: () => { leinwand.blur(); schliessen(); wurzel.remove(); }, }; } afterEach(() => { document.body.replaceChildren(); }); function waehleWerkzeug(ansicht: Ansicht, beschriftung: string): void { const knopf = [ ...ansicht.wurzel.querySelectorAll('.lageplan-werkzeuge button'), ].find((b) => (b.textContent ?? '').trim() === beschriftung); if (knopf === undefined) throw new Error(`Kein Werkzeug "${beschriftung}"`); knopf.click(); } /** Tippt auf das Element, das gerade den Fokus hat; false heisst abgefangen. */ function tippe(key: string, male = 1): boolean { let zugestellt = true; for (let i = 0; i < male; i += 1) { const ziel = (document.activeElement as HTMLElement | null) ?? document.body; zugestellt = ziel.dispatchEvent( new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }), ); } return zugestellt; } function ansage(ansicht: Ansicht): string { return ansicht.wurzel.querySelector('.lageplan-rahmen [role="status"]')?.textContent ?? ''; } describe('Rückschritttaste beim Werkzeug "Maßstab"', () => { it('nimmt den ersten Punkt der Maßstabsstrecke zurück und sagt es an', () => { const ansicht = baueAnsicht(planOhneMassstab()); try { waehleWerkzeug(ansicht, 'Maßstab'); ansicht.leinwand.focus(); tippe(' '); expect(ansage(ansicht)).toContain('Erster Punkt der Maßstabsstrecke gesetzt'); // Abgefangen werden muss die Taste auch: Sonst blaettert der Browser // unter der Zeichenflaeche zurueck. expect(tippe('Backspace'), 'Rückschritt nicht abgefangen').toBe(false); expect(ansage(ansicht)).toContain('zurückgenommen'); // Der Nachweis, dass wirklich nichts mehr steht: Der naechste // Leertastendruck beginnt von vorn, statt die Strecke zu schliessen. tippe('ArrowRight', 20); tippe(' '); expect(ansage(ansicht)).toContain('Erster Punkt der Maßstabsstrecke gesetzt'); expect(document.querySelector('.dialog'), 'Fenster nach nur einem Punkt').toBeNull(); expect(ansicht.store.getProject().lageplan.kalibrierung).toBeNull(); } finally { ansicht.aufraeumen(); } }); }); describe('Rückschritttaste beim Werkzeug "Haltlinie"', () => { it('nimmt den ersten Fahrbahnrand zurück und sagt es an', () => { const ansicht = baueAnsicht(planMitMassstab()); try { waehleWerkzeug(ansicht, 'Haltlinie'); ansicht.leinwand.focus(); tippe(' '); expect(ansage(ansicht)).toContain('Erster Punkt der Haltlinie gesetzt'); expect(tippe('Backspace'), 'Rückschritt nicht abgefangen').toBe(false); expect(ansage(ansicht)).toContain('zurückgenommen'); tippe('ArrowDown', 12); tippe(' '); expect(ansage(ansicht)).toContain('Erster Punkt der Haltlinie gesetzt'); expect(document.querySelector('.dialog'), 'Fenster nach nur einem Punkt').toBeNull(); expect(ansicht.store.getProject().lageplan.haltlinien).toHaveLength(0); } finally { ansicht.aufraeumen(); } }); });