waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app e2e kapitelumfang.spec.ts

4,8 KB Rohdatei
app/e2e/kapitelumfang.spec.ts — 132 Zeilen
1 /**
2 * Die dauerhafte Kapitelabwahl im echten Fenster.
3 *
4 * Was die Vitest-Tests an einer Attrappe und an einer Wegwerf-Datenbank
5 * prüfen, läuft hier am echten Katalog mit seinen 575 Fragen und an der
6 * echten SQLite-Datei: Der Schalter schreibt wirklich in die Profilzeile,
7 * die Zahl im Gesamtfortschritt ändert sich wirklich, und die Umstellung
8 * überlebt einen Neuaufbau der Oberfläche wirklich.
9 *
10 * Der Neuaufbau geschieht per `reload()` – kein Prozessneustart, aber genau
11 * der Weg, um den es geht: React baut frisch auf und liest die Profilzeile
12 * neu aus der Datenbank.
13 */
14
15 import { type ElectronApplication, type Page } from '@playwright/test';
16
17 import { amStart, appStarten, bauPruefen } from './electron-hilfe';
18 import { expect, test } from './konsolenwache';
19
20 /** Der amtliche Katalog, und was ohne Kapitel IV davon übrig bleibt. */
21 const GESAMT = 575;
22 const OHNE_KAPITEL_IV = 486;
23
24 let app: ElectronApplication | undefined;
25 let fenster: Page;
26
27 test.beforeAll(async () => {
28 bauPruefen();
29 ({ app, fenster } = await appStarten());
30 });
31
32 test.afterAll(async () => {
33 await app?.close();
34 });
35
36 test.describe.configure({ mode: 'serial' });
37
38 function schalter() {
39 return fenster.getByRole('checkbox', { name: /Kapitel IV .* mitlernen/u });
40 }
41
42 /**
43 * Der Lernumfang, wie ihn die Ampel ausweist.
44 *
45 * Aus `aria-valuemax` statt aus dem Text: Der Satz der Ampel ändert seine
46 * Form je nach Stufe – bei leerem Stand nennt er die Regel statt einer Zahl –,
47 * die Obergrenze des Balkens ist dagegen immer der Umfang.
48 */
49 async function umfang(): Promise<number> {
50 const wert = await fenster
51 .getByRole('progressbar', { name: 'Prüfungsreife insgesamt' })
52 .getAttribute('aria-valuemax');
53 return Number(wert ?? 0);
54 }
55
56 async function zumStart(): Promise<void> {
57 for (let i = 0; i < 4; i++) {
58 if (await amStart(fenster)) {
59 return;
60 }
61 const zurueck = fenster.getByRole('button', { name: /^(Zum Start|Sitzung beenden)$/u });
62 if ((await zurueck.count()) === 0) {
63 return;
64 }
65 await zurueck.first().click();
66 await fenster.waitForTimeout(250);
67 }
68 }
69
70 test.describe('Kapitel IV dauerhaft abwählen', () => {
71 test('nimmt Kapitel IV aus dem Lernumfang und sagt es an', async () => {
72 await zumStart();
73 expect(await umfang()).toBe(GESAMT);
74
75 await schalter().click();
76
77 /* Die Ansage steht nur für Bildschirmleser im Dokument, ist aber ein
78 echtes Element – sichtbar im Sinne von Playwright ist sie nicht. */
79 await expect(fenster.getByText(/Kapitel IV ist für dieses Profil abgewählt/u)).toBeAttached();
80 await expect.poll(umfang).toBe(OHNE_KAPITEL_IV);
81 });
82
83 test('überlebt den Neuaufbau der Oberfläche', async () => {
84 /* Der Punkt der ganzen Übung: „dauerhaft“ heißt in der Profilzeile, nicht
85 im Zustand einer React-Komponente. */
86 await fenster.reload();
87 await fenster.waitForLoadState('domcontentloaded');
88 await fenster.waitForSelector('h1', { state: 'visible' });
89
90 await expect(schalter()).not.toBeChecked();
91 await expect.poll(umfang).toBe(OHNE_KAPITEL_IV);
92 });
93
94 test('lässt die Suche den ganzen Katalog behalten', async () => {
95 /* Abgewählt heißt: zählt nicht mit. Nicht: gibt es nicht mehr. Wer im
96 Lehrgang nach einer Signalpistole gefragt wird, muss sie nachschlagen
97 können. */
98 await fenster.getByRole('button', { name: /Fragen durchsuchen/u }).click();
99 await fenster.getByRole('heading', { name: 'Fragen durchsuchen', level: 1 }).waitFor();
100
101 await expect(
102 fenster.getByText(`Durchsucht wird der Wortlaut aller ${String(GESAMT)} amtlichen Fragen`, {
103 exact: false,
104 }),
105 ).toBeVisible();
106
107 await fenster.getByLabel('Suchbegriff').fill('Seenotsignalmittel');
108 await expect(fenster.locator('.treffer__frage').first()).toBeVisible();
109 });
110
111 test('vermerkt das Kapitel in der Kapitelwahl, statt es zu verbergen', async () => {
112 /* Weder verschwinden noch sperren: Verschwände es, fände niemand den Weg
113 zurück zum Schalter; und ein gesperrter Knopf nimmt keinen Fokus – die
114 Erklärung daneben würde nie vorgelesen. */
115 await zumStart();
116 await fenster.getByRole('button', { name: /Kapitel wählen/u }).click();
117 await fenster.getByRole('heading', { name: 'Kapitel wählen', level: 1 }).waitFor();
118
119 await expect(fenster.getByText(/Für dieses Profil abgewählt/u)).toBeVisible();
120 await expect(fenster.getByRole('button', { name: /Ganzes Kapitel IV üben/u })).toBeEnabled();
121 });
122
123 test('holt das Kapitel vollständig zurück', async () => {
124 /* Zuletzt, damit die Datei den Zustand hinterlässt, in dem sie ihn
125 vorgefunden hat – die übrigen Prüfläufe rechnen mit 575. */
126 await zumStart();
127 await schalter().click();
128
129 await expect(fenster.getByText(/Kapitel IV wird wieder mitgelernt/u)).toBeAttached();
130 await expect.poll(umfang).toBe(GESAMT);
131 });
132 });