lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests ui datenschutzText.test.ts

8,5 KB Rohdatei
tests/ui/datenschutzText.test.ts — 205 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { gliedereText, textbloeckeAlsKnoten, type Textblock } from '@/ui/datenschutzText';
3
4 /**
5 * Der kleine Leser fuer docs/datenschutz.md - als Einheit, ohne Fenster.
6 *
7 * Die Faelle folgen den Zusagen im Kopf von src/ui/datenschutzText.ts: das
8 * Format der Datei, die drei Abweichungen, die der Leser wie CommonMark liest,
9 * und die, die er anders liest. Am echten Fenster mit der echten Datei - und
10 * die Wache, dass die Datei keine der anders gelesenen enthaelt - prueft
11 * tests/ui/datenschutzAnsicht.test.ts.
12 */
13
14 const ERSATZ = 'Ersatztitel';
15
16 function absatz(text: string): Textblock {
17 return { art: 'absatz', text };
18 }
19
20 function abschnitt(text: string): Textblock {
21 return { art: 'abschnitt', text };
22 }
23
24 function aufzaehlung(...punkte: string[]): Textblock {
25 return { art: 'aufzaehlung', punkte };
26 }
27
28 describe('gliedereText: Titel', () => {
29 it('nimmt die Titelzeile als Titel und nicht als Block', () => {
30 const text = gliedereText('# Hinweise\n\nErster Absatz.\n', ERSATZ);
31 expect(text.titel).toBe('Hinweise');
32 expect(text.bloecke).toEqual([absatz('Erster Absatz.')]);
33 });
34
35 it('nimmt den Ersatztitel, wenn die Titelzeile fehlt', () => {
36 const text = gliedereText('## Abschnitt\n\nAbsatz.', ERSATZ);
37 expect(text.titel).toBe(ERSATZ);
38 expect(text.bloecke).toEqual([abschnitt('Abschnitt'), absatz('Absatz.')]);
39 });
40
41 it('verwirft einen zweiten Titel nicht, sondern fuehrt ihn als Abschnitt', () => {
42 const text = gliedereText('# Erster\n\n# Zweiter\n\nText.', ERSATZ);
43 expect(text.titel).toBe('Erster');
44 expect(text.bloecke).toEqual([abschnitt('Zweiter'), absatz('Text.')]);
45 });
46
47 it('liest ein Nummernzeichen ohne Leerzeichen nicht als Ueberschrift', () => {
48 // "#Hinweise" ist kein Titel und "###" keine Ebene, die der Leser kennt -
49 // beides bleibt sichtbar Text, statt still verschluckt zu werden.
50 const text = gliedereText('#Hinweise\n\n### Dritte Ebene', ERSATZ);
51 expect(text.titel).toBe(ERSATZ);
52 expect(text.bloecke).toEqual([absatz('#Hinweise'), absatz('### Dritte Ebene')]);
53 });
54 });
55
56 describe('gliedereText: Absaetze', () => {
57 it('fuegt die Zeilen eines Absatzes mit einem Leerzeichen zusammen', () => {
58 const text = gliedereText('Die erste Zeile\nlaeuft hier weiter\nund endet hier.', ERSATZ);
59 expect(text.bloecke).toEqual([absatz('Die erste Zeile laeuft hier weiter und endet hier.')]);
60 });
61
62 it('trennt Absaetze an der Leerzeile und macht aus mehreren Leerzeilen keinen leeren Absatz', () => {
63 const text = gliedereText('Eins.\n\n\n \nZwei\nZeilen.', ERSATZ);
64 expect(text.bloecke).toEqual([absatz('Eins.'), absatz('Zwei Zeilen.')]);
65 });
66
67 it('laesst Leerzeilen und Leerzeichen am Anfang und am Ende nicht als Block stehen', () => {
68 const text = gliedereText('\n\n Absatz mit Rand. \n\n\n\n', ERSATZ);
69 expect(text.bloecke).toEqual([absatz('Absatz mit Rand.')]);
70 });
71
72 it('liest Wagenruecklaeufe am Zeilenende nicht mit', () => {
73 const text = gliedereText('# Titel\r\n\r\nZeile eins\r\nZeile zwei\r\n\r\n- Punkt\r\n', ERSATZ);
74 expect(text.titel).toBe('Titel');
75 expect(text.bloecke).toEqual([absatz('Zeile eins Zeile zwei'), aufzaehlung('Punkt')]);
76 });
77
78 it('beendet einen Absatz an einer Ueberschrift auch ohne Leerzeile', () => {
79 const text = gliedereText('Absatz.\n## Abschnitt\nWeiter.', ERSATZ);
80 expect(text.bloecke).toEqual([absatz('Absatz.'), abschnitt('Abschnitt'), absatz('Weiter.')]);
81 });
82 });
83
84 describe('gliedereText: Aufzaehlungen', () => {
85 it('fasst aufeinanderfolgende Punkte zu einer Aufzaehlung zusammen', () => {
86 const text = gliedereText('- eins\n- zwei\n- drei', ERSATZ);
87 expect(text.bloecke).toEqual([aufzaehlung('eins', 'zwei', 'drei')]);
88 });
89
90 it('beginnt nach einem Absatz auch ohne Leerzeile eine Aufzaehlung', () => {
91 const text = gliedereText('Einleitung:\n- erster Punkt\n- zweiter Punkt', ERSATZ);
92 expect(text.bloecke).toEqual([
93 absatz('Einleitung:'),
94 aufzaehlung('erster Punkt', 'zweiter Punkt'),
95 ]);
96 });
97
98 it('setzt einen Punkt mit der unmittelbar folgenden Zeile fort', () => {
99 const text = gliedereText('- erster Punkt,\n umbrochen\n- zweiter Punkt', ERSATZ);
100 expect(text.bloecke).toEqual([aufzaehlung('erster Punkt, umbrochen', 'zweiter Punkt')]);
101 });
102
103 it('beendet eine Aufzaehlung an der Leerzeile, der Absatz danach steht fuer sich', () => {
104 const text = gliedereText('- Punkt\n\nAbsatz danach.', ERSATZ);
105 expect(text.bloecke).toEqual([aufzaehlung('Punkt'), absatz('Absatz danach.')]);
106 });
107
108 it('beendet eine Aufzaehlung an einer Ueberschrift', () => {
109 const text = gliedereText('- Punkt\n## Abschnitt', ERSATZ);
110 expect(text.bloecke).toEqual([aufzaehlung('Punkt'), abschnitt('Abschnitt')]);
111 });
112
113 it('schliesst eine Aufzaehlung am Dateiende ohne Zeilenumbruch ab', () => {
114 const text = gliedereText('Absatz.\n\n- letzter Punkt', ERSATZ);
115 expect(text.bloecke).toEqual([absatz('Absatz.'), aufzaehlung('letzter Punkt')]);
116 });
117
118 it('liest einen Bindestrich ohne Leerzeichen nicht als Punkt', () => {
119 const text = gliedereText('-kein Punkt', ERSATZ);
120 expect(text.bloecke).toEqual([absatz('-kein Punkt')]);
121 });
122 });
123
124 describe('gliedereText: anders als CommonMark', () => {
125 /*
126 * Die Liste im Kopf von src/ui/datenschutzText.ts, Fall fuer Fall: was dieser
127 * Leser liest. Nach CommonMark gliedert sich jeder dieser Texte anders, und
128 * deshalb darf docs/datenschutz.md keinen davon enthalten
129 * (`gliederungsabweichungen` in tests/ui/datenschutzAnsicht.test.ts). Liest
130 * der Leser einen davon eines Tages anders, gehoeren Kopf und Wache mit
131 * geaendert.
132 */
133 it.each<[string, string, Textblock[]]>([
134 [
135 'zwei Punkte mit einer Leerzeile dazwischen: zwei Aufzaehlungen',
136 '- a\n\n- b',
137 [aufzaehlung('a'), aufzaehlung('b')],
138 ],
139 [
140 'eine eingerueckte Zeile nach Leerzeile hinter einem Punkt: ein eigener Absatz',
141 '- a\n\n b',
142 [aufzaehlung('a'), absatz('b')],
143 ],
144 ['Bindestriche unter einer Absatzzeile: Text', 'Titel\n---', [absatz('Titel ---')]],
145 ['Gleichheitszeichen unter einer Absatzzeile: Text', 'Titel\n===', [absatz('Titel ===')]],
146 [
147 'eine Zeile nur aus Sternen oder Unterstrichen: ein Absatz',
148 'Eins.\n\n***\n\n___',
149 [absatz('Eins.'), absatz('***'), absatz('___')],
150 ],
151 ['ein Bindestrich allein hinter einem Punkt: Fortsetzung', '- a\n-', [aufzaehlung('a -')]],
152 ['ein Zitat: Text', '> Zitat', [absatz('> Zitat')]],
153 ['Tilden: Text', '~~~\nCode\n~~~', [absatz('~~~ Code ~~~')]],
154 [
155 'eine Verweisdefinition: sichtbarer Text',
156 '[name]: https://example.org',
157 [absatz('[name]: https://example.org')],
158 ],
159 ['ein Tabulator nach dem Bindestrich: kein Punkt', '-\tPunkt', [absatz('-\tPunkt')]],
160 ])('%s', (_fall, wortlaut, erwartet) => {
161 expect(gliedereText(wortlaut, ERSATZ).bloecke).toEqual(erwartet);
162 });
163 });
164
165 describe('textbloeckeAlsKnoten', () => {
166 it('macht aus einem Abschnitt h3, aus einem Absatz p und aus einer Aufzaehlung ul mit li', () => {
167 const knoten = textbloeckeAlsKnoten(
168 [abschnitt('Abschnitt'), absatz('Absatz.'), aufzaehlung('eins', 'zwei')],
169 'de',
170 );
171 expect(knoten.map((k) => k.tagName)).toEqual(['H3', 'P', 'UL']);
172 expect(knoten.map((k) => k.textContent)).toEqual(['Abschnitt', 'Absatz.', 'einszwei']);
173 const punkte = [...knoten[2]!.children];
174 expect(punkte.map((p) => p.tagName)).toEqual(['LI', 'LI']);
175 expect(punkte.map((p) => p.textContent)).toEqual(['eins', 'zwei']);
176 });
177
178 it('zeichnet jeden Block mit der uebergebenen Sprache aus', () => {
179 for (const sprache of ['de', 'en'] as const) {
180 const knoten = textbloeckeAlsKnoten([abschnitt('A'), absatz('B'), aufzaehlung('C')], sprache);
181 expect(knoten.map((k) => k.getAttribute('lang'))).toEqual([sprache, sprache, sprache]);
182 }
183 });
184
185 it('setzt Text als Text: Eine spitze Klammer wird kein Element', () => {
186 const knoten = textbloeckeAlsKnoten(
187 [
188 abschnitt('<em>Abschnitt</em>'),
189 absatz('<img src=x onerror=alert(1)>'),
190 aufzaehlung('<b>fett</b>'),
191 ],
192 'de',
193 );
194 expect(knoten[0]!.children).toHaveLength(0);
195 expect(knoten[0]!.textContent).toBe('<em>Abschnitt</em>');
196 expect(knoten[1]!.children).toHaveLength(0);
197 expect(knoten[1]!.textContent).toBe('<img src=x onerror=alert(1)>');
198 expect(knoten[2]!.querySelector('b')).toBeNull();
199 expect(knoten[2]!.textContent).toBe('<b>fett</b>');
200 });
201
202 it('liefert fuer keine Bloecke keine Knoten', () => {
203 expect(textbloeckeAlsKnoten([], 'de')).toEqual([]);
204 });
205 });