Öffnungszeiten: Editor für Pläne, Feiertage und Ausnahmen
Speichern über PUT /api/zeiten (Version, 409, Feldfehler per ID-Pfad), Zeitpläne mit Wochentagen, Hauptplan wechseln und Löschen (R10, R33), Feiertage je Bundesland, Betriebsurlaub und Sondertage mit Hinweis-Leisten-Frage. Die Hülle behält den Editor bei einem neuen Stand und zeichnet nur den Kopf neu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cee44d6852
commit
04a3525f79
8 files changed
+1974
-21
No files matched your search
@@ -19,10 +19,21 @@ const STRIPE = 'https://checkout.stripe.test/c/pay/cs_test_1'
|
||||
const KNOPF_BEZAHLT = REGISTER.consent.knopf
|
||||
const HAKEN_BEZAHLT = REGISTER.consent.items.filter((item) => item.haken).map((item) => item.id)
|
||||
|
||||
// Der Editor im Editorplatz (public/zeiten-editor.js, eigene Tests in test/zeitenEditor.test.js) lädt GET /api/zeiten/<id>: hier mit fester Antwort
|
||||
// (noch nichts gespeichert), ohne die Warteschlange der Antworten und fx.fetchLog zu berühren; fx.editorAufrufe zählt diese Abrufe.
|
||||
const EDITOR_STAND = { state: 'active', daten: null, version: 0, kundenansicht: false, vorschau: null, feiertage: [], googleText: '' }
|
||||
const mitDom = (optionen, fn) => async () => {
|
||||
const fx = installFakeDom(optionen)
|
||||
const echtes = globalThis.fetch
|
||||
fx.editorAufrufe = []
|
||||
globalThis.fetch = async (url, anfrage) => {
|
||||
if (!String(url).startsWith('/api/zeiten/')) return echtes(url, anfrage)
|
||||
fx.editorAufrufe.push(String(url))
|
||||
return { ok: true, status: 200, json: async () => structuredClone(EDITOR_STAND) }
|
||||
}
|
||||
try {
|
||||
await fn(fx)
|
||||
await fx.ruhe() // ein Editor, der noch lädt, soll fertig sein, bevor das Fake-DOM geht
|
||||
} finally {
|
||||
fx.restore()
|
||||
}
|
||||
@@ -703,11 +714,14 @@ test('Ansicht: der Editorplatz steht nur in setup, active und (mit Daten) disabl
|
||||
assert.equal(await platz(projekt('p1', 'disabled', { disabledReason: 'team' }, { zeitenLeer: true })), null)
|
||||
}))
|
||||
|
||||
test('app.js ruft initZeiten bei jedem Wechsel in die Ansicht: gleiches Projekt und gleicher Stand bauen den Editorplatz nicht neu auf, ein neuer Stand oder die Rückkehr von Stripe schon', mitDom({}, async (fx) => {
|
||||
test('app.js ruft initZeiten bei jedem Wechsel in die Ansicht: gleiches Projekt und gleicher Stand bauen den Editorplatz nicht neu auf, ein neuer Stand zeichnet nur den Kopf neu, die Rückkehr von Stripe alles', mitDom({}, async (fx) => {
|
||||
const aktiv = () => liste([projekt('p1', 'active', {}, { zeitenLeer: false })], angebot(true))
|
||||
fx.antworten.push(aktiv())
|
||||
const root = await oeffne(fx)
|
||||
let editor = fx.finde(root, '.zt-editor')
|
||||
await fx.ruhe() // der Editor lädt (fester Stand, siehe mitDom)
|
||||
const editor = fx.finde(root, '.zt-editor')
|
||||
assert.deepEqual(fx.editorAufrufe, ['/api/zeiten/p1'])
|
||||
assert.ok(fx.finde(editor, 'details[data-plan]'), 'der Editor steht im Platz')
|
||||
const eingabe = h('input', { type: 'text' })
|
||||
eingabe.value = 'ungespeichert'
|
||||
editor.append(eingabe)
|
||||
@@ -716,7 +730,7 @@ test('app.js ruft initZeiten bei jedem Wechsel in die Ansicht: gleiches Projekt
|
||||
fx.antworten.push(aktiv())
|
||||
await initZeiten(root, { projectId: 'p1' })
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor, 'derselbe Editorplatz')
|
||||
assert.equal(editor.firstChild, eingabe)
|
||||
assert.equal(eingabe.parentNode, editor)
|
||||
assert.equal(eingabe.value, 'ungespeichert')
|
||||
assert.equal(fx.fetchLog.length, 2)
|
||||
assert.equal(fx.fetchLog.every((a) => a.method === 'GET' && a.url === '/api/funktionen'), true, 'keine Zusatzabfrage')
|
||||
@@ -727,35 +741,41 @@ test('app.js ruft initZeiten bei jedem Wechsel in die Ansicht: gleiches Projekt
|
||||
await initZeiten(root)
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor)
|
||||
|
||||
// die Abo-Lage hat sich geändert (Behalten gibt es nur ohne Abo): die Karte muss stimmen, also neu aufgebaut
|
||||
// die Abo-Lage hat sich geändert (Behalten gibt es nur ohne Abo): die Karte muss stimmen, also wird der Kopf neu gezeichnet – der Editor bleibt mit seinen Eingaben
|
||||
const kopf = fx.finde(root, '.zt-kopf')
|
||||
fx.antworten.push(liste([projekt('p1', 'active', {}, { zeitenLeer: false })], angebot(false, 'kein_abo')))
|
||||
await initZeiten(root)
|
||||
assert.notEqual(fx.finde(root, '.zt-editor'), editor)
|
||||
editor = fx.finde(root, '.zt-editor')
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor, 'der Editorplatz bleibt stehen')
|
||||
assert.equal(eingabe.value, 'ungespeichert', 'ungespeicherte Eingaben gehen nicht verloren')
|
||||
assert.notEqual(fx.finde(root, '.zt-kopf'), kopf, 'der Kopf ist neu gezeichnet')
|
||||
assert.equal(root.dataset.zeitenKey.includes('kein_abo'), true, 'der neue Stand gilt')
|
||||
|
||||
// „Für 30 € behalten“ aus der Kachel: Behalten-Seite; der Stand hat sich geändert (aboEndeAt), also neu aufgebaut
|
||||
// „Für 30 € behalten“ aus der Kachel: Behalten-Seite; der Stand hat sich geändert (aboEndeAt), der Editor bleibt auch dann
|
||||
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], angebot(false, 'kein_abo')))
|
||||
root.dataset.wunschWeg = 'behalten'
|
||||
await initZeiten(root)
|
||||
assert.notEqual(fx.finde(root, '.zt-editor'), editor)
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor)
|
||||
assert.equal(editor.hidden, true, 'die Behalten-Seite versteckt den Editor')
|
||||
assert.ok(fx.finde(root, '[data-variante="behalten"]'))
|
||||
|
||||
// gleicher neuer Stand noch einmal mit Weg: der Editor bleibt, die Seite öffnet
|
||||
const editor2 = fx.finde(root, '.zt-editor')
|
||||
await fx.klick(knopf(fx, root, 'zurueck'))
|
||||
assert.equal(editor.hidden, false)
|
||||
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], angebot(false, 'kein_abo')))
|
||||
root.dataset.wunschWeg = 'behalten'
|
||||
await initZeiten(root)
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor2, 'der Editor bleibt')
|
||||
assert.equal(editor2.hidden, true)
|
||||
assert.equal(fx.finde(root, '.zt-editor'), editor, 'der Editor bleibt')
|
||||
assert.equal(editor.hidden, true)
|
||||
assert.ok(fx.finde(root, '[data-variante="behalten"]'))
|
||||
assert.deepEqual(fx.editorAufrufe, ['/api/zeiten/p1'], 'der Editor hat in der ganzen Zeit nur einmal geladen')
|
||||
|
||||
// Rückkehr von Stripe: immer neu
|
||||
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], angebot(false, 'kein_abo')))
|
||||
fx.window.location.hash = '#zeiten?behalten=1&projekt=p1'
|
||||
await initZeiten(root)
|
||||
assert.notEqual(fx.finde(root, '.zt-editor'), editor2)
|
||||
assert.notEqual(fx.finde(root, '.zt-editor'), editor)
|
||||
assert.ok(fx.finde(root, '[data-danke="behalten"]'))
|
||||
assert.equal(fx.editorAufrufe.length, 2, 'ein frischer Editor lädt')
|
||||
}))
|
||||
|
||||
test('Fremdtext (Projektname, Meldung des Servers) kommt nur als Text in die Seite', mitDom({}, async (fx) => {
|
||||
@@ -819,7 +839,7 @@ test('Portal: Ansicht #zeiten ist in app.js und dashboard.html verdrahtet', () =
|
||||
|
||||
test('Quelltext: zeiten.js und zeiten-bestellen.js importieren nur relativ und nur das Nötige; kein HTML aus Daten, kein Speicher, Du-Form', () => {
|
||||
const importe = (datei) => [...ohneKommentare(lesen(`public/${datei}`)).matchAll(/\bfrom\s+'([^']+)'/g)].map((m) => m[1])
|
||||
assert.deepEqual(importe('zeiten.js'), ['./termine-dom.js', './zeiten-bestellen.js'])
|
||||
assert.deepEqual(importe('zeiten.js'), ['./termine-dom.js', './zeiten-bestellen.js', './zeiten-editor.js'])
|
||||
assert.deepEqual(importe('zeiten-bestellen.js'), ['./funktion-bestellen.js', './termine-dom.js'])
|
||||
const dateien = readdirSync(new URL('../public/', import.meta.url)).filter((f) => /^zeiten.*\.js$/.test(f))
|
||||
for (const datei of ['zeiten.js', 'zeiten-bestellen.js']) {
|
||||
|
||||
Reference in new issue
Block a user