Der Server berechnet kurzinfo (Terminplaner, Webshop, News, Buchungskalender, Öffnungszeiten) nur noch, wenn die neue Ansicht ?kurzinfo=1 sendet. Das tut allein die Seite "Meine"; Etagen, Einschalt-Ablauf und Module fragen ohne den Parameter, dann fehlt das Feld und es gibt keine Abfrage auf Termine, Bestellungen, Beiträge und Anfragen. Mit dem Parameter bleibt die Antwort wie bisher (Text oder null je Funktion). Nur der Wert "1" gilt; doppelte oder als Liste gesendete Parameter heißen: ohne. Alte Ansicht unverändert. Tests: kundenansichtKurzinfo (neuer Fall ohne/mit Parameter, Abfragen und Feld), knFunktionenAnsicht (die Seite lädt genau einmal ?kurzinfo=1, auch nach "Nochmal versuchen"). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
366 lines
23 KiB
JavaScript
366 lines
23 KiB
JavaScript
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: '<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('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)
|
||
}))
|