waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
| 1 | /** |
| 2 | * Lernprofile im echten Fenster. |
| 3 | * |
| 4 | * Was die Vitest-Tests mit einer Attrappe pruefen, laeuft hier gegen die |
| 5 | * echte SQLite-Datei und die echten Einstellungen: Ein zweites Profil |
| 6 | * entsteht wirklich, die Startfrage kommt beim naechsten Aufbau wirklich, |
| 7 | * und die Wahl ueberlebt ihn wirklich. |
| 8 | * |
| 9 | * Der Neuaufbau geschieht per `reload()`. Das ist kein Neustart des |
| 10 | * Prozesses, prueft aber genau den Weg, um den es geht: React baut frisch |
| 11 | * auf, liest die Profilliste aus der Datenbank und die zuletzt benutzte |
| 12 | * Nummer aus den Einstellungen. |
| 13 | */ |
| 14 | |
| 15 | import { type ElectronApplication, type Page } from '@playwright/test'; |
| 16 | |
| 17 | import { appStarten, bauPruefen } from './electron-hilfe'; |
| 18 | import { expect, test } from './konsolenwache'; |
| 19 | |
| 20 | let app: ElectronApplication | undefined; |
| 21 | let fenster: Page; |
| 22 | |
| 23 | test.beforeAll(async () => { |
| 24 | // Fehlender oder veralteter Bau ist ein Fehler, kein Grund zum Schweigen. |
| 25 | bauPruefen(); |
| 26 | ({ app, fenster } = await appStarten()); |
| 27 | }); |
| 28 | |
| 29 | test.afterAll(async () => { |
| 30 | await app?.close(); |
| 31 | }); |
| 32 | |
| 33 | /** Baut die Oberflaeche neu auf, ohne die Daten zu verlieren. */ |
| 34 | async function neuAufbauen(): Promise<void> { |
| 35 | await fenster.reload(); |
| 36 | await fenster.waitForLoadState('domcontentloaded'); |
| 37 | await fenster.waitForSelector('h1', { state: 'visible' }); |
| 38 | } |
| 39 | |
| 40 | test.describe.configure({ mode: 'serial' }); |
| 41 | |
| 42 | test.describe('Lernprofile', () => { |
| 43 | test('fragt bei einem Profil nicht, wer lernt', async () => { |
| 44 | await expect(fenster.getByRole('heading', { name: 'Heute lernen' })).toBeVisible(); |
| 45 | await expect(fenster.getByRole('heading', { name: 'Wer lernt?' })).toHaveCount(0); |
| 46 | }); |
| 47 | |
| 48 | test('legt ein zweites Profil an', async () => { |
| 49 | await fenster.getByRole('button', { name: 'Neues Profil anlegen' }).click(); |
| 50 | await fenster.getByLabel('Name des neuen Profils').fill('Zweiter'); |
| 51 | await fenster.getByRole('button', { name: 'Profil anlegen' }).click(); |
| 52 | |
| 53 | await expect(fenster.getByText(/Das Profil „Zweiter“ ist angelegt/u)).toBeVisible(); |
| 54 | await expect(fenster.locator('.profile__liste > li')).toHaveCount(2); |
| 55 | }); |
| 56 | |
| 57 | test('fragt ab dem zweiten Profil beim Aufbau, wer lernt', async () => { |
| 58 | await neuAufbauen(); |
| 59 | |
| 60 | await expect(fenster.getByRole('heading', { name: 'Wer lernt?' })).toBeVisible(); |
| 61 | // Das bisherige Profil steht vorausgewaehlt – die Eingabetaste genuegt. |
| 62 | await expect(fenster.getByRole('radio', { name: 'Standard' })).toBeChecked(); |
| 63 | await expect(fenster.getByRole('heading', { name: 'Heute lernen' })).toHaveCount(0); |
| 64 | }); |
| 65 | |
| 66 | test('wechselt erst auf ausdrueckliche Anforderung (WCAG 3.2.2)', async () => { |
| 67 | await fenster.getByRole('radio', { name: 'Zweiter' }).check(); |
| 68 | |
| 69 | /* Die Auswahl allein darf den Zusammenhang nicht aendern: Der |
| 70 | Startbildschirm ist noch nicht da. */ |
| 71 | await expect(fenster.getByRole('heading', { name: 'Heute lernen' })).toHaveCount(0); |
| 72 | |
| 73 | await fenster.getByRole('button', { name: 'Weiter' }).click(); |
| 74 | |
| 75 | await expect(fenster.getByRole('heading', { name: 'Heute lernen' })).toBeVisible(); |
| 76 | await expect(fenster.locator('.einstieg__profil')).toHaveText('Lernstand von Zweiter'); |
| 77 | }); |
| 78 | |
| 79 | test('merkt sich die Wahl fuer den naechsten Aufbau', async () => { |
| 80 | await neuAufbauen(); |
| 81 | |
| 82 | await expect(fenster.getByRole('radio', { name: 'Zweiter' })).toBeChecked(); |
| 83 | }); |
| 84 | |
| 85 | test('loescht ein Profil erst nach einer Rueckfrage', async () => { |
| 86 | await fenster.getByRole('button', { name: 'Weiter' }).click(); |
| 87 | await expect(fenster.getByRole('heading', { name: 'Heute lernen' })).toBeVisible(); |
| 88 | |
| 89 | const zeile = fenster.locator('.profile__liste > li', { hasText: 'Standard' }); |
| 90 | await zeile.getByRole('button', { name: /^Löschen/u }).click(); |
| 91 | |
| 92 | await expect( |
| 93 | fenster.getByRole('heading', { name: 'Profil „Standard“ wirklich löschen?' }), |
| 94 | ).toBeVisible(); |
| 95 | await fenster.getByRole('button', { name: 'Nein, behalten' }).click(); |
| 96 | await expect(fenster.locator('.profile__liste > li')).toHaveCount(2); |
| 97 | |
| 98 | await zeile.getByRole('button', { name: /^Löschen/u }).click(); |
| 99 | await fenster.getByRole('button', { name: 'Ja, Profil löschen' }).click(); |
| 100 | |
| 101 | await expect(fenster.getByText(/Das Profil „Standard“ ist gelöscht/u)).toBeVisible(); |
| 102 | await expect(fenster.locator('.profile__liste > li')).toHaveCount(1); |
| 103 | }); |
| 104 | |
| 105 | test('laesst das letzte Profil stehen', async () => { |
| 106 | /* Ohne Profil haette die Anwendung keinen Ort fuer Antworten mehr. */ |
| 107 | await expect( |
| 108 | fenster.locator('.profile__liste').getByRole('button', { name: /^Löschen/u }), |
| 109 | ).toHaveCount(0); |
| 110 | await expect(fenster.getByText(/Das letzte Profil lässt sich nicht löschen/u)).toBeVisible(); |
| 111 | }); |
| 112 | }); |