import { test } from 'node:test'
import assert from 'node:assert/strict'
import { installFakeDom } from './support/fakeDom.js'
import { zeigeFunktionen, vergissMerker } from '../public/neu/ansicht-funktionen.js'
import { merkeMeldung } from '../public/neu/funktion-einschalten.js'
import { daten, projekt, feature } from './support/knFunktionenDaten.js'
const JETZT = () => new Date('2026-10-20T10:00:00Z')
const MEINE = { bereich: 'funktionen', reiter: 'meine', query: '' }
const ENTDECKEN = { bereich: 'funktionen', reiter: 'entdecken', query: '' }
const mit = (fn) => async () => { vergissMerker(); const fx = installFakeDom(); try { await fn(fx) } finally { fx.restore() } }
function umgebung(fx, { d = daten(), abo, api, post = { feature: { state: 'setup' }, ticketWoid: '100301' }, me = {} } = {}) {
const wurzel = fx.document.createElement('div'); fx.document.body.append(wurzel)
const u = { wurzel, d, rechnungen: [], rechnungenFehler: false, gegangen: [], aufrufe: [], gespeichert: {}, overlay: null }
const basis = async (pfad, o = {}) => {
u.aufrufe.push([o.method ?? 'GET', pfad])
if (o.method === 'POST') return post
if (pfad === '/api/funktionen?kurzinfo=1') return u.d // nur „Meine“ fragt die Kurzinfo ab; ein anderer Pfad wäre „unerwartet“
if (pfad === '/api/invoices') { if (u.rechnungenFehler) throw Object.assign(new Error('x'), { status: 500, body: {} }); return { invoices: u.rechnungen, incomplete: false } }
if (pfad === '/api/abo') { if (abo instanceof Error) throw abo; return abo ?? { subscription: null } }
throw new Error(`unerwartet ${pfad}`)
}
const abh = {
api: api ?? basis, jetzt: JETZT, speicher: { setItem: (k, v) => { u.gespeichert[k] = v } },
geheZu: (hash, o) => { u.gegangen.push(o ? [hash, o] : [hash]) },
oeffneOverlay: ({ schliesse }) => { u.overlay = schliesse },
beendeOverlay: () => { const s = u.overlay; u.overlay = null; s?.() },
}
u.zeige = (route, ctx = {}) => zeigeFunktionen(wurzel, { route, me: { customer: { kundenartWirksam: 'unternehmen' }, viewAs: false, ...me }, nav: 'tab', ...ctx }, abh)
return u
}
const tab = (fx, u, text) => fx.alle(u.wurzel, '[role="tab"]').find((t) => t.textContent === text)
test('Meine: Liste mit Name, Detailzeile und Pfeil, Pille nur ohne aktiven Zustand; Tipp öffnet die Etage mit der Website der Zeile, eine angenommene Anfrage die Bestätigung', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei', { termine: feature('active', { kurzinfo: '3 Termine heute · nächster 10:30' }), shop: feature('off', { anfrageStatus: 'angenommen' }) }), projekt('p2', 'Café', { termine: feature('active') })] }) })
await u.zeige(MEINE)
const zeilen = fx.alle(u.wurzel, 'li[data-fn]')
assert.deepEqual(zeilen.map((z) => [z.getAttribute('data-fn'), z.getAttribute('data-projekt')]), [['termine', 'p1'], ['termine', 'p2'], ['shop', 'p1']])
assert.deepEqual(zeilen.map((z) => [fx.finde(z, '.kn-fn-name').textContent, fx.finde(z, '.kn-fn-zeile2').textContent]), [
['Terminplaner', 'Bäckerei · 3 Termine heute · nächster 10:30'],
['Terminplaner', 'Café · Aktiv'],
['Webshop', 'Bäckerei · Jetzt einschalten'],
])
assert.deepEqual(zeilen.map((z) => fx.finde(z, '.kn-fn-pille')?.textContent ?? null), [null, null, 'Angenommen'])
assert.doesNotMatch(fx.text(u.wurzel), /Aktiv seit/)
for (const z of zeilen) {
const pfeil = fx.finde(z, '.kn-fn-pfeil')
assert.deepEqual([pfeil.textContent, pfeil.getAttribute('aria-hidden')], ['›', 'true'])
assert.equal(fx.finde(z, 'button').lastElementChild.lastElementChild, pfeil, 'der Pfeil steht ganz rechts')
}
await fx.klick(fx.finde(zeilen[0], 'button'))
await fx.klick(fx.finde(zeilen[1], 'button'))
assert.deepEqual(u.gegangen, [['#termine?projekt=p1'], ['#termine?projekt=p2']])
await fx.klick(fx.finde(zeilen[2], 'button'))
assert.ok(fx.finde(u.wurzel, 'section.kn-fn-einschalten[data-key="shop"][data-projekt="p1"]'))
}))
test('Meine leer: der erste Aufruf springt ersetzend zu Entdecken, ein ausdrücklich gewähltes Meine zeigt den Leerzustand', mit(async (fx) => {
const u = umgebung(fx)
await u.zeige(MEINE)
assert.deepEqual(u.gegangen, [['#entdecken', { ersetzen: true }]])
await u.zeige(ENTDECKEN)
u.gegangen.length = 0
await fx.klick(tab(fx, u, 'Meine'))
assert.deepEqual(u.gegangen, [['#funktionen', { ersetzen: true }]])
await u.zeige(MEINE) // so ruft der Router danach auf
assert.equal(u.gegangen.length, 1)
assert.match(fx.text(u.wurzel), /Du hast noch keine Funktion eingeschaltet\./)
}))
test('Rechnungszeile oben: die Ansicht lädt GET /api/invoices selbst (ohne Zähler), nur balance > 0, in Meine und Entdecken; fällt der Abruf aus, bleibt die Liste', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'B', { news: feature('active') })] }) })
await u.zeige(MEINE)
assert.equal(fx.finde(u.wurzel, '.kn-rechnung-offen'), null)
assert.ok(u.aufrufe.some(([m, p]) => m === 'GET' && p === '/api/invoices'))
u.rechnungen = [{ id: 'r0', number: 'RE-2026-0040', balance: 0, portalUrl: null }, { id: 'r1', number: 'RE-2026-0042', balance: 50, portalUrl: 'https://invoice.stripe.com/i/x' }]
await u.zeige(MEINE)
assert.match(fx.text(fx.finde(u.wurzel, '.kn-rechnung-offen')), /^Rechnung offen · RE-2026-0042 · 50,00\s€ · Zahlen$/)
assert.ok(fx.finde(u.wurzel, 'li[data-fn]'))
await u.zeige(ENTDECKEN)
assert.ok(fx.finde(u.wurzel, '.kn-rechnung-offen'))
u.rechnungenFehler = true
await u.zeige(MEINE)
assert.equal(fx.finde(u.wurzel, '.kn-rechnung-offen'), null)
assert.ok(fx.finde(u.wurzel, 'li[data-fn]'))
}))
test('Entdecken: Bild, Name, Beschreibung, "Im … Abo enthalten", Knopf; Katalogpreise erscheinen nie', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ katalogEdit: { termine: { priceLabel: '149 €' } } }) })
await u.zeige(ENTDECKEN)
const e = fx.alle(u.wurzel, 'section[data-fn]')
assert.deepEqual(e.map((x) => x.getAttribute('data-fn')), ['news', 'termine', 'shop', 'unterkunft', 'zeiten', 'analyse'])
assert.equal(fx.finde(e[1], 'img').getAttribute('src'), '/neu/bilder/termine.webp')
assert.deepEqual(['alt', 'width', 'height', 'loading'].map((a) => fx.finde(e[1], 'img').getAttribute(a)), ['Beispiel: Terminplaner', '960', '600', 'lazy'])
assert.match(fx.text(e[1]), /Terminplaner.*Beschreibung Terminplaner\..*Im Wartungs-Abo enthalten/)
assert.match(fx.text(e[5]), /Im Hosting-Abo enthalten/)
assert.equal(fx.finde(e[1], '[data-aktion="einschalten"]').textContent, 'Einschalten')
assert.doesNotMatch(fx.text(u.wurzel), /€|Euro/)
}))
test('Einschalten-Tap (eine Website) geht je Abo-Lage an den richtigen Ort', mit(async (fx) => {
const faelle = [ // [Abo, Funktion, Sprung ('seite' = Bestätigung ohne Sprung)]
[{ produkt: 'wartung' }, 'news', 'seite'],
[{ produkt: 'wartung' }, 'termine', ['#termine?projekt=p1']],
[{ produkt: 'wartung' }, 'shop', ['#webshop?projekt=p1']],
[{ produkt: 'hosting' }, 'analyse', ['#analyse?projekt=p1']],
[{ produkt: 'hosting' }, 'zeiten', ['#wechsel']],
[{ produkt: null, laeuft: false }, 'zeiten', ['#bestellen-wartung']],
[{ produkt: null, laeuft: false }, 'analyse', ['#bestellen-hosting']],
]
for (const [abo, key, sprung] of faelle) {
const u = umgebung(fx, { d: daten({ abo }) })
await u.zeige(ENTDECKEN)
await fx.klick(fx.finde(u.wurzel, `section[data-fn="${key}"] [data-aktion="einschalten"]`))
if (sprung === 'seite') { assert.deepEqual(u.gegangen, []); assert.ok(fx.finde(u.wurzel, `section.kn-fn-einschalten[data-key="${key}"]`)); continue }
assert.deepEqual(u.gegangen, [sprung], `${abo.produkt} ${key}`)
if (sprung[0] === '#wechsel') assert.deepEqual(JSON.parse(u.gespeichert['kn-wechsel-funktion']), { key, projectId: 'p1' })
}
}))
test('Einschalten-Tap mit Meldung: gekündigtes Abo zeigt den Text im Eintrag und geht nirgends hin', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ abo: { gekuendigt: true } }) })
await u.zeige(ENTDECKEN)
const news = fx.finde(u.wurzel, 'section[data-fn="news"]')
await fx.klick(fx.finde(news, '[data-aktion="einschalten"]'))
assert.match(fx.text(fx.finde(news, '[role="alert"]')), /^Dein Abo ist gekündigt\./)
assert.deepEqual(u.gegangen, [])
}))
test('Privatkunde: Webshop zeigt den Hinweis statt des Knopfes; ohne Websites steht ein Hinweis statt der Knöpfe', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ kundenart: 'privat' }), me: { customer: { kundenartWirksam: 'privat' } } })
await u.zeige(ENTDECKEN)
const shop = fx.finde(u.wurzel, 'section[data-fn="shop"]')
assert.match(fx.text(shop), /Webshop gibt es für Betriebe\. Stimmt das nicht\? Schreib uns im Chat\./)
assert.equal(fx.finde(shop, '[data-aktion="einschalten"]'), null)
const leer = umgebung(fx, { d: daten({ projekte: [] }) })
await leer.zeige(ENTDECKEN)
assert.match(fx.text(leer.wurzel), /Sobald deine Website angelegt ist, kannst du Funktionen einschalten\./)
assert.equal(fx.alle(leer.wurzel, '[data-aktion="einschalten"]').length, 0)
}))
test('Zwei Websites: Einschalten fragt "Für welche Website?" und bietet nur Websites ohne die Funktion', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei', { news: feature('active') }), projekt('p2', 'Café'), projekt('p3', 'Kiosk')] }) })
await u.zeige(ENTDECKEN)
const news = fx.finde(u.wurzel, 'section[data-fn="news"]')
await fx.klick(fx.finde(news, '[data-aktion="einschalten"]'))
const wahl = fx.finde(news, '[role="group"]')
assert.equal(wahl.getAttribute('aria-label'), 'Für welche Website?')
assert.deepEqual(fx.alle(wahl, 'button').map((b) => b.textContent), ['Café', 'Kiosk'])
await fx.klick(fx.alle(wahl, 'button')[1])
assert.ok(fx.finde(u.wurzel, 'section.kn-fn-einschalten[data-key="news"][data-projekt="p3"]'))
}))
test('Zwei Websites: ein zweiter Tipp schließt die Auswahl wieder', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei'), projekt('p2', 'Café')] }) })
await u.zeige(ENTDECKEN)
const news = fx.finde(u.wurzel, 'section[data-fn="news"]')
await fx.klick(fx.finde(news, '[data-aktion="einschalten"]'))
assert.ok(fx.finde(news, '[role="group"]'))
await fx.klick(fx.finde(news, '[data-aktion="einschalten"]'))
assert.equal(fx.finde(news, '[role="group"]'), null)
}))
test('Laden und Fehler: erst "Wird geladen …", dann Text und "Nochmal versuchen"; Antwort ohne Abo-Modell', mit(async (fx) => {
let los; const warten = new Promise((r) => { los = r }); let n = 0
const u = umgebung(fx, { api: async (pfad) => { if (pfad === '/api/invoices') return { invoices: [], incomplete: false }; if (++n === 1) { await warten; throw Object.assign(new Error('x'), { status: 500, body: {} }) } return n === 2 ? daten() : { ...daten(), modell: undefined } } })
const lauf = u.zeige(ENTDECKEN)
assert.match(fx.text(u.wurzel), /Wird geladen …/)
los(); await lauf
assert.match(fx.text(u.wurzel), /Die Funktionen konnten gerade nicht geladen werden\./)
await fx.klick(fx.finde(u.wurzel, '[data-aktion="nochmal"]'))
assert.equal(fx.alle(u.wurzel, 'section[data-fn]').length, 6)
await u.zeige(ENTDECKEN)
assert.match(fx.text(u.wurzel), /Die Funktionen sind gerade nicht verfügbar\. Lade die Seite neu\./)
}))
test('veraltete Antwort überschreibt die neuere nicht', mit(async (fx) => {
let langsam; const erster = new Promise((r) => { langsam = r }); let n = 0
const mit1 = (name) => daten({ katalogEdit: { news: { name } }, projekte: [projekt('p1', 'B', { news: feature('active') })] })
const u = umgebung(fx, { api: async (pfad) => (pfad === '/api/invoices' ? { invoices: [], incomplete: false } : ++n === 1 ? (await erster, mit1('Alt-Pinnwand')) : mit1('Neu-Pinnwand')) })
const a = u.zeige(MEINE); const b = u.zeige(MEINE)
await b; langsam(); await a
assert.match(fx.text(u.wurzel), /Neu-Pinnwand/)
assert.doesNotMatch(fx.text(u.wurzel), /Alt-Pinnwand/)
}))
test('Fremdtext und fehlendes Bild: Namen nur als Text, ohne Bild bleibt der Eintrag lesbar', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ katalogEdit: { news: { name: 'News', beschreibung: 'x' } }, projekte: [projekt('p1', 'Café')] }) })
await u.zeige(ENTDECKEN)
assert.equal(fx.alle(u.wurzel, 'b, i, u').length, 0)
const news = fx.finde(u.wurzel, 'section[data-fn="news"]')
await fx.sende(fx.finde(news, 'img'), 'error')
assert.equal(fx.finde(news, 'img'), null)
assert.match(fx.text(news), /News<\/b>/)
}))
test('Bildadresse: nur /neu/bilder/.webp; alles andere erscheint ohne Bild', mit(async (fx) => {
for (const bild of ['javascript:alert(1)', 'https://example.org/x.webp', '//example.org/x.webp', '/neu/bilder/../x.webp', '', null]) {
const u = umgebung(fx, { d: daten({ katalogEdit: { news: { bild } } }) })
await u.zeige(ENTDECKEN)
assert.equal(fx.finde(fx.finde(u.wurzel, 'section[data-fn="news"]'), 'img'), null, String(bild))
}
}))
test('Meine: bei einer Website steht die Detailzeile allein, bei mehreren der Websitename davor; ohne Kurzinfo "Aktiv"', mit(async (fx) => {
const einzel = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei', { news: feature('active', { kurzinfo: '4 Beiträge sichtbar' }), zeiten: feature('active', { kurzinfo: null }) })] }) })
await einzel.zeige(MEINE)
assert.deepEqual(fx.alle(einzel.wurzel, 'li[data-fn] .kn-fn-zeile2').map((e) => e.textContent), ['4 Beiträge sichtbar', 'Aktiv'])
const zwei = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei', { news: feature('active', { kurzinfo: '4 Beiträge sichtbar' }) }), projekt('p2', 'Café')] }) })
await zwei.zeige(MEINE)
assert.deepEqual(fx.alle(zwei.wurzel, 'li[data-fn] .kn-fn-zeile2').map((e) => e.textContent), ['Bäckerei · 4 Beiträge sichtbar'])
}))
test('Meine: Pille und Pfeil liegen rechts nebeneinander (Pille zuerst); Namen und Kurzinfo werden nur als Text gesetzt', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Café', { news: feature('active', { kurzinfo: '4 Beiträge' }), termine: feature('setup', { ticketWoid: '100301' }) })] }) })
await u.zeige(MEINE)
assert.equal(fx.alle(u.wurzel, 'b, u').length, 0)
const [news, termine] = fx.alle(u.wurzel, 'li[data-fn]')
assert.equal(fx.finde(news, '.kn-fn-zeile2').textContent, '4 Beiträge')
const rechts = fx.finde(termine, '.kn-fn-rechts')
assert.deepEqual([...rechts.children].map((k) => k.className), ['kn-fn-pille kn-fn-pille--blau', 'kn-fn-pfeil'])
assert.equal(fx.finde(termine, '.kn-fn-zeile2').textContent, 'Wir richten sie gerade ein')
}))
/* ---------------------------------------------------------------- Teil 2: Overlay, Erfolgsmeldung, Wechsel --- */
test('Kurzinfo nur auf Anfrage: die Seite lädt genau einmal GET /api/funktionen?kurzinfo=1, in beiden Reitern und nach „Nochmal versuchen“', mit(async (fx) => {
const u = umgebung(fx)
await u.zeige(MEINE)
assert.deepEqual(u.aufrufe.filter(([, p]) => p.startsWith('/api/funktionen')), [['GET', '/api/funktionen?kurzinfo=1']])
vergissMerker()
const e = umgebung(fx)
await e.zeige(ENTDECKEN)
assert.deepEqual(e.aufrufe.filter(([, p]) => p.startsWith('/api/funktionen')), [['GET', '/api/funktionen?kurzinfo=1']])
vergissMerker()
let versuche = 0
const f = umgebung(fx, { api: async (pfad) => { if (pfad.startsWith('/api/funktionen')) { versuche += 1; assert.equal(pfad, '/api/funktionen?kurzinfo=1'); if (versuche === 1) throw new Error('x'); return daten() } if (pfad === '/api/invoices') return { invoices: [], incomplete: false }; throw new Error(`unerwartet ${pfad}`) } })
await f.zeige(MEINE)
await fx.klick(fx.finde(f.wurzel, '[data-aktion="nochmal"]'))
assert.equal(versuche, 2)
assert.equal(fx.finde(f.wurzel, '.kn-fehler'), null) // die Fehlermeldung ist weg, die Seite steht
}))
test('Bestätigung als Overlay: Zurück-Geste bringt die Liste ohne neuen Abruf zurück, Abbrechen ebenso', mit(async (fx) => {
const u = umgebung(fx)
await u.zeige(ENTDECKEN)
const geladen = () => u.aufrufe.filter(([m, p]) => m === 'GET' && p === '/api/funktionen?kurzinfo=1').length
await fx.klick(fx.finde(u.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]'))
assert.match(fx.text(u.wurzel), /News-Pinnwand einschalten/)
assert.equal(fx.alle(u.wurzel, 'section[data-fn]').length, 0)
u.overlay() // Zurück-Geste des Browsers (der Router ruft schliesse)
assert.equal(fx.alle(u.wurzel, 'section[data-fn]').length, 6)
await fx.klick(fx.finde(u.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]'))
await fx.klick(fx.finde(u.wurzel, '[data-aktion="abbrechen"]'))
assert.equal(fx.alle(u.wurzel, 'section[data-fn]').length, 6)
assert.deepEqual([geladen(), u.gegangen], [1, []])
}))
test('Overlay: eine ältere Seite zeichnet nach einem neuen Aufruf nichts mehr; die Rechnungszeile und eine gezeigte Meldung bleiben', mit(async (fx) => {
const u = umgebung(fx)
u.rechnungen = [{ id: 'r1', number: 'RE-1', balance: 20, portalUrl: null }]
await u.zeige(ENTDECKEN)
await fx.klick(fx.finde(u.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]'))
const schliesse = u.overlay
await u.zeige(ENTDECKEN) // ein neuer Aufruf (z. B. Router) ersetzt die Anzeige
const vorher = fx.text(u.wurzel)
schliesse() // das alte Overlay meldet sich spät
assert.equal(fx.text(u.wurzel), vorher)
const v = umgebung(fx)
v.rechnungen = [{ id: 'r1', number: 'RE-1', balance: 20, portalUrl: null }]
await v.zeige(ENTDECKEN)
merkeMeldung('Eine Meldung')
await v.zeige(ENTDECKEN)
const aufrufe = v.aufrufe.length
await fx.klick(fx.finde(v.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]'))
v.overlay()
assert.ok(fx.finde(v.wurzel, '.kn-rechnung-offen'))
assert.equal(fx.text(fx.finde(v.wurzel, '[role="status"]')), 'Eine Meldung') // nach dem Schließen noch da, aber nicht neu gemerkt
assert.equal(v.aufrufe.length, aufrufe) // auch kein neuer Abruf der Rechnungen
await v.zeige(ENTDECKEN)
assert.equal(fx.finde(v.wurzel, '[role="status"]'), null)
}))
test('Erfolg: Meldung merken, ersetzend zu Meine, Meldung genau einmal', mit(async (fx) => {
const u = umgebung(fx)
await u.zeige(ENTDECKEN)
await fx.klick(fx.finde(u.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]'))
await fx.klick(fx.finde(u.wurzel, '[data-aktion="einschalten"]'))
assert.deepEqual(u.aufrufe.filter(([m]) => m === 'POST'), [['POST', '/api/funktionen/news/einschalten']])
assert.deepEqual(u.gegangen, [['#funktionen', { ersetzen: true }]])
u.d = daten({ projekte: [projekt('p1', 'Bäckerei Sonnenschein', { news: feature('setup', { ticketWoid: '100301' }) })] })
await u.zeige(MEINE) // so ruft der Router danach auf
assert.equal(fx.text(fx.finde(u.wurzel, '[role="status"]')), 'News-Pinnwand ist eingeschaltet. Die Einrichtung läuft jetzt. Ticket #100301.')
await u.zeige(MEINE)
assert.equal(fx.finde(u.wurzel, '[role="status"]'), null)
}))
test('wechselFunktion (V11): öffnet den Ablauf der Funktion, einmal, nur mit Wartungs-Abo', mit(async (fx) => {
const aboMit = (key) => ({ subscription: { wechselFunktion: { key, projectId: 'p1' } } })
const abgefragt = (u) => u.aufrufe.some(([, p]) => p === '/api/abo')
let u = umgebung(fx, { abo: aboMit('news') })
await u.zeige(MEINE)
assert.ok(fx.finde(u.wurzel, 'section.kn-fn-einschalten[data-key="news"]')) // Bestätigung, kein Sprung
assert.deepEqual(u.gegangen, [])
await u.zeige(MEINE) // zweiter Aufruf: nicht noch einmal in die Funktion
assert.deepEqual(u.gegangen, [['#entdecken', { ersetzen: true }]])
vergissMerker()
u = umgebung(fx, { abo: aboMit('termine') }); await u.zeige(MEINE)
assert.deepEqual(u.gegangen, [['#termine?projekt=p1']]) // Fragebogen zuerst
vergissMerker()
u = umgebung(fx, { d: daten({ abo: { produkt: 'hosting' } }), abo: aboMit('news') }); await u.zeige(MEINE)
assert.equal(abgefragt(u), false)
vergissMerker()
for (const abo of [aboMit('gibtsnicht'), new Error('weg')]) {
u = umgebung(fx, { abo }); await u.zeige(MEINE)
assert.deepEqual(u.gegangen, [['#entdecken', { ersetzen: true }]]) // ungültig oder Fehler: wie ohne Wechsel
}
}))
test('wechselFunktion: nicht, wenn die Website die Funktion schon hat, die Website fehlt oder die Funktion nicht verfügbar ist', mit(async (fx) => {
const abo = { subscription: { wechselFunktion: { key: 'news', projectId: 'p1' } } }
const faelle = [
daten({ projekte: [projekt('p1', 'B', { news: feature('active') })] }), // hat sie schon: die Liste erscheint
daten({ projekte: [projekt('p9', 'Anders')] }), // Website nicht (mehr) da
daten({ katalogEdit: { news: { verfuegbar: false } } }), // C9
]
for (const d of faelle) {
vergissMerker()
const u = umgebung(fx, { d, abo })
await u.zeige(ENTDECKEN)
assert.equal(fx.finde(u.wurzel, 'section.kn-fn-einschalten'), null)
assert.deepEqual(u.gegangen, [])
}
}))
test('Mitarbeiter-Kundenansicht (me.viewAs), Entdecken: jeder Einschalten-Knopf ist gesperrt, der Hinweis steht daneben, der Tipp geht nirgends hin', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ abo: { produkt: 'wartung' } }), me: { viewAs: true } })
await u.zeige(ENTDECKEN)
const karten = fx.alle(u.wurzel, 'section[data-fn]')
assert.ok(karten.length >= 5)
for (const karte of karten) {
const knopf = fx.finde(karte, '[data-aktion="einschalten"]')
if (!knopf) continue // Funktion nur für Betriebe: dort steht ohnehin ein Hinweis statt des Knopfes
assert.equal(knopf.disabled, true, karte.getAttribute('data-fn'))
assert.match(fx.text(karte), /In der Kundenansicht nicht möglich\./, karte.getAttribute('data-fn'))
await fx.klick(knopf)
}
assert.deepEqual(u.gegangen, [])
assert.equal(fx.finde(u.wurzel, 'section.kn-fn-einschalten'), null)
// ohne Kundenansicht bleibt der Knopf frei und ohne Hinweis
const frei = umgebung(fx, { d: daten({ abo: { produkt: 'wartung' } }) })
await frei.zeige(ENTDECKEN)
assert.equal(fx.finde(frei.wurzel, 'section[data-fn="news"] [data-aktion="einschalten"]').disabled, false)
assert.doesNotMatch(fx.text(frei.wurzel), /In der Kundenansicht nicht möglich/)
}))
test('Mitarbeiter-Kundenansicht, Meine: eine angenommene Anfrage öffnet die Bestätigung mit gesperrtem Knopf und Hinweis, ohne Abruf zum Einschalten', mit(async (fx) => {
const u = umgebung(fx, { d: daten({ projekte: [projekt('p1', 'Bäckerei', { shop: feature('off', { anfrageStatus: 'angenommen' }) })] }), me: { viewAs: true } })
await u.zeige(MEINE)
await fx.klick(fx.finde(fx.finde(u.wurzel, 'li[data-fn="shop"]'), 'button'))
const knopf = fx.finde(u.wurzel, 'section.kn-fn-einschalten [data-aktion="einschalten"]')
assert.equal(knopf.disabled, true)
assert.match(fx.text(u.wurzel), /In der Kundenansicht kann nichts eingeschaltet werden\./)
await fx.klick(knopf)
assert.equal(u.aufrufe.some(([m]) => m === 'POST'), false)
}))