Shop G1: Verdrahtung #webshop, Webshop-Kachel, Bestellseite mit key und optionaler Zusammenfassung
Dashboard-Abschnitt view-webshop, shop.css und Platzhalter shop.js, app.js (Hash, Refresher, Label, Elternansicht), Kachel in funktionen.js nach shop.verfuegbar, GET /api/funktionen liefert shop.verfuegbar, funktion-bestellen.js nimmt key/name/zusammenfassung (Standard: Terminplaner unverändert). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ebd2e6663
commit
809da80c9b
11 files changed
+333
-19
No files matched your search
@@ -488,3 +488,79 @@ test('Ausschalten: Hinweis vorher, nach "Ja, ausschalten" Bestätigung, erst "OK
|
||||
ok.handlers.click()
|
||||
assert.equal(reloads, 1)
|
||||
})
|
||||
|
||||
// ---- Webshop-Kachel (Task G1): nur bei shop.verfuegbar, Pille und Knopf nach dem Zustand des Funktionsdokuments
|
||||
const shopProject = (id, state, extra = {}) => ({ projectId: id, projectName: `P ${id}`, liveUrl: '', features: { news, shop: { state, ...extra } }, analyse: { comingSoon: true } })
|
||||
|
||||
test('Webshop-Kachel: ohne shop.verfuegbar (fehlt, false) gar keine Kachel', async () => {
|
||||
for (const extra of [{}, { shop: { verfuegbar: false } }, { shop: {} }, { shop: null }]) {
|
||||
const html = await renderMit([shopProject('a', 'off')], extra)
|
||||
assert.doesNotMatch(html, /data-fn-tile="shop"/, JSON.stringify(extra))
|
||||
assert.doesNotMatch(html, /Webshop/, JSON.stringify(extra))
|
||||
}
|
||||
})
|
||||
|
||||
test('Webshop-Kachel: Text mit Preis, Knopf und Pille je Zustand, Ziel #webshop mit Projekt', async () => {
|
||||
const faelle = [
|
||||
['off', 'Webshop anfragen', 'Aus', 'off'],
|
||||
['ordered', 'Zahlung offen', 'Zahlung offen', 'setup'],
|
||||
['setup', 'In Einrichtung', 'In Einrichtung', 'setup'],
|
||||
['active', 'Webshop öffnen', 'Aktiv', 'on'],
|
||||
['disabled', 'Webshop ansehen', 'Abgeschaltet', 'off'],
|
||||
]
|
||||
for (const [state, knopf, pille, klasse] of faelle) {
|
||||
const html = await renderMit([shopProject('p1', state)], { shop: { verfuegbar: true } })
|
||||
const t = tile(html, 'shop')
|
||||
assert.match(t, /<h3>Webshop<\/h3>/, state)
|
||||
assert.match(t, /Dein eigener Online-Shop auf Deiner Website\. Zahlungen gehen direkt auf Dein Stripe-Konto\. 299 € einmalig\./, state)
|
||||
assert.match(t, new RegExp(`<button type="button" class="kb-btn-primary fn-tile-cta" data-view="webshop" data-project="p1">${knopf}</button>`), state)
|
||||
assert.match(t, new RegExp(`fn-pill fn-pill-${klasse}">${pille}</span>`), state)
|
||||
}
|
||||
// ohne Dokument (features.shop fehlt) gilt off
|
||||
const ohne = await renderMit([{ projectId: 'p1', projectName: 'P', features: { news }, analyse: { comingSoon: true } }], { shop: { verfuegbar: true } })
|
||||
assert.match(tile(ohne, 'shop'), /Webshop anfragen/)
|
||||
assert.match(tile(ohne, 'shop'), /fn-pill-off">Aus</)
|
||||
})
|
||||
|
||||
test('Webshop-Kachel: Preis aus dem Register maskiert, Rückfall 299 €, Kachel steht nach dem Terminplaner und vor der Analyse', async () => {
|
||||
const html = await renderMit([shopProject('p1', 'off')], { shop: { verfuegbar: true }, features: { shop: { priceLabel: '<img src=x onerror=1> 349 €' } } })
|
||||
assert.doesNotMatch(html, /<img/)
|
||||
assert.match(tile(html, 'shop'), /<img src=x onerror=1> 349 € einmalig/)
|
||||
const rueck = await renderMit([shopProject('p1', 'off')], { shop: { verfuegbar: true }, features: { shop: { priceLabel: '' } } })
|
||||
assert.match(tile(rueck, 'shop'), /299 € einmalig/)
|
||||
const mitAnalyse = await renderMit([{ ...shopProject('p1', 'off'), analyse: { state: 'off', eligible: true } }], { shop: { verfuegbar: true }, termine: { verfuegbar: true, bestellbar: true } })
|
||||
const reihenfolge = ['news', 'termine', 'shop', 'analyse'].map((k) => mitAnalyse.indexOf(`data-fn-tile="${k}"`))
|
||||
assert.ok(reihenfolge.every((i) => i >= 0))
|
||||
assert.deepEqual([...reihenfolge].sort((a, b) => a - b), reihenfolge)
|
||||
})
|
||||
|
||||
test('Webshop-Kachel: mehrere Websites ⇒ aktives zuerst, dann Einrichtung, dann abgeschaltet; ohne Website ein Hinweis statt Knopf', async () => {
|
||||
const mehrere = await renderMit([shopProject('a', 'off'), shopProject('b', 'disabled'), shopProject('c', 'setup'), shopProject('d', 'active')], { shop: { verfuegbar: true } })
|
||||
assert.match(tile(mehrere, 'shop'), /data-project="d">Webshop öffnen/)
|
||||
assert.match(tile(mehrere, 'shop'), /aktiv für 1 von 4 Websites/)
|
||||
const ohneAktiv = await renderMit([shopProject('a', 'off'), shopProject('b', 'disabled'), shopProject('c', 'ordered')], { shop: { verfuegbar: true } })
|
||||
assert.match(tile(ohneAktiv, 'shop'), /data-project="c">Zahlung offen/)
|
||||
const keine = await renderMit([], { shop: { verfuegbar: true } })
|
||||
assert.match(tile(keine, 'shop'), /Sobald deine Website angelegt ist/)
|
||||
assert.doesNotMatch(tile(keine, 'shop'), /<button/)
|
||||
})
|
||||
|
||||
test('Webshop-Kachel: Projekt-ID maskiert, Klick legt das Wunschprojekt in #webshop-body', async () => {
|
||||
const bose = await renderMit([shopProject('"><img src=x>', 'off')], { shop: { verfuegbar: true } })
|
||||
assert.doesNotMatch(bose, /<img/)
|
||||
// Klick-Handler: derselbe Aufbau wie bei der Terminplaner-Kachel
|
||||
globalThis.fetch = async () => ({ ok: true, status: 200, json: async () => ({ projects: [shopProject('p7', 'active')], shop: { verfuegbar: true } }) })
|
||||
const handlers = {}
|
||||
const knopf = { dataset: { project: 'p7' }, addEventListener: (typ, fn) => { handlers[typ] = fn } }
|
||||
const root = { innerHTML: '', querySelector: (sel) => (sel === '[data-fn-tile="shop"] .fn-tile-cta[data-project]' ? knopf : null) }
|
||||
const body = { dataset: {} }
|
||||
const vorher = globalThis.document
|
||||
globalThis.document = { getElementById: (id) => (id === 'webshop-body' ? body : null) }
|
||||
try {
|
||||
await initFunktionen(root)
|
||||
handlers.click({ currentTarget: knopf })
|
||||
} finally {
|
||||
globalThis.document = vorher
|
||||
}
|
||||
assert.equal(body.dataset.wunschProjekt, 'p7')
|
||||
})
|
||||
Reference in new issue
Block a user