Kundenansicht neu P2: E-Mail-Speicher im Wortlaut der Fassung 5
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a23652e07
commit
f9c4fbe9bb
7 files changed
+266
-8
No files matched your search
@@ -2,7 +2,7 @@ import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
fmtEuro, fmtDatum, fmtDatumZeit, fmtTagMonat, fmtStd, fmtMinuten, fmtBytes, legacyRequest, meldeZaehlerNeu, merkeHinweis, nimmHinweis,
|
||||
setzeChatVorgabe, nimmChatVorgabe, fmtUhrzeit, berlinTag, wechselFunktionAus, zuAnsicht, meldeTitel, K, ABO_NICHT_GELADEN, navigiere, ladeAbo, safeUrl,
|
||||
setzeChatVorgabe, nimmChatVorgabe, fmtUhrzeit, berlinTag, ersetze, wechselFunktionAus, zuAnsicht, meldeTitel, K, ABO_NICHT_GELADEN, navigiere, ladeAbo, safeUrl,
|
||||
} from '../public/neu/ansicht-hilfen.js'
|
||||
import { mit, mitDom, abo, aboAntwort, me, projekt, rechnung, nachricht, angebot, ledger, anfrageGutschrift, VORSCHAU, VORSCHAU_SPEICHER, LIMITS, neueWurzel } from './support/knDaten.js'
|
||||
|
||||
@@ -22,6 +22,12 @@ test('Formate: Uhrzeit und Kalendertag in Berliner Zeit', () => {
|
||||
assert.deepEqual(berlinTag('2026-10-15T22:30:00Z'), { j: 2026, m: 10, t: 16 }); assert.equal(berlinTag(''), null)
|
||||
})
|
||||
|
||||
test('ersetze überspringt null, undefined und false, statt „null“ anzuhängen', () => mitDom((fx) => {
|
||||
const el = fx.document.createElement('div'); el.append('alt')
|
||||
ersetze(el, null, 'a', [undefined, fx.document.createElement('b'), false], 'c')
|
||||
assert.equal(el.textContent, 'ac'); assert.equal(el.childNodes.length, 3)
|
||||
}))
|
||||
|
||||
test('Formate: Stunden, Minuten, Bytes', () => {
|
||||
assert.deepEqual([150, 120, 45, 0].map(fmtStd), ['2,5 Std.', '2 Std.', '0,75 Std.', '0 Std.'])
|
||||
assert.deepEqual([0, 45, 120, 150].map(fmtMinuten), ['', '45 min', '2 h', '2 h 30 min'])
|
||||
@@ -60,11 +66,12 @@ test('Bausteinklassen K tragen die Klassen aus basis.css', () => {
|
||||
assert.match(ABO_NICHT_GELADEN, /nicht geladen werden/)
|
||||
})
|
||||
|
||||
test('ladeAbo liefert das Objekt, navigiere wirft ohne Router nicht', mit({ 'GET /api/abo': aboAntwort() }, async () => {
|
||||
test('ladeAbo liefert das Objekt, navigiere wirft ohne Router nicht', mit({ 'GET /api/abo': aboAntwort() }, async (fx) => {
|
||||
const antwort = await ladeAbo()
|
||||
assert.equal(antwort.subscription, null)
|
||||
assert.equal(antwort.termsVersion, '2026-09-30-v5')
|
||||
assert.doesNotThrow(() => navigiere('#chat'))
|
||||
assert.equal(fx.window.location.hash, '#chat') // ohne laufenden Router trägt der Hash die Adresse
|
||||
}))
|
||||
|
||||
test('ladeAbo bei Fehler: null', mit({ 'GET /api/abo': () => ({ status: 500, body: { error: 'kaputt' } }) }, async () => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { zeigeSpeicher } from '../public/neu/ansicht-speicher.js'
|
||||
import { nimmChatVorgabe } from '../public/neu/ansicht-hilfen.js'
|
||||
import { sichtbarerText, ladeAlteApp } from './support/knWortlautOrakel.js'
|
||||
import { mit, me, abo, aboAntwort, neueWurzel, VORSCHAU_SPEICHER } from './support/knDaten.js'
|
||||
|
||||
const alt = ladeAlteApp()
|
||||
const speicher = (o = {}, sub = {}) => aboAntwort({ subscription: abo({ storage: { units: 2, pendingSetup: false, pendingReduction: null }, canChangeStorage: true, ...sub }), ...o })
|
||||
const zeigeNeu = async (fx, daten, m = me()) => { const w = neueWurzel(fx); await zeigeSpeicher(w, { route: { bereich: 'vertraege', reiter: 'email', query: '' }, me: m, nav: 'tab', daten }); await fx.ruhe(); return w }
|
||||
for (const [name, daten] of [['zwei Pakete', speicher()], ['gesperrt', speicher({}, { canChangeStorage: false })], ['wird eingerichtet', speicher({}, { storage: { units: 1, pendingSetup: true, pendingReduction: null } })], ['Reduzierung geplant', speicher({}, { storage: { units: 3, pendingSetup: false, pendingReduction: { units: 1, effectiveAt: '2026-11-01T00:00:00.000Z' } } })]]) {
|
||||
test(`E-Mail-Speicher (${name}): Text wie in app.js`, mit({ 'POST /api/abo/storage/preview': VORSCHAU_SPEICHER }, async (fx) => {
|
||||
const w = await zeigeNeu(fx, daten)
|
||||
assert.equal(sichtbarerText(fx.finde(w, '[data-panel="speicher"]')), sichtbarerText(await alt.speicher(daten, VORSCHAU_SPEICHER)))
|
||||
}))
|
||||
}
|
||||
test('Plus: Vorschau, Zustimmung Pflicht, Body mit acceptTerms und termsVersion, Hinweis nach Erfolg', mit({
|
||||
'POST /api/abo/storage/preview': { direction: 'up', units: 3, totalGb: 20, monthlyCents: 300, deltaMonthlyCents: 100, prorationCents: 40, nextChargeAt: '2026-11-16T00:00:00.000Z', effectiveAt: null },
|
||||
'POST /api/abo/storage': { change: { task: true }, subscription: abo() }, 'GET /api/abo': speicher() }, async (fx) => {
|
||||
const w = await zeigeNeu(fx, speicher())
|
||||
await fx.klick(fx.finde(w, '#storage-plus')); await fx.ruhe(); assert.deepEqual(fx.fetchLog.at(-1).body, { units: 3 })
|
||||
await fx.klick(fx.finde(w, '#storage-submit')); assert.match(fx.text(w), /Bitte stimmen Sie den Regeln zum Zusatzspeicher zu\./)
|
||||
await fx.klick(fx.finde(w, '#storage-terms')); 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: '2026-09-30-v5' })
|
||||
assert.match(fx.text(w), /Danke, Ihre Bestellung ist eingegangen\. Der Speicher wird in der Regel innerhalb eines Werktags eingerichtet\./)
|
||||
}))
|
||||
test('Minus ohne Haken; 409 hängt den Hinweis zum Neuladen an; Ansichtsmodus sperrt', 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.deepEqual(fx.fetchLog.find((a) => a.url === '/api/abo/storage').body, { units: 1 }); assert.match(fx.text(w), /Stand geändert\. Bitte laden Sie die Seite neu, falls sich der Stand geändert hat\./)
|
||||
const gesperrt = await zeigeNeu(fx, speicher(), me({ viewAs: true })); assert.ok(['#storage-minus', '#storage-plus'].every((s) => fx.finde(gesperrt, s).hasAttribute('disabled')))
|
||||
assert.match(fx.text(gesperrt), /Im Ansichtsmodus nicht möglich\./)
|
||||
}))
|
||||
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('Speicher: 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(), me({ 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
|
||||
}))
|
||||
@@ -13,6 +13,7 @@ test('projektStatus', () => {
|
||||
assert.deepEqual(s({ previewUrl: '' }), { ton: 'grau', text: 'Noch keine Website' })
|
||||
assert.equal(vorschauBereit(projekt({ status: 'deployed' })), true); assert.equal(vorschauBereit(projekt({ status: 'building' })), false)
|
||||
})
|
||||
test('Website: leere Platzhalter erscheinen nie als Text „null“', mit(HAUPT, async (fx) => { assert.doesNotMatch(fx.text(await zeige(fx)), /null|undefined/) }))
|
||||
test('Website: Ladezustand ist Text und verschwindet', mit(HAUPT, async (fx) => {
|
||||
const w = neueWurzel(fx); const lauf = zeigeWebsite(w, { route: { bereich: 'website', query: '' }, me: me(), nav: 'tab' })
|
||||
assert.match(fx.text(w), /Wird geladen …/); await lauf; await fx.ruhe(); assert.doesNotMatch(fx.text(w), /Wird geladen …/)
|
||||
|
||||
Reference in new issue
Block a user