Terminplaner: im Abo-Modell Einschalten statt Bestellen, ohne Häkchen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 18:39:54 +00:00
1 parent 364ded1662
commit b6f70a502a
3 files changed
+119 -7

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { h } from '../public/termine-dom.js'
import { initTermine } from '../public/termine.js'
import { bestellbarerStand } from './support/fragebogenStand.js'
import { einschaltenAttrappe, hoereAufKn, mitDom, routen } from './support/modulAbo.js'
// Terminplaner im Abo-Modell (P4 Task 3): der letzte Schritt des Fragebogens heißt „Prüfen und einschalten“ und mountet den Ablauf von P3
// statt der Bestellseite; keine Haken, kein Preisblock, kein Checkout. Ohne modell === 'abo' bleibt die Bestellseite wie heute.
const STAND = bestellbarerStand({ now: new Date('2026-10-16T08:00:00.000Z') })
const daten = (extra) => ({ state: 'off', einstellung: STAND.einstellung, fragebogen: STAND.fragebogen, version: 1, branchen: [], kundenansicht: false, abo: { ok: true }, offen: [], bestellung: null, ...extra })
const liste = (modell, state = 'off', feature = {}) => ({
body: {
...(modell === undefined ? {} : { modell }),
projects: [{ projectId: 'p1', projectName: 'Salon', features: { termine: { state, siteKey: 'abcdefghijklmnop', ...feature } } }],
termine: { verfuegbar: true, bestellbar: true, grund: null, preis: '149 €', einrichtung: 'meist in 1–2 Wochen', hinweis: null },
},
})
const BESTELLUNG = {
preis: '149 €',
preisHinweis: 'einmalig, ohne Umsatzsteuer gemäß § 19 UStG',
einrichtung: '',
belehrung: null,
dokumente: [],
consent: { version: 'v-test', knopf: 'Zahlungspflichtig bestellen · 149 €', items: [{ id: 'beginn', text: 'Ich verlange den sofortigen Beginn.', haken: true }] },
}
const EINSTELLUNGEN = 'GET /api/termine/einstellungen?projectId=p1'
async function oeffne(fx, tabelle, optionen) {
routen(fx, tabelle)
const wurzel = h('div')
document.body.append(wurzel)
await initTermine(wurzel, { projectId: 'p1', ...optionen })
await fx.ruhe()
return wurzel
}
test('Abo, alles bestätigt: letzter Schritt E1 mountet den Ablauf, ohne Haken, Preisblock und Checkout; keine Reiter', mitDom(async (fx) => {
const a = einschaltenAttrappe()
const k = hoereAufKn(fx)
const wurzel = await oeffne(fx, { 'GET /api/funktionen': liste('abo'), [EINSTELLUNGEN]: { body: daten() } }, { einschalten: a.fn })
assert.equal(fx.text(fx.finde(wurzel, '.tm-fb-bestellen h3')), 'Prüfen und einschalten')
assert.match(fx.text(fx.finde(wurzel, '.tm-schritt[aria-current="step"]')), /Prüfen und einschalten/)
assert.match(fx.text(fx.finde(wurzel, '.tm-schritt-stand')), /Prüfen und einschalten/)
assert.deepEqual(a.aufrufe.map((x) => [x.key, x.projectId, x.viewAs]), [['termine', 'p1', false]])
assert.equal(fx.alle(wurzel, 'input[type="checkbox"]').length, 0, 'auch kein Haken „gesundheit“')
for (const sel of ['[data-aktion="bestellen"]', '.tm-bestellen-preis', '.tm-rechtstext', '.tm-bestellen-zustimmung']) assert.equal(fx.finde(wurzel, sel), null, sel)
assert.equal(fx.fetchLog.filter((x) => x.method === 'POST').length, 0)
assert.deepEqual(k.reiter.at(-1).eintraege, [])
}))
test('Abo, offene Punkte: kein Ablauf, Hinweis E2, „Zum Schritt“ führt zurück', mitDom(async (fx) => {
const a = einschaltenAttrappe()
const offen = [{ schritt: 'zeiten', text: 'Bitte wähle das Bundesland deines Betriebs.' }]
const wurzel = await oeffne(fx, { 'GET /api/funktionen': liste('abo'), [EINSTELLUNGEN]: { body: daten({ offen }) } }, { einschalten: a.fn })
assert.equal(a.aufrufe.length, 0)
assert.match(fx.text(wurzel), /Du kannst einschalten, sobald die offenen Punkte erledigt sind\./)
await fx.klick(fx.finde(wurzel, '[data-aktion="zum-schritt"][data-schritt="zeiten"]'))
assert.equal(fx.finde(wurzel, '.tm-schritt[aria-current="step"]').getAttribute('data-schritt'), 'zeiten')
}))
test('Abo: nach dem Einschalten lädt die Ansicht neu und zeigt die Statuskarte „In Einrichtung“ mit „Eingeschaltet am“', mitDom(async (fx) => {
const a = einschaltenAttrappe()
let n = 0
const wurzel = await oeffne(fx, {
'GET /api/funktionen': () => (n === 0 ? liste('abo') : liste('abo', 'setup', { orderedAt: '2026-10-16T09:00:00.000Z', ticketWoid: '100150' })),
[EINSTELLUNGEN]: () => ({ body: n === 0 ? daten() : daten({ state: 'setup' }) }),
}, { einschalten: a.fn })
n = 1
a.fertig(0)
await fx.ruhe()
const karte = fx.finde(wurzel, '.tm-statuskarte[data-state="setup"]')
assert.match(fx.text(karte), /Eingeschaltet am\s*16\.10\.2026/)
assert.doesNotMatch(fx.text(karte), /Bestellt am|Zahlung/)
}))
test('Abo: ohne Funktion einschalten steht E4; in der Kundenansicht steht F1 und der Ablauf bekommt viewAs true', mitDom(async (fx) => {
const ohne = await oeffne(fx, { 'GET /api/funktionen': liste('abo'), [EINSTELLUNGEN]: { body: daten() } })
assert.match(fx.text(ohne), /Das Einschalten ist gerade nicht verfügbar\. Bitte lade die Seite neu oder schreib uns im Chat\./)
const a = einschaltenAttrappe()
const ansicht = await oeffne(fx, { 'GET /api/funktionen': liste('abo'), [EINSTELLUNGEN]: { body: daten({ kundenansicht: true }) } }, { einschalten: a.fn })
assert.match(fx.text(ansicht), /Du siehst den Fragebogen in der Kundenansicht\. Angaben kannst du ändern, Schritte bestätigen und einschalten kann nur der Kunde selbst\./)
assert.equal(a.aufrufe[0].viewAs, true)
}))
test('Abo: die Zusammenfassung der Angaben bleibt auf der Einschalt-Seite (Gruppen mit „Ändern“)', mitDom(async (fx) => {
const a = einschaltenAttrappe()
const wurzel = await oeffne(fx, { 'GET /api/funktionen': liste('abo'), [EINSTELLUNGEN]: { body: daten() } }, { einschalten: a.fn })
assert.ok(fx.alle(wurzel, '.tm-bestellen-gruppe').length > 0)
assert.ok(fx.finde(wurzel, '[data-aktion="aendern"]'))
}))
test('Ohne modell (fehlt, leer, „Abo“, „abo “): unverändert Bestellseite mit Knopf des Registers, Ablauf wird nie gerufen', mitDom(async (fx) => {
for (const modell of [undefined, '', 'Abo', 'abo ']) {
const a = einschaltenAttrappe()
const wurzel = await oeffne(fx, { 'GET /api/funktionen': liste(modell), [EINSTELLUNGEN]: { body: daten({ bestellung: BESTELLUNG }) } }, { einschalten: a.fn })
assert.equal(a.aufrufe.length, 0, JSON.stringify(modell))
assert.equal(fx.text(fx.finde(wurzel, '[data-aktion="bestellen"]')), 'Zahlungspflichtig bestellen · 149 €')
assert.equal(fx.text(fx.finde(wurzel, '.tm-fb-bestellen h3')), 'Prüfen und bestellen')
assert.match(fx.text(fx.finde(wurzel, '.tm-schritt[aria-current="step"]')), /Prüfen und bestellen/)
}
}))