Kundenansicht neu P7: Gutschrift-Antrag schlank, Bedingungen als eigene Ansicht statt Klappe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b00771d11
commit
d17520c4f5
5 files changed
+235
-71
No files matched your search
@@ -1,13 +1,19 @@
|
||||
/* Kundenansicht neu (P2): Übernahmegutschrift beantragen (#gutschrift), Text und Logik 1:1 aus app.js (loadTakeoverCard und Helfer,
|
||||
Fassung 5, Sie-Form, C14). Antrag stellen, Stand sehen, Rückfragen beantworten. Kundentext (Rückfrage, Entscheidung, Anbieter, Domain)
|
||||
kommt nur als Text in die Seite. Das Wortlaut-Orakel prüft den sichtbaren Text, der Hash-Pin in legalTexts.test.js den Block der Bedingungen. */
|
||||
/* Kundenansicht neu (P7): Übernahmegutschrift beantragen (#gutschrift) im schlanken Muster der Spec 5.6, Fassung 6. Antrag stellen, Stand
|
||||
sehen, Rückfragen beantworten. Über dem Knopf steht ein Satz mit den Links zu den Bedingungen und zum Datenschutz (der Klick auf den Knopf
|
||||
ist die Zustimmung, kein Häkchen). Die Bedingungen sind eine eigene Ansicht (#gutschrift?bedingungen=1) mit unverändertem Rechtstext
|
||||
(Block TAKEOVER_CONDITIONS, Hash-Pin in legalTexts.test.js, conditionsVersion 2026-09-30). Kundentext (Rückfrage, Entscheidung, Anbieter,
|
||||
Domain) kommt nur als Text in die Seite. Der Titel der Bedingungen kommt von der Hülle (meldeTitel); keine eigene h1.
|
||||
Die Sie-Form steht nur noch im Rechtstext TAKEOVER_CONDITIONS (hash-gepinnt), sonst gilt „du“. */
|
||||
|
||||
import { api } from './api.js'
|
||||
import { h, leeren } from './dom.js'
|
||||
import { K, ersetze, zuAnsicht, ladeAbo, fmtDatum, fmtEuro, meldeZaehlerNeu, ABO_NICHT_GELADEN } from './ansicht-hilfen.js'
|
||||
import { K, ersetze, zuAnsicht, ladeAbo, fmtDatum, fmtEuro, meldeTitel, meldeZaehlerNeu, ABO_NICHT_GELADEN } from './ansicht-hilfen.js'
|
||||
import { baueLinkzeile, hatWortlaut } from './bestell-bausteine.js'
|
||||
|
||||
const laeufe = new WeakMap() // Wurzel → Nummer des jüngsten Aufrufs
|
||||
const TEXT_ANSICHTSMODUS = 'Im Ansichtsmodus kann kein Antrag gestellt werden.'
|
||||
const TEXT_NICHT_VERFUEGBAR = 'Die Online-Bestellung wird gerade eingerichtet – schreib uns gern im Chat.'
|
||||
const TITEL_BEDINGUNGEN = 'Bedingungen der Übernahmegutschrift'
|
||||
const GRENZEN_VORGABE = { maxFiles: 3, maxFileBytes: 8388608, maxTotalBytes: 10485760 }
|
||||
|
||||
const TAKEOVER_STATUS = {
|
||||
@@ -43,7 +49,7 @@ export const IO = { lies: leseAlsBase64 }
|
||||
export async function sammleDateien(dateien, grenzen, { erforderlich }, lies = IO.lies) {
|
||||
const files = [...(dateien || [])]
|
||||
const mb = (bytes) => Math.round(bytes / 1024 / 1024)
|
||||
if (erforderlich && !files.length) throw new Error('Bitte laden Sie die Rechnung Ihres bisherigen Anbieters hoch.')
|
||||
if (erforderlich && !files.length) throw new Error('Bitte lade die Rechnung deines bisherigen Anbieters hoch.')
|
||||
if (files.length > grenzen.maxFiles) throw new Error(`Bitte höchstens ${grenzen.maxFiles} Dateien auf einmal hochladen.`)
|
||||
const zuGross = files.find((f) => f.size > grenzen.maxFileBytes)
|
||||
if (zuGross) throw new Error(`„${zuGross.name}“ ist größer als ${mb(grenzen.maxFileBytes)} MB.`)
|
||||
@@ -54,11 +60,10 @@ export async function sammleDateien(dateien, grenzen, { erforderlich }, lies = I
|
||||
const feld = (label, steuerung, klein) => h('div', { class: 'kn-tk-feld' }, h('label', { for: steuerung.getAttribute('id') }, label, klein ? [' ', h('small', null, klein)] : null), steuerung)
|
||||
const leistung = (wert, text) => h('label', null, h('input', { type: 'checkbox', name: 'services', value: wert }), ` ${text}`)
|
||||
|
||||
function antragsFormular(grenzen, email) {
|
||||
function antragsFormular(grenzen, email, T) {
|
||||
const maxMb = Math.round(grenzen.maxTotalBytes / 1024 / 1024)
|
||||
const bedingungen = h('details', { class: 'kn-tk-bedingungen' }, h('summary', null, 'Bedingungen der Übernahmegutschrift'), h('ol', null, TAKEOVER_CONDITIONS.map((c) => h('li', null, c))))
|
||||
return h('form', { id: 'tk-form', class: 'kn-tk-form', novalidate: true, hidden: true },
|
||||
h('fieldset', { class: 'kn-tk-feld' }, h('legend', null, 'Welche Leistung haben Sie bisher woanders bezahlt?'),
|
||||
return h('form', { id: 'tk-form', class: 'kn-tk-form kn-bestellen', novalidate: true, hidden: true },
|
||||
h('fieldset', { class: 'kn-tk-feld' }, h('legend', null, 'Welche Leistung hast du bisher woanders bezahlt?'),
|
||||
h('div', { class: 'kn-tk-leistungen' }, leistung('domain', 'Domain'), leistung('hosting', 'Hosting / Website-Betrieb'), leistung('email', 'E-Mail-Postfächer'), leistung('sonstiges', 'Sonstiges'))),
|
||||
h('div', { class: 'kn-tk-zeile' },
|
||||
feld('Bisheriger Anbieter', h('input', { id: 'tk-provider', class: K.input, maxlength: 200, placeholder: 'z. B. IONOS, STRATO' })),
|
||||
@@ -69,15 +74,12 @@ function antragsFormular(grenzen, email) {
|
||||
feld('Bezahlt bis', h('input', { id: 'tk-paid-until', class: K.input, type: 'date' }), '(letzter Tag)')),
|
||||
feld('Grund / Nachricht an uns', h('textarea', { id: 'tk-message', class: K.textarea, maxlength: 4000, placeholder: 'z. B.: Domain und Postfach liegen noch bis Ende März bei IONOS, der Vertrag dort ist gekündigt.' })),
|
||||
feld('Rechnungsnachweis', h('input', { id: 'tk-files', type: 'file', accept: 'application/pdf,image/jpeg,image/png', multiple: true }), `(PDF, JPG oder PNG · bis zu ${grenzen.maxFiles} Dateien · zusammen max. ${maxMb} MB)`),
|
||||
h('p', { class: 'kn-hinweis' }, 'Tipp: Laden Sie auch die Kündigungsbestätigung Ihres bisherigen Anbieters hoch – daraus sehen wir, wann der Vertrag dort endet.'),
|
||||
h('p', { class: 'kn-hinweis' }, 'Tipp: Lade auch die Kündigungsbestätigung deines bisherigen Anbieters hoch – daraus sehen wir, wann der Vertrag dort endet.'),
|
||||
feld('E-Mail-Adresse für unsere Antwort', h('input', { id: 'tk-email', class: K.input, type: 'email', maxlength: 255, autocomplete: 'email', value: email })),
|
||||
bedingungen,
|
||||
h('label', { class: 'kn-check' }, h('input', { type: 'checkbox', id: 'tk-consent' }),
|
||||
h('span', null, 'Ich versichere, dass meine Angaben stimmen und die Rechnung auf mich bzw. mein Unternehmen ausgestellt ist. Die Bedingungen der Übernahmegutschrift habe ich gelesen. Angaben und Nachweise verwendet WEBklar zur Prüfung und Abwicklung des Antrags (',
|
||||
h('a', { href: '/datenschutz.html#uebernahmegutschrift', target: '_blank', rel: 'noopener' }, 'Datenschutz'), ').')),
|
||||
baueLinkzeile(T.gutschrift.satz, { id: 'tk-satz', neuerTab: true }),
|
||||
h('p', { class: K.fehler, id: 'tk-error', hidden: true }),
|
||||
h('div', { class: 'kn-aktionen' },
|
||||
h('button', { type: 'submit', class: K.primaer, id: 'tk-submit' }, 'Antrag absenden'),
|
||||
h('button', { type: 'submit', class: K.primaer, id: 'tk-submit' }, T.gutschrift.knopf || 'Antrag absenden'),
|
||||
h('button', { type: 'button', class: K.knopf, id: 'tk-cancel' }, 'Abbrechen')))
|
||||
}
|
||||
|
||||
@@ -90,7 +92,7 @@ function antrag(r, viewAs) {
|
||||
const [pilleText, ton] = TAKEOVER_STATUS[r.status] || [r.status, 'grau']
|
||||
let koerper
|
||||
if (r.status === 'genehmigt') {
|
||||
koerper = [h('p', null, 'Gutschrift: ', h('strong', null, fmtEuro(r.amountCents / 100)), ' – wird mit Ihren nächsten Monatsgebühren verrechnet.'),
|
||||
koerper = [h('p', null, 'Gutschrift: ', h('strong', null, fmtEuro(r.amountCents / 100)), ' – wird mit deinen nächsten Monatsgebühren verrechnet.'),
|
||||
r.decisionNote ? h('p', { class: 'kn-hinweis kn-umbruch' }, r.decisionNote) : null]
|
||||
} else if (r.status === 'abgelehnt') {
|
||||
koerper = [h('p', { class: 'kn-hinweis kn-umbruch' }, r.decisionNote)]
|
||||
@@ -98,12 +100,12 @@ function antrag(r, viewAs) {
|
||||
koerper = [
|
||||
h('div', { class: 'kn-tk-frage' }, h('strong', null, 'Unsere Rückfrage:'), h('br'), r.question),
|
||||
h('form', { class: 'kn-tk-antwort', 'data-reply-for': r.id, novalidate: true },
|
||||
h('textarea', { name: 'message', class: K.textarea, maxlength: 4000, placeholder: 'Ihre Antwort…' }),
|
||||
h('textarea', { name: 'message', class: K.textarea, maxlength: 4000, placeholder: 'Deine Antwort…' }),
|
||||
h('input', { type: 'file', name: 'files', accept: 'application/pdf,image/jpeg,image/png', multiple: true, 'aria-label': 'Weitere Nachweise' }),
|
||||
h('p', { class: K.fehler, hidden: true }),
|
||||
h('div', { class: 'kn-aktionen' }, h('button', { type: 'submit', class: K.primaer, disabled: viewAs }, 'Antwort senden')))]
|
||||
} else {
|
||||
koerper = [h('p', { class: 'kn-hinweis' }, 'Wir prüfen Ihre Angaben und antworten Ihnen per E-Mail.')]
|
||||
koerper = [h('p', { class: 'kn-hinweis' }, 'Wir prüfen deine Angaben und antworten dir per E-Mail.')]
|
||||
}
|
||||
return h('div', { class: 'kn-tk-antrag' },
|
||||
h('div', { class: 'kn-tk-kopf' }, h('strong', null, `Antrag ${r.ref} · ${r.services}`), h('span', { class: K.pille[ton] }, pilleText)),
|
||||
@@ -118,6 +120,14 @@ export async function zeigeGutschrift(wurzel, { route, me, nav } = {}) {
|
||||
const nr = (laeufe.get(wurzel) ?? 0) + 1
|
||||
laeufe.set(wurzel, nr)
|
||||
const aktuell = () => laeufe.get(wurzel) === nr
|
||||
// Die Bedingungen sind eine eigene Ansicht (#gutschrift?bedingungen=1): fünf Sätze, kein Abruf, kein Formular
|
||||
if (new URLSearchParams(String(route?.query ?? '')).get('bedingungen') === '1') {
|
||||
meldeTitel(wurzel, TITEL_BEDINGUNGEN)
|
||||
leeren(wurzel).append(h('div', { class: 'kn-panel kn-gutschrift-bedingungen', 'data-karte': 'gutschrift-bedingungen' },
|
||||
h('ol', { class: 'kn-tk-bedingungen' }, TAKEOVER_CONDITIONS.map((c) => h('li', null, c))),
|
||||
h('p', { class: 'kn-links' }, h('a', { ...zuAnsicht('gutschrift'), href: '#gutschrift' }, 'Zurück zum Antrag'))))
|
||||
return
|
||||
}
|
||||
leeren(wurzel).append(h('p', { class: K.leer }, 'Wird geladen …'))
|
||||
await lade('')
|
||||
|
||||
@@ -142,15 +152,18 @@ export async function zeigeGutschrift(wurzel, { route, me, nav } = {}) {
|
||||
const grenzen = daten.limits || GRENZEN_VORGABE
|
||||
if (!aktiv && !anfragen.length) {
|
||||
leeren(wurzel).append(h('div', { 'data-karte': 'gutschrift' }, h('div', { class: 'kn-panel' },
|
||||
h('p', { class: 'kn-hinweis' }, 'Die Übernahmegutschrift können Sie beantragen, sobald Ihr Abo läuft. ', h('a', zuAnsicht('vertraege'), 'Zum Abo')))))
|
||||
h('p', { class: 'kn-hinweis' }, 'Die Übernahmegutschrift kannst du beantragen, sobald dein Abo läuft. ', h('a', zuAnsicht('vertraege'), 'Zum Abo')))))
|
||||
return
|
||||
}
|
||||
const formular = aktiv ? antragsFormular(grenzen, email) : null
|
||||
const start = aktiv ? h('button', { type: 'button', class: K.knopf, id: 'tk-start', disabled: viewAs }, 'Gutschrift beantragen') : null
|
||||
// Ohne Wortlaut vom Server (der Satz über dem Knopf) gibt es kein Formular und keinen Knopf
|
||||
const wortlaut = hatWortlaut(abo, 'gutschrift')
|
||||
const formular = aktiv && wortlaut ? antragsFormular(grenzen, email, abo.bestelltexte) : null
|
||||
const start = aktiv && wortlaut ? h('button', { type: 'button', class: K.knopf, id: 'tk-start', disabled: viewAs }, 'Gutschrift beantragen') : null
|
||||
ersetze(wurzel, h('div', { 'data-karte': 'gutschrift' }, h('div', { class: 'kn-panel' },
|
||||
h('p', { class: 'kn-hinweis kn-tk-einleitung' }, 'Sie haben Domain, Hosting oder E-Mail bei Ihrem bisherigen Anbieter schon im Voraus bezahlt? Den Anteil, den Sie ab Beginn Ihres Abos doppelt zahlen, schreiben wir Ihnen gut und verrechnen ihn mit Ihren nächsten Monatsgebühren. Schicken Sie uns dazu einfach die Rechnung.'),
|
||||
h('p', { class: 'kn-hinweis kn-tk-einleitung' }, 'Hast du Domain, Hosting oder E-Mail bei deinem bisherigen Anbieter schon im Voraus bezahlt? Den Anteil, den du ab Beginn deines Abos doppelt zahlst, schreiben wir dir gut und verrechnen ihn mit deinen nächsten Monatsgebühren. Schick uns dazu einfach die Rechnung.'),
|
||||
notice ? h('div', { class: 'kn-meldung kn-meldung-ok', role: 'status' }, notice) : null,
|
||||
aktiv ? h('div', { class: 'kn-aktionen' }, start, viewAs ? h('span', { class: K.meldung }, TEXT_ANSICHTSMODUS) : null) : null,
|
||||
aktiv && wortlaut ? h('div', { class: 'kn-aktionen' }, start, viewAs ? h('span', { class: K.meldung }, TEXT_ANSICHTSMODUS) : null) : null,
|
||||
aktiv && !wortlaut ? h('p', { class: 'kn-hinweis' }, TEXT_NICHT_VERFUEGBAR) : null,
|
||||
formular,
|
||||
anfragen.length ? h('div', { class: 'kn-tk-liste' }, anfragen.map((r) => antrag(r, viewAs))) : null)))
|
||||
|
||||
@@ -161,7 +174,7 @@ export async function zeigeGutschrift(wurzel, { route, me, nav } = {}) {
|
||||
formular.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' })
|
||||
})
|
||||
formular?.querySelector('#tk-cancel')?.addEventListener('click', () => { formular.hidden = true; if (start) start.hidden = false })
|
||||
formular?.addEventListener('submit', (ev) => { ev.preventDefault(); sendeAntrag(formular, grenzen, neuLaden) })
|
||||
formular?.addEventListener('submit', (ev) => { ev.preventDefault(); sendeAntrag(formular, grenzen, neuLaden, abo.termsVersion) })
|
||||
wurzel.querySelectorAll('form[data-reply-for]').forEach((f) => f.addEventListener('submit', (ev) => { ev.preventDefault(); sendeAntwort(f, grenzen, neuLaden, viewAs) }))
|
||||
}
|
||||
}
|
||||
@@ -172,7 +185,7 @@ function zeigeFehler(el, text) {
|
||||
el.hidden = false
|
||||
}
|
||||
|
||||
async function sendeAntrag(form, grenzen, neuLaden) {
|
||||
async function sendeAntrag(form, grenzen, neuLaden, termsVersion) {
|
||||
const fehler = form.querySelector('#tk-error')
|
||||
const knopf = form.querySelector('#tk-submit')
|
||||
fehler.hidden = true
|
||||
@@ -186,18 +199,19 @@ async function sendeAntrag(form, grenzen, neuLaden) {
|
||||
paidUntil: wert('#tk-paid-until'),
|
||||
message: wert('#tk-message'),
|
||||
email: wert('#tk-email'),
|
||||
consent: form.querySelector('#tk-consent').checked,
|
||||
// Der Klick auf den Knopf ist die Zustimmung zu Satz und Bedingungen; die Kundenart gibt es hier nicht, der Server kennt sie
|
||||
consent: true,
|
||||
termsVersion,
|
||||
}
|
||||
try {
|
||||
if (!payload.services.length) throw new Error('Bitte wählen Sie, welche Leistung Sie bisher woanders bezahlt haben.')
|
||||
if (!payload.provider) throw new Error('Bitte nennen Sie Ihren bisherigen Anbieter.')
|
||||
if (!payload.invoiceAmount || !payload.billingMonths || !payload.paidUntil) throw new Error('Bitte geben Sie Rechnungsbetrag, Zeitraum und „Bezahlt bis“ an.')
|
||||
if (!payload.consent) throw new Error('Bitte bestätigen Sie Ihre Angaben und die Bedingungen.')
|
||||
if (!payload.services.length) throw new Error('Bitte wähle, welche Leistung du bisher woanders bezahlt hast.')
|
||||
if (!payload.provider) throw new Error('Bitte nenne deinen bisherigen Anbieter.')
|
||||
if (!payload.invoiceAmount || !payload.billingMonths || !payload.paidUntil) throw new Error('Bitte gib Rechnungsbetrag, Zeitraum und „Bezahlt bis“ an.')
|
||||
knopf.disabled = true
|
||||
knopf.textContent = 'Wird gesendet…'
|
||||
payload.files = await sammleDateien(form.querySelector('#tk-files').files, grenzen, { erforderlich: true })
|
||||
await api('/api/abo/takeover-credits', { method: 'POST', body: payload })
|
||||
await neuLaden(`Vielen Dank! Ihr Antrag ist eingegangen. Die Eingangsbestätigung haben wir an ${payload.email} geschickt; unsere Antwort kommt ebenfalls per E-Mail.`)
|
||||
await neuLaden(`Vielen Dank! Dein Antrag ist eingegangen. Die Eingangsbestätigung haben wir an ${payload.email} geschickt; unsere Antwort kommt ebenfalls per E-Mail.`)
|
||||
} catch (f) {
|
||||
zeigeFehler(fehler, f.message)
|
||||
knopf.disabled = false
|
||||
@@ -214,9 +228,9 @@ async function sendeAntwort(form, grenzen, neuLaden, viewAs) {
|
||||
try {
|
||||
const message = form.querySelector('textarea').value.trim()
|
||||
const files = await sammleDateien(form.querySelector('input[type="file"]').files, grenzen, { erforderlich: false })
|
||||
if (!message && !files.length) throw new Error('Bitte schreiben Sie eine Antwort oder laden Sie einen Nachweis hoch.')
|
||||
if (!message && !files.length) throw new Error('Bitte schreib eine Antwort oder lade einen Nachweis hoch.')
|
||||
await api(`/api/abo/takeover-credits/${encodeURIComponent(form.getAttribute('data-reply-for'))}/reply`, { method: 'POST', body: { message, files } })
|
||||
await neuLaden('Danke für Ihre Antwort! Wir prüfen Ihren Antrag weiter und melden uns per E-Mail.')
|
||||
await neuLaden('Danke für deine Antwort! Wir prüfen deinen Antrag weiter und melden uns per E-Mail.')
|
||||
} catch (f) {
|
||||
zeigeFehler(fehler, f.message)
|
||||
knopf.disabled = false
|
||||
|
||||
@@ -52,10 +52,13 @@ export function linkTeile(text, urls) {
|
||||
|
||||
const ausserhalb = (href) => !href.startsWith('#')
|
||||
|
||||
/** <p id> mit den Links der Vorlage; '/…' und http(s) öffnen in neuem Tab (target _blank, rel noopener), '#…' bleibt im Fenster. klasse: Vorgabe 'kn-linkzeile'. */
|
||||
export function baueLinkzeile({ text, urls } = {}, { id, klasse = 'kn-linkzeile' } = {}) {
|
||||
/**
|
||||
* <p id> mit den Links der Vorlage; '/…' und http(s) öffnen in neuem Tab (target _blank, rel noopener), '#…' bleibt im Fenster.
|
||||
* klasse: Vorgabe 'kn-linkzeile'. neuerTab: auch '#…' öffnet in neuem Tab (Bedingungen der Gutschrift neben dem Formular).
|
||||
*/
|
||||
export function baueLinkzeile({ text, urls } = {}, { id, klasse = 'kn-linkzeile', neuerTab = false } = {}) {
|
||||
const kinder = linkTeile(text, urls).map((t) =>
|
||||
t.href ? h('a', { href: t.href, ...(ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text)
|
||||
t.href ? h('a', { href: t.href, ...(neuerTab || ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text)
|
||||
return h('p', { id, class: klasse }, kinder)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,61 +3,187 @@ import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { zeigeGutschrift, sammleDateien, IO } from '../public/neu/ansicht-gutschrift.js'
|
||||
import { sichtbarerText, ladeAlteApp } from './support/knWortlautOrakel.js'
|
||||
import { klartext } from '../server/services/bestelltexte.js'
|
||||
import { mit, me, abo, aboAntwort, anfrageGutschrift, neueWurzel, LIMITS } from './support/knDaten.js'
|
||||
import { NEU, meMit, aboNeu } from './support/knBestellDaten.js'
|
||||
|
||||
const alt = ladeAlteApp(); const ABO = aboAntwort({ subscription: abo() })
|
||||
// Gutschrift-Antrag im schlanken Muster (Spec 5.6, Fassung 6): Satz mit Links über dem Knopf, keine Klappe, kein Häkchen;
|
||||
// die Bedingungen sind eine eigene Ansicht (#gutschrift?bedingungen=1), der Rechtstext bleibt unverändert
|
||||
const ABO = aboNeu({ subscription: abo() })
|
||||
const ANTRAEGE = { requests: [anfrageGutschrift({ id: 't1', status: 'rueckfrage', question: 'Bitte <b>Kündigungsbestätigung</b> hochladen' }), anfrageGutschrift({ id: 't2', status: 'genehmigt', amountCents: 4000, decisionNote: 'Passt.' }), anfrageGutschrift({ id: 't3', status: 'abgelehnt', decisionNote: 'Nicht erstattbar.' })], conditionsVersion: '2026-09-30', limits: LIMITS }
|
||||
const SRV = { 'GET /api/abo': ABO, 'GET /api/abo/takeover-credits': ANTRAEGE }
|
||||
const zeigeNeu = async (fx, m = me()) => { const w = neueWurzel(fx); await zeigeGutschrift(w, { route: { bereich: 'vertraege', etage: 'gutschrift', query: '' }, me: m, nav: 'push' }); await fx.ruhe(); return w }
|
||||
for (const [name, abo_, antwort] of [['mit Anträgen', ABO, ANTRAEGE], ['ohne Abo', aboAntwort(), { requests: [], limits: LIMITS }]]) {
|
||||
test(`Gutschrift (${name}): Karte wie in app.js`, mit({ 'GET /api/abo': abo_, 'GET /api/abo/takeover-credits': antwort }, async (fx) => {
|
||||
const w = await zeigeNeu(fx)
|
||||
assert.equal(sichtbarerText(fx.finde(w, '[data-karte="gutschrift"]')), sichtbarerText(await alt.gutschrift(abo_, antwort)))
|
||||
}))
|
||||
const zeigeNeu = async (fx, m = meMit('unternehmen'), query = '') => {
|
||||
const w = neueWurzel(fx)
|
||||
await zeigeGutschrift(w, { route: { bereich: 'vertraege', etage: 'gutschrift', query }, me: m, nav: 'push' })
|
||||
await fx.ruhe()
|
||||
return w
|
||||
}
|
||||
async function fuelleAntrag(fx, w) {
|
||||
await fx.klick(fx.finde(w, 'input[value="hosting"]'))
|
||||
await fx.setze(fx.finde(w, '#tk-provider'), 'IONOS')
|
||||
await fx.setze(fx.finde(w, '#tk-amount'), '119.88')
|
||||
await fx.setze(fx.finde(w, '#tk-months'), '12')
|
||||
await fx.setze(fx.finde(w, '#tk-paid-until'), '2027-03-31')
|
||||
await fx.setze(fx.finde(w, '#tk-message'), 'Der Vertrag dort läuft noch bis Ende März.')
|
||||
fx.finde(w, '#tk-files').files = [{ name: 'r.pdf', size: 10 }]
|
||||
}
|
||||
|
||||
test('Formular: Satz vor dem Knopf, keine Klappe, kein Häkchen, Links wie im Wortlaut', mit(SRV, async (fx) => {
|
||||
const w = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(w, '#tk-start'))
|
||||
assert.ok(fx.finde(w, '#tk-consent') === null, 'kein Häkchen für die Zustimmung')
|
||||
assert.deepEqual([fx.alle(w, 'details').length, fx.finde(w, '#tk-submit').textContent], [0, 'Antrag absenden'])
|
||||
const ids = fx.alle(w, '[id]').map((e) => e.id)
|
||||
assert.ok(ids.indexOf('tk-satz') > -1 && ids.indexOf('tk-satz') < ids.indexOf('tk-submit'))
|
||||
assert.equal(fx.text(fx.finde(w, '#tk-satz')), klartext(ABO.bestelltexte.gutschrift.satz.text))
|
||||
const [bed, ds] = fx.alle(w, '#tk-satz a')
|
||||
assert.deepEqual([bed.textContent, bed.getAttribute('href'), bed.getAttribute('target'), ds.getAttribute('href'), ds.getAttribute('target')], ['Bedingungen der Übernahmegutschrift', '#gutschrift?bedingungen=1', '_blank', '/datenschutz.html#uebernahmegutschrift', '_blank'])
|
||||
assert.equal(fx.alle(w, (e) => e.tagName === 'INPUT' && e.getAttribute('type') === 'checkbox' && e.getAttribute('name') !== 'services').length, 0, 'nur die Leistungen sind Häkchen')
|
||||
}))
|
||||
|
||||
test('Absenden sendet consent und die Fassung, nie eine Kundenart', mit({ ...SRV, 'POST /api/abo/takeover-credits': { request: ANTRAEGE.requests[0] } }, async (fx) => {
|
||||
IO.lies = async () => 'UERGLTEuNA=='
|
||||
const w = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(w, '#tk-start'))
|
||||
await fuelleAntrag(fx, w)
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
await fx.ruhe()
|
||||
const b = fx.fetchLog.find((a) => a.method === 'POST' && a.url === '/api/abo/takeover-credits').body
|
||||
assert.deepEqual([b.consent, b.termsVersion, 'kundenart' in b, 'unternehmer' in b], [true, NEU, false, false])
|
||||
assert.deepEqual({ ...b, files: undefined }, { services: ['hosting'], provider: 'IONOS', domain: '', invoiceAmount: '119.88', billingMonths: 12, paidUntil: '2027-03-31', message: 'Der Vertrag dort läuft noch bis Ende März.', email: 'anna@example.com', consent: true, termsVersion: NEU, files: undefined })
|
||||
assert.deepEqual(b.files, [{ name: 'r.pdf', data: 'UERGLTEuNA==' }])
|
||||
assert.match(fx.text(w), /Vielen Dank! Dein Antrag ist eingegangen\. Die Eingangsbestätigung haben wir an anna@example\.com geschickt; unsere Antwort kommt ebenfalls per E-Mail\./)
|
||||
}))
|
||||
|
||||
test('Bedingungen-Ansicht: Titel als Ereignis, fünf Sätze aus dem Block, Rückweg, kein Formular', mit(SRV, async (fx) => {
|
||||
const w = neueWurzel(fx)
|
||||
const titel = []
|
||||
w.addEventListener('kn:titel', (e) => titel.push(e.detail.text))
|
||||
await zeigeGutschrift(w, { route: { bereich: 'vertraege', etage: 'gutschrift', query: 'bedingungen=1' }, me: meMit('unternehmen'), nav: 'push' })
|
||||
await fx.ruhe()
|
||||
assert.deepEqual(titel, ['Bedingungen der Übernahmegutschrift'])
|
||||
assert.equal(fx.alle(w, 'h1').length, 0)
|
||||
assert.equal(fx.alle(w, 'ol > li').length, 5)
|
||||
assert.equal(fx.finde(w, 'ol > li').textContent, 'Die Übernahmegutschrift gibt es für Leistungen, die Ihr Abo ersetzt (Domain, Hosting bzw. Betrieb der Website, E-Mail-Postfächer) und die Sie bei Ihrem bisherigen Anbieter bereits im Voraus bezahlt haben.')
|
||||
assert.equal(fx.finde(w, 'a[href="#gutschrift"]').textContent, 'Zurück zum Antrag')
|
||||
assert.ok(fx.finde(w, '#tk-form') === null)
|
||||
assert.equal(fx.fetchLog.length, 0, 'die Bedingungen brauchen keinen Abruf')
|
||||
}))
|
||||
|
||||
test('Andere Abfragen zeigen den Antrag, nicht die Bedingungen', mit(SRV, async (fx) => {
|
||||
for (const query of ['', 'bedingungen=0', 'x=1']) {
|
||||
const w = await zeigeNeu(fx, meMit('unternehmen'), query)
|
||||
assert.ok(fx.finde(w, '#tk-start'), query)
|
||||
assert.equal(fx.alle(w, 'ol > li').length, 0, query)
|
||||
}
|
||||
}))
|
||||
|
||||
test('Bedingungen der Gutschrift: Block gleich dem Pin in legalTexts.test.js (Fassung 2026-09-30)', () => {
|
||||
const q = readFileSync(new URL('../public/neu/ansicht-gutschrift.js', import.meta.url), 'utf8'); const i = q.indexOf('const TAKEOVER_CONDITIONS'); const block = q.slice(i, q.indexOf(']', i))
|
||||
const q = readFileSync(new URL('../public/neu/ansicht-gutschrift.js', import.meta.url), 'utf8')
|
||||
const i = q.indexOf('const TAKEOVER_CONDITIONS')
|
||||
const block = q.slice(i, q.indexOf(']', i))
|
||||
assert.equal(createHash('sha256').update(block).digest('hex'), '2b7a745cb599c0ab28f20615a745bd86da6faa8d120391ca56beac35a0473e62')
|
||||
})
|
||||
test('sammleDateien: Prüftexte wörtlich', async () => {
|
||||
const g = { maxFiles: 3, maxFileBytes: 8 * 1048576, maxTotalBytes: 10 * 1048576 }; const f = (name, size) => ({ name, size }); const lies = async () => 'QQ=='
|
||||
await assert.rejects(sammleDateien([], g, { erforderlich: true }, lies), { message: 'Bitte laden Sie die Rechnung Ihres bisherigen Anbieters hoch.' })
|
||||
|
||||
test('sammleDateien: Prüftexte in du', async () => {
|
||||
const g = { maxFiles: 3, maxFileBytes: 8 * 1048576, maxTotalBytes: 10 * 1048576 }
|
||||
const f = (name, size) => ({ name, size })
|
||||
const lies = async () => 'QQ=='
|
||||
await assert.rejects(sammleDateien([], g, { erforderlich: true }, lies), { message: 'Bitte lade die Rechnung deines bisherigen Anbieters hoch.' })
|
||||
await assert.rejects(sammleDateien([1, 2, 3, 4].map((n) => f(`${n}.pdf`, 1)), g, { erforderlich: false }, lies), { message: 'Bitte höchstens 3 Dateien auf einmal hochladen.' })
|
||||
await assert.rejects(sammleDateien([f('groß.pdf', 9 * 1048576)], g, { erforderlich: false }, lies), { message: '„groß.pdf“ ist größer als 8 MB.' })
|
||||
await assert.rejects(sammleDateien([f('a.pdf', 6 * 1048576), f('b.pdf', 6 * 1048576)], g, { erforderlich: false }, lies), { message: 'Die Dateien sind zusammen größer als 10 MB.' })
|
||||
assert.deepEqual(await sammleDateien([f('a.pdf', 5)], g, { erforderlich: true }, lies), [{ name: 'a.pdf', data: 'QQ==' }])
|
||||
})
|
||||
test('Antrag: Prüfungen wörtlich, Body mit denselben Schlüsseln wie früher, Meldung nach Erfolg', mit({ ...SRV, 'POST /api/abo/takeover-credits': {} }, async (fx) => {
|
||||
IO.lies = async () => 'QQ=='; const w = await zeigeNeu(fx); await fx.klick(fx.finde(w, (e) => e.textContent === 'Gutschrift beantragen'))
|
||||
await fx.klick(fx.finde(w, '#tk-submit')); assert.match(fx.text(w), /Bitte wählen Sie, welche Leistung Sie bisher woanders bezahlt haben\./)
|
||||
await fx.klick(fx.finde(w, 'input[value="domain"]')); await fx.setze(fx.finde(w, '#tk-provider'), 'IONOS'); await fx.setze(fx.finde(w, '#tk-amount'), '119.88'); await fx.setze(fx.finde(w, '#tk-months'), '12'); await fx.setze(fx.finde(w, '#tk-paid-until'), '2027-03-31')
|
||||
await fx.klick(fx.finde(w, '#tk-submit')); assert.match(fx.text(w), /Bitte bestätigen Sie Ihre Angaben und die Bedingungen\./)
|
||||
fx.finde(w, '#tk-files').files = [{ name: 'r.pdf', size: 10 }]; await fx.klick(fx.finde(w, '#tk-consent')); await fx.klick(fx.finde(w, '#tk-submit')); await fx.ruhe()
|
||||
assert.deepEqual(fx.fetchLog.find((a) => a.method === 'POST').body, { services: ['domain'], provider: 'IONOS', domain: '', invoiceAmount: '119.88', billingMonths: 12, paidUntil: '2027-03-31', message: '', email: 'anna@example.com', consent: true, files: [{ name: 'r.pdf', data: 'QQ==' }] })
|
||||
assert.match(fx.text(w), /Vielen Dank! Ihr Antrag ist eingegangen\. Die Eingangsbestätigung haben wir an anna@example\.com geschickt; unsere Antwort kommt ebenfalls per E-Mail\./)
|
||||
|
||||
test('Antrag: Prüfungen in du, Meldung nach Erfolg', mit({ ...SRV, 'POST /api/abo/takeover-credits': {} }, async (fx) => {
|
||||
IO.lies = async () => 'QQ=='
|
||||
const w = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(w, (e) => e.textContent === 'Gutschrift beantragen'))
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
assert.match(fx.text(w), /Bitte wähle, welche Leistung du bisher woanders bezahlt hast\./)
|
||||
await fx.klick(fx.finde(w, 'input[value="domain"]'))
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
assert.match(fx.text(w), /Bitte nenne deinen bisherigen Anbieter\./)
|
||||
await fx.setze(fx.finde(w, '#tk-provider'), 'IONOS')
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
assert.match(fx.text(w), /Bitte gib Rechnungsbetrag, Zeitraum und „Bezahlt bis“ an\./)
|
||||
await fx.setze(fx.finde(w, '#tk-amount'), '119.88')
|
||||
await fx.setze(fx.finde(w, '#tk-months'), '12')
|
||||
await fx.setze(fx.finde(w, '#tk-paid-until'), '2027-03-31')
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
await fx.ruhe()
|
||||
assert.match(fx.text(w), /Bitte lade die Rechnung deines bisherigen Anbieters hoch\./)
|
||||
assert.ok(!fx.fetchLog.some((a) => a.method === 'POST'))
|
||||
fx.finde(w, '#tk-files').files = [{ name: 'r.pdf', size: 10 }]
|
||||
await fx.klick(fx.finde(w, '#tk-submit'))
|
||||
await fx.ruhe()
|
||||
assert.match(fx.text(w), /Vielen Dank! Dein Antrag ist eingegangen\./)
|
||||
}))
|
||||
|
||||
test('Rückfrage beantworten; Kundentext bleibt Text; Ansichtsmodus sperrt alles', mit({ ...SRV, 'POST /api/abo/takeover-credits/t1/reply': {} }, async (fx) => {
|
||||
const w = await zeigeNeu(fx); assert.equal(fx.finde(w, 'b'), null)
|
||||
await fx.setze(fx.finde(w, 'form[data-reply-for="t1"] textarea'), 'Anbei.'); await fx.klick(fx.finde(w, 'form[data-reply-for="t1"] button[type="submit"]')); await fx.ruhe()
|
||||
const w = await zeigeNeu(fx)
|
||||
assert.ok(fx.finde(w, 'b') === null)
|
||||
await fx.setze(fx.finde(w, 'form[data-reply-for="t1"] textarea'), 'Anbei.')
|
||||
await fx.klick(fx.finde(w, 'form[data-reply-for="t1"] button[type="submit"]'))
|
||||
await fx.ruhe()
|
||||
assert.deepEqual(fx.fetchLog.find((a) => a.url.endsWith('/reply')).body, { message: 'Anbei.', files: [] })
|
||||
const v = await zeigeNeu(fx, me({ viewAs: true })); assert.match(fx.text(v), /Im Ansichtsmodus kann kein Antrag gestellt werden\./)
|
||||
assert.match(fx.text(w), /Danke für deine Antwort! Wir prüfen deinen Antrag weiter und melden uns per E-Mail\./)
|
||||
const v = await zeigeNeu(fx, meMit('unternehmen', { viewAs: true }))
|
||||
assert.match(fx.text(v), /Im Ansichtsmodus kann kein Antrag gestellt werden\./)
|
||||
assert.ok(fx.alle(v, 'button').filter((b) => ['Gutschrift beantragen', 'Antwort senden'].includes(b.textContent)).every((b) => b.hasAttribute('disabled')))
|
||||
}))
|
||||
|
||||
test('Gutschrift ohne Abo: Satz und Link zum Abo über die Brücke', mit({ 'GET /api/abo': aboAntwort(), 'GET /api/abo/takeover-credits': { requests: [], limits: LIMITS } }, async (fx) => {
|
||||
const w = await zeigeNeu(fx); assert.match(fx.text(w), /Die Übernahmegutschrift können Sie beantragen, sobald Ihr Abo läuft\./)
|
||||
const a = fx.finde(w, 'a[href="#vertraege"]'); assert.deepEqual([a.textContent, a.getAttribute('data-view')], ['Zum Abo', 'vertraege'])
|
||||
}))
|
||||
test('Gutschrift: Antwort ohne Text und Nachweis wird abgewiesen, Abbrechen schließt das Formular, Ausfälle haben eigene Texte', mit({ ...SRV, 'POST /api/abo/takeover-credits/t1/reply': {} }, async (fx) => {
|
||||
const w = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(w, 'form[data-reply-for="t1"] button[type="submit"]')); await fx.ruhe()
|
||||
assert.match(fx.text(w), /Bitte schreiben Sie eine Antwort oder laden Sie einen Nachweis hoch\./); assert.ok(!fx.fetchLog.some((a) => a.url.endsWith('/reply')))
|
||||
assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), true); await fx.klick(fx.finde(w, '#tk-start')); assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), false)
|
||||
await fx.klick(fx.finde(w, '#tk-cancel')); assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), true)
|
||||
assert.match(fx.text(w), /Antrag .* · Domain/); assert.match(fx.text(w), /Nicht genehmigt/); assert.match(fx.text(w), /Gutschrift: 40,00 € – wird mit Ihren nächsten Monatsgebühren verrechnet\./)
|
||||
assert.match(fx.text(w), /Die Übernahmegutschrift kannst du beantragen, sobald dein Abo läuft\./)
|
||||
const a = fx.finde(w, 'a[href="#vertraege"]')
|
||||
assert.deepEqual([a.textContent, a.getAttribute('data-view')], ['Zum Abo', 'vertraege'])
|
||||
}))
|
||||
|
||||
test('Gutschrift: Antwort ohne Text und Nachweis wird abgewiesen, Abbrechen schließt das Formular', mit({ ...SRV, 'POST /api/abo/takeover-credits/t1/reply': {} }, async (fx) => {
|
||||
const w = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(w, 'form[data-reply-for="t1"] button[type="submit"]'))
|
||||
await fx.ruhe()
|
||||
assert.match(fx.text(w), /Bitte schreib eine Antwort oder lade einen Nachweis hoch\./)
|
||||
assert.ok(!fx.fetchLog.some((a) => a.url.endsWith('/reply')))
|
||||
assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), true)
|
||||
await fx.klick(fx.finde(w, '#tk-start'))
|
||||
assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), false)
|
||||
await fx.klick(fx.finde(w, '#tk-cancel'))
|
||||
assert.equal(fx.finde(w, '#tk-form').hasAttribute('hidden'), true)
|
||||
assert.match(fx.text(w), /Antrag .* · Domain/)
|
||||
assert.match(fx.text(w), /Nicht genehmigt/)
|
||||
assert.match(fx.text(w), /Gutschrift: 40,00\s€ – wird mit deinen nächsten Monatsgebühren verrechnet\./)
|
||||
}))
|
||||
|
||||
test('Gutschrift: Ausfall der Anträge und des Abos', mit({ 'GET /api/abo': ABO, 'GET /api/abo/takeover-credits': () => ({ status: 500, body: { error: 'Server kaputt' } }) }, async (fx) => {
|
||||
assert.match(fx.text(await zeigeNeu(fx)), /Übernahmegutschrift konnte nicht geladen werden: Server kaputt/)
|
||||
fx.tabelle['GET /api/abo'] = () => ({ status: 500, body: { error: 'x' } })
|
||||
assert.match(fx.text(await zeigeNeu(fx)), /Deine Verträge konnten gerade nicht geladen werden\./)
|
||||
}))
|
||||
|
||||
test('Ohne Wortlaut der Gutschrift: nur ein Satz, kein Formular, kein Knopf; unsichere Adressen werden Text', mit({ 'GET /api/abo': ABO, 'GET /api/abo/takeover-credits': ANTRAEGE }, async (fx) => {
|
||||
const ohne = aboNeu({ subscription: abo() })
|
||||
delete ohne.bestelltexte.gutschrift
|
||||
fx.tabelle['GET /api/abo'] = ohne
|
||||
const w = await zeigeNeu(fx)
|
||||
assert.ok(fx.finde(w, '#tk-start') === null)
|
||||
assert.ok(fx.finde(w, '#tk-form') === null)
|
||||
assert.match(fx.text(w), /Die Online-Bestellung wird gerade eingerichtet – schreib uns gern im Chat\./)
|
||||
const boese = aboNeu({ subscription: abo() })
|
||||
boese.bestelltexte.gutschrift.satz.urls = ['javascript:alert(1)', '//evil.example']
|
||||
fx.tabelle['GET /api/abo'] = boese
|
||||
const v = await zeigeNeu(fx)
|
||||
await fx.klick(fx.finde(v, '#tk-start'))
|
||||
assert.equal(fx.alle(v, '#tk-satz a[href]').length, 0)
|
||||
}))
|
||||
|
||||
test('Quelltext: der Block TAKEOVER_CONDITIONS ist die einzige Sie-Stelle, keine Klappe, kein Häkchen', () => {
|
||||
const q = readFileSync(new URL('../public/neu/ansicht-gutschrift.js', import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
const i = q.indexOf('const TAKEOVER_CONDITIONS')
|
||||
const ohneBlock = q.slice(0, i) + q.slice(q.indexOf(']', i))
|
||||
assert.doesNotMatch(ohneBlock, /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/)
|
||||
assert.doesNotMatch(ohneBlock, /details|dialog|innerHTML|tk-consent|tk-conditions/)
|
||||
assert.doesNotMatch(ohneBlock, /body:\s*\{[^}]*kundenart/s)
|
||||
})
|
||||
@@ -7,7 +7,9 @@ import { fileURLToPath } from 'node:url'
|
||||
const WURZEL = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const NEU = path.join(WURZEL, 'public', 'neu')
|
||||
const MODULE = ['ansicht-hilfen', 'rechnung-offen', 'install-hinweis', 'website-details', 'ansicht-website', 'ansicht-chat', 'ansicht-vertraege', 'vertraege-rechnungen', 'vertraege-abo', 'ansicht-stunden', 'ansicht-speicher', 'ansicht-bestellen', 'ansicht-wechsel', 'ansicht-gutschrift', 'ansicht-passwort', 'bestell-bausteine']
|
||||
const WORTLAUT = ['ansicht-speicher', 'ansicht-bestellen', 'ansicht-wechsel', 'ansicht-gutschrift']
|
||||
// Seit P7 sprechen auch die vier Bestellseiten „du“ (Fassung 6, C14); einzige Ausnahme ist der Rechtstext TAKEOVER_CONDITIONS (hash-gepinnt)
|
||||
const WORTLAUT = []
|
||||
const BESTELLSEITEN = ['ansicht-bestellen', 'ansicht-wechsel', 'ansicht-speicher', 'ansicht-gutschrift', 'bestell-bausteine']
|
||||
const vorhanden = MODULE.filter((n) => existsSync(path.join(NEU, `${n}.js`)))
|
||||
|
||||
const ohneKommentare = (q) => q.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:'"`\\])\/\/[^\n]*/g, '$1')
|
||||
@@ -32,8 +34,24 @@ test('P2-Module: kein HTML-Einsetzen, nur relative Importe, keine Admin-Verweise
|
||||
}
|
||||
})
|
||||
|
||||
test('P2-Module: Du-Form, nur die vier Wortlaut-Dateien behalten die Sie-Form (C14)', () => {
|
||||
for (const n of vorhanden.filter((x) => !WORTLAUT.includes(x))) assert.doesNotMatch(lies(n), /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/, n)
|
||||
/** Quelltext ohne den Rechtstext TAKEOVER_CONDITIONS (er bleibt Zeichen für Zeichen in der Sie-Form). */
|
||||
const ohneRechtstext = (q) => {
|
||||
const i = q.indexOf('const TAKEOVER_CONDITIONS')
|
||||
return i < 0 ? q : q.slice(0, i) + q.slice(q.indexOf(']', i))
|
||||
}
|
||||
|
||||
test('P2-Module: Du-Form in allen Modulen, einzige Ausnahme der Block TAKEOVER_CONDITIONS (C14, P7)', () => {
|
||||
for (const n of vorhanden.filter((x) => !WORTLAUT.includes(x))) assert.doesNotMatch(ohneRechtstext(lies(n)), /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/, n)
|
||||
assert.match(lies('ansicht-gutschrift'), /Ihr Abo ersetzt/, 'der Rechtstext bleibt unverändert in der Sie-Form')
|
||||
})
|
||||
|
||||
test('Bestellseiten: kein kundenart im Body, keine eigene h1 (Titel meldet die Ansicht)', () => {
|
||||
for (const n of BESTELLSEITEN) {
|
||||
assert.ok(existsSync(path.join(NEU, `${n}.js`)), n)
|
||||
const q = lies(n)
|
||||
assert.doesNotMatch(q, /body:\s*\{[^}]*kundenart/s, n)
|
||||
assert.doesNotMatch(q, /h\(\s*['"]h1['"]/, n)
|
||||
}
|
||||
})
|
||||
|
||||
test('public/neu/: kein Lila in irgendeiner Textdatei (die Prüfung lila liest jedes geladene Stylesheet samt ungenutzter Regeln)', () => {
|
||||
|
||||
@@ -43,6 +43,9 @@ test('baueLinkzeile, baueKundenartZeile, baueBlock', () =>
|
||||
const dl = baueBlock([{ id: 'r1', label: 'Preis', text: '<b>50</b>' }], { id: 'blk' })
|
||||
assert.deepEqual([fx.alle(dl, 'dt').length, fx.finde(dl, 'dd').textContent, fx.finde(dl, 'b'), fx.finde(dl, '#r1').textContent], [1, '<b>50</b>', null, '<b>50</b>'])
|
||||
assert.deepEqual([dl.tagName, dl.id, dl.className], ['DL', 'blk', 'kn-block'])
|
||||
const neuerTab = baueLinkzeile({ text: 'Siehe [A].', urls: ['#gutschrift?bedingungen=1'] }, { id: 'z', neuerTab: true })
|
||||
assert.deepEqual([fx.finde(neuerTab, 'a').getAttribute('target'), fx.finde(neuerTab, 'a').getAttribute('rel')], ['_blank', 'noopener'])
|
||||
assert.equal(baueLinkzeile({ text: 'Siehe [A].', urls: ['#chat'] }, { id: 'z', klasse: 'x-klasse' }).className, 'x-klasse')
|
||||
// eine unsichere Adresse macht keinen Link
|
||||
assert.equal(fx.alle(baueLinkzeile({ text: 'Siehe [A].', urls: ['javascript:alert(1)'] }, { id: 'y' }), 'a').length, 0)
|
||||
// keine h1 in den Bausteinen
|
||||
|
||||
Reference in new issue
Block a user