waffensachkunde

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

/ app tests fragennummer.test.ts

4,9 KB Rohdatei
app/tests/fragennummer.test.ts — 144 Zeilen
1 // @vitest-environment node
2 /**
3 * Der Nummernweg, gemessen am echten Katalog.
4 *
5 * Die beiden Fehler, die hier passieren, sind still: Eine Präfixsuche nach
6 * „2.5“ liefert die Fragen 2.50 bis 2.59 – das Falsche statt nichts –, und
7 * eine Teilzeichenkettensuche nach „05“ hebt sechs beliebige Fragen über
8 * jeden Texttreffer. Beides fällt niemandem auf, der nicht danach sucht.
9 */
10
11 import { readFileSync } from 'node:fs';
12 import { join } from 'node:path';
13 import { fileURLToPath } from 'node:url';
14
15 import { describe, expect, it } from 'vitest';
16
17 import { nummerSchluessel, nummernindexBauen, nummernTreffer } from '../src/shared/fragennummer';
18 import type { Katalog } from '../src/shared/katalog';
19
20 const wurzel = fileURLToPath(new URL('..', import.meta.url));
21
22 const KATALOG = JSON.parse(
23 readFileSync(join(wurzel, '..', 'content', 'katalog', 'katalog.json'), 'utf8'),
24 ) as Katalog;
25
26 const INDEX = nummernindexBauen(KATALOG);
27
28 function treffer(eingabe: string): readonly string[] {
29 return nummernTreffer(INDEX, eingabe);
30 }
31
32 describe('Nummern des Kapitels I', () => {
33 it.each([
34 ['1.87', 'I.1-87'],
35 ['2.150', 'I.2-150'],
36 ['1.01', 'I.1-01'],
37 ['5.01', 'I.5-01'],
38 ])('„%s“ führt zu %s', (eingabe, id) => {
39 expect(treffer(eingabe)).toEqual([id]);
40 });
41
42 it('füllt führende Nullen auf, statt einen Präfix zu suchen', () => {
43 /* Der teure Fehler: „2.5“ als Präfix genommen lieferte 2.50 bis 2.59 –
44 zehn falsche Fragen statt der einen richtigen. */
45 expect(treffer('2.5')).toEqual(['I.2-05']);
46 expect(treffer('2.05')).toEqual(['I.2-05']);
47 expect(treffer('2.50')).toEqual(['I.2-50']);
48 expect(treffer('2.50')).not.toEqual(treffer('2.5'));
49 });
50
51 it('nimmt auch das Komma als Trenner', () => {
52 // Deutsche Zehnertastatur, deutsche Gewohnheit.
53 expect(treffer('2,05')).toEqual(['I.2-05']);
54 });
55 });
56
57 describe('Nummern der Kapitel II bis IV', () => {
58 it('zeigt bei einer mehrdeutigen Nummer alle Kapitel nebeneinander', () => {
59 /* Von 437 Nummern sind 89 mehrdeutig. Heimlich eine auszuwählen wäre
60 die schlechteste Antwort – der Nutzer sähe nicht, dass es drei gibt. */
61 expect(treffer('45')).toEqual(['II-45', 'III-45', 'IV-45']);
62 });
63
64 it('behandelt „05“ und „5“ gleich', () => {
65 expect(treffer('5')).toEqual(treffer('05'));
66 expect(treffer('5')).toEqual(['II-05', 'III-05', 'IV-05']);
67 });
68
69 it('liefert nur die Kapitel, in denen es die Nummer gibt', () => {
70 // Kapitel III endet bei 49, Kapitel IV bei 89, Kapitel II bei 92.
71 expect(treffer('60')).toEqual(['II-60', 'IV-60']);
72 expect(treffer('91')).toEqual(['II-91']);
73 });
74 });
75
76 describe('Vollständige Kennungen', () => {
77 it.each([
78 ['I.2-150', 'I.2-150'],
79 ['i.2-150', 'I.2-150'],
80 ['II-45', 'II-45'],
81 ['ii-45', 'II-45'],
82 ['II 45', 'II-45'],
83 ['iv 89', 'IV-89'],
84 ])('„%s“ führt zu %s', (eingabe, id) => {
85 expect(treffer(eingabe)).toEqual([id]);
86 });
87
88 it('versteht einen Vorspann', () => {
89 expect(treffer('Frage 2.150')).toEqual(['I.2-150']);
90 expect(treffer('Nr. 45')).toEqual(['II-45', 'III-45', 'IV-45']);
91 });
92 });
93
94 describe('Was keine Nummer ist', () => {
95 it.each(['waffe', '', ' ', 'notwehr angriff', 'abs. 1 waffg', '7,5 joule', '§ 3'])(
96 '„%s“ ergibt keinen Nummerntreffer',
97 (eingabe) => {
98 expect(treffer(eingabe)).toEqual([]);
99 },
100 );
101
102 it('erkennt gar nicht erst einen Schlüssel', () => {
103 expect(nummerSchluessel('waffe')).toBeNull();
104 expect(nummerSchluessel('')).toBeNull();
105 });
106
107 it('gibt es die Nummer nicht, bleibt die Liste leer', () => {
108 expect(treffer('9.99')).toEqual([]);
109 expect(treffer('999')).toEqual([]);
110 });
111 });
112
113 describe('Vollständigkeit', () => {
114 it('löst jede der 437 amtlichen Nummern auf', () => {
115 /* Nicht „einige“, nicht „die geprüften“: jede. Eine Nummer, die ins
116 Leere führt, ist in einem Nachschlagewerk ein Widerspruch in sich. */
117 const nummern = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))];
118 expect(nummern.length).toBe(437);
119
120 const ausfaelle = nummern.filter((n) => treffer(n).length === 0);
121 expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]);
122 });
123
124 it('löst jede der 575 Kennungen auf – und zwar auf sich selbst', () => {
125 const ausfaelle = KATALOG.fragen.filter((f) => !treffer(f.id).includes(f.id)).map((f) => f.id);
126 expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]);
127 });
128
129 it('führt eine Nummer immer auf Fragen, die es gibt', () => {
130 const bekannt = new Set(KATALOG.fragen.map((f) => f.id));
131 for (const [, ids] of INDEX.nach) {
132 for (const id of ids) {
133 expect(bekannt.has(id)).toBe(true);
134 }
135 }
136 });
137
138 it('zählt 89 mehrdeutige Nummern', () => {
139 const mehrdeutig = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))].filter(
140 (n) => treffer(n).length > 1,
141 );
142 expect(mehrdeutig.length).toBe(89);
143 });
144 });