waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
/ app tests fehlerauffang.test.tsx
| 1 | // @vitest-environment jsdom |
| 2 | /** |
| 3 | * Der Auffangbereich der Oberfläche. |
| 4 | * |
| 5 | * Er ist die letzte Sperre vor einem weißen Fenster – und war bis 0.24.1 der |
| 6 | * einzige Baustein dieser Größe, den kein Test je aufgerufen hat. Genau dort |
| 7 | * fiel dann auch etwas heraus: `autoFocus` an einer `h1` tut nichts. React |
| 8 | * wendet das Attribut nur auf `button`, `input`, `select` und `textarea` an; |
| 9 | * an jedem anderen Element verschwindet es ersatzlos, und der Fokus blieb |
| 10 | * nach einem Absturz auf `<body>` stehen. |
| 11 | * |
| 12 | * Für jemanden am Bildschirmleser ist das die Sackgasse, die dieser Bereich |
| 13 | * verhindern soll: Der Fokus steht auf einem Element, das es nicht mehr gibt, |
| 14 | * und die Tastatur hat keinen Anker. |
| 15 | */ |
| 16 | |
| 17 | import { render, screen } from '@testing-library/react'; |
| 18 | import userEvent from '@testing-library/user-event'; |
| 19 | import { beforeEach, describe, expect, it, vi } from 'vitest'; |
| 20 | |
| 21 | import { Fehlerauffang } from '../src/renderer/src/components/Fehlerauffang'; |
| 22 | |
| 23 | /** Eine Komponente, die beim Rendern wirft – wie ein echter Absturz. */ |
| 24 | function Kaputt({ was }: { readonly was: unknown }): never { |
| 25 | throw was; |
| 26 | } |
| 27 | |
| 28 | beforeEach(() => { |
| 29 | /* React meldet den gefangenen Fehler selbst auf der Konsole, und der |
| 30 | Auffangbereich schreibt ihn absichtlich dorthin. Beides ist hier |
| 31 | erwartet. `restoreMocks` in `vitest.config.ts` räumt danach auf. */ |
| 32 | vi.spyOn(console, 'error').mockImplementation(() => undefined); |
| 33 | }); |
| 34 | |
| 35 | describe('Fehlerauffang', () => { |
| 36 | it('lässt den Inhalt stehen, solange nichts schiefgeht', () => { |
| 37 | render( |
| 38 | <Fehlerauffang> |
| 39 | <p>Alles in Ordnung</p> |
| 40 | </Fehlerauffang>, |
| 41 | ); |
| 42 | |
| 43 | expect(screen.getByText('Alles in Ordnung')).toBeInTheDocument(); |
| 44 | }); |
| 45 | |
| 46 | it('zeigt statt eines leeren Fensters eine Seite mit Überschrift und Ausweg', () => { |
| 47 | render( |
| 48 | <Fehlerauffang> |
| 49 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 50 | </Fehlerauffang>, |
| 51 | ); |
| 52 | |
| 53 | expect(screen.getByRole('heading', { name: 'Die Anzeige ist abgestürzt' })).toBeInTheDocument(); |
| 54 | expect(screen.getByRole('button', { name: 'Anwendung neu laden' })).toBeInTheDocument(); |
| 55 | }); |
| 56 | |
| 57 | it('meldet sich als Warnung, damit ein Bildschirmleser es ungefragt vorliest', () => { |
| 58 | render( |
| 59 | <Fehlerauffang> |
| 60 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 61 | </Fehlerauffang>, |
| 62 | ); |
| 63 | |
| 64 | expect(screen.getByRole('alert')).toBeInTheDocument(); |
| 65 | }); |
| 66 | |
| 67 | /* Der eigentliche Befund. */ |
| 68 | it('holt den Fokus auf die Überschrift, statt ihn auf <body> fallen zu lassen', () => { |
| 69 | render( |
| 70 | <Fehlerauffang> |
| 71 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 72 | </Fehlerauffang>, |
| 73 | ); |
| 74 | |
| 75 | const titel = screen.getByRole('heading', { name: 'Die Anzeige ist abgestürzt' }); |
| 76 | expect(document.activeElement).toBe(titel); |
| 77 | expect(document.activeElement).not.toBe(document.body); |
| 78 | }); |
| 79 | |
| 80 | it('nennt den Grund im Klartext', () => { |
| 81 | render( |
| 82 | <Fehlerauffang> |
| 83 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 84 | </Fehlerauffang>, |
| 85 | ); |
| 86 | |
| 87 | expect(screen.getByText('Etwas ist zerbrochen')).toBeInTheDocument(); |
| 88 | }); |
| 89 | |
| 90 | it('kommt auch mit einem geworfenen Wert zurecht, der kein Fehler ist', () => { |
| 91 | render( |
| 92 | <Fehlerauffang> |
| 93 | <Kaputt was="bloß eine Zeichenkette" /> |
| 94 | </Fehlerauffang>, |
| 95 | ); |
| 96 | |
| 97 | expect(screen.getByText('bloß eine Zeichenkette')).toBeInTheDocument(); |
| 98 | }); |
| 99 | |
| 100 | it('nennt „Unbekannter Fehler“, wenn der geworfene Wert nichts hergibt', () => { |
| 101 | render( |
| 102 | <Fehlerauffang> |
| 103 | <Kaputt was="" /> |
| 104 | </Fehlerauffang>, |
| 105 | ); |
| 106 | |
| 107 | expect(screen.getByText('Unbekannter Fehler')).toBeInTheDocument(); |
| 108 | }); |
| 109 | |
| 110 | it('sagt ausdrücklich, dass der Lernstand nicht betroffen ist', () => { |
| 111 | /* Wer eine Fehlerseite sieht, fragt sich zuerst, ob die Arbeit weg ist. |
| 112 | Die Antwort gehört auf die Seite und nicht in eine Anleitung. */ |
| 113 | render( |
| 114 | <Fehlerauffang> |
| 115 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 116 | </Fehlerauffang>, |
| 117 | ); |
| 118 | |
| 119 | expect(screen.getByText(/Ihr Lernstand ist nicht betroffen/u)).toBeInTheDocument(); |
| 120 | }); |
| 121 | |
| 122 | it('bietet den Ausweg als Schaltfläche und nennt den zweiten Weg dazu', async () => { |
| 123 | /* Der Klick selbst ruft `window.location.reload` – in jsdom ist die |
| 124 | Eigenschaft weder ersetzbar noch überwachbar, und ein echter Neuladen |
| 125 | risse die Testumgebung ab. Geprüft wird deshalb, dass der Ausweg da und |
| 126 | bedienbar ist, und dass die Seite den zweiten Weg nennt, falls der |
| 127 | erste nicht hilft. */ |
| 128 | const nutzer = userEvent.setup(); |
| 129 | render( |
| 130 | <Fehlerauffang> |
| 131 | <Kaputt was={new Error('Etwas ist zerbrochen')} /> |
| 132 | </Fehlerauffang>, |
| 133 | ); |
| 134 | |
| 135 | const knopf = screen.getByRole('button', { name: 'Anwendung neu laden' }); |
| 136 | expect(knopf).toBeEnabled(); |
| 137 | await nutzer.tab(); |
| 138 | expect(document.activeElement).toBe(knopf); |
| 139 | |
| 140 | expect( |
| 141 | screen.getByText(/schließen Sie das Fenster und starten Sie das Programm neu/u), |
| 142 | ).toBeInTheDocument(); |
| 143 | }); |
| 144 | }); |