Files
Webklar-Kundenbereich/test/shopTabUebersicht.test.js
T
ClaudeandClaude Opus 5.5 3ed96a5838 Shop G3: Reiter Übersicht mit Checkliste und Öffnen-Schalter, Stripe-Konto, Einbau-Code mit Vorschau und Kennzahlen
Die Platzhalter-Prüfungen in shopView.test.js hängen nicht mehr am Text „Kommt gleich …“.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:10:30 +00:00

415 lines
23 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { installFakeDom } from './support/fakeDom.js'
import { h, request } from '../public/shop-dom.js'
import { render } from '../public/shop-tab-uebersicht.js'
// Task G3: Reiter „Übersicht“ (public/shop-tab-uebersicht.js) mit Fake-DOM und Fake-fetch. Die Antworten sind von Hand gebaut
// (Form: server/routes/shop.js, GET /status, POST /oeffnen, POST /stripe/einrichten|link|aktualisieren).
const SNIPPET = '<div data-webklar-shop="abc123"></div>\n<script src="https://project.webklar.com/embed/shop.js" defer></script>'
const VORSCHAU = 'https://project.webklar.com/shop/abc123'
const URL_STRIPE = 'https://connect.stripe.test/setup/e/acct_1/xyz'
const punkte = (...oks) => oks.map((ok, i) => ({ id: `p${i}`, ok, text: `Punkt ${i} ${ok ? 'erledigt' : 'fehlt'}` }))
const bereitschaft = (...oks) => ({ bereit: oks.every(Boolean), punkte: punkte(...oks) })
const STRIPE_OK = { charges_enabled: true, payouts_enabled: true, details_submitted: true, currently_due: [], past_due: [], disabled_reason: null }
const status = (extra = {}) => ({
verfuegbar: true, state: 'active', bereitschaft: bereitschaft(true, true, true), geoeffnet: false, schalter: false, siteKey: 'abc123',
snippet: SNIPPET, vorschauUrl: VORSCHAU, stripe: { verbunden: true, status: STRIPE_OK },
zahlen: { artikel: 4, aktiveArtikel: 3, offeneBestellungen: 2, bestellungen30: 11, umsatz30Cent: 123450 }, ...extra,
})
/** Fake-Server: jede Anfrage geht an route(aufruf) ⇒ { status, body }; Standard 200 {}. */
function starte(route = () => ({ body: {} })) {
return installFakeDom({ antworten: Array.from({ length: 40 }, () => route) })
}
/** ctx wie shop.js ihn baut; meldungen sammelt zeigeMeldung, neuLaden gibt `neu` (oder den alten Stand) zurück. */
function baueCtx(st, { nurLesen = false, stripeRueckkehr = '', neu = null } = {}) {
const ctx = {
projectId: 'p1',
status: st,
nurLesen,
stripeRueckkehr,
meldungen: [],
geladen: 0,
aktuell: () => true,
request,
zeigeMeldung: (text, art) => ctx.meldungen.push({ text, art }),
neuLaden: async () => {
ctx.geladen += 1
if (neu) ctx.status = typeof neu === 'function' ? neu() : neu
return ctx.status
},
}
return ctx
}
async function zeige(fx, st, optionen = {}) {
const ctx = baueCtx(st, optionen)
const panel = h('div', { id: 'sh-panel' })
fx.document.body.append(panel)
await render(panel, ctx)
await fx.ruhe()
return { panel, ctx }
}
const mit = (route, fn) => async () => {
const fx = starte(route)
try {
await fn(fx)
} finally {
fx.restore()
}
}
const knopf = (fx, panel, aktion) => fx.finde(panel, `[data-aktion="${aktion}"]`)
const posts = (fx) => fx.fetchLog.filter((a) => a.method !== 'GET')
const karte = (fx, panel, titel) => fx.alle(panel, 'section').find((s) => fx.finde(s, 'h3')?.textContent === titel)
/* ------------------------------------------------------------ Darstellung --- */
test('Übersicht: vier Karten, Checkliste mit Haken und Kreis, Zustand geschlossen', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ bereitschaft: bereitschaft(true, false, true) }))
assert.deepEqual(fx.alle(panel, 'section').map((s) => fx.finde(s, 'h3').textContent), ['Bereit zum Öffnen', 'Stripe-Konto', 'Auf deiner Website', 'Zahlen'])
const eintraege = fx.alle(panel, '[data-punkt]')
assert.equal(eintraege.length, 3)
assert.deepEqual(eintraege.map((li) => li.dataset.ok), ['1', '0', '1'])
assert.match(fx.text(eintraege[0]), /✓/)
assert.match(fx.text(eintraege[1]), /○/)
assert.match(fx.text(eintraege[1]), /Punkt 1 fehlt/)
assert.equal(fx.finde(panel, '[data-zustand]').textContent, 'Dein Shop ist geschlossen.')
}))
test('Übersicht: geöffnet zeigt den Zustand und „Shop schließen“', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ geoeffnet: true, schalter: true }))
assert.equal(fx.finde(panel, '[data-zustand]').textContent, 'Dein Shop ist geöffnet.')
assert.equal(knopf(fx, panel, 'schliessen').textContent, 'Shop schließen')
assert.equal(knopf(fx, panel, 'oeffnen'), null)
}))
test('Übersicht: ohne Bereitschaft (null) gibt es Hinweis, aber keinen Schalter', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ bereitschaft: null, snippet: null, vorschauUrl: null, zahlen: null, stripe: { verbunden: false, status: null } }))
assert.match(fx.text(karte(fx, panel, 'Bereit zum Öffnen')), /Die Checkliste erscheint/)
assert.equal(knopf(fx, panel, 'oeffnen'), null)
assert.equal(knopf(fx, panel, 'schliessen'), null)
assert.ok(!karte(fx, panel, 'Zahlen'), 'ohne Zahlen keine Karte')
assert.match(fx.text(karte(fx, panel, 'Auf deiner Website')), /Der Einbau-Code erscheint/)
}))
test('Stripe nicht verbunden: Erklärung (eigenes Konto, Gebühren, kein Geld über WEBklar, kein PayPal) und „Stripe-Konto einrichten“', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ stripe: { verbunden: false, status: null } }))
const text = fx.text(karte(fx, panel, 'Stripe-Konto'))
assert.match(text, /direkt auf dein eigenes Stripe-Konto/)
assert.match(text, /Stripe-Gebühren zahlst du direkt an Stripe/)
assert.match(text, /WEBklar bekommt kein Geld deiner Käufer/)
assert.match(text, /PayPal ist über Stripe für Shops wie deinen leider nicht möglich/)
assert.equal(knopf(fx, panel, 'stripe-einrichten').textContent, 'Stripe-Konto einrichten')
assert.equal(knopf(fx, panel, 'stripe-link'), null)
assert.equal(fx.finde(panel, '[data-link="dashboard"]'), null)
}))
test('Stripe verbunden und fertig: Zahlungen und Auszahlungen aktiv, keine fehlenden Angaben, drei Aktionen', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status())
const k = karte(fx, panel, 'Stripe-Konto')
assert.equal(fx.finde(k, '[data-stripe="zahlungen"]').textContent, 'Zahlungen: aktiv')
assert.equal(fx.finde(k, '[data-stripe="auszahlungen"]').textContent, 'Auszahlungen: aktiv')
assert.equal(fx.finde(k, '[data-stripe="fehlend"]').textContent, 'Keine fehlenden Angaben')
assert.equal(knopf(fx, panel, 'stripe-link').textContent, 'Angaben ergänzen')
assert.equal(knopf(fx, panel, 'stripe-aktualisieren').textContent, 'Status aktualisieren')
const dash = fx.finde(panel, '[data-link="dashboard"]')
assert.equal(dash.tagName, 'A')
assert.equal(dash.getAttribute('href'), 'https://dashboard.stripe.com/')
assert.equal(dash.getAttribute('target'), '_blank')
assert.equal(dash.getAttribute('rel'), 'noopener noreferrer')
assert.equal(dash.textContent, 'Stripe-Dashboard öffnen')
assert.equal(knopf(fx, panel, 'stripe-einrichten'), null)
}))
test('Stripe verbunden mit offenen Punkten: Zähler der fehlenden Angaben, Hinweis und Einschränkung', mit(undefined, async (fx) => {
const s = { charges_enabled: false, payouts_enabled: false, details_submitted: false, currently_due: ['a', 'b', 'c'], past_due: ['b'], disabled_reason: 'requirements.past_due', hinweis: 'Klarna ist in deinem Stripe-Konto noch nicht aktiv.' }
const { panel } = await zeige(fx, status({ stripe: { verbunden: true, status: s } }))
const k = karte(fx, panel, 'Stripe-Konto')
assert.equal(fx.finde(k, '[data-stripe="zahlungen"]').textContent, 'Zahlungen: noch nicht freigeschaltet')
assert.equal(fx.finde(k, '[data-stripe="auszahlungen"]').textContent, 'Auszahlungen: noch nicht freigeschaltet')
assert.equal(fx.finde(k, '[data-stripe="fehlend"]').textContent, '3 Angaben fehlen noch', 'überfällige zählen nicht doppelt')
assert.match(fx.finde(k, '[data-stripe="angaben"]').textContent, /noch nicht abgeschickt/)
assert.match(fx.finde(k, '[data-stripe="gesperrt"]').textContent, /requirements\.past_due/)
assert.equal(fx.finde(k, '[data-stripe="hinweis"]').textContent, 'Klarna ist in deinem Stripe-Konto noch nicht aktiv.')
const eins = await zeige(fx, status({ stripe: { verbunden: true, status: { ...STRIPE_OK, currently_due: ['x'] } } }))
assert.equal(fx.finde(eins.panel, '[data-stripe="fehlend"]').textContent, '1 Angabe fehlt noch')
}))
test('Stripe verbunden, Stand noch nicht gelesen (status null): Hinweis statt Zeilen', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ stripe: { verbunden: true, status: null } }))
assert.match(fx.text(karte(fx, panel, 'Stripe-Konto')), /noch nicht gelesen/)
}))
test('Website-Karte: Code nur lesbar im <pre><code>, Vorschau-Link, Satz „WEBklar baut den Shop ein“', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status())
const k = karte(fx, panel, 'Auf deiner Website')
const code = fx.finde(k, 'pre code')
assert.equal(code.textContent, SNIPPET)
assert.equal(fx.finde(k, 'textarea, input'), null, 'nicht editierbar')
assert.match(fx.text(k), /WEBklar baut den Shop für dich in deine Website ein/)
const vorschau = fx.finde(k, '[data-link="vorschau"]')
assert.equal(vorschau.getAttribute('href'), VORSCHAU)
assert.equal(vorschau.getAttribute('target'), '_blank')
assert.equal(vorschau.getAttribute('rel'), 'noopener noreferrer')
}))
test('Vorschau-Link nur für https-Adressen', mit(undefined, async (fx) => {
for (const schlecht of ['http://project.webklar.com/shop/x', 'javascript:alert(1)', '//evil.example/x', 'https://user:pw@evil.example/']) {
const { panel } = await zeige(fx, status({ vorschauUrl: schlecht }))
assert.equal(fx.finde(panel, '[data-link="vorschau"]'), null, schlecht)
}
}))
test('Zahlen: Artikel, offene Bestellungen, Bestellungen und Umsatz 30 Tage; fehlende Werte fehlen', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status())
const k = karte(fx, panel, 'Zahlen')
assert.equal(fx.finde(k, '[data-zahl="artikel"] dd').textContent, '4 (davon 3 aktiv)')
assert.equal(fx.finde(k, '[data-zahl="offene"] dd').textContent, '2')
assert.equal(fx.finde(k, '[data-zahl="bestellungen30"] dd').textContent, '11')
assert.equal(fx.finde(k, '[data-zahl="umsatz30"] dd').textContent, '1.234,50\u00a0€')
const teil = await zeige(fx, status({ zahlen: { artikel: 0 } }))
assert.equal(fx.alle(teil.panel, '[data-zahl]').length, 1)
assert.equal(fx.finde(teil.panel, '[data-zahl="artikel"] dd').textContent, '0')
}))
test('Fremdtext (Punkte, Hinweis, Snippet) kommt nur als Text in die Seite', mit(undefined, async (fx) => {
const boese = '<img src=x onerror=alert(1)>'
const st = status({
bereitschaft: { bereit: false, punkte: [{ id: 'x', ok: false, text: boese }] },
stripe: { verbunden: true, status: { ...STRIPE_OK, hinweis: boese } },
snippet: boese,
})
const { panel } = await zeige(fx, st)
assert.equal(fx.alle(panel, 'img').length, 0)
assert.ok(fx.text(panel).includes(boese))
}))
/* ---------------------------------------------------------------- Öffnen --- */
test('„Shop öffnen“ ist gesperrt, solange nicht alles bereit ist, und tut dann nichts', mit(undefined, async (fx) => {
const { panel, ctx } = await zeige(fx, status({ bereitschaft: bereitschaft(true, false, true) }))
const k = knopf(fx, panel, 'oeffnen')
assert.equal(k.textContent, 'Shop öffnen')
assert.equal(k.disabled, true)
await fx.klick(k)
assert.equal(posts(fx).length, 0)
assert.equal(ctx.meldungen.length, 0)
}))
test('„Shop öffnen“ schickt { projectId, offen: true }, zeigt den neuen Zustand und lädt den Stand neu', mit((a) => {
if (a.method === 'POST' && a.url === '/api/shop/oeffnen') return { body: { schalter: true, geoeffnet: true, bereitschaft: bereitschaft(true, true, true) } }
return { body: {} }
}, async (fx) => {
const { panel, ctx } = await zeige(fx, status(), { neu: () => status({ schalter: true, geoeffnet: true }) })
assert.equal(knopf(fx, panel, 'oeffnen').disabled, false)
await fx.klick(knopf(fx, panel, 'oeffnen'))
assert.deepEqual(posts(fx).map((a) => [a.method, a.url, a.body]), [['POST', '/api/shop/oeffnen', { projectId: 'p1', offen: true }]])
assert.equal(fx.finde(panel, '[data-zustand]').textContent, 'Dein Shop ist geöffnet.')
assert.equal(knopf(fx, panel, 'schliessen').textContent, 'Shop schließen')
assert.equal(ctx.geladen, 1)
assert.deepEqual(ctx.meldungen, [{ text: 'Dein Shop ist jetzt geöffnet.', art: 'ok' }])
}))
test('„Shop schließen“ schickt { projectId, offen: false }', mit((a) => {
if (a.url === '/api/shop/oeffnen') return { body: { schalter: false, geoeffnet: false, bereitschaft: bereitschaft(true, true, true) } }
return { body: {} }
}, async (fx) => {
const { panel, ctx } = await zeige(fx, status({ schalter: true, geoeffnet: true }), { neu: () => status() })
await fx.klick(knopf(fx, panel, 'schliessen'))
assert.deepEqual(posts(fx).map((a) => a.body), [{ projectId: 'p1', offen: false }])
assert.equal(fx.finde(panel, '[data-zustand]').textContent, 'Dein Shop ist geschlossen.')
assert.equal(knopf(fx, panel, 'oeffnen').textContent, 'Shop öffnen')
assert.equal(ctx.meldungen[0].text, 'Dein Shop ist jetzt geschlossen.')
}))
test('409 nicht_bereit: die Punkte des Servers ersetzen die Checkliste, der Fehlertext erscheint, der Knopf ist wieder frei zu', mit((a) => {
if (a.url === '/api/shop/oeffnen') return { status: 409, body: { error: 'Der Shop kann noch nicht geöffnet werden. Bitte erledige zuerst die offenen Punkte.', code: 'nicht_bereit', punkte: punkte(true, false, false) } }
return { body: {} }
}, async (fx) => {
const { panel, ctx } = await zeige(fx, status()) // veralteter Stand: alles bereit
await fx.klick(knopf(fx, panel, 'oeffnen'))
assert.deepEqual(fx.alle(panel, '[data-punkt]').map((li) => li.dataset.ok), ['1', '0', '0'])
assert.equal(ctx.meldungen.length, 1)
assert.equal(ctx.meldungen[0].art, 'fehler')
assert.match(ctx.meldungen[0].text, /noch nicht geöffnet werden/)
assert.equal(knopf(fx, panel, 'oeffnen').disabled, true, 'nach dem Fehler stimmt der Stand: nicht bereit')
}))
test('Kundenansicht: 403 des Servers wird als Fehlertext gezeigt', mit((a) => {
if (a.url.startsWith('/api/shop/')) return { status: 403, body: { error: 'In der Kundenansicht kann das nicht geändert werden.' } }
return { body: {} }
}, async (fx) => {
const { panel, ctx } = await zeige(fx, status())
await fx.klick(knopf(fx, panel, 'oeffnen'))
await fx.klick(knopf(fx, panel, 'stripe-aktualisieren'))
assert.deepEqual(ctx.meldungen, [
{ text: 'In der Kundenansicht kann das nicht geändert werden.', art: 'fehler' },
{ text: 'In der Kundenansicht kann das nicht geändert werden.', art: 'fehler' },
])
assert.equal(knopf(fx, panel, 'oeffnen').disabled, false, 'Knöpfe sind nach dem Fehler wieder bedienbar')
}))
/* ---------------------------------------------------------------- Stripe --- */
test('„Stripe-Konto einrichten“ schickt { projectId } und geht nur auf eine https-Adresse', mit((a) => {
if (a.url === '/api/shop/stripe/einrichten') return { body: { url: URL_STRIPE } }
return { body: {} }
}, async (fx) => {
const { panel } = await zeige(fx, status({ stripe: { verbunden: false, status: null } }))
await fx.klick(knopf(fx, panel, 'stripe-einrichten'))
assert.deepEqual(posts(fx).map((a) => [a.url, a.body]), [['/api/shop/stripe/einrichten', { projectId: 'p1' }]])
assert.equal(fx.window.location.href, URL_STRIPE)
}))
test('Adresse von Stripe ohne https: keine Navigation, Fehlertext, Stand wird neu geladen', async () => {
for (const url of ['http://connect.stripe.test/x', 'javascript:alert(1)', '//evil.example', '', undefined, 'https://u:p@evil.example/']) {
const fx = starte((a) => (a.url === '/api/shop/stripe/einrichten' ? { body: { url } } : { body: {} }))
try {
fx.window.location.href = 'http://localhost/dashboard.html'
const { panel, ctx } = await zeige(fx, status({ stripe: { verbunden: false, status: null } }))
await fx.klick(knopf(fx, panel, 'stripe-einrichten'))
assert.equal(fx.window.location.href, 'http://localhost/dashboard.html', String(url))
assert.equal(ctx.meldungen.at(-1).art, 'fehler')
assert.match(ctx.meldungen.at(-1).text, /keine gültige Adresse/)
assert.equal(ctx.geladen, 1, 'das Konto ist angelegt: Stand neu holen')
} finally {
fx.restore()
}
}
})
test('„Angaben ergänzen“ holt einen neuen Link (POST /stripe/link) und geht dorthin', mit((a) => {
if (a.url === '/api/shop/stripe/link') return { body: { url: URL_STRIPE } }
return { body: {} }
}, async (fx) => {
const { panel } = await zeige(fx, status({ stripe: { verbunden: true, status: { ...STRIPE_OK, currently_due: ['x'] } } }))
await fx.klick(knopf(fx, panel, 'stripe-link'))
assert.deepEqual(posts(fx).map((a) => [a.url, a.body]), [['/api/shop/stripe/link', { projectId: 'p1' }]])
assert.equal(fx.window.location.href, URL_STRIPE)
}))
test('„Status aktualisieren“ schickt { projectId } und zeigt den neuen Stand samt Meldung', mit((a) => {
if (a.url === '/api/shop/stripe/aktualisieren') return { body: { stripe: { verbunden: true, status: { ...STRIPE_OK, charges_enabled: false, currently_due: ['a', 'b'] } } } }
return { body: {} }
}, async (fx) => {
const neuerStand = status({ stripe: { verbunden: true, status: { ...STRIPE_OK, charges_enabled: false, currently_due: ['a', 'b'] } } })
const { panel, ctx } = await zeige(fx, status(), { neu: neuerStand })
await fx.klick(knopf(fx, panel, 'stripe-aktualisieren'))
assert.deepEqual(posts(fx).map((a) => [a.url, a.body]), [['/api/shop/stripe/aktualisieren', { projectId: 'p1' }]])
assert.equal(fx.finde(panel, '[data-stripe="zahlungen"]').textContent, 'Zahlungen: noch nicht freigeschaltet')
assert.equal(fx.finde(panel, '[data-stripe="fehlend"]').textContent, '2 Angaben fehlen noch')
assert.equal(ctx.geladen, 1)
assert.deepEqual(ctx.meldungen, [{ text: 'Der Stand deines Stripe-Kontos ist aktuell.', art: 'ok' }])
}))
test('Rückkehr von Stripe (stripeRueckkehr): der Stand wird einmal automatisch gelesen', mit((a) => {
if (a.url === '/api/shop/stripe/aktualisieren') return { body: { stripe: { verbunden: true, status: STRIPE_OK } } }
return { body: {} }
}, async (fx) => {
const ctx = baueCtx(status({ stripe: { verbunden: true, status: { ...STRIPE_OK, charges_enabled: false } } }), { stripeRueckkehr: 'zurueck', neu: status() })
const panel = h('div')
fx.document.body.append(panel)
await render(panel, ctx)
assert.deepEqual(posts(fx).map((a) => [a.url, a.body]), [['/api/shop/stripe/aktualisieren', { projectId: 'p1' }]])
assert.equal(fx.finde(panel, '[data-stripe="zahlungen"]').textContent, 'Zahlungen: aktiv')
assert.match(ctx.meldungen[0].text, /Willkommen zurück von Stripe/)
assert.equal(ctx.meldungen[0].art, 'ok')
await render(panel, ctx) // Reiterwechsel mit demselben ctx: nicht noch einmal
assert.equal(posts(fx).length, 1)
}))
test('Rückkehr „neu“ (Link abgelaufen): Stand lesen und auf „Angaben ergänzen“ hinweisen; ohne Konto kein Aufruf', mit((a) => {
if (a.url === '/api/shop/stripe/aktualisieren') return { body: { stripe: { verbunden: true, status: STRIPE_OK } } }
return { body: {} }
}, async (fx) => {
const { ctx } = await zeige(fx, status(), { stripeRueckkehr: 'neu' })
assert.equal(posts(fx).length, 1)
assert.match(ctx.meldungen[0].text, /abgelaufen/)
const ohne = await zeige(fx, status({ stripe: { verbunden: false, status: null } }), { stripeRueckkehr: 'zurueck' })
assert.equal(posts(fx).length, 1, 'kein Konto, nichts zu lesen')
assert.equal(ohne.ctx.meldungen.length, 0)
}))
test('Fehler beim automatischen Lesen nach der Rückkehr wird gemeldet', mit(() => ({ status: 502, body: { error: 'Stripe ist gerade nicht erreichbar. Bitte versuch es gleich noch einmal.' } }), async (fx) => {
const { ctx, panel } = await zeige(fx, status(), { stripeRueckkehr: 'zurueck' })
assert.deepEqual(ctx.meldungen, [{ text: 'Stripe ist gerade nicht erreichbar. Bitte versuch es gleich noch einmal.', art: 'fehler' }])
assert.ok(knopf(fx, panel, 'stripe-aktualisieren'), 'die Karte bleibt bedienbar')
}))
/* ---------------------------------------------------------------- Kopieren --- */
function setzeNavigator(wert) {
const alt = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
Object.defineProperty(globalThis, 'navigator', { value: wert, configurable: true, writable: true, enumerable: true })
return () => (alt ? Object.defineProperty(globalThis, 'navigator', alt) : delete globalThis.navigator)
}
test('„Code kopieren“ schreibt den Code in die Zwischenablage und bestätigt', mit(undefined, async (fx) => {
const kopiert = []
const zurueck = setzeNavigator({ clipboard: { writeText: async (t) => { kopiert.push(t) } } })
try {
const { panel } = await zeige(fx, status())
await fx.klick(knopf(fx, panel, 'kopieren'))
assert.deepEqual(kopiert, [SNIPPET])
assert.equal(fx.finde(panel, '[data-kopiert]').textContent, 'Der Code ist kopiert.')
} finally {
zurueck()
}
}))
test('„Code kopieren“ ohne Zwischenablage: Ausweich über execCommand, sonst Hinweis zum Kopieren von Hand', mit(undefined, async (fx) => {
const zurueck = setzeNavigator({ clipboard: { writeText: async () => { throw new Error('verweigert') } } })
try {
const { panel } = await zeige(fx, status())
fx.document.execCommand = (befehl) => befehl === 'copy'
await fx.klick(knopf(fx, panel, 'kopieren'))
assert.equal(fx.finde(panel, '[data-kopiert]').textContent, 'Der Code ist kopiert.')
assert.equal(fx.alle(fx.document.body, 'textarea').length, 0, 'das Hilfsfeld wird wieder entfernt')
fx.document.execCommand = () => false
await fx.klick(knopf(fx, panel, 'kopieren'))
assert.match(fx.finde(panel, '[data-kopiert]').textContent, /Das Kopieren hat nicht geklappt/)
delete fx.document.execCommand
await fx.klick(knopf(fx, panel, 'kopieren'))
assert.match(fx.finde(panel, '[data-kopiert]').textContent, /Das Kopieren hat nicht geklappt/)
} finally {
zurueck()
}
}))
/* ---------------------------------------------------------------- Nur lesen --- */
test('nurLesen (Shop abgeschaltet): keine Knöpfe, nur Links', mit(undefined, async (fx) => {
const { panel } = await zeige(fx, status({ geoeffnet: false, schalter: true }), { nurLesen: true })
assert.equal(fx.alle(panel, 'button').length, 0)
assert.ok(fx.finde(panel, '[data-link="dashboard"]'))
assert.ok(fx.finde(panel, '[data-link="vorschau"]'))
assert.ok(fx.finde(panel, '[data-zustand]'))
const ohneKonto = await zeige(fx, status({ stripe: { verbunden: false, status: null } }), { nurLesen: true })
assert.equal(fx.alle(ohneKonto.panel, 'button').length, 0)
assert.match(fx.text(karte(fx, ohneKonto.panel, 'Stripe-Konto')), /eigenes Stripe-Konto/)
}))
test('nurLesen: die Rückkehr von Stripe löst keinen Aufruf aus', mit(undefined, async (fx) => {
await zeige(fx, status(), { nurLesen: true, stripeRueckkehr: 'zurueck' })
assert.equal(fx.fetchLog.length, 0)
}))
test('Antwort nach dem Wechsel des Reiters zeichnet nicht in das fremde Panel', mit((a) => {
if (a.url === '/api/shop/oeffnen') return { body: { schalter: true, geoeffnet: true, bereitschaft: bereitschaft(true) } }
return { body: {} }
}, async (fx) => {
const { panel, ctx } = await zeige(fx, status())
const klickt = fx.klick(knopf(fx, panel, 'oeffnen'))
panel.replaceChildren(h('p', null, 'anderer Reiter'))
await klickt
assert.equal(fx.text(panel), 'anderer Reiter')
assert.equal(ctx.meldungen.length, 0)
}))
/* ---------------------------------------------------------------- Quelle --- */
test('Quelltext: kein innerHTML, outerHTML, insertAdjacentHTML, document.write, eval', () => {
const quelle = readFileSync(new URL('../public/shop-tab-uebersicht.js', import.meta.url), 'utf8')
for (const verboten of ['innerHTML', 'outerHTML', 'insertAdjacentHTML', 'document.write', 'eval(', 'new Function']) assert.ok(!quelle.includes(verboten), verboten)
})