waffensachkunde

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

/ app tests verlust.test.ts

4,8 KB Rohdatei
app/tests/verlust.test.ts — 124 Zeilen
1 // @vitest-environment node
2 /**
3 * Die Verlustanzeige – was ginge beim Ersetzen verloren?
4 *
5 * Reine Rechnerei ohne Datenbank. Geprüft wird der Fall, der die Anzeige
6 * überhaupt nötig macht: Zwei Gesamtzahlen nebeneinander können „mehr“
7 * sagen, während ein ganzes Profil verschwindet.
8 */
9
10 import { describe, expect, it } from 'vitest';
11
12 import type { Kennzahlen, Profilkennzahlen } from '../src/shared/sicherung';
13 import { OHNE_BEFUND_SATZ, verlustbefund, verlustsaetze } from '../src/shared/verlust';
14
15 function profil(
16 name: string,
17 antworten: number,
18 gemerkt = 0,
19 pruefungslaeufe = 0,
20 ): Profilkennzahlen {
21 return { name, antworten, gemerkt, pruefungslaeufe, letzteAntwort: null };
22 }
23
24 function stand(profile: readonly Profilkennzahlen[], offenerBogen = false): Kennzahlen {
25 return {
26 profilnamen: profile.map((p) => p.name),
27 jeProfil: profile,
28 antworten: profile.reduce((summe, p) => summe + p.antworten, 0),
29 letzteAntwort: null,
30 gemerkt: profile.reduce((summe, p) => summe + p.gemerkt, 0),
31 pruefungslaeufe: profile.reduce((summe, p) => summe + p.pruefungslaeufe, 0),
32 offenerBogen,
33 schemafassung: 8,
34 };
35 }
36
37 describe('verlustbefund', () => {
38 /*
39 Der Fall, der die ganze Anzeige rechtfertigt: Die Datei trägt in der Summe
40 MEHR Antworten, und trotzdem geht ein vollständiges Profil verloren. Wer
41 nur die beiden Gesamtzahlen sieht, gibt das gutgläubig frei.
42 */
43 it('erkennt ein verschwindendes Profil, obwohl die Datei mehr Antworten hat', () => {
44 const jetzt = stand([profil('Olaf', 600), profil('Anna', 400)]);
45 const ausDatei = stand([profil('Olaf', 1200)]);
46
47 expect(ausDatei.antworten).toBeGreaterThan(jetzt.antworten);
48
49 const befund = verlustbefund(jetzt, ausDatei);
50
51 expect(befund.ohneBefund).toBe(false);
52 expect(befund.fehlendeProfile).toEqual([{ name: 'Anna', antworten: 400 }]);
53 expect(verlustsaetze(befund)[0]).toContain('Das Profil „Anna“ kommt in der Datei nicht vor');
54 expect(verlustsaetze(befund)[0]).toContain('400 Antworten');
55 });
56
57 it('erkennt ein Profil, das in der Datei weniger enthält', () => {
58 const befund = verlustbefund(
59 stand([profil('Olaf', 600, 12, 3)]),
60 stand([profil('Olaf', 500, 4, 3)]),
61 );
62
63 expect(befund.geschrumpfteProfile).toEqual([
64 { name: 'Olaf', antwortenWeniger: 100, gemerktWeniger: 8, laeufeWeniger: 0 },
65 ]);
66 const satz = verlustsaetze(befund)[0] ?? '';
67 expect(satz).toContain('100 Antworten');
68 expect(satz).toContain('8 gemerkte Fragen');
69 expect(satz).not.toContain('Prüfungslauf');
70 });
71
72 it('meldet nichts, wenn die Datei überall mehr oder gleich viel enthält', () => {
73 const befund = verlustbefund(
74 stand([profil('Olaf', 500, 4, 1)]),
75 stand([profil('Olaf', 600, 12, 3), profil('Anna', 20)]),
76 );
77
78 expect(befund.ohneBefund).toBe(true);
79 expect(verlustsaetze(befund)).toEqual([]);
80 });
81
82 it('nennt den unterbrochenen Bogen als Verlust', () => {
83 const befund = verlustbefund(stand([profil('Olaf', 10)], true), stand([profil('Olaf', 10)]));
84
85 expect(befund.ohneBefund).toBe(false);
86 expect(befund.offenerBogenGehtVerloren).toBe(true);
87 expect(verlustsaetze(befund)[0]).toContain('unterbrochener Prüfungsbogen');
88 });
89
90 it('kennt den Singular', () => {
91 const befund = verlustbefund(
92 stand([profil('Olaf', 11, 1, 1), profil('Anna', 1)]),
93 stand([profil('Olaf', 10)]),
94 );
95 const saetze = verlustsaetze(befund);
96
97 expect(saetze.some((s) => s.includes('1 Antwort verloren'))).toBe(true);
98 expect(saetze.some((s) => s.includes('1 gemerkte Frage'))).toBe(true);
99 expect(saetze.some((s) => s.includes('1 Prüfungslauf'))).toBe(true);
100 expect(saetze.every((s) => !s.includes('1 Antworten'))).toBe(true);
101 });
102
103 it('vergleicht über den Namen, nicht über die Nummer', () => {
104 /* Die Profilnummern werden auf jedem Gerät unabhängig vergeben. Ein
105 Vergleich über sie führte geradewegs ins Profil einer anderen Person –
106 derselbe Grund, aus dem die Oberfläche nach dem Ersetzen zwingend ins
107 erste Profil der Datei wechselt. */
108 const befund = verlustbefund(stand([profil('Anna', 50)]), stand([profil('Olaf', 50)]));
109
110 expect(befund.fehlendeProfile).toHaveLength(1);
111 expect(befund.fehlendeProfile[0]?.name).toBe('Anna');
112 });
113
114 /*
115 Der Satz für den Fall ohne Befund darf nicht „alles stimmt“ sagen.
116 Prüfungstermin und Kapitelabwahl werden mitersetzt und sind nicht
117 verglichen; die Kapitelabwahl verschiebt den Nenner des Reifegrads.
118 */
119 it('sagt im Satz ohne Befund, was verglichen wurde und was nicht', () => {
120 expect(OHNE_BEFUND_SATZ).toContain('Verglichen wurden Profile, Antworten');
121 expect(OHNE_BEFUND_SATZ).toContain('Nicht verglichen wurden Prüfungstermin und');
122 expect(OHNE_BEFUND_SATZ).not.toMatch(/[Aa]lles,? was auf diesem Rechner steht/u);
123 });
124 });