import { test } from 'node:test' import assert from 'node:assert/strict' import { fmtEuro, fmtDatum, fmtDatumZeit, fmtTagMonat, fmtStd, fmtMinuten, fmtBytes, legacyRequest, meldeZaehlerNeu, merkeHinweis, nimmHinweis, setzeChatVorgabe, nimmChatVorgabe, fmtUhrzeit, berlinTag, ersetze, sichereAdresse, wechselFunktionAus, zuAnsicht, meldeTitel, K, ABO_NICHT_GELADEN, navigiere, ladeAbo, safeUrl, } from '../public/neu/ansicht-hilfen.js' import { mit, mitDom, abo, aboAntwort, me, projekt, rechnung, nachricht, angebot, ledger, anfrageGutschrift, VORSCHAU, VORSCHAU_SPEICHER, LIMITS, neueWurzel } from './support/knDaten.js' test('Formate: Euro mit geschütztem Leerzeichen, Daten in Berliner Zeit', () => { assert.equal(fmtEuro(50), '50,00 €') assert.equal(fmtDatum('2026-10-17T10:00:00Z'), '17.10.2026') assert.equal(fmtDatum('17.10.2026'), '17.10.2026') assert.equal(fmtDatum(''), '–') assert.equal(fmtDatumZeit('2026-10-17T10:00:00Z'), '17.10.26, 12:00') assert.equal(fmtTagMonat('2026-10-17T21:59:59Z'), '17.10.') assert.equal(fmtTagMonat('2026-10-17T22:00:00Z'), '18.10.') assert.equal(fmtTagMonat('kaputt'), '') }) test('Formate: Uhrzeit und Kalendertag in Berliner Zeit', () => { assert.equal(fmtUhrzeit('2026-10-17T10:05:00Z'), '12:05'); assert.equal(fmtUhrzeit('kaputt'), '') assert.deepEqual(berlinTag('2026-10-15T22:30:00Z'), { j: 2026, m: 10, t: 16 }); assert.equal(berlinTag(''), null) }) test('ersetze überspringt null, undefined und false, statt „null“ anzuhängen', () => mitDom((fx) => { const el = fx.document.createElement('div'); el.append('alt') ersetze(el, null, 'a', [undefined, fx.document.createElement('b'), false], 'c') assert.equal(el.textContent, 'ac'); assert.equal(el.childNodes.length, 3) })) test('Formate: Stunden, Minuten, Bytes', () => { assert.deepEqual([150, 120, 45, 0].map(fmtStd), ['2,5 Std.', '2 Std.', '0,75 Std.', '0 Std.']) assert.deepEqual([0, 45, 120, 150].map(fmtMinuten), ['', '45 min', '2 h', '2 h 30 min']) assert.deepEqual([12, 1536, 5 * 1024 * 1024].map(fmtBytes), ['12 B', '1,5 KB', '5,0 MB']) }) test('legacyRequest: String-Body wird zum Objekt, Objekt und fehlender Body bleiben', async () => { const aufrufe = [] const alt = legacyRequest(async (pfad, opts) => { aufrufe.push([pfad, opts]); return {} }) await alt('/a', { method: 'POST', body: '{"x":1}' }); await alt('/b', { method: 'POST', body: { y: 2 } }); await alt('/c') assert.deepEqual(aufrufe, [['/a', { method: 'POST', body: { x: 1 } }], ['/b', { method: 'POST', body: { y: 2 } }], ['/c', {}]]) }) test('meldeZaehlerNeu ohne Dokument tut nichts, Hinweis und Chat-Vorgabe gelten einmal', () => { assert.doesNotThrow(() => meldeZaehlerNeu('chat')) merkeHinweis('Gespeichert.'); assert.equal(nimmHinweis(), 'Gespeichert.'); assert.equal(nimmHinweis(), '') setzeChatVorgabe('Hallo'); assert.equal(nimmChatVorgabe(), 'Hallo'); assert.equal(nimmChatVorgabe(), '') }) test('wechselFunktionAus: nur vollständige Werte zählen (V11)', () => { assert.deepEqual(wechselFunktionAus({ wechselFunktion: { key: 'termine', projectId: 'p1', extra: 1 } }), { key: 'termine', projectId: 'p1' }) for (const sub of [undefined, null, {}, { wechselFunktion: null }, { wechselFunktion: { key: 'termine' } }, { wechselFunktion: { key: '', projectId: 'p1' } }, { wechselFunktion: { key: 1, projectId: 'p1' } }]) assert.equal(wechselFunktionAus(sub), null) }) test('zuAnsicht ist die Brücke der Hülle, meldeTitel steigt zu den Eltern auf', () => mitDom((fx) => { assert.deepEqual(zuAnsicht('takeover'), { href: '#gutschrift', 'data-view': 'takeover' }) assert.deepEqual(zuAnsicht('projekt', 'p1'), { href: '#projekt?projekt=p1', 'data-view': 'projekt', 'data-project': 'p1' }) assert.equal(zuAnsicht('invoices').href, '#rechnungen') const aussen = fx.document.createElement('div'); const innen = fx.document.createElement('p'); aussen.append(innen); const titel = [] aussen.addEventListener('kn:titel', (e) => titel.push(e.detail.text)); meldeTitel(innen, 'Zwei'); assert.deepEqual(titel, ['Zwei']) })) test('Bausteinklassen K tragen die Klassen aus basis.css', () => { assert.equal(K.knopf, 'kn-knopf'); assert.equal(K.primaer, 'kn-knopf-primaer'); assert.equal(K.zweit, 'kn-knopf-zweit'); assert.equal(K.textknopf, 'kn-textknopf') assert.equal(K.versteckt, 'hidden'); assert.equal(K.pille.gruen, 'kn-pille kn-pille-gruen'); assert.equal(K.pille.rot, 'kn-pille kn-pille-rot') assert.match(ABO_NICHT_GELADEN, /nicht geladen werden/) }) test('ladeAbo liefert das Objekt, navigiere wirft ohne Router nicht', mit({ 'GET /api/abo': aboAntwort() }, async (fx) => { const antwort = await ladeAbo() assert.equal(antwort.subscription, null) assert.equal(antwort.termsVersion, '2026-09-30-v5') assert.doesNotThrow(() => navigiere('#chat')) assert.equal(fx.window.location.hash, '#chat') // ohne laufenden Router trägt der Hash die Adresse })) test('ladeAbo bei Fehler: null', mit({ 'GET /api/abo': () => ({ status: 500, body: { error: 'kaputt' } }) }, async () => { assert.equal(await ladeAbo(), null) })) test('Testdaten: Bauer liefern die Formen der Schnittstellen', () => { assert.equal(me().customer.email, 'anna@example.com') assert.equal(abo().productKey, 'wartung'); assert.equal(abo({ produkt: 'hosting' }).productKey, 'hosting') assert.equal(abo().status, 'active'); assert.equal(abo().storage.units, 0) assert.equal(aboAntwort().products.length, 2); assert.equal(aboAntwort().mailStorage.includedGb, 5) assert.equal(projekt().id.length > 0, true); assert.equal(rechnung().balance, 0); assert.equal(typeof nachricht().text, 'string') assert.equal(angebot().months > 0, true); assert.equal(ledger().balanceMinutes >= 0, true); assert.equal(typeof anfrageGutschrift().status, 'string') assert.equal(VORSCHAU.amountDueCents, 3000); assert.equal(VORSCHAU_SPEICHER.direction, 'down'); assert.equal(LIMITS.maxFiles, 3) assert.equal(safeUrl('javascript:alert(1)'), ''); assert.equal(safeUrl('https://a.de/x'), 'https://a.de/x') }) test('mit(): unbekannte Pfade antworten 404, Funktionen liefern Status und Body, Neuaufbau je Test', mit({ '/api/x': { a: 1 }, 'POST /api/y': (aufruf) => ({ status: 201, body: { echo: aufruf.body } }) }, async (fx) => { const w = neueWurzel(fx) assert.equal(w.parentNode, fx.document.body) const a = await fetch('/api/x'); assert.equal(a.status, 200); assert.deepEqual(await a.json(), { a: 1 }) const b = await fetch('/api/y', { method: 'POST', body: JSON.stringify({ q: 1 }) }); assert.equal(b.status, 201); assert.deepEqual(await b.json(), { echo: { q: 1 } }) const c = await fetch('/api/z'); assert.equal(c.status, 404); assert.deepEqual(await c.json(), { error: 'unbekannt' }) const ev = new CustomEvent('x', { detail: 1 }); assert.equal(ev.detail, 1); ev.target = 'frei'; assert.equal(ev.target, 'frei') })) test('sichereAdresse: http(s) und Pfade auf derselben Herkunft, sonst leer', () => { assert.equal(sichereAdresse('https://a.de/x'), 'https://a.de/x'); assert.equal(sichereAdresse('/vorschau-hinweis.html'), '/vorschau-hinweis.html') for (const v of ['javascript:alert(1)', 'data:text/html,x', '//evil.example/x', '/\\evil.example/x', 'vorschau.html', '', null, undefined, 5]) assert.equal(sichereAdresse(v), '', String(v)) })