Files
Webklar-Kundenbereich/test/knAnsichtSpeicher.test.js
T
ClaudeandClaude Sonnet 5.5 306c53871d Kundenansicht neu E-Fix M1: Widerrufsstreifen und Pille nach Kauf, Wechsel und Speicher-Plus neu laden
ladeFristen lief nur einmal beim Start; eine neue 14-Tage-Frist nach einem Wechsel oder Speicher-Plus erschien erst nach Neuladen, ein
abgelaufener Streifen blieb in einem langen PWA-Tab stehen. Die Hülle lädt die Fristen jetzt bei kn:zaehler-neu (vertraege, alle) und beim
Zurückkehren in den Tab neu (ladeFristenNeu: ein Fehler lässt den Stand stehen, die jüngste Antwort gilt); das Speicher-Plus meldet
kn:zaehler-neu vertraege nach Erfolg, der Wechsel tat es schon.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 21:40:29 +00:00

155 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { zeigeSpeicher } from '../public/neu/ansicht-speicher.js'
import { nimmChatVorgabe } from '../public/neu/ansicht-hilfen.js'
import { klartext } from '../server/services/bestelltexte.js'
import { mit, me, abo, aboAntwort, neueWurzel, VORSCHAU_SPEICHER } from './support/knDaten.js'
import { NEU, TERMS_URL, meMit, aboNeu } from './support/knBestellDaten.js'
// E-Mail-Speicher im schlanken Muster (Spec 5.6, Fassung 6): Plus mit Block, Linkzeile, Knopf und Kundenart-Zeile ohne Häkchen,
// Minus mit genau einer Warnzeile und dem Knopf „Speichern“
const speicher = (o = {}, sub = {}) => aboNeu({ subscription: abo({ storage: { units: 2, pendingSetup: false, pendingReduction: null }, canChangeStorage: true, ...sub }), ...o })
const PLUS = { direction: 'up', units: 3, totalGb: 20, monthlyCents: 300, deltaMonthlyCents: 100, prorationCents: 40, nextChargeAt: '2026-11-16T00:00:00.000Z', effectiveAt: null }
const IDS = ['storage-block', 'storage-linkzeile', 'storage-warnung', 'storage-error', 'storage-submit', 'storage-kundenart']
const reihe = (fx, w) => fx.alle(fx.finde(w, '[data-panel="speicher"]'), (el) => IDS.includes(el.id)).map((el) => el.id)
const zeigeNeu = async (fx, daten, m = meMit('unternehmen')) => {
const w = neueWurzel(fx)
await zeigeSpeicher(w, { route: { bereich: 'vertraege', reiter: 'email', query: '' }, me: m, nav: 'tab', daten })
await fx.ruhe()
return w
}
test('Plus: Block, Linkzeile, Knopf, Kundenart-Zeile, kein Häkchen, Body ohne Kundenart', mit({ 'POST /api/abo/storage/preview': PLUS, 'POST /api/abo/storage': { change: { task: true }, subscription: abo() }, 'GET /api/abo': speicher() }, async (fx) => {
const events = []; fx.document.dispatchEvent = (e) => events.push(`${e.type}:${e.detail.was}`)
const w = await zeigeNeu(fx, speicher(), meMit('unternehmen'))
await fx.klick(fx.finde(w, '#storage-plus'))
await fx.ruhe()
assert.deepEqual(reihe(fx, w), ['storage-block', 'storage-linkzeile', 'storage-error', 'storage-submit', 'storage-kundenart'])
assert.deepEqual([fx.finde(w, '#storage-terms'), fx.finde(w, '#storage-submit').textContent, fx.finde(w, '#storage-warnung')], [null, 'Zahlungspflichtig bestellen', null])
assert.equal(fx.alle(w, 'input[type="checkbox"], details, dialog').length, 0)
assert.equal(fx.finde(w, '#storage-linkzeile a').getAttribute('href'), `${TERMS_URL}#zusatzspeicher`)
const zeilen = fx.alle(fx.finde(w, '#storage-block'), 'dt').map((e) => fx.text(e))
assert.deepEqual(zeilen, ['Neue Gesamtgröße', 'Preis', 'Zusätzlich jetzt', 'Nächste Abbuchung', 'Einrichtung', 'Laufzeit'])
assert.equal(fx.text(fx.finde(w, '#storage-kundenart')), 'Du bestellst als Betrieb · Stimmt nicht? Schreib uns im Chat')
await fx.klick(fx.finde(w, '#storage-submit'))
await fx.ruhe()
assert.deepEqual(fx.fetchLog.find((a) => a.url === '/api/abo/storage').body, { units: 3, acceptTerms: true, termsVersion: NEU })
assert.match(fx.text(w), /Danke, deine Bestellung ist eingegangen\. Der Speicher wird in der Regel innerhalb eines Werktags eingerichtet\./)
// M1: ein Plus eröffnet für Privatkunden eine neue Widerrufsfrist: die Hülle lädt Streifen und Pille neu (kn:zaehler-neu vertraege)
assert.ok(events.includes('kn:zaehler-neu:vertraege'), `Ereignisse: ${events.join(', ')}`)
}))
test('Minus: genau eine Warnzeile aus dem Wortlaut, Knopf „Speichern“, Body nur mit units', mit({ 'POST /api/abo/storage/preview': VORSCHAU_SPEICHER, 'POST /api/abo/storage': { change: { effectiveAt: '2026-11-01T00:00:00.000Z' }, subscription: abo() } }, async (fx) => {
const w = await zeigeNeu(fx, speicher(), meMit('privat'))
await fx.klick(fx.finde(w, '#storage-minus'))
await fx.ruhe()
assert.equal(fx.alle(w, '#storage-warnung').length, 1)
assert.equal(fx.text(fx.finde(w, '#storage-warnung')), klartext(speicher().bestelltexte.speicher.warnung.text))
assert.equal(fx.finde(w, '#storage-warnung a').getAttribute('href'), `${TERMS_URL}#zusatzspeicher`)
assert.deepEqual([fx.finde(w, '#storage-linkzeile'), fx.finde(w, '#storage-kundenart'), fx.finde(w, '#storage-submit').textContent], [null, null, 'Speichern'])
assert.deepEqual(reihe(fx, w), ['storage-block', 'storage-warnung', 'storage-error', 'storage-submit'])
await fx.klick(fx.finde(w, '#storage-submit'))
await fx.ruhe()
assert.deepEqual(fx.fetchLog.find((a) => a.url === '/api/abo/storage').body, { units: 1 })
assert.match(fx.text(w), /Gespeichert\. Die Verkleinerung wirkt zum 01\.11\.2026\./)
}))
test('Privat und leer: Kundenart-Zeile nennt privat; Ansichtsmodus sperrt Stepper und Knöpfe', mit({ 'POST /api/abo/storage/preview': PLUS }, async (fx) => {
for (const m of [meMit('privat'), meMit('')]) {
const w = await zeigeNeu(fx, speicher(), m)
await fx.klick(fx.finde(w, '#storage-plus'))
await fx.ruhe()
assert.ok(fx.text(fx.finde(w, '#storage-kundenart')).startsWith('Du bestellst privat · '))
}
const v = await zeigeNeu(fx, speicher(), meMit('unternehmen', { viewAs: true }))
assert.ok(['#storage-minus', '#storage-plus'].every((s) => fx.finde(v, s).hasAttribute('disabled')))
assert.match(fx.text(v), /Im Ansichtsmodus nicht möglich\./)
}))
test('Ohne Wortlaut: Plus und Minus zeigen nur den Satz, keinen Knopf', mit({ 'POST /api/abo/storage/preview': PLUS }, async (fx) => {
const w = await zeigeNeu(fx, speicher({ bestelltexte: undefined }), meMit('unternehmen'))
for (const knopf of ['#storage-plus', '#storage-minus', '#storage-minus']) {
await fx.klick(fx.finde(w, knopf))
await fx.ruhe()
if (fx.finde(w, '#storage-block')) assert.fail('kein Block ohne Wortlaut')
if (knopf === '#storage-plus') {
assert.match(fx.text(w), /Die Online-Bestellung wird gerade eingerichtet – schreib uns gern im Chat\./)
assert.equal(fx.finde(w, '#storage-submit'), null)
}
}
}))
test('Unsichere Adressen in Linkzeile und Warnung werden Text', mit({ 'POST /api/abo/storage/preview': PLUS }, async (fx) => {
const boese = speicher()
boese.bestelltexte.speicher.linkzeile.urls = ['javascript:alert(1)', '//evil.example']
const w = await zeigeNeu(fx, boese)
await fx.klick(fx.finde(w, '#storage-plus'))
await fx.ruhe()
assert.equal(fx.alle(w, '#storage-linkzeile a[href]').length, 0)
}))
for (const [name, daten, erwartet] of [
['gesperrt', speicher({}, { canChangeStorage: false }), /Änderungen sind derzeit nicht möglich – bei Fragen schreib uns gern im Chat\./],
['gesperrt (gekündigt)', speicher({}, { canChangeStorage: false, cancelAtPeriodEnd: true }), /Änderungen sind derzeit nicht möglich – Abo gekündigt\./],
['gesperrt (Wechsel)', speicher({}, { canChangeStorage: false, switchPending: { type: 'wechsel' } }), /Änderungen sind derzeit nicht möglich – Wechsel wartet auf die Zahlung\./],
['gesperrt (Zahlung)', speicher({}, { canChangeStorage: false, paymentProblem: true }), /Änderungen sind derzeit nicht möglich – Zahlung offen\./],
['wird eingerichtet', speicher({}, { storage: { units: 1, pendingSetup: true, pendingReduction: null } }), /Dein zusätzlicher Speicher wird eingerichtet\. Das dauert in der Regel bis zum nächsten Werktag\./],
['Reduzierung geplant', speicher({}, { storage: { units: 3, pendingSetup: false, pendingReduction: { units: 1, effectiveAt: '2026-11-01T00:00:00.000Z' } } }), /Geplant: ab 01\.11\. 10 GB\./],
]) {
test(`E-Mail-Speicher (${name}): Texte in du`, mit({ 'POST /api/abo/storage/preview': VORSCHAU_SPEICHER }, async (fx) => {
const w = await zeigeNeu(fx, daten)
assert.match(fx.text(fx.finde(w, '[data-panel="speicher"]')), erwartet)
assert.doesNotMatch(fx.text(w), /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/)
}))
}
test('409 hängt den Hinweis zum Neuladen an', mit({ 'POST /api/abo/storage/preview': VORSCHAU_SPEICHER, 'POST /api/abo/storage': () => ({ status: 409, body: { error: 'Stand geändert.' } }) }, async (fx) => {
const w = await zeigeNeu(fx, speicher())
await fx.klick(fx.finde(w, '#storage-minus'))
await fx.ruhe()
await fx.klick(fx.finde(w, '#storage-submit'))
await fx.ruhe()
assert.match(fx.text(w), /Stand geändert\. Bitte lade die Seite neu, falls sich der Stand geändert hat\./)
}))
test('Postfach anfragen setzt die Chat-Vorgabe und geht zum Chat', mit({}, async (fx) => {
fx.window.history.pushState = (_s, _t, url) => { fx.window.location.hash = url }
const w = await zeigeNeu(fx, speicher())
await fx.klick(fx.finde(w, (e) => e.textContent === 'Postfach anfragen'))
assert.equal(nimmChatVorgabe(), 'Bitte lege ein neues Postfach an: ')
assert.equal(fx.window.location.hash, '#chat')
}))
test('Speicher: ohne Daten wird das Abo geladen, Ausfall und fehlendes Abo sagen es', mit({ 'GET /api/abo': () => ({ status: 500, body: { error: 'x' } }) }, async (fx) => {
const w = neueWurzel(fx)
await zeigeSpeicher(w, { route: { bereich: 'vertraege', query: '' }, me: me(), nav: 'tab' })
await fx.ruhe()
assert.match(fx.text(w), /Deine Verträge konnten gerade nicht geladen werden\./)
const ohne = await zeigeNeu(fx, aboAntwort())
assert.equal(fx.finde(ohne, '[data-panel="speicher"]'), null)
assert.match(fx.text(ohne), /Den E-Mail-Speicher gibt es mit einem laufenden Abo\./)
}))
test('Ansichtsmodus schickt nichts, ein Vorschau-Fehler sperrt den Knopf', mit({ 'POST /api/abo/storage/preview': () => ({ status: 500, body: { error: 'Vorschau kaputt' } }), 'POST /api/abo/storage': {} }, async (fx) => {
const w = await zeigeNeu(fx, speicher())
await fx.klick(fx.finde(w, '#storage-plus'))
await fx.ruhe()
assert.match(fx.text(w), /Vorschau kaputt/)
assert.equal(fx.finde(w, '#storage-submit').hasAttribute('disabled'), true)
const vorher = fx.fetchLog.length
const v = await zeigeNeu(fx, speicher(), meMit('unternehmen', { viewAs: true }))
await fx.klick(fx.finde(v, '#storage-plus'))
await fx.ruhe()
assert.deepEqual(fx.fetchLog.slice(vorher), []) // im Ansichtsmodus geht nichts raus, auch keine Vorschau
}))
test('Quelltext: kein Sie, keine Klappe, kein Häkchen, Body ohne kundenart', () => {
const q = readFileSync(new URL('../public/neu/ansicht-speicher.js', import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
assert.doesNotMatch(q, /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/)
assert.doesNotMatch(q, /details|dialog|innerHTML|storage-terms|checkbox/)
const aufruf = q.slice(q.indexOf('const body = '), q.indexOf("api('/api/abo/storage'"))
assert.ok(aufruf.includes('acceptTerms: true'))
assert.doesNotMatch(aufruf, /kundenart|unternehmer|startChoice/)
})