Öffnungszeiten: Behalten nur ohne Abo anbieten (kein_abo)

Behalten (30 €) gab es bei jedem inklusive false. nicht_aktiv und gekuendigt heißen aber,
dass das Wartungs-Abo läuft (Server: 409 Abo läuft wieder). Jetzt gilt an allen fünf Stellen
(Kachel, Karte, Behalten-Seite, Weg aus der Kachel, Abgeschaltet-Karte) dasselbe Prädikat:
inklusive false UND grund kein_abo; sonst „Abo läuft wieder“ ohne Bezahlknopf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 05:41:24 +00:00
1 parent 7bb918390a
commit cee44d6852
5 files changed
+166 -20

No files matched your search

+41
View File
@@ -211,6 +211,47 @@ test('aboEndeAt: Behalten-Hinweis mit Datum', async () => {
assert.doesNotMatch(nurText(kaputt), /bis NaN|Invalid|undefined/)
})
// Fix Runde 1: Behalten (30 €) gibt es nur ohne Abo (inklusive false UND grund kein_abo). nicht_aktiv und gekuendigt heissen, dass das Abo läuft (R20/R25).
test('aboEndeAt bei laufendem Abo (nicht_aktiv, gekuendigt): „Abo läuft wieder“, nie ein Bezahlknopf', async () => {
for (const grund of ['nicht_aktiv', 'gekuendigt']) {
for (const state of ['active', 'setup']) {
const k = kachel((await render([projekt('p1', state, { aboEndeAt: '2026-10-01T10:00:00.000Z', einbau: 'angestossen' }, { zeitenLeer: false })], { zeiten: ANGEBOT(false, grund) })).html)
const was = `${grund}/${state}`
assert.match(nurText(k), /Dein Wartungs-Abo läuft wieder – Öffnungszeiten live ist darin enthalten\./, was)
assert.deepEqual(knoepfe(k), ['Zeiten bearbeiten'], was)
assert.doesNotMatch(nurText(k), /ist beendet|behalte die Funktion|für einmalig|Für 30 €|30 €/, was)
assert.doesNotMatch(k, /data-zeiten-weg/, was)
}
}
// unbekannter Grund oder kein Grund (false, null): nicht auf Verdacht bezahlen lassen
for (const grund of [null, 'unbekannt']) {
const k = kachel((await render([projekt('p1', 'active', { aboEndeAt: '2026-10-01T10:00:00.000Z' }, { zeitenLeer: false })], { zeiten: ANGEBOT(false, grund) })).html)
assert.deepEqual(knoepfe(k), ['Zeiten bearbeiten'], String(grund))
assert.doesNotMatch(nurText(k), /Für 30 €|behalte die Funktion/, String(grund))
}
// Gegenprobe: ohne Abo (kein_abo) bleibt es beim Angebot
const ohne = kachel((await render([projekt('p1', 'active', { aboEndeAt: '2026-10-01T10:00:00.000Z' }, { zeitenLeer: false })], { zeiten: ANGEBOT(false, 'kein_abo') })).html)
assert.deepEqual(knoepfe(ohne), ['Zeiten bearbeiten', 'Für 30 € behalten'])
})
test('disabled abo_ende mit Daten bei laufendem Abo (nicht_aktiv, gekuendigt): kein „Für 30 € wieder einschalten“, stattdessen „Abo läuft wieder“', async () => {
const feature = { disabledReason: 'abo_ende', disabledAt: '2026-10-04T10:00:00.000Z', loeschungAm: '2999-01-01' }
for (const grund of ['nicht_aktiv', 'gekuendigt']) {
const k = kachel((await render([projekt('p1', 'disabled', feature, { zeitenLeer: false })], { zeiten: ANGEBOT(false, grund) })).html)
assert.doesNotMatch(k, /Für 30 €|data-zeiten-weg|<button/, grund)
assert.match(nurText(k), /Dein Wartungs-Abo läuft wieder – wir schalten Öffnungszeiten live automatisch wieder ein/, grund)
assert.match(nurText(k), /Deine Eingaben bleiben bis 01\.01\.2999 gespeichert/, grund)
}
for (const grund of [null, 'unbekannt']) {
const k = kachel((await render([projekt('p1', 'disabled', feature, { zeitenLeer: false })], { zeiten: ANGEBOT(false, grund) })).html)
assert.doesNotMatch(k, /Für 30 €|<button/, String(grund))
assert.match(nurText(k), /Gerade nicht prüfbar/, String(grund))
}
// Gegenprobe: kein_abo bietet es an
const ohne = kachel((await render([projekt('p1', 'disabled', feature, { zeitenLeer: false })], { zeiten: ANGEBOT(false, 'kein_abo') })).html)
assert.deepEqual(knoepfe(ohne), ['Für 30 € wieder einschalten'])
})
test('ordered: „Zahlung wird bestätigt …“', async () => {
const k = kachel((await render([projekt('p1', 'ordered', { unlockedBy: null })], { zeiten: ANGEBOT(false, 'kein_abo') })).html)
assert.match(k, /fn-pill fn-pill-setup">Zahlung offen</)
+67 -1
View File
@@ -7,7 +7,7 @@ import { featureConfig, featureHaken, freiweg, oeffentlicheAnsicht } from '../se
import { oeffentlicheConsent } from '../server/services/featureOrder.js'
import { h } from '../public/termine-dom.js'
import { initZeiten } from '../public/zeiten.js'
import { behaltenFrist, renderZeitenBestellen, tagText, zeitenStatusKarte } from '../public/zeiten-bestellen.js'
import { behaltenFrist, behaltenLage, renderZeitenBestellen, tagText, zeitenStatusKarte } from '../public/zeiten-bestellen.js'
// Einschalt-, Bestell- und Behalten-Weg der Öffnungszeiten live im Portal (public/zeiten.js, public/zeiten-bestellen.js; Spec 3.1 bis 3.3,
// Rulings R20 und R32). Fake-DOM und Fake-fetch aus test/support/fakeDom.js: jeder Aufruf steht in fx.fetchLog, die Antworten kommen der Reihe nach
@@ -500,6 +500,72 @@ test('Behalten: 409 „Abo läuft wieder“ zeichnet die Ansicht nach der neuen
assert.ok(fx.finde(root, '.zt-status'))
}))
// Fix Runde 1: Behalten (30 €) gibt es nur ohne Abo (inklusive false UND grund kein_abo). nicht_aktiv und gekuendigt heissen, dass das Abo läuft (R20/R25, Z12).
test('behaltenLage: bezahlen nur bei inklusive false und kein_abo', () => {
assert.equal(behaltenLage(angebot(false, 'kein_abo')), 'bezahlen')
assert.equal(behaltenLage(angebot(false, 'nicht_aktiv')), 'abo')
assert.equal(behaltenLage(angebot(false, 'gekuendigt')), 'abo')
assert.equal(behaltenLage(angebot(true)), 'inklusive')
for (const unklar of [angebot(null, null), angebot(false, null), angebot(false, 'unbekannt'), { verfuegbar: true }, undefined, null]) assert.equal(behaltenLage(unklar), 'unklar', JSON.stringify(unklar))
})
test('Behalten bei laufendem Abo (nicht_aktiv, gekuendigt): die Karte sagt „Abo läuft wieder“, es gibt keinen Behalten-Knopf und keine Behalten-Seite', mitDom({}, async (fx) => {
for (const grund of ['nicht_aktiv', 'gekuendigt']) {
// aktiv mit erkanntem Abo-Ende, auch wenn die Kachel „Für 30 € behalten“ (wunschWeg) schicken wollte
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], angebot(false, grund)))
let root = await oeffne(fx, { dataset: { wunschWeg: 'behalten' } })
assert.equal(knopf(fx, root, 'behalten'), null, grund)
assert.equal(fx.finde(root, '[data-variante]'), null, `${grund}: keine Behalten-Seite`)
assert.match(fx.text(fx.finde(root, '[data-hinweis="abo-laeuft"]')), /Dein Wartungs-Abo läuft wieder – Öffnungszeiten live ist darin enthalten\./, grund)
assert.doesNotMatch(fx.text(root), /ist beendet|behalte die Funktion|Für 30 €/, grund)
assert.equal(fx.alle(root, 'input[type="checkbox"]').length, 0)
// abgeschaltet (abo_ende) mit Daten
fx.antworten.push(liste([projekt('p1', 'disabled', { disabledReason: 'abo_ende', disabledAt: '2026-10-04T10:00:00.000Z', loeschungAm: '2999-01-01' }, { zeitenLeer: false })], angebot(false, grund)))
root = await oeffne(fx, { dataset: { wunschWeg: 'behalten' } })
assert.equal(knopf(fx, root, 'behalten'), null, `${grund}: abgeschaltet`)
assert.equal(fx.finde(root, '[data-variante]'), null)
assert.match(fx.text(fx.finde(root, '[data-hinweis="abo-laeuft"]')), /Dein Wartungs-Abo läuft wieder/, grund)
assert.doesNotMatch(fx.text(root), /Für 30 €|schaltest du die Funktion wieder ein/, grund)
}
// unbekannter Grund (false, null): kein Bezahlknopf auf Verdacht
for (const an of [angebot(false, null), angebot(false, 'unbekannt')]) {
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], an))
const root = await oeffne(fx, { dataset: { wunschWeg: 'behalten' } })
assert.equal(knopf(fx, root, 'behalten'), null)
assert.equal(fx.finde(root, '[data-variante]'), null)
fx.antworten.push(liste([projekt('p1', 'disabled', { disabledReason: 'abo_ende', loeschungAm: '2999-01-01' }, { zeitenLeer: false })], an))
const aus = await oeffne(fx)
assert.equal(knopf(fx, aus, 'behalten'), null)
assert.ok(fx.finde(aus, '[data-hinweis="nicht-pruefbar"]'))
}
// Gegenprobe: ohne Abo (kein_abo) bleibt Behalten
fx.antworten.push(liste([projekt('p1', 'active', ABO_ENDE, { zeitenLeer: false })], angebot(false, 'kein_abo')))
const ohne = await oeffne(fx, { dataset: { wunschWeg: 'behalten' } })
assert.ok(fx.finde(ohne, '[data-variante="behalten"]'))
}))
test('Behalten-Seite direkt gerufen (renderZeitenBestellen, modus behalten): nur bei kein_abo die bezahlte Seite, sonst Hinweis ohne Haken und Knopf', mitDom({}, (fx) => {
const ctx = { projectId: 'p1', features: { zeiten: oeffentlicheAnsicht('zeiten') }, ustHinweis: config.ustHinweis, kundenansicht: false }
const zeige = (an) => {
const wurzel = h('div')
renderZeitenBestellen(wurzel, { ctx, angebot: an, projekt: projekt('p1', 'active', ABO_ENDE), modus: 'behalten' })
return wurzel
}
assert.ok(fx.finde(zeige(angebot(false, 'kein_abo')), '[data-variante="behalten"]'))
for (const grund of ['nicht_aktiv', 'gekuendigt']) {
const wurzel = zeige(angebot(false, grund))
assert.match(fx.text(wurzel), /Dein Wartungs-Abo läuft wieder/, grund)
assert.equal(fx.alle(wurzel, 'input,button').length, 0, grund)
}
for (const grund of [null, 'unbekannt']) {
const wurzel = zeige(angebot(false, grund))
assert.match(fx.text(wurzel), /Gerade nicht prüfbar – bitte später erneut/, String(grund))
assert.equal(fx.alle(wurzel, 'input').length, 0)
assert.equal(fx.finde(wurzel, '[data-variante]'), null)
}
}))
test('renderZeitenBestellen im Modus behalten: Abo läuft wieder ⇒ Hinweis statt Seite; Abo nicht lesbar ⇒ „Gerade nicht prüfbar“', mitDom({}, (fx) => {
const ctx = { projectId: 'p1', features: { zeiten: oeffentlicheAnsicht('zeiten') }, ustHinweis: config.ustHinweis, kundenansicht: false }
const wurzel = h('div')