Webshop: im Abo-Modell Anfrage ohne Unternehmer-Haken, Privat-Hinweis, Einschalten

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 18:45:17 +00:00
1 parent 1cabab2826
commit 28da4180bd
7 files changed
+147 -22

No files matched your search

@@ -23,7 +23,7 @@ test('mountet den Ablauf genau einmal in den Platz, mit key, projectId und viewA
assert.deepEqual([a.aufrufe[0].key, a.aufrufe[0].projectId, a.aufrufe[0].viewAs], ['shop', 'p1', false])
assert.ok(a.aufrufe[0].wurzel === fx.finde(wurzel, '[data-einschalten="shop"]'))
assert.ok(fx.finde(wurzel, '.tm-einschalten-platz'))
assert.equal(fx.finde(wurzel, 'h3'), null)
assert.ok(fx.finde(wurzel, 'h3') === null)
assert.equal(fx.alle(wurzel, 'input').length, 0)
assert.doesNotMatch(fx.text(wurzel), KEIN_PREIS)
const hulle = fx.finde(wurzel, 'section.kb-panel.tm-bestellen.tm-einschalten')
@@ -36,7 +36,7 @@ test('Zusammenfassung mit E1; offene Punkte sperren den Ablauf (E2); „Ändern
const wurzel = seite({ einschalten: a.fn, zusammenfassung: GRUPPEN, zumSchritt: (k) => schritte.push(k), daten: { offen: [{ schritt: 'zeiten', text: 'Bitte wähle das Bundesland.' }], kundenansicht: false } })
assert.equal(fx.text(fx.finde(wurzel, 'h3')), 'Prüfen und einschalten')
assert.equal(a.aufrufe.length, 0)
assert.equal(fx.finde(wurzel, '.tm-einschalten-platz'), null)
assert.ok(fx.finde(wurzel, '.tm-einschalten-platz') === null)
assert.match(fx.text(wurzel), /Du kannst einschalten, sobald die offenen Punkte erledigt sind\./)
assert.doesNotMatch(fx.text(wurzel), /bestellen/i)
await fx.klick(fx.finde(wurzel, '[data-aktion="zum-schritt"][data-schritt="zeiten"]'))
@@ -51,7 +51,7 @@ test('Zusammenfassung ohne offene Punkte: Gruppe, dann der Platz mit dem Ablauf'
assert.equal(fx.text(fx.finde(wurzel, '.tm-bestellen-gruppe h4')), 'Deine Anfrage')
assert.equal(a.aufrufe.length, 1)
assert.doesNotMatch(fx.text(wurzel), /Du kannst einschalten, sobald/)
assert.equal(fx.finde(wurzel, '[data-aktion="aendern"]'), null, 'ohne zumSchritt kein „Ändern“')
assert.ok(fx.finde(wurzel, '[data-aktion="aendern"]') === null, 'ohne zumSchritt kein „Ändern“')
}))
test('Kundenansicht: der Ablauf bekommt viewAs true (daten und ctx)', mitDom(async () => {
+2 -2
View File
@@ -87,8 +87,8 @@ for (const f of FAELLE) {
test(`${f.name}, Abo: keine eigene Leiste, kn:reiter mit Kurzlabels, bei() wechselt`, mitDom(async (fx) => {
const k = hoereAufKn(fx)
const root = await oeffne(fx, f, 'abo')
assert.equal(fx.finde(root, '[role="tablist"]'), null)
assert.equal(fx.finde(root, '[role="tab"]'), null)
assert.ok(fx.finde(root, '[role="tablist"]') === null)
assert.ok(fx.finde(root, '[role="tab"]') === null)
const panel = fx.finde(root, '[role="tabpanel"]')
assert.ok(panel, 'das Panel bleibt')
assert.equal(panel.hasAttribute('aria-labelledby'), false)
+109
View File
@@ -0,0 +1,109 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { h } from '../public/shop-dom.js'
import { initShop, vergissAuswahl } from '../public/shop.js'
import { AUSGESCHLOSSEN } from '../public/shop-anfrage.js'
import { KEIN_PREIS, einschaltenAttrappe, hoereAufKn, mitDom, routen } from './support/modulAbo.js'
// Webshop im Abo-Modell (P4 Task 5): Anfrage ohne Unternehmer-Haken und ohne Preis, Satz über dem Knopf, Hinweis für Privatkunden,
// nach der Zusage der Ablauf des Einschaltens statt der Bestellseite. Ohne modell === 'abo' bleibt alles wie heute.
const ANFRAGE = { warenart: 'kleidung', beschreibung: 'Handgemachte Mützen und Schals.', artikelzahl: 'bis20', versand: true, abholung: false, unternehmer: true, keineAusgeschlossenen: true, nachricht: '' }
const funk = (state, kopf = {}) => ({
projects: [{ projectId: 'p1', projectName: 'Mein Laden', features: { shop: { state } } }],
features: { shop: { priceLabel: '299 €', consent: { version: 'v', knopf: 'Zahlungspflichtig bestellen · 299 €', items: [] } } },
ustHinweis: 'einmalig, ohne Umsatzsteuer gemäß § 19 UStG',
legal: { shop: {} },
shop: { verfuegbar: true },
...kopf,
})
async function oeffne(fx, { kopf = {}, state = 'off', anfrage = null, einschalten } = {}) {
vergissAuswahl()
routen(fx, {
'GET /api/funktionen': { body: funk(state, kopf) },
'GET /api/shop/status?projectId=p1': { body: { verfuegbar: true, state, ticketWoid: null, anfrage } },
'POST /api/shop/anfrage': { body: { anfrage: {} } },
})
const root = h('div')
document.body.append(root)
await initShop(root, { projectId: 'p1', einschalten })
await fx.ruhe()
return root
}
const ZUGESAGT = { status: 'angenommen', anfrage: ANFRAGE, at: '2026-10-05T10:30:00.000Z' }
test('Abo, Betrieb: Anfrage ohne Unternehmer-Haken, Satz S2 über dem Knopf, kein Preis; Körper ohne unternehmer', mitDom(async (fx) => {
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' } })
assert.ok(fx.finde(root, '[data-feld="unternehmer"]') === null)
assert.ok(fx.finde(root, '[data-feld="keineAusgeschlossenen"]') === null)
const satz = fx.finde(root, '[data-satz="ausgeschlossen"]')
assert.equal(fx.text(satz), `Mit „Anfrage senden“ bestätigst du, dass du keine ausgeschlossenen Waren verkaufst (${AUSGESCHLOSSEN}).`)
const knoten = fx.alle(root, '*')
assert.ok(knoten.indexOf(satz) < knoten.indexOf(fx.finde(root, '[data-aktion="anfrage-senden"]')), 'Satz steht über dem Knopf')
const erklaerung = fx.text(fx.finde(root, '.sh-erklaerung'))
assert.match(erklaerung, /Im Wartungs-Abo enthalten\./)
assert.match(erklaerung, /Wir sehen uns deine Anfrage kurz an\. Erst nach unserer Zusage kannst du einschalten\./)
assert.doesNotMatch(erklaerung, KEIN_PREIS)
assert.doesNotMatch(erklaerung, /bestellen/)
assert.doesNotMatch(fx.text(root), KEIN_PREIS)
await fx.setze(fx.finde(root, '[data-feld="warenart"]'), 'kleidung')
await fx.setze(fx.finde(root, '[data-feld="beschreibung"]'), 'Handgemachte Mützen und Schals.')
await fx.setze(fx.finde(root, '[data-feld="artikelzahl"]'), 'bis20')
await fx.klick(fx.finde(root, '[data-feld="versand"]'))
await fx.klick(fx.finde(root, '[data-aktion="anfrage-senden"]'))
assert.deepEqual(fx.fetchLog.find((a) => a.method === 'POST').body, {
projectId: 'p1',
anfrage: { warenart: 'kleidung', beschreibung: 'Handgemachte Mützen und Schals.', artikelzahl: 'bis20', versand: true, abholung: false, keineAusgeschlossenen: true, nachricht: '' },
})
}))
test('Abo, privat oder Kundenart leer/fehlend: Hinweis S3 mit Chat, nie das Formular, kein POST, leere Reiterliste', mitDom(async (fx) => {
const k = hoereAufKn(fx)
for (const kundenart of ['privat', '', undefined]) {
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart } })
assert.ok(fx.finde(root, 'form') === null, String(kundenart))
assert.match(fx.text(root), /Der Webshop ist für Betriebe/)
assert.match(fx.text(root), /Für Privatpersonen gibt es den Webshop leider nicht\. Stimmt das nicht\? Schreib uns im Chat\./)
assert.ok(fx.finde(root, 'button[data-view="chat"]'))
}
assert.equal(fx.fetchLog.filter((a) => a.method === 'POST').length, 0)
assert.deepEqual(k.reiter.at(-1).eintraege, [])
}))
test('Abo, privat mit bestehendem Shop (active): die fünf Reiter bleiben', mitDom(async (fx) => {
const k = hoereAufKn(fx)
await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'privat' }, state: 'active' })
assert.equal(k.reiter.at(-1).eintraege.length, 5)
}))
test('Abo, Anfrage angenommen: Gruppe B1 und Ablauf key shop statt Bestellseite; onFertig lädt neu', mitDom(async (fx) => {
const a = einschaltenAttrappe()
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' }, anfrage: ZUGESAGT, einschalten: a.fn })
assert.equal(a.aufrufe[0].key, 'shop')
assert.equal(a.aufrufe[0].projectId, 'p1')
assert.equal(fx.text(fx.finde(root, '.tm-bestellen-gruppe h4')), 'Deine Anfrage')
assert.ok(fx.finde(root, '[data-aktion="bestellen"]') === null)
assert.doesNotMatch(fx.text(root), KEIN_PREIS)
a.fertig(0)
await fx.ruhe()
assert.equal(fx.fetchLog.filter((x) => x.url === '/api/shop/status?projectId=p1').length, 2)
}))
test('Abo, Anfrage angenommen, ohne Funktion einschalten: Fehlerkarte E4', mitDom(async (fx) => {
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' }, anfrage: ZUGESAGT })
assert.match(fx.text(root), /Das Einschalten ist gerade nicht verfügbar\./)
}))
test('Ohne modell (fehlt, „Abo“): Formular mit Unternehmer-Haken und 299 €, nach der Zusage die Bestellseite, der Ablauf wird nie gerufen', mitDom(async (fx) => {
for (const modell of [undefined, 'Abo']) {
const a = einschaltenAttrappe()
const kopf = modell ? { modell } : {}
const formular = await oeffne(fx, { kopf, einschalten: a.fn })
assert.ok(fx.finde(formular, '[data-feld="unternehmer"]'))
assert.ok(fx.finde(formular, '[data-feld="keineAusgeschlossenen"]'))
assert.match(fx.text(formular), /299 € einmalig/)
const zugesagt = await oeffne(fx, { kopf, anfrage: ZUGESAGT, einschalten: a.fn })
assert.ok(fx.finde(zugesagt, '[data-aktion="bestellen"]'))
assert.equal(a.aufrufe.length, 0)
}
}))
+1 -1
View File
@@ -45,7 +45,7 @@ test('Abo, alles bestätigt: letzter Schritt E1 mountet den Ablauf, ohne Haken,
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)
for (const sel of ['[data-aktion="bestellen"]', '.tm-bestellen-preis', '.tm-rechtstext', '.tm-bestellen-zustimmung']) assert.ok(fx.finde(wurzel, sel) === null, sel)
assert.equal(fx.fetchLog.filter((x) => x.method === 'POST').length, 0)
assert.deepEqual(k.reiter.at(-1).eintraege, [])
}))
+1 -1
View File
@@ -28,7 +28,7 @@ test('app.js: VIEW_HASHES, viewRefreshers, FEATURE_LABELS und APP_PARENT kennen
test('shop.js und shop.css: die Hülle exportiert initShop, die Styles hängen an #view-webshop und sh-', () => {
const js = lies('public/shop.js')
assert.match(js, /export function initShop\(root, \{ projectId \} = \{\}\)/)
assert.match(js, /export function initShop\(root, \{ projectId, einschalten \} = \{\}\)/)
assert.doesNotMatch(js.replace(/\/\*[\s\S]*?\*\//g, ''), /innerHTML|insertAdjacentHTML|outerHTML|document\.write/, 'Text nie als HTML')
const css = lies('public/shop.css')
assert.match(css, /#view-webshop/)