// @vitest-environment node /** * Die Zahlen im Handbuch gegen den **echten** Katalog. * * Seit 0.26.4 sagt das Kapitel „Die Lernsitzung“, wie eine frei zu * formulierende Antwort gebaut ist – und begründet es mit gezählten * Anfangswörtern: 26 Fragen beginnen mit „Was“, 25 mit „Welche“, 7 mit * „Wie“, je 5 mit „Wann“ und „Nennen Sie“. Solche Sätze sind Zusagen: Ein * neuer Katalogstand verschiebt sie, ohne dass es jemandem auffiele, und * dann steht im Handbuch eine Zahl, die niemand mehr nachzählen kann. * * Deshalb zählt dieser Test sie bei jedem Lauf nach. Er liest die Zahlen aus * dem Handbuchtext selbst – nicht aus einer zweiten Liste daneben, die * ihrerseits auseinanderlaufen könnte. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import { HILFE_KAPITEL } from '../src/shared/hilfe'; import type { Katalog } from '../src/shared/katalog'; const wurzel = fileURLToPath(new URL('..', import.meta.url)); const KATALOG = JSON.parse( readFileSync(join(wurzel, '..', 'content', 'katalog', 'katalog.json'), 'utf8'), ) as Katalog; /** Alle Fließtexte des Handbuchs, zu einer Zeichenkette verbunden. */ function handbuchtext(): string { return HILFE_KAPITEL.flatMap((kapitel) => kapitel.bloecke.map((block) => { if (block.art === 'absatz') return block.text; if (block.art === 'liste' || block.art === 'schritte') return block.punkte.join(' '); return ''; }), ).join(' '); } /** Die offenen Fragen: die ohne Antwortmöglichkeiten. */ function offeneFragen() { return KATALOG.fragen.filter((frage) => (frage.optionen ?? []).length === 0); } /** Das erste großgeschriebene Wort des Fragetextes. */ function anfangswort(text: string): string { return /[A-ZÄÖÜ][a-zäöüß]+/u.exec(text)?.[0] ?? ''; } describe('Die Zahlen des Handbuchs stimmen mit dem Katalog überein', () => { it('nennt so viele offene Fragen, wie der Katalog hat', () => { const text = handbuchtext(); const anzahl = offeneFragen().length; expect(text, `Der Katalog hat ${String(anzahl)} offene Fragen.`).toContain( `${String(anzahl)} offenen Fragen des Katalogs`, ); expect(text).toContain(`Nur 5 der ${String(anzahl)} Fragen nennen selbst eine Anzahl.`); }); it('zählt die Anfangswörter richtig ab', () => { const text = handbuchtext(); const gezaehlt = new Map(); for (const frage of offeneFragen()) { const wort = anfangswort(frage.frage.text); gezaehlt.set(wort, (gezaehlt.get(wort) ?? 0) + 1); } /* Genau die fünf, die der Satz im Handbuch aufzählt. */ expect(gezaehlt.get('Was')).toBe(26); expect(gezaehlt.get('Welche')).toBe(25); expect(gezaehlt.get('Wie')).toBe(7); expect(gezaehlt.get('Wann')).toBe(5); expect(gezaehlt.get('Nennen')).toBe(5); expect(text).toContain( '26 beginnen mit „Was“, 25 mit „Welche“, 7 mit „Wie“, je 5 mit „Wann“ und „Nennen Sie“', ); }); it('nennt so viele Glossareinträge, wie das Glossar führt', () => { /* Die Wache, die gefehlt hat. Das Kapitel „Die Lernsitzung“ sagt, wie viele Fachbegriffe das Glossar führt — eine Zahl, die mit jeder redaktionellen Ergänzung wandert. Sie stand am 03.09.2026 richtig auf 118; ohne diesen Fall bliebe sie beim nächsten Zuwachs stumm stehen, wie es `docs/stand.md` mit derselben Zahl passiert ist (dort 87). */ const glossar = JSON.parse( readFileSync(join(wurzel, '..', 'content', 'glossar.json'), 'utf8'), ) as { eintraege: readonly unknown[] }; expect(handbuchtext()).toContain(`das Glossar führt ${String(glossar.eintraege.length)} davon`); }); it('nennt so viele Fragen mit ausgeschriebener Anzahl, wie es gibt', () => { /* „zwei Fälle“, „drei Voraussetzungen“ – ausgeschrieben, nicht als Ziffer. */ const mitAnzahl = offeneFragen().filter((frage) => /\b(zwei|drei|vier|fünf|sechs)\b/iu.test(frage.frage.text), ); expect(mitAnzahl).toHaveLength(5); expect(handbuchtext()).toContain('Nur 5 der'); }); });