// @vitest-environment jsdom
/**
* Der Auffangbereich der Oberfläche.
*
* Er ist die letzte Sperre vor einem weißen Fenster – und war bis 0.24.1 der
* einzige Baustein dieser Größe, den kein Test je aufgerufen hat. Genau dort
* fiel dann auch etwas heraus: `autoFocus` an einer `h1` tut nichts. React
* wendet das Attribut nur auf `button`, `input`, `select` und `textarea` an;
* an jedem anderen Element verschwindet es ersatzlos, und der Fokus blieb
* nach einem Absturz auf `
` stehen.
*
* Für jemanden am Bildschirmleser ist das die Sackgasse, die dieser Bereich
* verhindern soll: Der Fokus steht auf einem Element, das es nicht mehr gibt,
* und die Tastatur hat keinen Anker.
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Fehlerauffang } from '../src/renderer/src/components/Fehlerauffang';
/** Eine Komponente, die beim Rendern wirft – wie ein echter Absturz. */
function Kaputt({ was }: { readonly was: unknown }): never {
throw was;
}
beforeEach(() => {
/* React meldet den gefangenen Fehler selbst auf der Konsole, und der
Auffangbereich schreibt ihn absichtlich dorthin. Beides ist hier
erwartet. `restoreMocks` in `vitest.config.ts` räumt danach auf. */
vi.spyOn(console, 'error').mockImplementation(() => undefined);
});
describe('Fehlerauffang', () => {
it('lässt den Inhalt stehen, solange nichts schiefgeht', () => {
render(
Alles in Ordnung
,
);
expect(screen.getByText('Alles in Ordnung')).toBeInTheDocument();
});
it('zeigt statt eines leeren Fensters eine Seite mit Überschrift und Ausweg', () => {
render(
,
);
expect(screen.getByRole('heading', { name: 'Die Anzeige ist abgestürzt' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Anwendung neu laden' })).toBeInTheDocument();
});
it('meldet sich als Warnung, damit ein Bildschirmleser es ungefragt vorliest', () => {
render(
,
);
expect(screen.getByRole('alert')).toBeInTheDocument();
});
/* Der eigentliche Befund. */
it('holt den Fokus auf die Überschrift, statt ihn auf fallen zu lassen', () => {
render(
,
);
const titel = screen.getByRole('heading', { name: 'Die Anzeige ist abgestürzt' });
expect(document.activeElement).toBe(titel);
expect(document.activeElement).not.toBe(document.body);
});
it('nennt den Grund im Klartext', () => {
render(
,
);
expect(screen.getByText('Etwas ist zerbrochen')).toBeInTheDocument();
});
it('kommt auch mit einem geworfenen Wert zurecht, der kein Fehler ist', () => {
render(
,
);
expect(screen.getByText('bloß eine Zeichenkette')).toBeInTheDocument();
});
it('nennt „Unbekannter Fehler“, wenn der geworfene Wert nichts hergibt', () => {
render(
,
);
expect(screen.getByText('Unbekannter Fehler')).toBeInTheDocument();
});
it('sagt ausdrücklich, dass der Lernstand nicht betroffen ist', () => {
/* Wer eine Fehlerseite sieht, fragt sich zuerst, ob die Arbeit weg ist.
Die Antwort gehört auf die Seite und nicht in eine Anleitung. */
render(
,
);
expect(screen.getByText(/Ihr Lernstand ist nicht betroffen/u)).toBeInTheDocument();
});
it('bietet den Ausweg als Schaltfläche und nennt den zweiten Weg dazu', async () => {
/* Der Klick selbst ruft `window.location.reload` – in jsdom ist die
Eigenschaft weder ersetzbar noch überwachbar, und ein echter Neuladen
risse die Testumgebung ab. Geprüft wird deshalb, dass der Ausweg da und
bedienbar ist, und dass die Seite den zweiten Weg nennt, falls der
erste nicht hilft. */
const nutzer = userEvent.setup();
render(
,
);
const knopf = screen.getByRole('button', { name: 'Anwendung neu laden' });
expect(knopf).toBeEnabled();
await nutzer.tab();
expect(document.activeElement).toBe(knopf);
expect(
screen.getByText(/schließen Sie das Fenster und starten Sie das Programm neu/u),
).toBeInTheDocument();
});
});