// @vitest-environment node /** * Der Nummernweg, gemessen am echten Katalog. * * Die beiden Fehler, die hier passieren, sind still: Eine Präfixsuche nach * „2.5“ liefert die Fragen 2.50 bis 2.59 – das Falsche statt nichts –, und * eine Teilzeichenkettensuche nach „05“ hebt sechs beliebige Fragen über * jeden Texttreffer. Beides fällt niemandem auf, der nicht danach sucht. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; import { nummerSchluessel, nummernindexBauen, nummernTreffer } from '../src/shared/fragennummer'; 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; const INDEX = nummernindexBauen(KATALOG); function treffer(eingabe: string): readonly string[] { return nummernTreffer(INDEX, eingabe); } describe('Nummern des Kapitels I', () => { it.each([ ['1.87', 'I.1-87'], ['2.150', 'I.2-150'], ['1.01', 'I.1-01'], ['5.01', 'I.5-01'], ])('„%s“ führt zu %s', (eingabe, id) => { expect(treffer(eingabe)).toEqual([id]); }); it('füllt führende Nullen auf, statt einen Präfix zu suchen', () => { /* Der teure Fehler: „2.5“ als Präfix genommen lieferte 2.50 bis 2.59 – zehn falsche Fragen statt der einen richtigen. */ expect(treffer('2.5')).toEqual(['I.2-05']); expect(treffer('2.05')).toEqual(['I.2-05']); expect(treffer('2.50')).toEqual(['I.2-50']); expect(treffer('2.50')).not.toEqual(treffer('2.5')); }); it('nimmt auch das Komma als Trenner', () => { // Deutsche Zehnertastatur, deutsche Gewohnheit. expect(treffer('2,05')).toEqual(['I.2-05']); }); }); describe('Nummern der Kapitel II bis IV', () => { it('zeigt bei einer mehrdeutigen Nummer alle Kapitel nebeneinander', () => { /* Von 437 Nummern sind 89 mehrdeutig. Heimlich eine auszuwählen wäre die schlechteste Antwort – der Nutzer sähe nicht, dass es drei gibt. */ expect(treffer('45')).toEqual(['II-45', 'III-45', 'IV-45']); }); it('behandelt „05“ und „5“ gleich', () => { expect(treffer('5')).toEqual(treffer('05')); expect(treffer('5')).toEqual(['II-05', 'III-05', 'IV-05']); }); it('liefert nur die Kapitel, in denen es die Nummer gibt', () => { // Kapitel III endet bei 49, Kapitel IV bei 89, Kapitel II bei 92. expect(treffer('60')).toEqual(['II-60', 'IV-60']); expect(treffer('91')).toEqual(['II-91']); }); }); describe('Vollständige Kennungen', () => { it.each([ ['I.2-150', 'I.2-150'], ['i.2-150', 'I.2-150'], ['II-45', 'II-45'], ['ii-45', 'II-45'], ['II 45', 'II-45'], ['iv 89', 'IV-89'], ])('„%s“ führt zu %s', (eingabe, id) => { expect(treffer(eingabe)).toEqual([id]); }); it('versteht einen Vorspann', () => { expect(treffer('Frage 2.150')).toEqual(['I.2-150']); expect(treffer('Nr. 45')).toEqual(['II-45', 'III-45', 'IV-45']); }); }); describe('Was keine Nummer ist', () => { it.each(['waffe', '', ' ', 'notwehr angriff', 'abs. 1 waffg', '7,5 joule', '§ 3'])( '„%s“ ergibt keinen Nummerntreffer', (eingabe) => { expect(treffer(eingabe)).toEqual([]); }, ); it('erkennt gar nicht erst einen Schlüssel', () => { expect(nummerSchluessel('waffe')).toBeNull(); expect(nummerSchluessel('')).toBeNull(); }); it('gibt es die Nummer nicht, bleibt die Liste leer', () => { expect(treffer('9.99')).toEqual([]); expect(treffer('999')).toEqual([]); }); }); describe('Vollständigkeit', () => { it('löst jede der 437 amtlichen Nummern auf', () => { /* Nicht „einige“, nicht „die geprüften“: jede. Eine Nummer, die ins Leere führt, ist in einem Nachschlagewerk ein Widerspruch in sich. */ const nummern = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))]; expect(nummern.length).toBe(437); const ausfaelle = nummern.filter((n) => treffer(n).length === 0); expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]); }); it('löst jede der 575 Kennungen auf – und zwar auf sich selbst', () => { const ausfaelle = KATALOG.fragen.filter((f) => !treffer(f.id).includes(f.id)).map((f) => f.id); expect(ausfaelle, `Nicht auflösbar: ${ausfaelle.slice(0, 10).join(', ')}`).toEqual([]); }); it('führt eine Nummer immer auf Fragen, die es gibt', () => { const bekannt = new Set(KATALOG.fragen.map((f) => f.id)); for (const [, ids] of INDEX.nach) { for (const id of ids) { expect(bekannt.has(id)).toBe(true); } } }); it('zählt 89 mehrdeutige Nummern', () => { const mehrdeutig = [...new Set(KATALOG.fragen.map((f) => f.amtliche_nummer))].filter( (n) => treffer(n).length > 1, ); expect(mehrdeutig.length).toBe(89); }); });