waffensachkunde

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

/ app tests hilfe-zahlen.test.ts

3,4 KB Rohdatei
app/tests/hilfe-zahlen.test.ts — 93 Zeilen
1 // @vitest-environment node
2 /**
3 * Die Zahlen im Handbuch gegen den **echten** Katalog.
4 *
5 * Seit 0.26.4 sagt das Kapitel „Die Lernsitzung“, wie eine frei zu
6 * formulierende Antwort gebaut ist – und begründet es mit gezählten
7 * Anfangswörtern: 26 Fragen beginnen mit „Was“, 25 mit „Welche“, 7 mit
8 * „Wie“, je 5 mit „Wann“ und „Nennen Sie“. Solche Sätze sind Zusagen: Ein
9 * neuer Katalogstand verschiebt sie, ohne dass es jemandem auffiele, und
10 * dann steht im Handbuch eine Zahl, die niemand mehr nachzählen kann.
11 *
12 * Deshalb zählt dieser Test sie bei jedem Lauf nach. Er liest die Zahlen aus
13 * dem Handbuchtext selbst – nicht aus einer zweiten Liste daneben, die
14 * ihrerseits auseinanderlaufen könnte.
15 */
16
17 import { readFileSync } from 'node:fs';
18 import { join } from 'node:path';
19 import { fileURLToPath } from 'node:url';
20
21 import { describe, expect, it } from 'vitest';
22
23 import { HILFE_KAPITEL } from '../src/shared/hilfe';
24 import type { Katalog } from '../src/shared/katalog';
25
26 const wurzel = fileURLToPath(new URL('..', import.meta.url));
27
28 const KATALOG = JSON.parse(
29 readFileSync(join(wurzel, '..', 'content', 'katalog', 'katalog.json'), 'utf8'),
30 ) as Katalog;
31
32 /** Alle Fließtexte des Handbuchs, zu einer Zeichenkette verbunden. */
33 function handbuchtext(): string {
34 return HILFE_KAPITEL.flatMap((kapitel) =>
35 kapitel.bloecke.map((block) => {
36 if (block.art === 'absatz') return block.text;
37 if (block.art === 'liste' || block.art === 'schritte') return block.punkte.join(' ');
38 return '';
39 }),
40 ).join(' ');
41 }
42
43 /** Die offenen Fragen: die ohne Antwortmöglichkeiten. */
44 function offeneFragen() {
45 return KATALOG.fragen.filter((frage) => (frage.optionen ?? []).length === 0);
46 }
47
48 /** Das erste großgeschriebene Wort des Fragetextes. */
49 function anfangswort(text: string): string {
50 return /[A-ZÄÖÜ][a-zäöüß]+/u.exec(text)?.[0] ?? '';
51 }
52
53 describe('Die Zahlen des Handbuchs stimmen mit dem Katalog überein', () => {
54 it('nennt so viele offene Fragen, wie der Katalog hat', () => {
55 const text = handbuchtext();
56 const anzahl = offeneFragen().length;
57
58 expect(text, `Der Katalog hat ${String(anzahl)} offene Fragen.`).toContain(
59 `${String(anzahl)} offenen Fragen des Katalogs`,
60 );
61 expect(text).toContain(`Nur 5 der ${String(anzahl)} Fragen nennen selbst eine Anzahl.`);
62 });
63
64 it('zählt die Anfangswörter richtig ab', () => {
65 const text = handbuchtext();
66 const gezaehlt = new Map<string, number>();
67 for (const frage of offeneFragen()) {
68 const wort = anfangswort(frage.frage.text);
69 gezaehlt.set(wort, (gezaehlt.get(wort) ?? 0) + 1);
70 }
71
72 /* Genau die fünf, die der Satz im Handbuch aufzählt. */
73 expect(gezaehlt.get('Was')).toBe(26);
74 expect(gezaehlt.get('Welche')).toBe(25);
75 expect(gezaehlt.get('Wie')).toBe(7);
76 expect(gezaehlt.get('Wann')).toBe(5);
77 expect(gezaehlt.get('Nennen')).toBe(5);
78
79 expect(text).toContain(
80 '26 beginnen mit „Was“, 25 mit „Welche“, 7 mit „Wie“, je 5 mit „Wann“ und „Nennen Sie“',
81 );
82 });
83
84 it('nennt so viele Fragen mit ausgeschriebener Anzahl, wie es gibt', () => {
85 /* „zwei Fälle“, „drei Voraussetzungen“ – ausgeschrieben, nicht als Ziffer. */
86 const mitAnzahl = offeneFragen().filter((frage) =>
87 /\b(zwei|drei|vier|fünf|sechs)\b/iu.test(frage.frage.text),
88 );
89
90 expect(mitAnzahl).toHaveLength(5);
91 expect(handbuchtext()).toContain('Nur 5 der');
92 });
93 });