import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' // Anzeige-Logik der Hustler-Seite: Zahlenformate, Status, Kacheln, Zähler, sichere Links, Spur-Zeilen const V = await loadModule('src/lib/workersView.js') const NB = String.fromCharCode(160) // geschütztes Leerzeichen vor $ und € // Tagesgrenze 6 $, Monatsgrenze 150 $ (Kenso, 17.10.: max. 150 € im Monat) const K = { newAbosMonth: 3, mrrNewEur: 60, dealsMonth: 3, openThreads: 7, pultOpen: 2, botLeads: 112, apiTodayUsd: 5.1, apiMonthUsd: 50, dailyBudgetUsd: 6, monthlyBudgetUsd: 150, } test('Zahlenformate', () => { assert.equal(V.formatUsd(7.84), `7,84${NB}$`) assert.equal(V.formatUsd(1234.5), `1.234,50${NB}$`) assert.equal(V.formatUsd(0), `0,00${NB}$`) for (const bad of [null, undefined, NaN, 'x']) assert.equal(V.formatUsd(bad), '–') assert.equal(V.formatEur(120), `120${NB}€`) assert.equal(V.formatEur(19.5), `19,50${NB}€`) assert.equal(V.formatCount(1234), '1.234') assert.equal(V.formatCount(null), '–') }) test('formatTime: Berlin-Zeit, heute nur Uhrzeit', () => { const now = Date.parse('2026-10-17T10:00:00Z') assert.equal(V.formatTime('2026-10-17T20:30:00Z', now), '22:30') assert.equal(V.formatTime('2026-10-16T21:30:00Z', now), '16.10. 23:30') assert.equal(V.formatTime('kaputt', now), '–') assert.equal(V.formatTime(null, now), '–') }) test('Status: Texte und Töne', () => { const s = ['arbeitet', 'wartet', 'leerlauf', 'grau', 'fehler', 'x'] assert.deepEqual(s.map(V.workerStatusLabel), ['Arbeitet', 'Wartet auf Auftrag', 'Leerlauf', 'Noch nicht angeschlossen', 'Fehler', 'Unbekannt']) assert.deepEqual(s.map(V.statusTone), ['green', 'orange', 'plain', 'plain', 'red', 'plain']) }) test('kpiTiles: sechs Kacheln, Töne nach 80-%-Regel', () => { const t = V.kpiTiles(K) assert.deepEqual(t.map((x) => x.key), ['abos', 'deals', 'threads', 'pult', 'botleads', 'api']) assert.deepEqual(t.map((x) => x.label), ['Neue Abos (Monat)', 'Deals (Monat)', 'Gespräche offen', 'Wartet auf dich', 'Bot-Leads gesamt', 'API heute']) assert.deepEqual(t.map((x) => x.value), ['3', '3', '7', '2', '112', `5,10${NB}$`]) assert.equal(t[0].sub, `60${NB}€ neu pro Monat`) assert.equal(t[5].sub, `von 6,00${NB}$`) assert.deepEqual(t.map((x) => x.tone), ['', '', '', 'warn', '', 'warn']) assert.equal(V.kpiTiles({ ...K, apiTodayUsd: 6 })[5].tone, 'neg') assert.equal(V.kpiTiles({ ...K, apiTodayUsd: 4.7 })[5].tone, '') assert.equal(V.kpiTiles({ ...K, dailyBudgetUsd: 0 })[5].tone, '') assert.equal(V.kpiTiles({ ...K, pultOpen: 0 })[3].tone, '') }) test('kpiTiles: genau 80 % der Tagesgrenze warnt (keine Gleitkomma-Lücke)', () => { assert.equal(V.kpiTiles({ ...K, apiTodayUsd: 4.8 })[5].tone, 'warn') assert.equal(V.kpiTiles({ ...K, apiTodayUsd: 4.79 })[5].tone, '') }) test('kpiTiles ohne Daten: Striche, kein NaN (Review Focus 1)', () => { for (const k of [null, undefined, {}]) { const tiles = V.kpiTiles(k) assert.equal(tiles.length, 6) for (const x of tiles) { assert.equal(x.value, '–') assert.equal(typeof x.sub, 'string') assert.ok(!/NaN|undefined/.test(x.sub)) assert.equal(x.tone, '') } } }) test('counterLines: bekannte Zähler zuerst, Rest alphabetisch, unbekannter Schlüssel als Beschriftung', () => { assert.deepEqual(V.counterLines({ zeta: 1, gesendet: 3, gelesen: 41 }), [ { key: 'gelesen', label: 'gelesen', value: '41' }, { key: 'gesendet', label: 'gesendet', value: '3' }, { key: 'zeta', label: 'zeta', value: '1' }, ]) assert.equal(V.counterLines({ entwuerfe: 2 })[0].label, 'Entwürfe') assert.equal(V.counterLines({ geprueft: 2 })[0].label, 'geprüft') assert.deepEqual(V.counterLines(null), []) assert.equal(V.tokenTotal(1200), 1200) assert.equal(V.tokenTotal({ in: 1000, out: 234 }), 1234) assert.equal(V.tokenTotal(undefined), 0) }) test('safeLink: nur http(s) und interne Pfade (Review Focus 5)', () => { assert.deepEqual(V.safeLink('https://x.de/a'), { href: 'https://x.de/a', external: true }) assert.deepEqual(V.safeLink('/customers/1'), { href: '/customers/1', external: false }) const bad = ['javascript:alert(1)', ' JavaScript:alert(1)', 'data:text/html,x', '//evil.example', 'mailto:a@b.de', '', null, 42] // Browser lesen "/\host" und "//host" wie "//host": auch das bleibt Text bad.push('/\\evil.example', '/\t/evil.example', '/\n/evil.example') for (const b of bad) assert.equal(V.safeLink(b), null, String(b)) }) test('traceRows: neueste zuerst, höchstens 50, unsicherer Link bleibt Text', () => { const evs = Array.from({ length: 60 }, (_, i) => ({ id: `e${i}`, ts: new Date(Date.UTC(2026, 9, 17, 8, 0, i)).toISOString(), text: `T${i}`, link: i === 59 ? 'javascript:x' : null, })) const rows = V.traceRows(evs, Date.parse('2026-10-17T12:00:00Z')) assert.equal(rows.length, 50) assert.equal(rows[0].id, 'e59') assert.equal(rows[0].link, null) assert.equal(rows[0].text, 'T59') assert.equal(rows[0].time, '10:00') assert.equal(rows[49].id, 'e10') }) test('traceRows: gültiger Link wird übernommen, ungültige Zeiten zuletzt, Nicht-Listen ergeben []', () => { const now = Date.parse('2026-10-17T12:00:00Z') const rows = V.traceRows([ { id: 'a', ts: 'kaputt', text: 'ohne Zeit', link: null }, { id: 'b', ts: '2026-10-17T08:00:00Z', text: 'mit Link', link: '/customers/1' }, ], now) assert.deepEqual(rows.map((r) => r.id), ['b', 'a']) assert.deepEqual(rows[0].link, { href: '/customers/1', external: false }) assert.equal(rows[1].time, '–') assert.deepEqual(V.traceRows(null, now), []) assert.deepEqual(V.traceRows(undefined, now), []) }) test('pauseLabel und pickTab', () => { assert.equal(V.pauseLabel(false), 'Alles anhalten') assert.equal(V.pauseLabel(true), 'Fortsetzen') const tabs = [{ id: 'hochhaus' }, { id: 'pult' }] assert.equal(V.pickTab(tabs, 'pult').id, 'pult') assert.equal(V.pickTab(tabs, 'nope').id, 'hochhaus') assert.equal(V.pickTab(tabs, null).id, 'hochhaus') })