Funktionen-Seite: Preis und Bestelltexte aus dem Register
GET /api/funktionen liefert features.news und features.termine (oeffentlicheAnsicht, auch wenn der Terminplaner noch nicht freigegeben ist) und ustHinweis; setupEstimate und consentText bleiben als News-Alias. Die News-Kachel und die Buchungsseite lesen Preis, Knopftext und Zustimmungstext aus features.news und fallen ohne features auf die bisherigen Werte zurueck. News sendet weiter consent: true an /api/funktionen/news/checkout. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f57dad73ca
commit
cf9f9b30a0
4 files changed
+186
-13
No files matched your search
@@ -13,7 +13,7 @@ const src = readFileSync(new URL('../public/funktionen.js', import.meta.url), 'u
|
||||
.replace("'/geraete.js'", `'${fileUrl('geraete.js')}'`)
|
||||
const file = join(dir, 'funktionen.mjs')
|
||||
writeFileSync(file, src)
|
||||
const { initFunktionen, initAnalyse, renderAnalyseOff } = await import(pathToFileURL(file).href)
|
||||
const { initFunktionen, initNews, initAnalyse, renderAnalyseOff } = await import(pathToFileURL(file).href)
|
||||
|
||||
const news = { state: 'off' }
|
||||
const project = (id, analyse) => ({ projectId: id, projectName: `P ${id}`, liveUrl: '', features: { news }, analyse })
|
||||
@@ -49,6 +49,100 @@ test('Kacheln rendern ohne Fehler: News, Analyse comingSoon/aus/aktiv/gesperrt',
|
||||
assert.doesNotMatch(disabled, /Dev-Modus/)
|
||||
})
|
||||
|
||||
// ---- News-Kachel und -Buchungsseite: Preis und Zustimmungstext aus dem Register (features.news), Rückfall ohne features
|
||||
async function renderMit(projects, extra) {
|
||||
globalThis.fetch = async () => ({ ok: true, status: 200, json: async () => ({ projects, ...extra }) })
|
||||
const root = { innerHTML: '', querySelector: () => null }
|
||||
await initFunktionen(root)
|
||||
return root.innerHTML
|
||||
}
|
||||
|
||||
test('News-Kachel: Rückfall ohne features ⇒ bisheriger Preis 30 €', async () => {
|
||||
const html = await render([project('a', { comingSoon: true })])
|
||||
assert.match(tile(html, 'news'), /Einrichten lassen · 30 €/)
|
||||
// auch bei leerem oder unvollständigem features-Objekt
|
||||
for (const features of [{}, { news: {} }, { news: { priceLabel: '' } }, { news: { priceLabel: 31 } }, { termine: { priceLabel: '149 €' } }]) {
|
||||
const rueck = await renderMit([project('a', { comingSoon: true })], { features })
|
||||
assert.match(tile(rueck, 'news'), /Einrichten lassen · 30 €/, JSON.stringify(features))
|
||||
}
|
||||
})
|
||||
|
||||
test('News-Kachel: Preis aus dem Register ⇒ „Einrichten lassen · 31 €“', async () => {
|
||||
const html = await renderMit([project('a', { comingSoon: true })], { features: { news: { priceLabel: '31 €' } } })
|
||||
assert.match(tile(html, 'news'), /Einrichten lassen · 31 €/)
|
||||
assert.doesNotMatch(tile(html, 'news'), /30 €/)
|
||||
// der Preis kommt maskiert in die Kachel
|
||||
const bose = await renderMit([project('a', { comingSoon: true })], { features: { news: { priceLabel: '<img src=x onerror=1>' } } })
|
||||
assert.doesNotMatch(bose, /<img/)
|
||||
})
|
||||
|
||||
const freiesProjekt = { projectId: 'p1', projectName: 'Muster', features: { news: { state: 'off' } } }
|
||||
async function renderBuchung(antwort) {
|
||||
const calls = []
|
||||
globalThis.window = { location: { hash: '#news', pathname: '/dashboard.html', search: '', href: '' }, history: { replaceState() {} } }
|
||||
globalThis.fetch = async (url, opts) => {
|
||||
calls.push({ url: String(url), method: opts?.method, body: opts?.body ? JSON.parse(opts.body) : undefined })
|
||||
if (String(url) === '/api/funktionen/news/checkout') return { ok: true, status: 200, json: async () => ({ url: 'https://checkout.example.test/s1' }) }
|
||||
return { ok: true, status: 200, json: async () => antwort }
|
||||
}
|
||||
const stage = fakeStage()
|
||||
const root = fakeEl()
|
||||
root.querySelector = (sel) => (sel === '#news-stage' ? stage : null)
|
||||
try {
|
||||
await initNews(root, { projectId: 'p1' })
|
||||
// Haken setzen und bestellen
|
||||
const box = stage.els['#news-consent']
|
||||
box.checked = true
|
||||
box.handlers.change()
|
||||
const button = stage.els['#news-order']
|
||||
const knopfAktiv = button.disabled === false
|
||||
await button.handlers.click()
|
||||
return { stage, calls, knopfAktiv, href: globalThis.window.location.href }
|
||||
} finally {
|
||||
delete globalThis.window
|
||||
}
|
||||
}
|
||||
|
||||
test('News-Buchungsseite: Preis, Knopf und Zustimmungstext aus features.news, sendet weiter consent: true', async () => {
|
||||
const register = { priceLabel: '31 €', consent: { version: 'v', knopf: 'Jetzt kaufen · 31 €', items: [{ id: 'beginn', text: 'Registertext der Zustimmung', haken: true }] } }
|
||||
const { stage, calls, knopfAktiv, href } = await renderBuchung({
|
||||
projects: [freiesProjekt],
|
||||
setupEstimate: 'meist innerhalb von 1–2 Wochen',
|
||||
consentText: 'Alter Text',
|
||||
features: { news: register },
|
||||
})
|
||||
assert.match(stage.innerHTML, /<strong>31 €<\/strong>/)
|
||||
assert.match(stage.innerHTML, /inklusive Mehrwertsteuer/, 'der Wortlaut bleibt bis Block F')
|
||||
assert.match(stage.innerHTML, /id="news-order" disabled>Jetzt kaufen · 31 €</)
|
||||
assert.equal(stage.els['#news-consent-text'].textContent, 'Registertext der Zustimmung')
|
||||
assert.equal(knopfAktiv, true)
|
||||
const bestellung = calls.filter((c) => c.url === '/api/funktionen/news/checkout')
|
||||
assert.equal(bestellung.length, 1)
|
||||
assert.equal(bestellung[0].method, 'POST')
|
||||
assert.deepEqual(bestellung[0].body, { projectId: 'p1', consent: true })
|
||||
assert.equal(href, 'https://checkout.example.test/s1')
|
||||
})
|
||||
|
||||
test('News-Buchungsseite: Rückfall ohne features ⇒ 30 €, Standardknopf, consentText der Antwort', async () => {
|
||||
const { stage, calls } = await renderBuchung({ projects: [freiesProjekt], setupEstimate: '', consentText: 'Alter Text' })
|
||||
assert.match(stage.innerHTML, /<strong>30 €<\/strong>/)
|
||||
assert.match(stage.innerHTML, /id="news-order" disabled>Zahlungspflichtig bestellen · 30 €</)
|
||||
assert.equal(stage.els['#news-consent-text'].textContent, 'Alter Text')
|
||||
assert.deepEqual(calls.find((c) => c.url === '/api/funktionen/news/checkout').body, { projectId: 'p1', consent: true })
|
||||
})
|
||||
|
||||
test('News-Buchungsseite: Preis ohne Knopftext ⇒ Standardknopf mit Registerpreis, Knopftext kommt maskiert', async () => {
|
||||
const nurPreis = await renderBuchung({ projects: [freiesProjekt], consentText: 'Alter Text', features: { news: { priceLabel: '31 €' } } })
|
||||
assert.match(nurPreis.stage.innerHTML, /id="news-order" disabled>Zahlungspflichtig bestellen · 31 €</)
|
||||
assert.equal(nurPreis.stage.els['#news-consent-text'].textContent, 'Alter Text')
|
||||
const bose = await renderBuchung({ projects: [freiesProjekt], features: { news: { priceLabel: '31 €', consent: { knopf: '<img src=x onerror=1>', items: [] } } } })
|
||||
assert.doesNotMatch(bose.stage.innerHTML, /<img/)
|
||||
})
|
||||
|
||||
test('statisch: News postet weiter consent: true an /api/funktionen/news/checkout', () => {
|
||||
assert.match(src, /request\('\/api\/funktionen\/news\/checkout',\s*\{[^}]*body:\s*\{[^}]*consent: true/)
|
||||
})
|
||||
|
||||
// ---- Terminplaner-Kachel: nur mit Schalter (termine.verfuegbar), Zustand je Projekt aus features.termine
|
||||
const withTermine = (id, state) => ({ projectId: id, projectName: `P ${id}`, liveUrl: '', features: { news, termine: { state } } })
|
||||
const noAnalyse = { comingSoon: true }
|
||||
|
||||
Reference in new issue
Block a user