/** * Jeder Aufklapper zeigt sein Dreieck. * * ## Warum das im echten Fenster gemessen wird * * Ein `` ohne Marke sieht aus wie eine gewöhnliche Zeile: Es sagt * niemandem, dass sich dahinter etwas aufklappen lässt. Wer die Maus nicht * darüberführt, findet den Inhalt nie. Chromium malt die Marke nur, solange * das Element `display: list-item` behält – ein `display: flex` (oder `grid`, * `inline-flex`, `block`) auf dem `` schaltet sie stillschweigend ab. * * Genau das war in `styles/lernen.css` bei `.sitzung__kuerzel summary` und in * `styles/ueber.css` bei `.lizenzblock__schalter` passiert. Aufgefallen ist es * erst im Bild: „Tastenkürzel dieser Ansicht“ stand ohne Dreieck neben zwei * Nachbarn, die eines hatten. Die Warnung davor stand zu diesem Zeitpunkt * bereits im Kommentar vor `.erklaerung--klappbar` in derselben Datei. * * Unter jsdom ist die Frage nicht zu beantworten: Dort gibt es keine * CSS-Kaskade, `getComputedStyle` liefert für ein `` weder das * `list-item` der UA-Vorlage noch das `flex` aus dem Stilblatt. Erst Chromium * rechnet – deshalb steht diese Wache hier und nicht in `tests/`. * * ## Was gemessen wird * * Für jedes sichtbare `` der Ansicht: `display` muss `list-item` * sein, der Markentyp darf nicht `none` sein und der Inhalt des `::marker` * darf nicht abgeschaltet sein. Alle drei Wege führen sonst zum selben * Ergebnis – ein Aufklapper, dem man das Aufklappen nicht ansieht. */ import { type ElectronApplication, type Page } from '@playwright/test'; import { amStart, appStarten, bauPruefen } from './electron-hilfe'; import { expect, test } from './konsolenwache'; let app: ElectronApplication | undefined; let fenster: Page; test.beforeAll(async () => { // Fehlender oder veralteter Bau ist ein Fehler, kein Grund zum Schweigen. bauPruefen(); ({ app, fenster } = await appStarten()); }); test.afterAll(async () => { await app?.close(); }); // Electron-Fenster, ein Profil: die Ansichten werden nacheinander angefahren. test.describe.configure({ mode: 'serial' }); /** Ein ``, dem die Aufklappmarke fehlt – samt Grund. */ interface Befund { readonly text: string; readonly klasse: string; readonly display: string; readonly markentyp: string; readonly markeninhalt: string; } /** * Alle sichtbaren `` der laufenden Ansicht ohne Aufklappmarke. * * Zugeklappte `
` zählen mit: Ihr `` steht sichtbar da, und * gerade dort braucht es die Marke am dringendsten. Ausgenommen sind nur * wirklich verborgene Elemente – die malt Chromium ohnehin nicht. */ async function ohneMarke(): Promise { return fenster.evaluate(() => { const sichtbare = [...document.querySelectorAll('summary')].filter( (summary) => summary.getClientRects().length > 0 && getComputedStyle(summary).visibility !== 'hidden', ); return sichtbare .map((summary) => { const stil = getComputedStyle(summary); return { text: summary.textContent.trim().slice(0, 60), klasse: summary.getAttribute('class') ?? '(ohne Klasse)', display: stil.display, markentyp: stil.listStyleType, markeninhalt: getComputedStyle(summary, '::marker').content, }; }) .filter( (b) => /* Alle drei Wege enden beim selben Bild: kein Dreieck. */ b.display !== 'list-item' || b.markentyp === 'none' || b.markeninhalt === 'none', ); }); } /** Meldet die Fundstellen so, dass die Ursache ohne Nachsuchen dasteht. */ async function markenPruefen(ansicht: string): Promise { const befunde = await ohneMarke(); const liste = befunde .map( (b) => `„${b.text}“ (${b.klasse}): display=${b.display}, ` + `list-style-type=${b.markentyp}, ::marker content=${b.markeninhalt}`, ) .join('\n '); expect( befunde, `${ansicht}: ${String(befunde.length)} Aufklapper ohne Dreieck.\n ${liste}\n` + 'Ein `display` ausser `list-item` auf einem schaltet die Marke ab.', ).toEqual([]); } /** Zurück zum Startbildschirm, egal wo der vorige Test aufgehört hat. */ async function zumStart(): Promise { for (let i = 0; i < 4; i++) { if (await amStart(fenster)) { return; } const zurueck = fenster.getByRole('button', { name: /^(Zum Start|Sitzung beenden)$/u }); if ((await zurueck.count()) === 0) { return; } await zurueck.first().click(); await fenster.waitForTimeout(250); } } test.describe('Aufklappmarke je Ansicht', () => { test('Startbildschirm', async () => { await zumStart(); await markenPruefen('Startbildschirm'); }); test('Lernsitzung', async () => { /* Die Ansicht des ursprünglichen Befundes: Hier stehen „Tastenkürzel dieser Ansicht“ und „Fehler zu dieser Frage melden“ nebeneinander. Die Tastenkürzel sind ab Werk eingeschaltet – ohne Zutun sichtbar. */ await zumStart(); await fenster.getByRole('button', { name: /Weiterlernen/u }).click(); await fenster.getByRole('heading', { level: 1, name: /, Frage \d/u }).waitFor(); await expect(fenster.getByText('Tastenkürzel dieser Ansicht')).toBeVisible(); await markenPruefen('Lernsitzung'); }); test('Lernsitzung nach der Antwort', async () => { /* Erst nach der Antwort erscheinen „Warum das so ist“ und – bei Fragen mit Bild – „Was dieses Zeichen bedeutet“. Gesucht ist irgendeine Auswahlfrage: Die Sitzung wird zufällig zusammengestellt. */ const auswahlfeld = fenster.getByRole('radio').or(fenster.getByRole('checkbox')); for (let i = 0; i < 20; i++) { if ((await auswahlfeld.count()) > 0) { break; } await fenster .getByRole('button', { name: /Musterantwort anzeigen|^Antwort bestätigen$/u }) .first() .click(); const bewerten = fenster.getByRole('button', { name: /^(Gewusst|Sicher gewusst)$/u }); if ((await bewerten.count()) > 0) { await bewerten.first().click(); } const weiter = fenster.getByRole('button', { name: /^Nächste Frage$/u }); if ((await weiter.count()) === 0) { break; } await weiter.first().click(); await fenster.waitForTimeout(150); } await auswahlfeld.first().click(); await fenster.getByRole('button', { name: 'Antwort bestätigen' }).click(); await fenster.waitForTimeout(300); await markenPruefen('Lernsitzung nach der Antwort'); }); test('Über diese Software', async () => { /* Zweite Fundstelle desselben Fehlers: `.lizenzblock__schalter`. */ await zumStart(); await fenster.getByRole('button', { name: /Lizenzen und Herkunft anzeigen/u }).click(); await fenster.getByRole('heading', { name: 'Über diese Software' }).waitFor(); await expect(fenster.locator('summary.lizenzblock__schalter').first()).toBeVisible(); await markenPruefen('Über diese Software'); }); test('Fragen durchsuchen', async () => { /* Mit Trefferliste, nicht leer: Erst dann steht in jeder Trefferkarte der Aufklapper „Antwortmöglichkeiten anzeigen“. */ await zumStart(); await fenster.getByRole('button', { name: /Fragen durchsuchen/u }).click(); await fenster.getByRole('heading', { name: 'Fragen durchsuchen', level: 1 }).waitFor(); await fenster.getByLabel('Suchbegriff').fill('Anscheinswaffe'); await fenster.waitForTimeout(400); await markenPruefen('Fragen durchsuchen'); await zumStart(); }); test('Startbildschirm mit Lernstand', async () => { /* Die Lernstandsübersicht steht auf dem Startbildschirm, erscheint dort aber erst, wenn etwas beantwortet ist – der erste Test dieser Datei läuft auf einem frischen Profil und sieht sie noch nicht. Ihre beiden Aufklapper („Was heißt ‚belegt‘?“, „Nach Kapiteln und Abschnitten“) kommen deshalb erst hier unter die Lupe. */ await zumStart(); await expect(fenster.getByText('Was heißt „belegt“?')).toBeVisible(); await markenPruefen('Startbildschirm mit Lernstand'); }); });