TP2 Abschlussprüfung: Fragebogen und Bestellseite (veralteter Tab, Abo-Hinweis, Zurück aus Stripe)
- PUT /fragebogen: ein Tab mit veralteter Branche bekommt 409 EINSTELLUNG_VERALTET (die Branche wechselt nur die Vorlage), bestaetigt wird mit dem gespeicherten Stand vereinigt; die ungeprüften Vorlagenpreise können nicht mehr als geprüft gelten - GET /einstellungen liefert in off aboHinweis (aboMeldung, auch bei nicht lesbarem Abo); die Hinweiskarte zeigt ihn immer und hat „Neu laden“ - Bestellseite und Statuskarte laden bei pageshow (persisted) neu, wenn der Browser sie nach der Zahlungsseite aus dem bfcache holt - renderBestellen zeichnet „Ändern“ und „Zum Schritt“ nur mit zumSchritt; nach einem 409 bekommt die Meldung den Fokus - Zusammenfassung nennt „Kleinunternehmer: ja/nein“ Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2fc48df915
commit
5f99f08a39
8 files changed
+314
-39
No files matched your search
+22
-3
@@ -3,7 +3,8 @@
|
||||
// const fx = installFakeDom({ antworten: [{ status: 400, body: { fehler: {...} } }] })
|
||||
// try { ...Client-Code bauen, fx.klick(knopf), fx.fetchLog pruefen... } finally { fx.restore() }
|
||||
//
|
||||
// installFakeDom stellt `document`, `window` (nur location mit href, pathname, search, hash und history.replaceState), `CSS.escape` und `fetch` auf globalThis, restore() stellt den
|
||||
// installFakeDom stellt `document`, `window` (location mit href, pathname, search, hash und reload() - jeder Aufruf steht in fx.reloads -, history.replaceState und
|
||||
// addEventListener/removeEventListener/dispatchEvent fuer Ereignisse des Fensters wie 'pageshow'), `CSS.escape` und `fetch` auf globalThis, restore() stellt den
|
||||
// alten Zustand wieder her. `h` aus public/termine-dom.js liest `document` bei jedem Aufruf: der Client braucht keine Einhaengepunkte.
|
||||
// Wurzeln, die `isConnected` brauchen (neuerSpeicher.nochHier), haengt der Test selbst an `document.body`.
|
||||
//
|
||||
@@ -471,7 +472,7 @@ const GLOBALE = ['document', 'window', 'CSS', 'fetch']
|
||||
|
||||
/**
|
||||
* Fake-DOM und Fake-fetch auf globalThis stellen.
|
||||
* => { document, window, fetchLog, antworten, finde, alle, text, klick, setze, sende, ruhe, restore }
|
||||
* => { document, window, fetchLog, reloads, antworten, finde, alle, text, klick, setze, sende, ruhe, restore }
|
||||
* finde(baum, vergleich) / alle(baum, vergleich): erstes / alle Elemente im Baum (baum selbst eingeschlossen, Dokumentreihenfolge), fuer die
|
||||
* vergleich(el) wahr ist (Funktion) oder die zum Selektor passen (String).
|
||||
* text(baum): Text des Baums (textContent). klick(el), setze(el, wert), ruhe(): siehe Kopf der Datei.
|
||||
@@ -479,11 +480,28 @@ const GLOBALE = ['document', 'window', 'CSS', 'fetch']
|
||||
export function installFakeDom({ antworten = [] } = {}) {
|
||||
const vorher = Object.fromEntries(GLOBALE.map((name) => [name, Object.getOwnPropertyDescriptor(globalThis, name)]))
|
||||
const document = neuesDokument()
|
||||
const location = { href: 'http://localhost/', pathname: '/dashboard.html', search: '', hash: '' }
|
||||
const reloads = []
|
||||
const location = { href: 'http://localhost/', pathname: '/dashboard.html', search: '', hash: '', reload: () => { reloads.push(location.href) } }
|
||||
const fensterEreignisse = new Map()
|
||||
const window = {
|
||||
document,
|
||||
location,
|
||||
history: { replaceState: (_zustand, _titel, url) => { location.hash = String(url ?? '').includes('#') ? `#${String(url).split('#')[1]}` : '' } },
|
||||
addEventListener(typ, fn) {
|
||||
if (!fensterEreignisse.has(typ)) fensterEreignisse.set(typ, [])
|
||||
fensterEreignisse.get(typ).push(fn)
|
||||
},
|
||||
removeEventListener(typ, fn) {
|
||||
const liste = fensterEreignisse.get(typ)
|
||||
if (liste?.includes(fn)) liste.splice(liste.indexOf(fn), 1)
|
||||
},
|
||||
/** Ereignis an die Handler des Fensters (z. B. { type: 'pageshow', persisted: true }); Rueckgaben werden nicht abgewartet. */
|
||||
dispatchEvent(ev) {
|
||||
for (const fn of [...(fensterEreignisse.get(ev.type) ?? [])]) fn(ev)
|
||||
return true
|
||||
},
|
||||
/** Zahl der Handler eines Typs (fuer Tests, die Aufraeumen pruefen). */
|
||||
anzahlHandler: (typ) => (fensterEreignisse.get(typ) ?? []).length,
|
||||
}
|
||||
const fetchLog = []
|
||||
const warteschlange = [...antworten]
|
||||
@@ -503,6 +521,7 @@ export function installFakeDom({ antworten = [] } = {}) {
|
||||
document,
|
||||
window,
|
||||
fetchLog,
|
||||
reloads,
|
||||
antworten: warteschlange,
|
||||
finde: (baum, vergleich) => knoten(baum).find(pruefung(vergleich)) ?? null,
|
||||
alle: (baum, vergleich) => knoten(baum).filter(pruefung(vergleich)),
|
||||
|
||||
@@ -329,6 +329,7 @@ test('abo gibt es nur in off; offen und bestellung nur in off und ordered (R110)
|
||||
setzeZustand(zustand)
|
||||
const { body } = await send('GET', einstellungenUrl(), { cookie })
|
||||
assert.equal(body.abo, null, zustand)
|
||||
assert.equal(body.aboHinweis, null, `${zustand}: den Abo-Hinweis gibt es nur in off`)
|
||||
assert.equal(body.offen, null, zustand)
|
||||
assert.equal(body.bestellung, null, zustand)
|
||||
}
|
||||
@@ -340,6 +341,7 @@ test('abo gibt es nur in off; offen und bestellung nur in off und ordered (R110)
|
||||
assert.notEqual(body.offen, null, zustand)
|
||||
assert.notEqual(body.bestellung, null, zustand)
|
||||
assert.equal(body.abo === null, zustand === 'ordered', `${zustand}: das Abo liest die Route nur in off`)
|
||||
assert.equal(body.aboHinweis, null, `${zustand}: mit laufendem Abo (off) und in ordered kein Hinweis`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -406,6 +408,7 @@ test('off mit bestellbarem Entwurf und laufendem Wartungs-Abo: abo { ok: true },
|
||||
setzeZustand('off')
|
||||
const { body } = await send('GET', einstellungenUrl(), { cookie })
|
||||
assert.deepEqual(body.abo, { ok: true })
|
||||
assert.equal(body.aboHinweis, null, 'laeuft das Abo, gibt es keinen Hinweis')
|
||||
assert.deepEqual(body.offen, [])
|
||||
assert.deepEqual(body.bestellung, JSON.parse(JSON.stringify(await bestellungAnsicht(T))))
|
||||
assert.equal(body.bestellung.consent.knopf, T.consent.knopf, 'der Knopf zeigt genau den Registertext')
|
||||
@@ -454,6 +457,7 @@ test('Abo ohne laufendes Wartungs-Abo (nur Hosting, oder gar keins): abo { ok: f
|
||||
const { body } = await send('GET', einstellungenUrl(), { cookie })
|
||||
assert.equal(body.state, 'off', name)
|
||||
assert.deepEqual(body.abo, { ok: false, grund: 'kein_abo' }, name)
|
||||
assert.equal(body.aboHinweis, text, `${name}: B2, der Hinweis ist derselbe Text aus aboMeldung`)
|
||||
assert.deepEqual(body.offen, [{ schritt: 'bestellen', text }], name)
|
||||
}
|
||||
})
|
||||
@@ -463,12 +467,14 @@ test('Abo mit vorgemerkter Kündigung: grund gekuendigt; past_due: grund nicht_a
|
||||
wartung({ cancelAtPeriodEnd: true })
|
||||
let body = (await send('GET', einstellungenUrl(), { cookie })).body
|
||||
assert.deepEqual(body.abo, { ok: false, grund: 'gekuendigt' })
|
||||
assert.equal(body.aboHinweis, aboMeldung(T, 'gekuendigt'))
|
||||
assert.deepEqual(body.offen, [{ schritt: 'bestellen', text: aboMeldung(T, 'gekuendigt') }])
|
||||
assert.equal(body.offen[0].text, 'Dein Wartungs-Abo ist gekündigt. Für den Terminplaner brauchst du ein laufendes Abo.')
|
||||
|
||||
wartung({ status: 'past_due' })
|
||||
body = (await send('GET', einstellungenUrl(), { cookie })).body
|
||||
assert.deepEqual(body.abo, { ok: false, grund: 'nicht_aktiv' })
|
||||
assert.equal(body.aboHinweis, aboMeldung(T, 'nicht_aktiv'))
|
||||
assert.deepEqual(body.offen, [{ schritt: 'bestellen', text: aboMeldung(T, 'nicht_aktiv') }])
|
||||
})
|
||||
|
||||
@@ -478,6 +484,8 @@ test('Lesefehler beim Abo: GET bleibt 200, abo null und der offene Punkt "nicht
|
||||
const res = await send('GET', einstellungenUrl(), { cookie })
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(res.body.abo, null)
|
||||
assert.equal(res.body.aboHinweis, aboMeldung(T, 'unbekannt'), 'B2: auch ohne lesbares Abo bekommt der Kunde einen Text, nie eine leere Karte')
|
||||
assert.equal(res.body.aboHinweis, 'Das Abo konnte gerade nicht geprüft werden.')
|
||||
assert.deepEqual(res.body.offen, [{ schritt: 'bestellen', text: aboMeldung(T, 'unbekannt') }])
|
||||
assert.equal(res.body.offen[0].text, 'Das Abo konnte gerade nicht geprüft werden.')
|
||||
assert.ok(!JSON.stringify(res.body).includes('geheimer'))
|
||||
@@ -1374,8 +1382,17 @@ const beispielFragebogen = (over = {}) => ({
|
||||
...over,
|
||||
})
|
||||
|
||||
/**
|
||||
* Bestellbarer Stand, aber noch kein Schritt bestätigt und noch keine Branche gewählt (B1): PUT vereinigt bestaetigt mit dem gespeicherten Stand und
|
||||
* lässt einen Wechsel der gespeicherten Branche nicht zu. Die Tests, die einen beliebigen Fragebogen per PUT ablegen, gehen von diesem Stand aus.
|
||||
*/
|
||||
function standOhneBestaetigung({ branche = 'salon' } = {}) {
|
||||
const stand = bestellbarerStand({ now: JETZT })
|
||||
return { ...stand, fragebogen: { ...stand.fragebogen, branche, bestaetigt: [] } }
|
||||
}
|
||||
|
||||
test('PUT fragebogen speichert nur den Fragebogen: version und einstellung der Einstellung bleiben, updatedAt/updatedBy ändern sich, Antwort { fragebogen, offen }', async () => {
|
||||
setzeZustand('off')
|
||||
setzeZustand('off', { stand: standOhneBestaetigung() })
|
||||
const vorher = termineConfigDoc()
|
||||
const res = await putFragebogen(beispielFragebogen())
|
||||
assert.equal(res.status, 200, JSON.stringify(res.body))
|
||||
@@ -1394,7 +1411,7 @@ test('PUT fragebogen speichert nur den Fragebogen: version und einstellung der E
|
||||
})
|
||||
|
||||
test('PUT fragebogen: bereinigt die Eingabe wie normalisiereFragebogen (Reihenfolge, Doppelte, Fremdes, Steuerzeichen) und ersetzt den ganzen Fragebogen', async () => {
|
||||
setzeZustand('off')
|
||||
setzeZustand('off', { stand: standOhneBestaetigung() })
|
||||
const res = await putFragebogen({
|
||||
branche: 'salon',
|
||||
bestaetigt: ['website', 'branche', 'branche', 'unbekannt'],
|
||||
@@ -1459,7 +1476,7 @@ test('PUT fragebogen ohne Einstellung => 409 keine_einstellung, nichts angelegt
|
||||
for (const zustand of ZUSTAENDE) {
|
||||
const erwartet = { keins: '409 keine_einstellung', off: '200', ordered: '409 gesperrt', setup: '409 gesperrt', active: '200', disabled: '409 abgeschaltet' }[zustand]
|
||||
test(`PUT fragebogen: Zustand ${zustand} => ${erwartet}`, async () => {
|
||||
setzeZustand(zustand)
|
||||
setzeZustand(zustand, { stand: standOhneBestaetigung() })
|
||||
const vorher = dokumente()
|
||||
const res = await putFragebogen(beispielFragebogen())
|
||||
if (erwartet === '200') {
|
||||
@@ -1504,7 +1521,8 @@ test('PUT fragebogen: in off nur mit laufendem Wartungs-Abo (409 kein_abo, 503 b
|
||||
})
|
||||
|
||||
test('PUT fragebogen: offen ist die Bestellprüfung des gespeicherten Stands (nur in off), null in active', async () => {
|
||||
const stand = setzeZustand('off')
|
||||
const stand = bestellbarerStand({ now: JETZT })
|
||||
setzeZustand('off', { stand: standOhneBestaetigung() })
|
||||
const unvollstaendig = await putFragebogen(beispielFragebogen())
|
||||
assert.equal(unvollstaendig.status, 200)
|
||||
const erwartet = bestellpruefung({ einstellung: stand.einstellung, fragebogen: unvollstaendig.body.fragebogen, feature: T, abo: { ok: true } })
|
||||
@@ -1523,13 +1541,66 @@ test('PUT fragebogen: offen ist die Bestellprüfung des gespeicherten Stands (nu
|
||||
assert.equal((await putFragebogen(beispielFragebogen())).body.offen, null)
|
||||
})
|
||||
|
||||
test('PUT fragebogen: Branche gesundheit lässt sich speichern, das Gate (offen) blockt sie aber', async () => {
|
||||
setzeZustand('off')
|
||||
test('PUT fragebogen: Branche gesundheit lässt sich speichern, solange noch keine Branche gespeichert ist; das Gate (offen) blockt sie aber', async () => {
|
||||
setzeZustand('off', { stand: standOhneBestaetigung({ branche: null }) })
|
||||
const res = await putFragebogen({ ...bestellbarerStand({ now: JETZT }).fragebogen, branche: 'gesundheit' })
|
||||
assert.equal(res.status, 200, JSON.stringify(res.body))
|
||||
assert.ok(res.body.offen.some((p) => p.schritt === 'branche'))
|
||||
})
|
||||
|
||||
test('B1: ein veralteter Tab (Branche inzwischen gewechselt) bekommt 409 EINSTELLUNG_VERALTET; Branche, Bestätigungen und Preise des neuen Stands bleiben', async () => {
|
||||
setzeZustand('off')
|
||||
const tabA = (await send('GET', einstellungenUrl(), { cookie })).body // Tab A zeigt Branche salon, alle Schritte bestätigt, Preise geprüft
|
||||
assert.equal(tabA.fragebogen.branche, 'salon')
|
||||
assert.equal(tabA.fragebogen.preiseGeprueft, true)
|
||||
|
||||
// Tab B wechselt die Branche: Vorlage ersetzt, Fragebogen beginnt neu (Bestätigungen und Preisprüfung weg)
|
||||
const tabB = await vorlage({ branche: 'kosmetik', ersetzen: true, version: tabA.version })
|
||||
assert.equal(tabB.status, 201, JSON.stringify(tabB.body))
|
||||
const nachB = termineConfigDoc()
|
||||
|
||||
// Tab A drückt „Weiter“: PUT mit dem alten Stand
|
||||
const res = await putFragebogen(tabA.fragebogen)
|
||||
assert.equal(res.status, 409, JSON.stringify(res.body))
|
||||
assert.equal(res.body.code, 'EINSTELLUNG_VERALTET')
|
||||
assert.match(res.body.error, /Bitte neu laden/)
|
||||
assert.equal(termineConfigDoc().fragebogen, nachB.fragebogen, 'der gespeicherte Fragebogen bleibt byte-gleich')
|
||||
const gespeichert = gespeicherterFragebogen()
|
||||
assert.deepEqual([gespeichert.branche, gespeichert.bestaetigt, gespeichert.preiseGeprueft], ['kosmetik', [], false])
|
||||
const gate = (await send('GET', einstellungenUrl(), { cookie })).body
|
||||
assert.ok(gate.offen.some((p) => p.schritt === 'leistungen'), 'die ungeprüften Vorlagenpreise gelten nicht als geprüft')
|
||||
})
|
||||
|
||||
test('B1: bestaetigt ist die Vereinigung aus gespeichertem und gesendetem Stand; die übrigen Felder ersetzt der PUT wie bisher', async () => {
|
||||
const stand = standOhneBestaetigung()
|
||||
stand.fragebogen.bestaetigt = ['branche', 'wer', 'leistungen'] // Tab B hat schon weiter bestätigt
|
||||
setzeZustand('off', { stand })
|
||||
|
||||
// Tab A kennt nur branche und wer und bestätigt zeiten; seine Preisangabe und sein Einbau gelten (PUT ersetzt den Rest)
|
||||
const res = await putFragebogen(beispielFragebogen({ bestaetigt: ['branche', 'wer', 'zeiten'], preiseGeprueft: false }))
|
||||
assert.equal(res.status, 200, JSON.stringify(res.body))
|
||||
assert.deepEqual(res.body.fragebogen.bestaetigt, ['branche', 'wer', 'leistungen', 'zeiten'], 'nichts geht verloren, in der Reihenfolge der Schritte')
|
||||
assert.equal(res.body.fragebogen.preiseGeprueft, false)
|
||||
assert.deepEqual(gespeicherterFragebogen(), res.body.fragebogen)
|
||||
|
||||
// ohne gespeicherte Bestätigungen oder mit einer Eingabe ohne bestaetigt: nur die Vereinigung zählt
|
||||
const leer = await putFragebogen({ ...beispielFragebogen(), bestaetigt: undefined })
|
||||
assert.equal(leer.status, 200)
|
||||
assert.deepEqual(leer.body.fragebogen.bestaetigt, ['branche', 'wer', 'leistungen', 'zeiten'])
|
||||
})
|
||||
|
||||
test('B1: dieselbe Branche geht durch; eine andere als die gespeicherte (auch null) ist 409, solange eine gespeichert ist', async () => {
|
||||
setzeZustand('off', { stand: standOhneBestaetigung() })
|
||||
assert.equal((await putFragebogen(beispielFragebogen({ branche: 'salon' }))).status, 200)
|
||||
const vorher = dokumente()
|
||||
for (const branche of ['kosmetik', 'gesundheit', null]) {
|
||||
const res = await putFragebogen(beispielFragebogen({ branche }))
|
||||
assert.equal(res.status, 409, String(branche))
|
||||
assert.equal(res.body.code, 'EINSTELLUNG_VERALTET')
|
||||
}
|
||||
assert.equal(dokumente(), vorher, 'nichts geändert')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- PUT: Kundenansicht
|
||||
|
||||
for (const zustand of ZUSTAENDE) {
|
||||
@@ -1553,7 +1624,7 @@ for (const zustand of ZUSTAENDE) {
|
||||
}
|
||||
|
||||
test('PUT fragebogen am Router: dieselbe Anfrage mit der Sitzung des Kunden selbst geht durch (der Router sperrt nur die Kundenansicht)', async () => {
|
||||
setzeZustand('off')
|
||||
setzeZustand('off', { stand: standOhneBestaetigung() })
|
||||
const res = await sendDirekt('PUT', FRAGEBOGEN_URL, { cookie, body: { projectId: 'p1', fragebogen: beispielFragebogen() } })
|
||||
assert.equal(res.status, 200, JSON.stringify(res.body))
|
||||
assert.deepEqual(gespeicherterFragebogen(), beispielFragebogen())
|
||||
|
||||
@@ -337,6 +337,23 @@ test('zusammenfassung (Vorlage salon): Name, Branche und Anschrift', () => {
|
||||
assert.ok(zeilen.includes('Anschrift: Hauptstraße 1, 67655 Kaiserslautern'))
|
||||
})
|
||||
|
||||
test('B5: zusammenfassung nennt in „Branche und Betrieb“ ausdrücklich „Kleinunternehmer: ja“ oder „nein“; ohne Angabe keine Zeile', () => {
|
||||
const zeilen = (einstellung, fragebogen) => gruppe(zusammenfassung(einstellung, fragebogen, BRANCHEN), 'branche').zeilen
|
||||
const nein = stand()
|
||||
assert.equal(nein.einstellung.betrieb.kleinunternehmer, false)
|
||||
assert.deepEqual(zeilen(nein.einstellung, nein.fragebogen).filter((z) => z.startsWith('Kleinunternehmer')), ['Kleinunternehmer: nein'])
|
||||
const ja = stand((e) => { e.betrieb.kleinunternehmer = true })
|
||||
assert.deepEqual(zeilen(ja.einstellung, ja.fragebogen).filter((z) => z.startsWith('Kleinunternehmer')), ['Kleinunternehmer: ja'])
|
||||
assert.deepEqual(zeilen(ja.einstellung, ja.fragebogen).slice(0, 4), ['Branche: Friseur und Salon', 'Betrieb: Salon Muster', 'Anschrift: Hauptstraße 1, 67655 Kaiserslautern', 'Kleinunternehmer: ja'], 'nach der Anschrift')
|
||||
// fehlt die Angabe oder ist sie kein Wahrheitswert, wird nichts erfunden
|
||||
for (const wert of [undefined, null, 'ja', 1]) {
|
||||
const ohne = stand((e) => { e.betrieb.kleinunternehmer = wert })
|
||||
assert.ok(!zeilen(ohne.einstellung, ohne.fragebogen).some((z) => z.startsWith('Kleinunternehmer')), String(wert))
|
||||
}
|
||||
const keine = zusammenfassung(null, null, BRANCHEN)
|
||||
assert.ok(!JSON.stringify(keine).includes('Kleinunternehmer'))
|
||||
})
|
||||
|
||||
test('zusammenfassung (Vorlage salon): Leistungen mit Preis', () => {
|
||||
const { einstellung, fragebogen } = stand()
|
||||
const zeilen = gruppe(zusammenfassung(einstellung, fragebogen, BRANCHEN), 'leistungen').zeilen
|
||||
@@ -1189,16 +1206,64 @@ test('renderBestellen: Zusammenfassung zeigt die acht Gruppen aus zusammenfassun
|
||||
assert.deepEqual(schritte, ['leistungen', 'website'])
|
||||
}))
|
||||
|
||||
test('renderBestellen: eine Gruppe ohne Angaben sagt es und hat trotzdem „Ändern“; fehlendes zumSchritt ist kein Fehler', mitDom(undefined, async (fx) => {
|
||||
test('renderBestellen: eine Gruppe ohne Angaben sagt es und hat trotzdem „Ändern“ (mit zumSchritt)', mitDom(undefined, async (fx) => {
|
||||
const daten = await bestellDaten({ einstellung: null, fragebogen: null })
|
||||
const wurzel = h('div')
|
||||
document.body.append(wurzel)
|
||||
const ctx = huelle.baueCtx({ daten, nurLesen: false }, { projekt: { projectId: 'p1' }, aktuell: () => true })
|
||||
bestellen.renderBestellen(wurzel, { ctx, daten })
|
||||
const { wurzel, schritte } = await bestellSeite(daten)
|
||||
const gruppen = fx.alle(wurzel, '.tm-bestellen-gruppe')
|
||||
assert.equal(gruppen.length, 8)
|
||||
assert.match(fx.text(gruppen[0]), /Noch keine Angaben/)
|
||||
await fx.klick(fx.finde(gruppen[0], '[data-aktion="aendern"]'))
|
||||
assert.deepEqual(schritte, ['branche'])
|
||||
}))
|
||||
|
||||
test('B4: ohne zumSchritt (keine Funktion) zeichnet die Bestellseite weder „Ändern“ noch „Zum Schritt“ - keine Knöpfe, die nichts tun', mitDom(undefined, async (fx) => {
|
||||
const offen = [{ schritt: 'website', text: 'Bitte trage die Adresse deines Impressums ein.' }, { schritt: 'bestellen', text: 'Das Abo konnte gerade nicht geprüft werden.' }]
|
||||
for (const zumSchritt of [undefined, null, 'website', 42, {}]) {
|
||||
const daten = await bestellDaten({ offen })
|
||||
const wurzel = h('div')
|
||||
document.body.append(wurzel)
|
||||
const ctx = huelle.baueCtx({ daten, nurLesen: false }, { projekt: { projectId: 'p1' }, aktuell: () => true })
|
||||
bestellen.renderBestellen(wurzel, { ctx, daten, zumSchritt })
|
||||
const name = JSON.stringify(zumSchritt)
|
||||
assert.equal(fx.alle(wurzel, '.tm-bestellen-gruppe').length, 8, `${name}: die Zusammenfassung steht`)
|
||||
assert.equal(fx.alle(wurzel, '[data-aktion="aendern"]').length, 0, `${name}: kein „Ändern“`)
|
||||
assert.equal(fx.alle(wurzel, '[data-aktion="zum-schritt"]').length, 0, `${name}: kein „Zum Schritt“`)
|
||||
assert.deepEqual(fx.alle(wurzel, '.tm-bestellen-offen li span').map((span) => span.textContent), offen.map((p) => p.text), `${name}: die offenen Punkte bleiben als Text`)
|
||||
}
|
||||
// mit Funktion ist alles wie bisher da
|
||||
const { wurzel } = await bestellSeite(await bestellDaten({ offen }))
|
||||
assert.equal(fx.alle(wurzel, '[data-aktion="aendern"]').length, 8)
|
||||
assert.deepEqual(fx.alle(wurzel, '[data-aktion="zum-schritt"]').map((k) => k.dataset.schritt), ['website'])
|
||||
}))
|
||||
|
||||
test('B4: nach einem 409 bekommt die Meldung den Fokus (tabindex -1), auf der neuen Seite, bei fehlgeschlagenem Neuladen und auf der Hinweiskarte', mitDom(undefined, async (fx) => {
|
||||
const fokusAufMeldung = (wurzel, was) => {
|
||||
const meldung = fx.finde(wurzel, '[data-meldung="bestellen"]')
|
||||
assert.ok(meldung, was)
|
||||
assert.equal(meldung.getAttribute('tabindex'), '-1', `${was}: fokussierbar, aber nicht im Tabulatorweg`)
|
||||
assert.equal(fx.document.activeElement, meldung, `${was}: Fokus auf der Meldung`)
|
||||
}
|
||||
// a) Seite wird neu gezeichnet
|
||||
fx.antworten.push({ status: 409, body: { error: 'Die Bestellung ist noch nicht vollständig.', code: 'bestellung_offen', offen: [{ schritt: 'website', text: 'x' }] } }, { body: await bestellDaten() })
|
||||
const a = await bestellSeite(await bestellDaten())
|
||||
await alleAnhaken(fx, a.wurzel)
|
||||
await fx.klick(bestellKnopf(fx, a.wurzel))
|
||||
fokusAufMeldung(a.wurzel, 'neu gezeichnet')
|
||||
// b) Neuladen scheitert: dieselbe Seite, Meldung mit Fokus
|
||||
fx.antworten.push({ status: 409, body: { error: 'Dein Wartungs-Abo ist gekündigt.', code: 'gekuendigt' } }, { status: 500, body: { error: 'Server kaputt.' } })
|
||||
const b = await bestellSeite(await bestellDaten())
|
||||
await alleAnhaken(fx, b.wurzel)
|
||||
await fx.klick(bestellKnopf(fx, b.wurzel))
|
||||
fokusAufMeldung(b.wurzel, 'Neuladen gescheitert')
|
||||
// c) die Bestellung ist inzwischen aufgegeben: die Karte mit der Meldung bekommt den Fokus
|
||||
fx.antworten.push({ status: 409, body: { error: 'Der Terminplaner ist für dieses Projekt schon bestellt.' } }, { body: await bestellDaten({ state: 'ordered', bestellung: null, offen: null }) })
|
||||
const c = await bestellSeite(await bestellDaten())
|
||||
await alleAnhaken(fx, c.wurzel)
|
||||
await fx.klick(bestellKnopf(fx, c.wurzel))
|
||||
const karteEl = fx.finde(c.wurzel, '.tm-info')
|
||||
assert.ok(karteEl)
|
||||
assert.equal(karteEl.getAttribute('tabindex'), '-1')
|
||||
assert.equal(fx.document.activeElement, karteEl, 'Fokus auf der Hinweiskarte')
|
||||
}))
|
||||
|
||||
test('renderBestellen: offene Punkte mit „Zum Schritt“ zu ihrem Schritt, der Abo-Punkt (bestellen) ohne Knopf; Knopf gesperrt', mitDom(undefined, async (fx) => {
|
||||
@@ -1443,6 +1508,41 @@ test('statusKarte ordered: „Zahlung fortsetzen“ sendet fortsetzenKoerper und
|
||||
assert.equal(fx.window.location.href, KNOPF_URL)
|
||||
}))
|
||||
|
||||
test('B3: zurück aus dem Bfcache (pageshow persisted) lädt die Karte „Zahlung fortsetzen“ neu - nicht gesperrt und ohne Meldung stehen lassen', mitDom({ antworten: [{ body: { url: KNOPF_URL } }] }, async (fx) => {
|
||||
const wurzel = karte()
|
||||
const knopf = fx.finde(wurzel, '[data-aktion="fortsetzen"]')
|
||||
await fx.klick(knopf)
|
||||
assert.equal(fx.window.location.href, KNOPF_URL)
|
||||
assert.equal(knopf.disabled, true, 'bis zum Verlassen der Seite gesperrt')
|
||||
// ein normales pageshow (erste Anzeige) ändert nichts
|
||||
fx.window.dispatchEvent({ type: 'pageshow', persisted: false })
|
||||
assert.deepEqual(fx.reloads, [])
|
||||
// Zurück aus der Zahlungsseite: der Browser stellt die Seite samt gesperrtem Knopf wieder her
|
||||
fx.window.dispatchEvent({ type: 'pageshow', persisted: true })
|
||||
assert.equal(fx.reloads.length, 1, 'die Seite wird neu geladen')
|
||||
}))
|
||||
|
||||
test('B3: eine Karte, die nicht mehr im Baum steht, lädt nichts neu und ihr Handler verschwindet', mitDom(undefined, (fx) => {
|
||||
const wurzel = karte()
|
||||
assert.equal(fx.window.anzahlHandler('pageshow'), 1)
|
||||
wurzel.remove()
|
||||
fx.window.dispatchEvent({ type: 'pageshow', persisted: true })
|
||||
assert.deepEqual(fx.reloads, [])
|
||||
assert.equal(fx.window.anzahlHandler('pageshow'), 0)
|
||||
}))
|
||||
|
||||
test('B3: auch die Bestellseite lädt bei der Rückkehr aus dem Bfcache neu, solange sie im Baum steht', mitDom({ antworten: [{ body: { url: KNOPF_URL } }] }, async (fx) => {
|
||||
const { wurzel } = await bestellSeite(await bestellDaten())
|
||||
await alleAnhaken(fx, wurzel)
|
||||
await fx.klick(bestellKnopf(fx, wurzel))
|
||||
assert.equal(fx.window.location.href, KNOPF_URL)
|
||||
assert.equal(bestellKnopf(fx, wurzel).disabled, true)
|
||||
fx.window.dispatchEvent({ type: 'pageshow', persisted: false })
|
||||
assert.deepEqual(fx.reloads, [])
|
||||
fx.window.dispatchEvent({ type: 'pageshow', persisted: true })
|
||||
assert.equal(fx.reloads.length, 1)
|
||||
}))
|
||||
|
||||
test('statusKarte ordered: 409 zustimmung_fehlt ⇒ weiter({ bestellen: true, meldung }) lädt neu und zeigt das Bestellformular, keine Navigation', mitDom({
|
||||
antworten: [{ status: 409, body: { error: 'Bitte stimme der Erklärung zu, um zu bestellen.', code: 'zustimmung_fehlt' } }],
|
||||
}, async (fx) => {
|
||||
@@ -3099,8 +3199,9 @@ test('Ansicht off ohne laufendes Abo: der Hinweis vom Server (kein_abo mit Link
|
||||
const karteEl = fx.finde(wurzel, '[data-hinweis="abo"]')
|
||||
assert.ok(karteEl)
|
||||
assert.ok(fx.text(karteEl).includes(SERVER_HINWEIS), 'der Text des Servers, unverändert')
|
||||
const link = fx.alle(karteEl, 'button')
|
||||
const link = fx.alle(karteEl, 'button[data-view]')
|
||||
assert.deepEqual(link.map((b) => [fx.text(b), b.getAttribute('data-view'), b.getAttribute('class'), b.getAttribute('type')]), [['Zu Verträge', 'vertraege', 'kb-link', 'button']])
|
||||
assert.deepEqual(fx.alle(karteEl, 'button').map((b) => b.getAttribute('data-aktion') ?? b.getAttribute('data-view')), ['vertraege', 'neu-laden'], 'dazu „Neu laden“ (B2)')
|
||||
assert.equal(fx.text(fx.finde(karteEl, 'p')), `${SERVER_HINWEIS} Zu Verträge`, 'Text, Leerzeichen, Link wie in der Kachel')
|
||||
assert.ok(fx.finde(wurzel, 'nav.tm-fortschritt') === null, 'kein Fragebogen')
|
||||
assert.equal(fx.alle(wurzel, 'input').length, 0)
|
||||
@@ -3120,11 +3221,41 @@ test('Ansicht off ohne Abo: gekündigt, nicht aktiv, nicht lesbar und fehlendes
|
||||
const karteEl = fx.finde(wurzel, '[data-hinweis="abo"]')
|
||||
assert.ok(karteEl, name)
|
||||
assert.equal(fx.text(fx.finde(karteEl, 'p')), termine.hinweis, name)
|
||||
assert.equal(fx.alle(karteEl, 'button').length, 0, `${name}: kein Link`)
|
||||
assert.equal(fx.alle(karteEl, 'button[data-view]').length, 0, `${name}: kein Link`)
|
||||
assert.ok(fx.finde(wurzel, 'nav.tm-fortschritt') === null, name)
|
||||
}
|
||||
}))
|
||||
|
||||
test('B2: der Text zum Abo kommt mit den Daten (aboHinweis), auch wenn die Liste „bestellbar“ sagt und das Abo nicht lesbar war; Link bei kein_abo; „Neu laden“ holt alles neu', mitDom(undefined, async (fx) => {
|
||||
// das Abo war beim Lesen der Daten nicht lesbar (abo null), die Liste kennt keinen Hinweis: früher stand nur die Überschrift da
|
||||
const HINWEIS = 'Das Abo konnte gerade nicht geprüft werden.'
|
||||
const wurzel = await oeffneAnsicht(fx, { tabelle: await einProjekt('off', { daten: { abo: null, aboHinweis: HINWEIS } }) })
|
||||
const karteEl = fx.finde(wurzel, '[data-hinweis="abo"]')
|
||||
assert.ok(karteEl)
|
||||
assert.equal(fx.text(fx.finde(karteEl, 'p')), HINWEIS, 'der Text des Servers aus den Daten')
|
||||
assert.deepEqual(AUFRUFE(fx), [AN_FUNKTIONEN, an('p1')], 'dafür braucht es keine weitere Abfrage der Liste')
|
||||
assert.equal(fx.alle(karteEl, 'button[data-view]').length, 0, 'ohne Grund kein_abo kein Link')
|
||||
|
||||
// kein_abo steht in den Daten: der Link zu Verträge auch ohne Angaben der Liste
|
||||
const kein = await oeffneAnsicht(fx, { tabelle: await einProjekt('off', { daten: { abo: { ok: false, grund: 'kein_abo' }, aboHinweis: SERVER_HINWEIS } }) })
|
||||
const kleiner = fx.finde(kein, '[data-hinweis="abo"]')
|
||||
assert.equal(fx.text(fx.finde(kleiner, 'p')), `${SERVER_HINWEIS} Zu Verträge`)
|
||||
|
||||
// „Neu laden“: Daten noch einmal holen und die Ansicht nach dem neuen Stand zeichnen (das Abo läuft inzwischen: der Fragebogen)
|
||||
const neu = await oeffneAnsicht(fx, { tabelle: await einProjekt('off', {
|
||||
daten: { abo: null, aboHinweis: HINWEIS },
|
||||
tabelle: { [an('p1')]: [{ body: await ansichtDaten('off', { abo: null, aboHinweis: HINWEIS }) }, { body: await ansichtDaten('off', { fragebogen: fbBis(1) }) }] },
|
||||
}) })
|
||||
const laden = fx.finde(neu, '[data-hinweis="abo"] button[data-aktion="neu-laden"]')
|
||||
assert.ok(laden, 'Knopf „Neu laden“')
|
||||
assert.equal(fx.text(laden), 'Neu laden')
|
||||
assert.equal(laden.getAttribute('type'), 'button')
|
||||
await fx.klick(laden)
|
||||
await fx.ruhe()
|
||||
assert.ok(fx.finde(neu, '[data-hinweis="abo"]') === null, 'die Karte ist weg')
|
||||
assert.ok(fx.finde(neu, 'nav.tm-fortschritt'), 'der Fragebogen steht da')
|
||||
}))
|
||||
|
||||
test('Ansicht off ohne Abo: der Hinweis kommt maskiert in die Seite und der Browser kennt keinen eigenen Text', mitDom(undefined, async (fx) => {
|
||||
const bose = '<img src=x onerror=alert(1)> & "Anführungszeichen"'
|
||||
const wurzel = await oeffneAnsicht(fx, { tabelle: await einProjekt('off', { daten: { abo: { ok: false, grund: 'gekuendigt' } }, termine: ohneAbo('gekuendigt', bose) }) })
|
||||
|
||||
Reference in new issue
Block a user