Entwurf umbenannt (zeiten-datenschutz-6e-entwurf.md), Leistungsbeschreibung, Kanzlei-Fragen, Livegang-Notiz und Test-Pins ziehen mit. N13: Rückgabe-Marker nach dem Löschen fehlt (vor on). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
369 lines
19 KiB
JavaScript
369 lines
19 KiB
JavaScript
import { test, after } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { existsSync, readFileSync } from 'node:fs'
|
||
import http from 'node:http'
|
||
import express from 'express'
|
||
import { useTestEnv } from './support/testEnv.js'
|
||
|
||
// Rechtstext-Entwürfe und Einbau-Anleitung von "Öffnungszeiten live" (Task 33, Spec 9, 4.6, Z19, Z20).
|
||
// Alles hier ist Entwurf für die Rechtsrunde: die Leistungsbeschreibung steht als statische Seite unter
|
||
// public/vertragsunterlagen/ (wie die der News-Pinnwand, Register-Adresse seit Task 5 festgelegt); die Texte für
|
||
// Datenschutzerklärung, K02/K03 und die Kanzlei-Fragen sind Markdown-Entwürfe, die keinen Code und keine
|
||
// veröffentlichte Seite ändern.
|
||
useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1' })
|
||
const { config } = await import('../server/config.js')
|
||
const { featureConfig, featureHaken } = await import('../server/services/featureRegistry.js')
|
||
|
||
const root = (rel) => new URL(`../${rel}`, import.meta.url)
|
||
const lies = (rel) => readFileSync(root(rel), 'utf8').replace(/\r/g, '')
|
||
/** Sichtbarer Text einer HTML-Seite: ohne Kommentare, Skripte, Stile und Tags, Leerraum zusammengefasst. */
|
||
const sichtbar = (html) =>
|
||
html
|
||
.replace(/<!--[\s\S]*?-->/g, ' ')
|
||
.replace(/<(script|style)[\s\S]*?<\/\1>/g, ' ')
|
||
.replace(/<[^>]+>/g, ' ')
|
||
.replace(/ /g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
const norm = (text) => text.replace(/\s+/g, ' ').trim()
|
||
|
||
const LB_PFAD = 'public/vertragsunterlagen/zeiten-leistungsbeschreibung.html'
|
||
const LB_URL = '/vertragsunterlagen/zeiten-leistungsbeschreibung.html'
|
||
const lb = () => lies(LB_PFAD)
|
||
|
||
// ---------------------------------------------------------------------------------------------- Leistungsbeschreibung
|
||
// Dieselbe Auslieferung wie in server/index.js: express.static(public) (die Adresse ist das Register des Terminplaners nicht, sondern
|
||
// der "vertragsunterlagen"-Weg der News-Pinnwand; die versionierten /rechtliches-Seiten samt PDF gibt es nur für die Terminplaner-Sätze)
|
||
const app = express()
|
||
app.use(express.static(new URL('../public/', import.meta.url).pathname))
|
||
const server = await new Promise((resolve) => {
|
||
const s = http.createServer(app).listen(0, '127.0.0.1', () => resolve(s))
|
||
})
|
||
after(() => new Promise((resolve) => server.close(resolve)))
|
||
const baseUrl = `http://127.0.0.1:${server.address().port}`
|
||
|
||
test('Leistungsbeschreibung ist erreichbar und als Entwurf markiert', async () => {
|
||
const antwort = await fetch(baseUrl + LB_URL)
|
||
assert.equal(antwort.status, 200)
|
||
assert.match(antwort.headers.get('content-type') || '', /^text\/html/)
|
||
const html = await antwort.text()
|
||
assert.equal(html, lb(), 'die ausgelieferte Seite ist die Datei im Repo')
|
||
|
||
// Entwurf: nur als HTML-Kommentar (der Kunde sieht nichts davon); der Test "Bestelltexte ohne ENTWURF" prüft das Register, nicht die Seite
|
||
assert.ok(html.includes('<!-- ENTWURF – vor Livegang Rechtsrunde -->'), 'Kopf-Kommentar fehlt')
|
||
assert.ok(html.indexOf('<!-- ENTWURF') < html.indexOf('<head>'), 'der Kommentar steht vor dem Kopf der Seite')
|
||
const text = sichtbar(html)
|
||
assert.doesNotMatch(text, /ENTWURF|Entwurf/, 'im sichtbaren Text steht kein Entwurfsvermerk')
|
||
assert.doesNotMatch(JSON.stringify(featureConfig('zeiten').leistungsbeschreibung), /ENTWURF/)
|
||
|
||
// Preis und Einbau
|
||
assert.ok(text.includes('§ 19 UStG'))
|
||
assert.ok(text.includes(`30 € einmalig (${config.ustHinweis})`), 'Preis mit dem Umsatzsteuer-Hinweis des Registers')
|
||
assert.ok(text.includes('ein Einbau je Website'))
|
||
assert.match(html, /<title>Leistungsbeschreibung Öffnungszeiten live – WEBklar<\/title>/)
|
||
assert.match(html, /<h1>Leistungsbeschreibung: Öffnungszeiten live<\/h1>/)
|
||
})
|
||
|
||
test('Leistungsbeschreibung: Stand ist die Fassung des Registers', () => {
|
||
const [j, m, t] = featureConfig('zeiten').leistungsbeschreibung.version.split('-')
|
||
assert.ok(lb().includes(`<p class="meta">Stand ${t}.${m}.${j}</p>`), 'Stand = Fassung der Leistungsbeschreibung im Register')
|
||
})
|
||
|
||
test('Leistungsbeschreibung folgt der Gliederung der Spec 9.1', () => {
|
||
const titel = [...lb().matchAll(/<h2 id="([a-z-]+)">(\d+)\. ([^<]+)<\/h2>/g)].map((m) => [Number(m[2]), m[3]])
|
||
assert.deepEqual(titel, [
|
||
[1, 'Geltung'],
|
||
[2, 'Leistung'],
|
||
[3, 'Einbau und Freischaltung'],
|
||
[4, 'Preis'],
|
||
[5, 'Nicht enthalten'],
|
||
[6, 'Pflichten des Kunden'],
|
||
[7, 'Verfügbarkeit und Aktualität'],
|
||
[8, 'Haftung'],
|
||
[9, 'Ende der Leistung'],
|
||
[10, 'Änderungen und Einstellung'],
|
||
[11, 'Widerruf'],
|
||
[12, 'Hinweise'],
|
||
])
|
||
})
|
||
|
||
test('Leistungsbeschreibung grenzt das Abo ab und nennt Einbau und Ausschlüsse', () => {
|
||
const text = sichtbar(lb())
|
||
for (const satz of [
|
||
// Z19: freiwilliges Extra, Abo-Bedingungen bleiben unverändert
|
||
'freiwilliges Zusatzangebot',
|
||
'gehört nicht zum Leistungsumfang der Abo-Bedingungen',
|
||
'Leistungsbeschreibung B Nr. 3 der Abo-Bedingungen',
|
||
'Der Preis des Wartungs-Abos ändert sich nicht',
|
||
// Z4, Z20: ohne Aufpreis, ohne Anrechnung, ein Einbau je Website inkl. Ersatz-Tabelle beim Abschalten, Sonderwünsche
|
||
'ohne Aufpreis',
|
||
'weder auf das Stundenkontingent angerechnet noch damit bezahlt',
|
||
'Aktualisierung der Ersatz-Tabelle',
|
||
'Sonderwünsche',
|
||
'nach Angebot',
|
||
// Einbau: automatisch nur bei WEBklar-Hosting, sonst durch das Team
|
||
'nur bei Websites, die WEBklar hostet',
|
||
'setzt das Team',
|
||
// nicht enthalten
|
||
'Übertragung der Öffnungszeiten zu Google',
|
||
'Feiertage einer Gemeinde',
|
||
// Pflichten des Kunden
|
||
'Zeiten, Feiertage und Hinweistexte',
|
||
'Preise und Rabatte',
|
||
'Datenschutz-Absatz',
|
||
'WhatsApp',
|
||
// Technik: nichts im Browser, Fremddienste erst nach Klick
|
||
'keine Cookies',
|
||
'erst nach dem Klick',
|
||
'Google Maps',
|
||
]) {
|
||
assert.ok(text.includes(satz), `fehlt: ${satz}`)
|
||
}
|
||
})
|
||
|
||
test('Leistungsbeschreibung nennt Haftung, Ende nach Abo und Einstellung', () => {
|
||
const text = sichtbar(lb())
|
||
for (const satz of [
|
||
// Haftung: Hilfsmittel, Angaben vom Kunden, Softwarefehler, Feiertagsdaten
|
||
'Die Live-Anzeige ist ein Hilfsmittel',
|
||
'Angaben des Kunden',
|
||
'Feiertagsdaten',
|
||
'entgangenen Gewinn',
|
||
// Ende der Leistung nach Z11: 30 Tage zum Behalten für 30 €, danach abgeschaltet, Ersatz-Tabelle bleibt, Daten 30 Tage
|
||
'Endet das Wartungs-Abo',
|
||
'noch 30 Tage',
|
||
'für einmalig 30 € behalten',
|
||
'schaltet WEBklar sie ab',
|
||
'Ersatz-Tabelle',
|
||
'weitere 30 Tage',
|
||
'Wartungs-Abo beginnt',
|
||
// Einstellung/Änderung nach Vorbild Analyse Nr. 8
|
||
'Technik von Öffnungszeiten live verbessern',
|
||
'mit einer Frist von vier Wochen beenden',
|
||
'mit einer Frist von drei Monaten einstellen',
|
||
'Sicherheitslücke',
|
||
'§ 327r Abs. 3 BGB',
|
||
'ändert ihre Einstellung den Preis des Wartungs-Abos nicht',
|
||
]) {
|
||
assert.ok(text.includes(satz), `fehlt: ${satz}`)
|
||
}
|
||
})
|
||
|
||
test('Haftung spiegelt § 13 Abs. 1 und 2 der Abo-Bedingungen wörtlich (auch für Kunden ohne Abo)', () => {
|
||
const abo = norm(sichtbar(lies('public/abo-bedingungen.html')))
|
||
const text = norm(sichtbar(lb()))
|
||
for (const bruchstueck of [
|
||
'unbeschränkt bei Vorsatz und grober Fahrlässigkeit, bei Verletzung von Leben, Körper oder Gesundheit, nach dem Produkthaftungsgesetz und soweit',
|
||
'nur für den vertragstypischen, vorhersehbaren Schaden',
|
||
'Wesentlich sind Pflichten, deren Erfüllung die ordnungsgemäße Durchführung des Vertrags überhaupt erst ermöglicht und auf deren Einhaltung der Kunde regelmäßig vertrauen darf',
|
||
'Im Übrigen ist die Haftung für leichte Fahrlässigkeit ausgeschlossen',
|
||
]) {
|
||
assert.ok(abo.includes(bruchstueck), `Abo-Bedingungen § 13 hat sich geändert: ${bruchstueck}`)
|
||
assert.ok(text.includes(bruchstueck), `die Leistungsbeschreibung muss § 13 spiegeln: ${bruchstueck}`)
|
||
}
|
||
// ohne Abo gibt es kein § 13 zum Verweisen: die Klausel steht ausgeschrieben im Abschnitt Haftung
|
||
const haftung = lb().split('<h2 id="haftung">')[1].split('<h2 id=')[0]
|
||
assert.match(sichtbar(haftung), /Dieser Abschnitt gilt auch für Kunden ohne Wartungs-Abo/)
|
||
})
|
||
|
||
test('Verweise innerhalb der Leistungsbeschreibung treffen vorhandene Nummern und Absätze', () => {
|
||
const html = lb()
|
||
const teile = html.split(/<h2 id="[a-z-]+">/).slice(1)
|
||
const absaetze = teile.map((teil) => [...teil.matchAll(/<p>\((\d+)\) /g)].map((m) => Number(m[1])))
|
||
assert.equal(teile.length, 12)
|
||
for (const [i, nummern] of absaetze.entries()) {
|
||
assert.ok(nummern.length > 0, `Abschnitt ${i + 1} hat keine Absätze`)
|
||
assert.deepEqual(nummern, nummern.map((_, k) => k + 1), `Abschnitt ${i + 1}: Absätze (1), (2), … ohne Lücke`)
|
||
}
|
||
// fremde Verweise (Abo-Bedingungen, Gesetz) herausnehmen, übrig bleiben "Nr. n [Abs. m]" dieser Leistungsbeschreibung
|
||
const text = sichtbar(html)
|
||
.replace(/Leistungsbeschreibung B Nr\. \d+/g, '')
|
||
.replace(/§ \d+[a-z]? Abs\. \d+ Nr\. \d+ BGB/g, '')
|
||
const verweise = [...text.matchAll(/Nr\. (\d+)(?: Abs\. (\d+))?/g)]
|
||
assert.ok(verweise.length >= 6, 'die Leistungsbeschreibung verweist an mehreren Stellen auf sich selbst')
|
||
for (const [roh, nr, abs] of verweise) {
|
||
assert.ok(absaetze[Number(nr) - 1], `${roh}: Abschnitt fehlt`)
|
||
if (abs) assert.ok(absaetze[Number(nr) - 1].includes(Number(abs)), `${roh}: Absatz fehlt`)
|
||
}
|
||
})
|
||
|
||
test('Leistungsbeschreibung: Sie-Form, Schreibweise WEBklar, nie „24 Uhr“', () => {
|
||
const text = sichtbar(lb())
|
||
assert.doesNotMatch(text, /\b(Du|Dein\w*|dein\w*|Dich|dich|Dir)\b/, 'Rechtstext in Sie-Form')
|
||
assert.match(text, /\bSie\b/)
|
||
assert.doesNotMatch(text, /24 Uhr|24:00|24 h\b/)
|
||
assert.doesNotMatch(lb(), /Webklar|WebKlar|WEBKlar|webKlar/, 'Schreibweise ist WEBklar')
|
||
assert.ok(text.includes('WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler'), 'Anbieter genannt')
|
||
// keine Rechtsberatung, keine Platzhalter
|
||
assert.ok(text.includes('Keine Rechtsberatung'))
|
||
assert.doesNotMatch(text, /TODO|XXX|\[Prüfen|⚖/)
|
||
})
|
||
|
||
test('Register verlinkt die Leistungsbeschreibung', () => {
|
||
const f = featureConfig('zeiten')
|
||
assert.equal(f.leistungsbeschreibung.url, LB_URL)
|
||
assert.ok(existsSync(new URL(`../public${f.leistungsbeschreibung.url}`, import.meta.url)), 'die Datei zur Register-Adresse existiert')
|
||
// beide Haken (bezahlt und gratis) nennen genau diese Leistungsbeschreibung
|
||
for (const consent of [f.consent, f.consentFrei]) {
|
||
assert.ok(consent.items[0].text.includes('Leistungsbeschreibung „Öffnungszeiten live“'))
|
||
}
|
||
assert.ok(sichtbar(lb()).includes('„Öffnungszeiten live“'))
|
||
// dieselbe Adresse nennen die Bestellseite und die Rechtliches-Karte (Spec 8)
|
||
assert.equal(new URL(`https://project.webklar.com${f.leistungsbeschreibung.url}`).pathname, LB_URL)
|
||
})
|
||
|
||
test('unterlagenBereit für zeiten ist erfüllt', async () => {
|
||
const haken = featureHaken('zeiten')
|
||
assert.equal(typeof haken.unterlagenBereit, 'function')
|
||
assert.equal(await haken.unterlagenBereit(), true)
|
||
// und die Unterlage, auf die das Register zeigt, ist wirklich lesbar (nicht leer)
|
||
const html = readFileSync(new URL(`../public${featureConfig('zeiten').leistungsbeschreibung.url}`, import.meta.url), 'utf8')
|
||
assert.ok(html.length > 5000)
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------- Datenschutz § 6e
|
||
test('Datenschutz-Entwurf § 6e und Ergänzung § 6 liegen als Entwurf vor, datenschutz.html bleibt unberührt', () => {
|
||
const md = lies('docs/rechtliches/zeiten-datenschutz-6e-entwurf.md')
|
||
assert.ok(md.includes('<h2 id="zeiten">6e. Öffnungszeiten live auf Websites unserer Kunden</h2>'))
|
||
for (const satz of [
|
||
'Entwurf',
|
||
'Art. 6 Abs. 1 lit. b DSGVO',
|
||
'Art. 6 Abs. 1 lit. f DSGVO',
|
||
'Erinnerung',
|
||
'höchstens eine',
|
||
'keine Cookies',
|
||
'nichts im Browser',
|
||
'erst',
|
||
'Klick',
|
||
'WhatsApp',
|
||
'Google Maps',
|
||
'30 Tage',
|
||
'Abschnitt 3', // Server-Protokolle (14 Tage) stehen dort
|
||
'Abschnitt 7', // Nachweis der Zustimmung
|
||
]) {
|
||
assert.ok(md.includes(satz), `fehlt: ${satz}`)
|
||
}
|
||
assert.doesNotMatch(md, /24 Uhr|Webklar|WebKlar/)
|
||
// § 6: der heutige Satz steht wörtlich da (Drift-Wächter gegen public/datenschutz.html), der neue nennt Öffnungszeiten live
|
||
const html = lies('public/datenschutz.html')
|
||
const heute = html.slice(html.indexOf('Um Sie zu unterstützen'), html.indexOf('ansehen und ändern.', html.indexOf('Um Sie zu unterstützen')) + 'ansehen und ändern.'.length)
|
||
assert.ok(heute.startsWith('Um Sie zu unterstützen') && heute.endsWith('ansehen und ändern.'))
|
||
assert.ok(md.includes(heute), 'der heutige § 6-Satz ist wörtlich zitiert')
|
||
assert.match(md, /wenn Sie Öffnungszeiten live nutzen, Ihre Öffnungszeiten, Hinweise und Kontaktangaben ansehen und ändern/)
|
||
// § 7 (Nachweis): der heutige Satz steht wörtlich da, der Vorschlag nennt Öffnungszeiten live und das Einschalten
|
||
const anfang7 = html.indexOf('Bestellen Sie eine Funktion (News-Pinnwand, Terminplaner)')
|
||
const heute7 = html.slice(anfang7, html.indexOf('wenn Sie nichts bestellt haben.', anfang7) + 'wenn Sie nichts bestellt haben.'.length)
|
||
assert.ok(anfang7 > 0 && heute7.endsWith('wenn Sie nichts bestellt haben.'))
|
||
assert.ok(md.includes(heute7), 'der heutige § 7-Satz ist wörtlich zitiert')
|
||
assert.match(md, /Bestellen Sie eine Funktion \(News-Pinnwand, Terminplaner, Öffnungszeiten live\) oder schalten Sie Öffnungszeiten live im Wartungs-Abo ein/)
|
||
assert.ok(md.includes('legal_datenschutz_kundenansicht'), 'nennt den Test, der den § 6-Satz pinnt')
|
||
// Wächter: die Entwurfsdatei ist nicht in die veröffentlichte Erklärung gerutscht
|
||
assert.doesNotMatch(html, /6e\. Öffnungszeiten live/)
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------- K02/K03
|
||
test('K02/K03-Entwurf zitiert den heutigen Satz und schlägt den neuen vor', () => {
|
||
const md = lies('docs/rechtliches/zeiten-k02-k03-entwurf.md')
|
||
const heute =
|
||
'Die Website-Analyse und der Terminplaner gehören zum Wartungs-Abo und enden mit ihm. Die Termindaten löschen wir 30 Tage nach dem Abschalten; vorher können Sie Ihre Termine im Kundenportal ansehen und exportieren.'
|
||
for (const vorlage of ['K02 Kuendigungsbestaetigung', 'K03 Eingang Kuendigung']) {
|
||
assert.ok(lies(`server/mail-templates/${vorlage}.html`).includes(heute), `${vorlage}: der Satz im Entwurf muss der heutigen Vorlage entsprechen`)
|
||
}
|
||
assert.ok(md.includes(heute), 'der heutige Satz steht wörtlich im Entwurf')
|
||
// die Pins bleiben mit dem Vorschlag grün: der alte Satz bleibt unverändert der Anfang des neuen
|
||
const vorschlag = md.split('## Vorschlag')[1]
|
||
assert.ok(vorschlag.includes(heute), 'der Vorschlag beginnt mit dem heutigen Satz')
|
||
for (const satz of [
|
||
'Haben Sie Öffnungszeiten live im Wartungs-Abo eingeschaltet',
|
||
'noch 30 Tage',
|
||
'für einmalig 30 € behalten',
|
||
'feste Öffnungszeiten',
|
||
'30 Tage später',
|
||
]) {
|
||
assert.ok(vorschlag.includes(satz), `fehlt im Vorschlag: ${satz}`)
|
||
}
|
||
assert.ok(md.includes('test/mailTemplates.test.js') && md.includes('test/publicForms.test.js'), 'nennt die Pins')
|
||
assert.ok(md.includes('Keine Codeänderung'))
|
||
assert.doesNotMatch(md, /24 Uhr|Webklar/)
|
||
assert.doesNotMatch(vorschlag, /\b(Du|Dein\w*|dein\w*|Dich|dich|Dir)\b/, 'Sie-Form')
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------- Einbau-Anleitung
|
||
test('Einbau-Anleitung enthält Einbau-Zeile, Platzhalter, Ersatz-HTML, Header-Selektor und Prüfliste', async () => {
|
||
const md = lies('docs/funktionen/zeiten-einbau.md')
|
||
const zeile = '<script data-wk-zeiten src="https://project.webklar.com/embed/zeiten.js" data-site="SITEKEY" defer></script>'
|
||
assert.ok(md.includes(zeile), 'Einbau-Zeile wie im Ticket')
|
||
// dieselbe Zeile erzeugt das Einrichtungs-Ticket (Wächter gegen Abweichung)
|
||
const { zeitenTicketDetails } = await import('../server/services/zeiten/ticketDetails.js')
|
||
const ticket = await zeitenTicketDetails({ feature: featureConfig('zeiten'), doc: { siteKey: 'SITEKEY' }, project: {}, customer: {} })
|
||
assert.ok(ticket.includes(zeile), 'das Ticket nennt dieselbe Einbau-Zeile')
|
||
for (const modus of ['tabelle', 'status', 'badge']) {
|
||
assert.ok(md.includes(`data-modus="${modus}"`), `Platzhalter ${modus}`)
|
||
}
|
||
for (const satz of [
|
||
'data-plan',
|
||
'Ersatz-HTML kopieren',
|
||
'WOMS → Funktionen',
|
||
'navSelektor',
|
||
'kontaktleisteAus',
|
||
'hinweisleisteAus',
|
||
'ZEITEN_KEIN_NEUBAU',
|
||
'ZEITEN_PUBLIC_BASE',
|
||
'einbau: manuell',
|
||
'Prüfliste',
|
||
'grep -ril',
|
||
'JSON-LD',
|
||
'Datenschutz-Absatz',
|
||
'website-datenschutz-absatz.md',
|
||
'Ersatz-Tabelle aktualisieren',
|
||
'Seite liegt woanders',
|
||
'keine Cookies',
|
||
]) {
|
||
assert.ok(md.includes(satz), `fehlt: ${satz}`)
|
||
}
|
||
// die Vorgabe der Sperrliste steht in der Config
|
||
for (const subdomain of config.zeiten.keinNeubau) assert.ok(md.includes(subdomain), `Sperrlisten-Vorgabe ${subdomain} fehlt`)
|
||
// die fünf Prüfpunkte des Tickets stehen in der Anleitung (Stichworte)
|
||
for (const stichwort of ['öffnungszeit', 'Footer', 'Header', 'Anruf-Leiste', 'JSON-LD', 'Datenschutz-Absatz']) {
|
||
assert.ok(md.toLowerCase().includes(stichwort.toLowerCase()), `Prüfliste: ${stichwort}`)
|
||
}
|
||
assert.doesNotMatch(md, /24 Uhr|Webklar/)
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------- Website-Absatz
|
||
test('Website-Absatz „Öffnungszeiten live“ in Variante A und B, WhatsApp und Google Maps erst nach Klick', () => {
|
||
const md = lies('docs/funktionen/website-datenschutz-absatz.md')
|
||
const a = md.slice(md.indexOf('## Variante A'), md.indexOf('## Variante B'))
|
||
const b = md.slice(md.indexOf('## Variante B'))
|
||
for (const [name, teil] of [['A', a], ['B', b]]) {
|
||
assert.ok(teil.includes('### Öffnungszeiten auf dieser Website'), `Variante ${name}: Überschrift`)
|
||
const absatz = teil.slice(teil.indexOf('### Öffnungszeiten auf dieser Website'))
|
||
for (const satz of ['IP-Adresse', 'Server-Protokollen', 'keine Cookies', 'nichts in Ihrem Browser', 'Art. 6 Abs. 1 lit. f DSGVO', 'nach 14 Tagen gelöscht', 'WhatsApp', 'Google Maps', 'Erst wenn Sie darauf klicken', 'Vorher werden keine Daten']) {
|
||
assert.ok(absatz.includes(satz), `Variante ${name}: fehlt ${satz}`)
|
||
}
|
||
assert.match(absatz, /nach dem Klick/)
|
||
assert.match(absatz, /Seitencode/, 'strukturierte Daten für Suchmaschinen')
|
||
}
|
||
assert.ok(b.slice(b.indexOf('### Öffnungszeiten auf dieser Website')).includes('[Prüfen: AVV mit WEBklar erforderlich]'), 'Variante B: AVV-Marker wie bei den News')
|
||
// bestehende Wächter aus legalTexts.test.js gelten weiter: keine Gedankenstriche im Kundentext, Marker der News bleibt
|
||
assert.ok(!/[–—]/.test(md.slice(md.indexOf('## Variante A'))), 'Kundentext ohne Gedankenstriche')
|
||
assert.ok(md.includes('[Prüfen: AVV mit WEBklar erforderlich]'))
|
||
assert.doesNotMatch(md, /24 Uhr/)
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------- Kanzlei-Fragen
|
||
test('Kanzlei-Fragen: AVV Funktionen per Haken und Behalten-Zustimmung stehen im Paket', () => {
|
||
const md = lies('docs/rechtliches/zeiten-kanzlei-fragen.md')
|
||
assert.ok(md.includes('⚖ AVV Funktionen'))
|
||
assert.match(md, /Haken/)
|
||
assert.ok(md.includes('Behalten') && md.includes('Einbau beginnt'), 'Behalten-Zustimmung nutzt den Haken „Einbau beginnt“')
|
||
const fragen = md.split(/^### ZQ\d+: /m).slice(1)
|
||
assert.ok(fragen.length >= 6, 'mehrere Fragen')
|
||
for (const frage of fragen) {
|
||
for (const teil of ['**Frage.**', '**Fundstelle im Text.**', '**Unsere Annahme.**', '**Alternative.**']) {
|
||
assert.ok(frage.includes(teil), `${frage.slice(0, 50)}: ${teil}`)
|
||
}
|
||
}
|
||
assert.doesNotMatch(md, /Webklar|WebKlar/)
|
||
})
|