Files
Webklar-Kundenbereich/test/knFunktionenAnsicht.test.js
T
ClaudeandClaude Sonnet 5.5 db62e320ff Kundenansicht neu (Meine, Live-Detail): Zeile mit Detailzeile und Pfeil, Pille nur wenn die Funktion nicht läuft
Jede Zeile in "Meine" zeigt den Funktionsnamen fett, darunter die Kurzinfo des Servers ("3 Termine heute ·
nächster 10:30") oder "Aktiv", rechts einen Pfeil. Eine Status-Pille steht nur noch bei Funktionen, die nicht
laufen (In Einrichtung, Zahlung offen, Angefragt, Abgeschaltet, Abgelehnt, Gesperrt ...); "Aktiv seit ..." entfällt.
Bei mehreren Websites steht der Websitename vorne in der Detailzeile. Die Zeilen für Funktionen, die nicht laufen,
sind kürzer ("Wir richten sie gerade ein", "Anfrage abgelehnt · Im Chat nachfragen"). Die Kurzinfo gilt nur bei
aktiver Funktion, nur als kurzer nicht leerer Text und wird nie als HTML gelesen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 15:10:43 +00:00

349 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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') return u.d
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: '<b>News</b>', beschreibung: '<i>x</i>' } }, projekte: [projekt('p1', '<u>Café</u>')] }) })
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), /<b>News<\/b>/)
}))
test('Bildadresse: nur /neu/bilder/<name>.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', '<u>Café</u>', { news: feature('active', { kurzinfo: '<b>4</b> 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, '<b>4</b> 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('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').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)
}))