Öffnungszeiten: Speichern zeichnet nur neu, was sich geändert hat

Ungespeicherte Eingaben in anderen Formularen bleiben stehen: nach dem Speichern
wird nur die Plankarte, die Planliste (mit den Eingaben der übrigen Karten),
Feiertage oder Ausnahmen neu gezeichnet, „Nein, danke“ entfernt nur die Frage.
Während des Speicherns sind auch die Eingabefelder gesperrt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 06:18:21 +00:00
1 parent 04a3525f79
commit f01ed15247
6 files changed
+409 -58

No files matched your search

+214
View File
@@ -626,6 +626,220 @@ test('Ausnahmen: bei 100 Einträgen sind beide Knöpfe gesperrt', mitDom({}, asy
assert.match(fx.text(abschnitt(fx, root, 'ausnahmen')), /Mehr als 100 Ausnahmen sind nicht möglich/)
}))
// ---------------------------------------------------------------------------------------------------------------- Nur neu zeichnen, was sich geändert hat
const kurzerUrlaub = (id, text = '') => ({ id, art: 'urlaub', von: tagPlus(HEUTE, 10), bis: tagPlus(HEUTE, 10), text, modus: 'zu', fenster: [], plan: null })
/** Einen eintägigen Betriebsurlaub über das Formular eintragen (ohne Frage nach der Hinweis-Leiste); die Antwort kommt als nächste aus der Warteschlange. */
async function urlaubEintragen(fx, root, antwortDaten, version) {
await fx.klick(fx.finde(root, '[data-werkzeug="urlaub-neu"]'))
const form = urlaubsformular(fx, root)
await fx.setze(feldIn(fx, form, 'von'), tagPlus(HEUTE, 10))
await fx.setze(feldIn(fx, form, 'bis'), tagPlus(HEUTE, 10))
fx.antworten.push(antwort(antwortDaten, version))
await fx.klick(fx.finde(form, 'button[type="submit"]'))
}
test('Plan speichern zeichnet nur diese Karte: ungespeicherte Eingaben in einem anderen Plan und in anderen Abschnitten bleiben stehen', mitDom({}, async (fx) => {
const zwei = daten({ plaene: [plan(P1), plan(P2)] })
const { root } = await oeffne(fx, { start: stand(zwei) })
const karteA = planKarte(fx, root, P1)
const karteB = planKarte(fx, root, P2)
const nameB = fx.finde(karteB, `[data-feld="plaene.${P2}.name"]`)
await fx.setze(nameB, 'Küche Entwurf')
await fx.klick(haken(fx, tagZeile(fx, root, P2, '7'))) // Sonntag im zweiten Plan geöffnet, nicht gespeichert
await fx.setze(fx.finde(root, 'select[data-feld="land"]'), 'BY') // Bundesland gewählt, nicht gespeichert
await fx.setze(fx.finde(fx.finde(root, '[data-feiertag="2026-12-25"]'), 'select'), 'rund')
await fx.setze(zeitFelder(fx, tagZeile(fx, root, P1, '2'))[0], '08:15') // Dienstag in A: wird gleich gespeichert
fx.antworten.push(antwort(daten({ plaene: [plan(P1, { tage: { ...werktags(), 2: { modus: 'zeiten', fenster: [['08:15', '18:00']] } } }), plan(P2)] }), 4))
await fx.klick(speichernKnopf(fx, root, P1))
const gesendet = puts(fx)[0].body.daten
assert.deepEqual(gesendet.plaene[0].tage['2'].fenster, [['08:15', '18:00']])
assert.equal(gesendet.plaene[1].name, 'Küche', 'der Entwurf des anderen Plans wird nicht mitgespeichert')
assert.equal(gesendet.plaene[1].tage['7'].modus, 'zu')
assert.notEqual(planKarte(fx, root, P1), karteA, 'die gespeicherte Karte ist neu gezeichnet')
assert.equal(planKarte(fx, root, P2), karteB, 'die andere Karte ist dieselbe geblieben')
assert.equal(fx.finde(planKarte(fx, root, P2), `[data-feld="plaene.${P2}.name"]`), nameB)
assert.equal(nameB.value, 'Küche Entwurf')
assert.equal(haken(fx, tagZeile(fx, root, P2, '7')).checked, true)
assert.equal(fx.finde(root, 'select[data-feld="land"]').value, 'BY', 'das ungespeicherte Bundesland bleibt')
assert.equal(fx.finde(fx.finde(root, '[data-feiertag="2026-12-25"]'), 'select').value, 'rund')
assert.deepEqual(zeitFelder(fx, tagZeile(fx, root, P1, '2')).slice(0, 2).map((f) => f.value), ['08:15', '18:00'], 'die gespeicherte Karte zeigt den gespeicherten Stand')
}))
test('Betriebsurlaub speichern zeichnet nur „Ausnahmen“: eine ungespeicherte Änderung am Wochenplan bleibt', mitDom({}, async (fx) => {
const { root } = await oeffne(fx)
const montag = tagZeile(fx, root, P1, '1')
const feld = zeitFelder(fx, montag)[0]
await fx.setze(feld, '07:45')
const karte = planKarte(fx, root, P1)
const feiertage = abschnitt(fx, root, 'feiertage')
const ausnahmenVorher = abschnitt(fx, root, 'ausnahmen')
await urlaubEintragen(fx, root, daten({ ausnahmen: [kurzerUrlaub('x_urlaub01')] }), 4)
assert.equal(planKarte(fx, root, P1), karte, 'dieselbe Karte')
assert.equal(zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0], feld)
assert.equal(feld.value, '07:45', 'die ungespeicherte Eingabe steht noch da')
assert.equal(abschnitt(fx, root, 'feiertage'), feiertage, 'Feiertage unberührt')
assert.notEqual(abschnitt(fx, root, 'ausnahmen'), ausnahmenVorher, 'Ausnahmen neu gezeichnet')
assert.ok(fx.finde(root, 'li[data-id="x_urlaub01"]'))
assert.match(meldung(fx, root), /eingetragen/)
assert.equal(puts(fx)[0].body.daten.plaene[0].tage['1'].fenster[0][0], '09:00', 'der Entwurf ging nicht mit hinaus')
// dasselbe beim Entfernen
const zeile = fx.finde(root, 'li[data-id="x_urlaub01"]')
await fx.klick(fx.finde(zeile, '[data-aktion="bearbeiten"]'))
await fx.klick(fx.finde(zeile, '[data-aktion="entfernen"]'))
fx.antworten.push(antwort(daten(), 5))
await fx.klick(fx.finde(fx.finde(zeile, '.tm-frage-box'), 'button[type="submit"]'))
assert.equal(feld.value, '07:45')
assert.equal(fx.finde(root, 'li[data-id="x_urlaub01"]'), null)
}))
test('„Nein, danke“ entfernt nur die Frage: andere ungespeicherte Eingaben und offene Formulare bleiben', mitDom({}, async (fx) => {
const { root } = await oeffne(fx)
await fx.klick(fx.finde(root, '[data-werkzeug="urlaub-neu"]'))
const form = urlaubsformular(fx, root)
const von = tagPlus(HEUTE, 20)
const bis = tagPlus(HEUTE, 25)
await fx.setze(feldIn(fx, form, 'von'), von)
await fx.setze(feldIn(fx, form, 'bis'), bis)
fx.antworten.push(antwort(daten({ ausnahmen: [{ ...kurzerUrlaub('x_urlaub01'), von, bis }] }), 4))
await fx.klick(fx.finde(form, 'button[type="submit"]'))
assert.ok(fx.finde(root, '[data-frage="hinweis"]'))
// jetzt nebenher etwas eingeben: ein Wochentag, ein offenes Sondertag-Formular mit Text
const feld = zeitFelder(fx, tagZeile(fx, root, P1, '3'))[1]
await fx.setze(feld, '17:20')
await fx.klick(fx.finde(root, '[data-werkzeug="sondertag-neu"]'))
const sonder = fx.finde(root, 'form[aria-label="Sondertag eintragen"]')
await fx.setze(feldIn(fx, sonder, 'text'), 'Tag der offenen Tür')
await fx.klick(fx.finde(fx.finde(root, '[data-frage="hinweis"]'), '[data-aktion="hinweis-nein"]'))
assert.equal(fx.finde(root, '[data-frage="hinweis"]'), null, 'die Frage ist weg')
assert.equal(feld.value, '17:20')
assert.equal(fx.finde(root, 'form[aria-label="Sondertag eintragen"]'), sonder, 'das offene Formular ist dasselbe')
assert.equal(feldIn(fx, sonder, 'text').value, 'Tag der offenen Tür')
assert.equal(puts(fx).length, 1)
}))
test('Feiertage speichern lässt ein noch ungespeichertes Bundesland stehen; Bundesland speichern nimmt Eingaben für Tage mit ins neue Land', mitDom({}, async (fx) => {
const { root } = await oeffne(fx)
const zeile = (tag) => fx.finde(root, `[data-feiertag="${tag}"]`)
await fx.setze(fx.finde(root, 'select[data-feld="land"]'), 'BY')
await fx.setze(fx.finde(zeile('2026-12-25'), 'select'), 'vereinbarung')
fx.antworten.push(antwort(daten({ feiertage: { '2026-12-25': { modus: 'vereinbarung', fenster: [] } } }), 4))
await fx.klick(fx.finde(root, 'form[aria-label="Feiertage"] button[type="submit"]'))
assert.equal(puts(fx)[0].body.daten.land, 'RP', 'das Bundesland ging nicht mit')
assert.equal(fx.finde(root, 'select[data-feld="land"]').value, 'BY', 'und steht noch da')
assert.match(meldung(fx, root), /Feiertage gespeichert/)
// jetzt das Bundesland speichern: ein Wochenplan-Entwurf bleibt, die Eingabe zu Neujahr (gibt es auch in Bayern) wandert mit
await fx.setze(fx.finde(zeile('2027-01-01'), 'select'), 'rund')
const feld = zeitFelder(fx, tagZeile(fx, root, P1, '4'))[0]
await fx.setze(feld, '10:10')
const bayern = [{ tag: '2026-12-25', name: 'Erster Weihnachtstag', art: 'feiertag' }, { tag: '2027-01-01', name: 'Neujahr', art: 'feiertag' }, { tag: '2027-01-06', name: 'Heilige Drei Könige', art: 'feiertag' }]
fx.antworten.push(antwort(daten({ land: 'BY', feiertage: { '2026-12-25': { modus: 'vereinbarung', fenster: [] } } }), 5, { feiertage: bayern }))
await fx.klick(fx.finde(root, 'form[aria-label="Bundesland"] button[type="submit"]'))
assert.deepEqual(fx.alle(root, '[data-feiertag]').map((z) => z.getAttribute('data-feiertag')), ['2026-12-25', '2027-01-01', '2027-01-06'])
assert.equal(fx.finde(zeile('2027-01-01'), 'select').value, 'rund', 'die Eingabe zu Neujahr ist erhalten')
assert.equal(fx.finde(zeile('2026-12-25'), 'select').value, 'vereinbarung', 'der gespeicherte Stand')
assert.equal(fx.finde(zeile('2027-01-06'), 'select').value, '')
assert.equal(feld.value, '10:10', 'der Wochenplan-Entwurf bleibt')
}))
test('Plan hinzufügen, Hauptplan wechseln und Plan löschen zeichnen die Planliste neu, behalten aber die ungespeicherten Eingaben der übrigen Karten und das offene „Plan hinzufügen“', mitDom({}, async (fx) => {
const P3 = 'p_dritter3'
const d3 = daten({
plaene: [plan(P1), plan(P2), plan(P3, { kurz: 'bar', name: 'Bar', haupt: false })],
ausnahmen: [{ ...kurzerUrlaub('x_kueche01', 'Nur Küche'), plan: P2 }, kurzerUrlaub('x_alle0001', 'Alle')],
})
const { root } = await oeffne(fx, { start: stand(d3) })
const name3 = fx.finde(planKarte(fx, root, P3), `[data-feld="plaene.${P3}.name"]`)
await fx.setze(name3, 'Bar Entwurf')
const montag = zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0]
await fx.setze(montag, '06:30')
await fx.klick(fx.finde(root, '[data-werkzeug="plan-neu"]'))
const neuForm = fx.finde(root, 'form[aria-label="Plan hinzufügen"]')
await fx.setze(fx.finde(neuForm, 'input[type="text"]'), 'Halbfertig')
const ausnahmenVorher = abschnitt(fx, root, 'ausnahmen')
// Hauptplan wechseln (P2): Pillen und Knöpfe aller Karten ändern sich, die Eingaben bleiben
fx.antworten.push(antwort(daten({ ...d3, plaene: [plan(P1, { haupt: false }), plan(P2, { haupt: true }), plan(P3, { kurz: 'bar', name: 'Bar', haupt: false })] }), 4))
await fx.klick(fx.finde(planKarte(fx, root, P2), '[data-aktion="haupt"]'))
assert.match(fx.text(fx.finde(planKarte(fx, root, P2), 'summary')), /Hauptplan/)
assert.doesNotMatch(fx.text(fx.finde(planKarte(fx, root, P1), 'summary')), /Hauptplan/)
assert.ok(fx.finde(planKarte(fx, root, P1), '[data-aktion="haupt"]'), 'der frühere Hauptplan hat jetzt den Knopf')
assert.equal(fx.finde(planKarte(fx, root, P3), `[data-feld="plaene.${P3}.name"]`).value, 'Bar Entwurf')
assert.equal(zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0].value, '06:30')
assert.equal(fx.finde(root, 'form[aria-label="Plan hinzufügen"]'), neuForm, 'das offene Formular bleibt')
assert.equal(fx.finde(neuForm, 'input[type="text"]').value, 'Halbfertig')
assert.equal(abschnitt(fx, root, 'ausnahmen'), ausnahmenVorher, 'die Ausnahmen kennen die Pläne nur beim Namen: nichts zu tun')
// Plan löschen (P3, jetzt weder Haupt- noch letzter Plan): P1 und P2 behalten ihre Eingaben, Ausnahmen werden neu gezeichnet
const montag2 = zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0]
await fx.klick(fx.finde(planKarte(fx, root, P3), '[data-aktion="entfernen"]'))
fx.antworten.push(antwort(daten({ ...d3, plaene: [plan(P1, { haupt: false }), plan(P2, { haupt: true })] }), 5))
await fx.klick(fx.finde(fx.finde(planKarte(fx, root, P3), '.tm-frage-box'), 'button[type="submit"]'))
assert.equal(planKarte(fx, root, P3), null)
assert.equal(zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0].value, '06:30', 'Eingabe in P1 bleibt')
assert.notEqual(zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0], montag2, '(die Karte wurde mit ihren Werten neu gebaut)')
assert.equal(fx.finde(root, 'form[aria-label="Plan hinzufügen"]'), neuForm)
assert.notEqual(abschnitt(fx, root, 'ausnahmen'), ausnahmenVorher, 'Ausnahmen neu gezeichnet')
// Plan hinzufügen: neue Karte, Formular zu, P1 behält die Eingabe, „Gilt für“ in neuen Formularen kennt den neuen Plan
await fx.setze(fx.finde(neuForm, 'input[type="text"]'), 'Lieferdienst')
fx.antworten.push((aufruf) => antwort(daten({ ...d3, plaene: [plan(P1, { haupt: false }), plan(P2, { haupt: true }), plan(aufruf.body.daten.plaene[2].id, { kurz: 'lieferdienst', name: 'Lieferdienst', haupt: false, tage: wochenplan() })] }), 6))
await fx.klick(fx.finde(neuForm, 'button[type="submit"]'))
const neueKarte = fx.alle(root, 'details[data-plan]').find((k) => /Lieferdienst/.test(fx.text(fx.finde(k, 'summary'))))
assert.ok(neueKarte, 'die neue Karte steht in der Liste')
assert.equal(fx.alle(root, 'details[data-plan]').length, 3)
assert.equal(fx.finde(root, 'form[aria-label="Plan hinzufügen"]'), null, 'das Formular ist zu')
assert.equal(zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0].value, '06:30', 'auch das Hinzufügen lässt die Eingaben der anderen Karten stehen')
await fx.klick(fx.finde(root, '[data-werkzeug="urlaub-neu"]'))
assert.match(fx.text(feldIn(fx, urlaubsformular(fx, root), 'plan')), /Lieferdienst/)
}))
test('Plan umbenennen: die Liste der Ausnahmen nennt den neuen Namen, sonst bleibt sie stehen', mitDom({}, async (fx) => {
const ausnahme = { ...kurzerUrlaub('x_kueche01', 'Nur Küche'), plan: P2 }
const { root } = await oeffne(fx, { start: stand(daten({ plaene: [plan(P1), plan(P2)], ausnahmen: [ausnahme] })) })
const vorher = abschnitt(fx, root, 'ausnahmen')
assert.match(fx.text(fx.finde(root, 'li[data-id="x_kueche01"]')), /nur Küche/)
// erst gespeichert ohne neuen Namen: Ausnahmen bleiben
fx.antworten.push(antwort(daten({ plaene: [plan(P1), plan(P2)], ausnahmen: [ausnahme] }), 4))
await fx.klick(speichernKnopf(fx, root, P2))
assert.equal(abschnitt(fx, root, 'ausnahmen'), vorher)
await fx.setze(fx.finde(planKarte(fx, root, P2), `[data-feld="plaene.${P2}.name"]`), 'Restaurant')
fx.antworten.push(antwort(daten({ plaene: [plan(P1), plan(P2, { name: 'Restaurant' })], ausnahmen: [ausnahme] }), 5))
await fx.klick(speichernKnopf(fx, root, P2))
assert.notEqual(abschnitt(fx, root, 'ausnahmen'), vorher)
assert.match(fx.text(fx.finde(root, 'li[data-id="x_kueche01"]')), /nur Restaurant/)
}))
test('Während ein Speichern läuft, sind Knöpfe und Eingabefelder gesperrt (nichts geht beim Tippen verloren); danach sind sie wieder frei', mitDom({}, async (fx) => {
const { root } = await oeffne(fx, { start: stand(daten({ plaene: [plan(P1), plan(P2)] })) })
const echtes = globalThis.fetch
let freigeben
const wartet = new Promise((resolve) => { freigeben = resolve })
globalThis.fetch = async (url, optionen) => {
if (optionen?.method === 'PUT') await wartet
return echtes(url, optionen)
}
try {
const ausserhalb = zeitFelder(fx, tagZeile(fx, root, P2, '1'))[0]
const selbst = zeitFelder(fx, tagZeile(fx, root, P1, '1'))[0]
fx.antworten.push(antwort(daten({ plaene: [plan(P1), plan(P2)] }), 4))
const lauf = fx.klick(speichernKnopf(fx, root, P1))
await fx.ruhe()
assert.equal(root.getAttribute('aria-busy'), 'true')
for (const steuerung of fx.alle(root, 'input, select, textarea, button')) assert.equal(steuerung.disabled, true, `${steuerung.tagName} ist gesperrt`)
await fx.setze(ausserhalb, '05:55')
assert.notEqual(ausserhalb.value, '05:55', 'in ein gesperrtes Feld lässt sich nichts tippen')
freigeben()
await lauf
assert.equal(root.hasAttribute('aria-busy'), false)
assert.equal(ausserhalb.disabled, false, 'die Karte, die stehen blieb, ist wieder frei')
assert.equal(fx.alle(root, 'input:disabled, select:disabled, button:disabled').length, 0)
void selbst
} finally {
globalThis.fetch = echtes
}
}))
// ---------------------------------------------------------------------------------------------------------------- Fehler des Servers
test('409 beim Speichern: Meldung „Inzwischen geändert – bitte neu laden.“ und Neu-laden-Knopf; der Knopf lädt und zeichnet neu', mitDom({}, async (fx) => {
const { root } = await oeffne(fx)