Gästetexte des Terminplaners: Fassung 2026-10-05, Erinnerungs- und Abo-Ende-Satz, Archiv je Fassung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a91a331b4
commit
c1266fcce7
11 files changed
+313
-33
No files matched your search
@@ -55,8 +55,11 @@ function fristBaustein() {
|
||||
|
||||
const belehrung = (betrieb = BETRIEB) => widerrufsbelehrung(betrieb)
|
||||
|
||||
test('Fassung der Rechtstexte', () => {
|
||||
assert.equal(RECHTSTEXTE_VERSION, '2026-10-02')
|
||||
test('version_gleich_register: RECHTSTEXTE_VERSION ist das Fassungsdatum des Registers und nicht mehr die Altfassung 2026-10-02', () => {
|
||||
assert.equal(RECHTSTEXTE_VERSION, config.termine.avvVersion)
|
||||
assert.notEqual(RECHTSTEXTE_VERSION, '2026-10-02')
|
||||
assert.match(RECHTSTEXTE_VERSION, /^\d{4}-\d{2}-\d{2}$/)
|
||||
assert.ok(RECHTSTEXTE_VERSION > '2026-10-02', 'die Fassung liegt nach der eingefrorenen Altfassung')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Widerrufsbelehrung
|
||||
@@ -177,8 +180,8 @@ test('Zustimmungstexte wortgleich zu den Vorgaben (Sie-Form)', () => {
|
||||
+ 'Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).',
|
||||
)
|
||||
assert.equal(ZUSTIMMUNG_STORNO, 'Ich habe die Stornobedingungen gelesen.')
|
||||
assert.equal(ZUSTIMMUNG_ERINNERUNG(24), 'Ich möchte 24 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten.')
|
||||
assert.equal(ZUSTIMMUNG_ERINNERUNG(2), 'Ich möchte 2 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten.')
|
||||
assert.equal(ZUSTIMMUNG_ERINNERUNG(24), 'Ich möchte 24 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten. Diese Einwilligung kann ich jederzeit widerrufen.')
|
||||
assert.equal(ZUSTIMMUNG_ERINNERUNG(2), 'Ich möchte 2 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten. Diese Einwilligung kann ich jederzeit widerrufen.')
|
||||
assert.equal(GESUNDHEIT_HINWEIS, 'Bitte keine Gesundheitsangaben.')
|
||||
})
|
||||
|
||||
@@ -187,6 +190,23 @@ test('Zustimmung zum vorzeitigen Beginn setzt den Betriebsnamen unverändert ein
|
||||
assert.ok(text.startsWith("Ich verlange ausdrücklich, dass Frisör $& $1 'Anna' mit der Leistung vor Ablauf"))
|
||||
})
|
||||
|
||||
test('erinnerung_widerrufssatz: Haken-Text, Hinweise-Dokument wörtlich gleich; der Haken trägt den Widerrufssatz (Art. 7 Abs. 3 DSGVO)', () => {
|
||||
const satz = (n) => `Ich möchte ${n} Stunden vor dem Termin eine Erinnerung per E-Mail erhalten. Diese Einwilligung kann ich jederzeit widerrufen.`
|
||||
for (const n of [2, 12, 24, 48]) assert.equal(ZUSTIMMUNG_ERINNERUNG(n), satz(n))
|
||||
const md = lies('docs/rechtliches/termine-hinweise.md').replace(/\r/g, '')
|
||||
assert.ok(md.includes(satz('{{n}}')), 'Hinweise-Dokument: Haken Erinnerung weicht ab')
|
||||
assert.ok(md.includes(ZUSTIMMUNG_ERINNERUNG('{{n}}')))
|
||||
assert.ok(!md.includes('Erinnerung per E-Mail erhalten. |'), 'der alte Haken ohne Widerrufssatz steht nicht mehr im Dokument')
|
||||
assert.equal(ZUSTIMMUNG_ERINNERUNG(24).split('\n').length, 1, 'ein Satz im Haken, keine Umbrüche')
|
||||
})
|
||||
|
||||
test('dokumente_nennen_fassung: die drei Gäste-Dokumente tragen die Fassung der Gästetexte', () => {
|
||||
for (const rel of ['docs/rechtliches/termine-hinweise.md', 'docs/rechtliches/termine-datenschutz-absatz.md', 'docs/rechtliches/termine-widerrufsbelehrung-muster.md']) {
|
||||
assert.ok(lies(rel).includes(`Fassung ${RECHTSTEXTE_VERSION}.`), `${rel} nennt nicht „Fassung ${RECHTSTEXTE_VERSION}.“`)
|
||||
assert.ok(!lies(rel).includes('Fassung 2026-10-02.'), `${rel} trägt noch die Altfassung`)
|
||||
}
|
||||
})
|
||||
|
||||
const EINSTELLUNG = { anrede: 'sie', aufbewahrungTage: 90, betrieb: BETRIEB }
|
||||
|
||||
test('Formular-Hinweis (Sie): Zweck, Auftragsverarbeiter, Löschfristen, keine Cookies, Gesundheit, Verantwortlicher', () => {
|
||||
@@ -271,8 +291,8 @@ const normalisiere = (text) => text.split(/\n[ \t]*\n/).map((a) => a.replace(/\s
|
||||
/**
|
||||
* Datenschutz-Absatz aus der Markdown-Quelle: Blockquote der Fassung („Sie“ | „Du“), `> `, `**` und `###` entfernt,
|
||||
* [[wenn …: X]] durch X (Schalter an) oder nichts (Schalter aus) ersetzt, Platzhalter mit den Testwerten gefüllt.
|
||||
* Ein Satzteil, der mit einem Wort beginnt („und die Angaben …“), steht in der Quelle ohne Leerzeichen am Vordertext
|
||||
* („Platz[[…: und die Angaben …]]“); er wird hier mit Leerzeichen angefügt (Satzteile mit Komma bleiben unmittelbar dran).
|
||||
* Ein Satzteil, der mit einem Wort beginnt („und die Angaben …“), trägt sein Leerzeichen selbst in der Klammer (zwei Leerzeichen
|
||||
* nach dem Doppelpunkt); die Ersetzung fügt nichts hinzu.
|
||||
*/
|
||||
function datenschutzQuelle(fassung, zustand, { tage = 90 } = {}) {
|
||||
const md = lies('docs/rechtliches/termine-datenschutz-absatz.md').replace(/\r/g, '')
|
||||
@@ -281,11 +301,10 @@ function datenschutzQuelle(fassung, zustand, { tage = 90 } = {}) {
|
||||
const zitat = teil.split('\n').filter((z) => z.startsWith('>')).map((z) => z.replace(/^>\s?/, '').replace(/^### /, ''))
|
||||
assert.ok(zitat.length > 10, 'Blockquote der Quelle nicht gefunden')
|
||||
const roh = zitat.join('\n').replace(/\*\*/g, '')
|
||||
const bedingt = roh.replace(/\[\[wenn ([^:]+): ([\s\S]*?)\]\]/g, (_, bedingung, satz, offset, ganz) => {
|
||||
const bedingt = roh.replace(/\[\[wenn ([^:]+): ([\s\S]*?)\]\]/g, (_, bedingung, satz) => {
|
||||
const eintrag = DS_BEDINGUNGEN.find(([muster]) => muster.test(bedingung))
|
||||
assert.ok(eintrag, `unbekannte Bedingung in der Quelle: ${bedingung}`)
|
||||
if (!zustand[eintrag[1]]) return ''
|
||||
return /^[A-Za-zÄÖÜäöüß]/.test(satz) && offset > 0 && !/\s/.test(ganz[offset - 1]) ? ` ${satz}` : satz
|
||||
return zustand[eintrag[1]] ? satz : ''
|
||||
})
|
||||
const werte = { Firma: BETRIEB.name, Anschrift: BETRIEB.adresse, 'E-Mail': BETRIEB.email, Telefon: BETRIEB.telefon, Tage: String(tage) }
|
||||
const gefuellt = bedingt.replace(/\{\{([^}]+)\}\}/g, (_, name) => {
|
||||
@@ -364,6 +383,63 @@ test('Datenschutz-Absatz: die Bedingungen lesen die Einstellung wie das Widget',
|
||||
assert.ok(text({ ...alleAn, betrieb: { ...BETRIEB, telefon: ' 06301\n1234 ' } }).includes('telefonisch erreichen: 06301 1234.'))
|
||||
})
|
||||
|
||||
const ABO_ENDE_SATZ = 'Wird die Online-Terminbuchung abgeschaltet, zum Beispiel weil unser Vertrag mit WEBklar endet, werden alle Termindaten 30 Tage nach dem Abschalten gelöscht.'
|
||||
|
||||
test('absatz_abo_ende: Funktion und Markdown (Sie und Du) nennen den Abo-Ende-Satz mit „30 Tage nach dem Abschalten“; der alte Satz ist weg', () => {
|
||||
const md = lies('docs/rechtliches/termine-datenschutz-absatz.md').replace(/\r/g, '')
|
||||
const fassungen = md.split(/^## Fassung /m).slice(1)
|
||||
assert.equal(fassungen.length, 2)
|
||||
for (const f of fassungen) {
|
||||
assert.ok(normalisiere(f.replace(/^> ?/gm, '')).includes(ABO_ENDE_SATZ), 'Markdown: Abo-Ende-Satz fehlt')
|
||||
assert.ok(f.includes('30 Tage nach dem Abschalten'))
|
||||
assert.ok(!f.includes('löschen wir alle Termindaten 30 Tage'), 'Markdown: alter Satz steht noch')
|
||||
}
|
||||
for (const anrede of ['sie', 'du']) {
|
||||
for (const zustand of [DS_ALLE_AN, DS_ALLE_AUS]) {
|
||||
const text = datenschutzAbsatz(dsEinstellung(zustand), anrede)
|
||||
assert.equal(text.split(ABO_ENDE_SATZ).length, 2, `${anrede}: Satz genau einmal`)
|
||||
assert.ok(text.includes('30 Tage nach dem Abschalten'))
|
||||
assert.ok(!text.includes('löschen wir alle Termindaten'), `${anrede}: alter Satz steht noch`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('haltezeit_aus_config: „der Bestätigungslink gilt N Minuten“ folgt config.termine.holdMinutes; die Markdown-Quelle nennt denselben Wert', () => {
|
||||
const md = lies('docs/rechtliches/termine-datenschutz-absatz.md')
|
||||
for (const anrede of ['sie', 'du']) {
|
||||
assert.ok(datenschutzAbsatz(dsEinstellung(DS_ALLE_AN), anrede).includes(`(der Bestätigungslink gilt ${config.termine.holdMinutes} Minuten)`))
|
||||
}
|
||||
assert.equal((md.match(new RegExp(`Bestätigungslink gilt ${config.termine.holdMinutes} Minuten`, 'g')) || []).length, 2, 'die Quelle nennt beide Fassungen mit dem Wert aus config')
|
||||
const vorher = config.termine.holdMinutes
|
||||
try {
|
||||
config.termine.holdMinutes = 45
|
||||
for (const anrede of ['sie', 'du']) {
|
||||
const text = datenschutzAbsatz(dsEinstellung(DS_ALLE_AN), anrede)
|
||||
assert.ok(text.includes('(der Bestätigungslink gilt 45 Minuten)'), anrede)
|
||||
assert.ok(!text.includes('30 Minuten'), `${anrede}: kein fester Wert mehr im Text`)
|
||||
}
|
||||
} finally {
|
||||
config.termine.holdMinutes = vorher
|
||||
}
|
||||
})
|
||||
|
||||
test('quelle_satzteile_mit_leerzeichen: ein Satzteil der Quelle, der mit einem Wort beginnt, trägt sein Leerzeichen in der Klammer', () => {
|
||||
const md = lies('docs/rechtliches/termine-datenschutz-absatz.md').replace(/\r/g, '')
|
||||
let mitLeerzeichen = 0
|
||||
for (const m of md.matchAll(/\[\[wenn ([^:]+): ([\s\S]*?)\]\]/g)) {
|
||||
if (m[1] === '…') continue // die Erklärung der Schreibweise im Kopf der Quelle
|
||||
const davor = md[m.index - 1]
|
||||
if (/^[A-Za-zÄÖÜäöüß]/.test(m[2])) {
|
||||
// Satzteil beginnt mit einem Wort: nur nach Leerzeichen erlaubt (Satzanfang), sonst klebte er am Vordertext („Platzund“)
|
||||
assert.ok(/\s/.test(davor), `Satzteil „${m[2].slice(0, 30)}…“ hängt ohne Leerzeichen am Vordertext`)
|
||||
} else if (m[2].startsWith(' ')) {
|
||||
mitLeerzeichen += 1
|
||||
assert.ok(/^ [A-Za-zÄÖÜäöüß]/.test(m[2]) && !/\s/.test(davor), `Leerzeichen in der Klammer nur am Anschluss an ein Wort: „${m[2].slice(0, 30)}…“`)
|
||||
}
|
||||
}
|
||||
assert.equal(mitLeerzeichen, 4, 'vier Satzteile mit „und“ tragen ihr Leerzeichen selbst (Sie und Du, je zwei)')
|
||||
})
|
||||
|
||||
test('Datenschutz-Absatz: Aufbewahrung nach dem Termin, ungültiger Wert wird zu 90 Tagen (wie der Formular-Hinweis)', () => {
|
||||
for (const anrede of ['sie', 'du']) {
|
||||
const mit = (aufbewahrungTage) => datenschutzAbsatz(dsEinstellung(DS_ALLE_AN, { aufbewahrungTage }), anrede)
|
||||
|
||||
Reference in new issue
Block a user