Files
tickte-system/tests/unit/zeitenView.test.mjs
T
ClaudeandClaude Opus 5.5 994347408d Öffnungszeiten: Register und Texte im Ticketsystem
Funktionen-Reiter kennt "Öffnungszeiten live" (zeiten) hinter dem Terminplaner:
Bestätigungstexte, Site-Key, Aktionen je Zustand (Abnahme nur ohne Hindernis
"zeiten_fehlen"). Neues Modul zeitenView.js: Statustext (Einbau läuft / wartet
auf die Zeiten des Kunden / Abo-Ende mit Frist), navSelektorFehler (Spiegel
der Portal-Regel) und teamBody für PATCH .../zeiten/team. Protokoll-Label
für zeiten_gespeichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:02:30 +00:00

155 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadModule } from './load.mjs'
// Öffnungszeiten live im Funktionen-Reiter: Statustexte, Prüfung des Navigations-Selektors (Spiegel des Portals) und Rumpf der Team-Einstellungen
const { zeitenStatusText, navSelektorFehler, NAV_SELEKTOR_MAX, NAV_SELEKTOR_FEHLER, teamBody } = await loadModule('src/lib/zeitenView.js')
const EINBAU = 'Leisten live – Einbau läuft'
const WARTET = 'wartet auf die Zeiten des Kunden'
test('zeitenStatusText: Einrichtung ohne Hindernis – Leisten sind live, der Einbau läuft', () => {
assert.equal(zeitenStatusText({ state: 'setup', hindernis: null }), EINBAU)
assert.equal(zeitenStatusText({ state: 'setup' }), EINBAU, 'ohne Feld hindernis')
assert.equal(zeitenStatusText({ state: 'setup', hindernis: '' }), EINBAU)
})
test('zeitenStatusText: Einrichtung mit hindernis "zeiten_fehlen" – wartet auf die Zeiten des Kunden', () => {
assert.equal(zeitenStatusText({ state: 'setup', hindernis: 'zeiten_fehlen' }), WARTET)
})
test('zeitenStatusText: unbekanntes Hindernis zeigt nichts Erfundenes, sondern den Einbau-Text', () => {
assert.equal(zeitenStatusText({ state: 'setup', hindernis: 'irgendwas_neues' }), EINBAU)
})
test('zeitenStatusText: Abo-Ende nennt die Frist (Berliner Tag des Abo-Endes plus 30 Tage)', () => {
const abo = (state, aboEndeAt) => zeitenStatusText({ state, hindernis: null, aboEndeAt })
const hinweis = (datum) => `Abo beendet – Abschaltung am ${datum}, falls der Kunde die Funktion nicht behält`
assert.equal(abo('active', '2026-10-04T10:00:00.000Z'), hinweis('03.11.2026'))
// 22:30 UTC ist in Berlin schon der nächste Tag (Sommerzeit, +2 h): 05.10. -> 04.11.
assert.equal(abo('active', '2026-10-04T22:30:00.000Z'), hinweis('04.11.2026'))
// Monats- und Jahreswechsel, Zeitumstellung am 25.10. spielt bei Kalendertagen keine Rolle
assert.equal(abo('active', '2026-10-20T12:00:00.000Z'), hinweis('19.11.2026'))
assert.equal(abo('active', '2026-12-15T12:00:00.000Z'), hinweis('14.01.2027'))
// Appwrite liefert auch +00:00
assert.equal(abo('active', '2026-10-04T10:00:00.000+00:00'), hinweis('03.11.2026'))
})
test('zeitenStatusText: Abo-Ende in der Einrichtung steht zusätzlich zum Einbau- bzw. Warte-Text', () => {
const aboEndeAt = '2026-10-04T10:00:00.000Z'
const frist = 'Abo beendet – Abschaltung am 03.11.2026, falls der Kunde die Funktion nicht behält'
assert.equal(zeitenStatusText({ state: 'setup', hindernis: null, aboEndeAt }), `${EINBAU} · ${frist}`)
assert.equal(zeitenStatusText({ state: 'setup', hindernis: 'zeiten_fehlen', aboEndeAt }), `${WARTET} · ${frist}`)
})
test('zeitenStatusText: Abo-Ende mit unlesbarem Datum nennt 30 Tage statt eines erfundenen Tages', () => {
const text = zeitenStatusText({ state: 'active', aboEndeAt: 'kein-datum' })
assert.equal(text, 'Abo beendet – Abschaltung nach 30 Tagen, falls der Kunde die Funktion nicht behält')
})
test('zeitenStatusText: aktiv ohne Abo-Ende, aus, bestellt, abgeschaltet, ohne Ansicht – leer', () => {
assert.equal(zeitenStatusText({ state: 'active', hindernis: null, aboEndeAt: null }), '')
assert.equal(zeitenStatusText({ state: 'active' }), '')
assert.equal(zeitenStatusText({ state: 'off' }), '')
assert.equal(zeitenStatusText({ state: 'ordered' }), '')
// abgeschaltet: Grund und Löschdatum zeigt abgeschaltetText, nichts doppelt
assert.equal(zeitenStatusText({ state: 'disabled', disabledReason: 'abo_ende', loeschungAm: '2026-11-04', aboEndeAt: '2026-10-04T10:00:00.000Z' }), '')
assert.equal(zeitenStatusText({ state: 'off', aboEndeAt: '2026-10-04T10:00:00.000Z' }), '')
assert.equal(zeitenStatusText(null), '')
assert.equal(zeitenStatusText(undefined), '')
})
test('zeitenStatusText: kein Text nennt "24 Uhr"', () => {
for (const feature of [
{ state: 'setup' },
{ state: 'setup', hindernis: 'zeiten_fehlen' },
{ state: 'active', aboEndeAt: '2026-10-04T10:00:00.000Z' },
{ state: 'setup', hindernis: 'zeiten_fehlen', aboEndeAt: '2026-10-04T22:30:00.000Z' },
]) {
assert.doesNotMatch(zeitenStatusText(feature), /24 Uhr/)
}
})
test('navSelektorFehler: leer ist erlaubt (kein Selektor)', () => {
for (const wert of [undefined, null, '', ' ', '\n\t ']) {
assert.equal(navSelektorFehler(wert), null, JSON.stringify(wert))
}
})
test('navSelektorFehler: übliche Selektoren sind gültig', () => {
for (const wert of [
'header',
'#nav',
'.site-header',
'.site-header > nav',
'header#main-nav.fixed',
'nav[role="navigation"]',
"a[href='x']",
'ul.menu:first-child',
'div_wrap .a-b',
'ABC xyz 019',
' header ',
]) {
assert.equal(navSelektorFehler(wert), null, wert)
}
})
test('navSelektorFehler: erlaubt sind nur A–Z a–z 0–9, Leerzeichen und # . - _ > : [ ] = " \'', () => {
const zeichen = 'AZaz09 #.-_>:[]="\''
assert.equal(navSelektorFehler(zeichen), null)
assert.equal(navSelektorFehler('a'.repeat(NAV_SELEKTOR_MAX)), null)
})
test('navSelektorFehler: < { } ; und alle übrigen Zeichen werden abgelehnt', () => {
for (const zeichen of ['<', '{', '}', ';', '(', ')', ',', '*', '+', '~', '/', '\\', '@', '!', '|', '$', '%', '^', '&', '?', '`', 'ä', 'ß', '€', '\n', '\t']) {
assert.equal(navSelektorFehler(`a${zeichen}b`), NAV_SELEKTOR_FEHLER, JSON.stringify(zeichen))
}
assert.equal(navSelektorFehler('header { display: none }'), NAV_SELEKTOR_FEHLER)
assert.equal(navSelektorFehler('</style><script>'), NAV_SELEKTOR_FEHLER)
assert.equal(navSelektorFehler('a\nb'), NAV_SELEKTOR_FEHLER, 'Zeilenumbruch mitten im Text')
})
test('navSelektorFehler: höchstens 120 Zeichen, gezählt nach dem Trimmen', () => {
assert.equal(NAV_SELEKTOR_MAX, 120)
assert.equal(navSelektorFehler('a'.repeat(120)), null)
assert.equal(navSelektorFehler(` ${'a'.repeat(120)} `), null, 'Leerraum außen zählt nicht')
assert.equal(navSelektorFehler('a'.repeat(121)), NAV_SELEKTOR_FEHLER)
assert.equal(navSelektorFehler(`${'a'.repeat(60)} ${'b'.repeat(60)}`), NAV_SELEKTOR_FEHLER, '121 mit Leerzeichen innen')
})
test('navSelektorFehler: kein Text wird abgelehnt wie auf dem Server (Zahl, Objekt, Wahrheitswert)', () => {
for (const wert of [5, true, {}, []]) {
assert.equal(navSelektorFehler(wert), NAV_SELEKTOR_FEHLER, JSON.stringify(wert))
}
})
test('NAV_SELEKTOR_FEHLER: wörtlich wie die Meldung des Portals', () => {
assert.equal(NAV_SELEKTOR_FEHLER, 'Der Selektor darf höchstens 120 Zeichen lang sein und nur A–Z, 0–9, Leerzeichen und # . - _ > : [ ] = " \' enthalten.')
})
test('teamBody: genau die fünf Felder der Team-Route, Selektor getrimmt, leer wird null', () => {
assert.deepEqual(
teamBody({ projectId: 'p1', teamVersion: 3, navSelektor: ' header > nav ', kontaktleisteAus: true, hinweisleisteAus: false }),
{ projectId: 'p1', teamVersion: 3, navSelektor: 'header > nav', kontaktleisteAus: true, hinweisleisteAus: false }
)
for (const leer of ['', ' ', null, undefined]) {
assert.equal(teamBody({ projectId: 'p1', teamVersion: 0, navSelektor: leer, kontaktleisteAus: false, hinweisleisteAus: false }).navSelektor, null, JSON.stringify(leer))
}
})
test('teamBody: Schalter sind immer echte Wahrheitswerte, nur true gilt als an', () => {
const body = (kontaktleisteAus, hinweisleisteAus) => teamBody({ projectId: 'p1', teamVersion: 1, navSelektor: '', kontaktleisteAus, hinweisleisteAus })
assert.deepEqual(body(true, true), { projectId: 'p1', teamVersion: 1, navSelektor: null, kontaktleisteAus: true, hinweisleisteAus: true })
assert.deepEqual(body(undefined, undefined), { projectId: 'p1', teamVersion: 1, navSelektor: null, kontaktleisteAus: false, hinweisleisteAus: false })
assert.deepEqual(body('true', 1), { projectId: 'p1', teamVersion: 1, navSelektor: null, kontaktleisteAus: false, hinweisleisteAus: false })
})
test('teamBody: die Version wird unverändert durchgereicht (das Portal prüft sie), auch 0', () => {
assert.equal(teamBody({ projectId: 'p1', teamVersion: 0, navSelektor: '', kontaktleisteAus: false, hinweisleisteAus: false }).teamVersion, 0)
assert.equal(teamBody({ projectId: 'p1', teamVersion: 41, navSelektor: '', kontaktleisteAus: false, hinweisleisteAus: false }).teamVersion, 41)
})
test('teamBody: ein ungültiger Selektor wird nicht "repariert", sondern unverändert gesendet (das Formular sperrt vorher, das Portal prüft noch einmal)', () => {
assert.equal(teamBody({ projectId: 'p1', teamVersion: 1, navSelektor: 'a{b}', kontaktleisteAus: false, hinweisleisteAus: false }).navSelektor, 'a{b}')
})