Terminplaner: Portal-Ansicht, Kachel, Termine
Kachel in Funktionen (Kommt bald / Aus / Aktiv mit Anfragen-Zahl / Abgeschaltet), Ansicht #termine als Unterseite von Funktionen: Projektauswahl, Vorlagenauswahl beim ersten Oeffnen, Reiter-Geruest (Platzhalter fuer Leistungen, Team & Plaetze, Einstellungen) und der Reiter Termine mit Tag/Woche/Monat, Filtern, Aktionen mit Rueckfragen in der Seite, Termin eintragen und Zeit sperren. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
70940e02c8
commit
43e4826ae3
10 files changed
+2552
-8
No files matched your search
@@ -49,6 +49,103 @@ test('Kacheln rendern ohne Fehler: News, Analyse comingSoon/aus/aktiv/gesperrt',
|
||||
assert.doesNotMatch(disabled, /Dev-Modus/)
|
||||
})
|
||||
|
||||
// ---- Terminplaner-Kachel: nur mit Schalter (termine.verfuegbar), Zustand je Projekt aus features.termine
|
||||
const withTermine = (id, state) => ({ projectId: id, projectName: `P ${id}`, liveUrl: '', features: { news, termine: { state } } })
|
||||
const noAnalyse = { comingSoon: true }
|
||||
async function renderTermine(projects, { verfuegbar, anfragen = {} } = {}) {
|
||||
const calls = []
|
||||
globalThis.fetch = async (url) => {
|
||||
calls.push(String(url))
|
||||
if (String(url).startsWith('/api/termine/einstellungen')) {
|
||||
const id = new URL(url, 'http://x').searchParams.get('projectId')
|
||||
return { ok: true, status: 200, json: async () => ({ anfragenOffen: anfragen[id] ?? 0 }) }
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ projects, ...(verfuegbar === undefined ? {} : { termine: { verfuegbar } }) }) }
|
||||
}
|
||||
const count = { textContent: '', hidden: true }
|
||||
const root = { innerHTML: '', querySelector: (sel) => (sel === '[data-termine-count]' ? count : null) }
|
||||
await initFunktionen(root)
|
||||
return { html: root.innerHTML, count, calls }
|
||||
}
|
||||
|
||||
test('Terminplaner-Kachel: fehlt termine.verfuegbar oder ist false ⇒ „Kommt bald“ ohne Knopf', async () => {
|
||||
for (const verfuegbar of [undefined, false]) {
|
||||
const { html, calls } = await renderTermine([{ ...withTermine('a', 'active'), analyse: noAnalyse }], { verfuegbar })
|
||||
assert.doesNotMatch(html, /data-fn-tile="termine"/)
|
||||
assert.match(html, /fn-tile-soon[\s\S]*Terminplaner/)
|
||||
assert.match(html, /Kommt bald/)
|
||||
assert.doesNotMatch(html, /data-view="termine"/)
|
||||
assert.deepEqual(calls, ['/api/funktionen'], 'ohne Schalter keine Abfrage an /api/termine')
|
||||
}
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Aus ⇒ „Wir schalten den Terminplaner für dich frei.“ + Chat', async () => {
|
||||
for (const projects of [[{ ...project('a', noAnalyse), features: { news } }], [withTermine('a', 'off')], [withTermine('a', 'ordered')]]) {
|
||||
const { html } = await renderTermine(projects, { verfuegbar: true })
|
||||
const kachel = tile(html, 'termine')
|
||||
assert.match(kachel, /Terminplaner/)
|
||||
assert.match(kachel, /fn-pill-off">Aus</)
|
||||
assert.match(kachel, /Wir schalten den Terminplaner für dich frei\./)
|
||||
assert.match(kachel, /data-view="chat"[^>]*>Chat öffnen</)
|
||||
assert.doesNotMatch(kachel, /data-view="termine"/)
|
||||
}
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Aktiv ⇒ „Termine öffnen“ mit Projekt, Anfragen aus anfragenOffen', async () => {
|
||||
const { html, count, calls } = await renderTermine([withTermine('a', 'off'), withTermine('b', 'active')], { verfuegbar: true, anfragen: { b: 3 } })
|
||||
const kachel = tile(html, 'termine')
|
||||
assert.match(kachel, /fn-pill-on">Aktiv</)
|
||||
assert.match(kachel, /data-view="termine" data-project="b"[^>]*>Termine öffnen</)
|
||||
assert.deepEqual(calls, ['/api/funktionen', '/api/termine/einstellungen?projectId=b'], 'nur aktive Projekte werden gefragt')
|
||||
assert.equal(count.textContent, '3 Anfragen warten')
|
||||
assert.equal(count.hidden, false)
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Anfragen zählen über alle aktiven Websites, 1 ⇒ Einzahl, 0 ⇒ nichts', async () => {
|
||||
const zwei = await renderTermine([withTermine('a', 'active'), withTermine('b', 'active')], { verfuegbar: true, anfragen: { a: 1, b: 2 } })
|
||||
assert.equal(zwei.count.textContent, '3 Anfragen warten')
|
||||
const eine = await renderTermine([withTermine('a', 'active')], { verfuegbar: true, anfragen: { a: 1 } })
|
||||
assert.equal(eine.count.textContent, '1 Anfrage wartet')
|
||||
const keine = await renderTermine([withTermine('a', 'active')], { verfuegbar: true, anfragen: {} })
|
||||
assert.equal(keine.count.textContent, '')
|
||||
assert.equal(keine.count.hidden, true)
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Fehler bei der Anfragen-Zahl lässt die Kachel stehen', async () => {
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).startsWith('/api/termine/')) throw new Error('offline')
|
||||
return { ok: true, status: 200, json: async () => ({ projects: [withTermine('a', 'active')], termine: { verfuegbar: true } }) }
|
||||
}
|
||||
const count = { textContent: '', hidden: true }
|
||||
const root = { innerHTML: '', querySelector: (sel) => (sel === '[data-termine-count]' ? count : null) }
|
||||
await initFunktionen(root)
|
||||
assert.match(tile(root.innerHTML, 'termine'), /Termine öffnen/)
|
||||
assert.equal(count.textContent, '')
|
||||
assert.equal(count.hidden, true)
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Abgeschaltet ⇒ Pille, Hinweis, Termine nur ansehen', async () => {
|
||||
const { html, calls } = await renderTermine([withTermine('a', 'disabled')], { verfuegbar: true })
|
||||
const kachel = tile(html, 'termine')
|
||||
assert.match(kachel, /fn-pill-off">Abgeschaltet</)
|
||||
assert.match(kachel, /abgeschaltet/)
|
||||
assert.match(kachel, /data-view="termine" data-project="a"[^>]*>Termine ansehen</)
|
||||
assert.deepEqual(calls, ['/api/funktionen'], 'abgeschaltet: keine Anfragen-Zahl')
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: mehrere Websites, Hinweis „aktiv für n von m“, Aktive zuerst', async () => {
|
||||
const { html } = await renderTermine([withTermine('a', 'disabled'), withTermine('b', 'off'), withTermine('c', 'active')], { verfuegbar: true })
|
||||
const kachel = tile(html, 'termine')
|
||||
assert.match(kachel, /aktiv für 1 von 3 Websites/)
|
||||
assert.match(kachel, /data-project="c"/)
|
||||
})
|
||||
|
||||
test('Terminplaner-Kachel: Projektname und Zustand kommen nur maskiert in die Kachel', async () => {
|
||||
const { html } = await renderTermine([{ ...withTermine('a<b>', 'active'), projectName: '<img src=x onerror=1>' }], { verfuegbar: true })
|
||||
assert.doesNotMatch(html, /<img/)
|
||||
assert.match(tile(html, 'termine'), /data-project="a<b>"/)
|
||||
})
|
||||
|
||||
// ---- Analyse-Einschalten und Ausschalten (ohne DOM: Attrappen, die innerHTML und Klick-Handler festhalten)
|
||||
function fakeEl() {
|
||||
const handlers = {}
|
||||
|
||||
Reference in new issue
Block a user