Files
tickte-system/tests/unit/botleadsView.test.mjs
T

99 lines
7.6 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 fs from 'node:fs'
import { loadModule } from './load.mjs'
const { safeHref, phoneLabel, botleadRow, ROOM_FILTERS, pageInfo } = await loadModule('src/lib/botleadsView.js')
// Die React-Teile sind unter Node nicht ladbar (JSX): der Quelltext wird geprüft (Muster workersComponents.test.mjs); das Verhalten prüft botleadList.test.mjs
const read = (p) => fs.readFileSync(new URL('../../' + p, import.meta.url), 'utf8')
test('safeHref: nur http und https, keine Zugangsdaten in der Adresse', () => {
assert.equal(safeHref('https://beispiel-elektro.de/impressum'), 'https://beispiel-elektro.de/impressum')
assert.equal(safeHref(' http://beispiel-elektro.de/ '), 'http://beispiel-elektro.de/')
for (const bose of ['javascript:alert(1)', '//evil.example', 'data:text/html,x', '', ' ', 'ftp://x.example/', 'mailto:a@b.de', '/pfad', 'https://', null, undefined, 42, {}]) assert.equal(safeHref(bose), '', String(bose))
assert.equal(safeHref('https://user:pw@beispiel-elektro.de/'), '') // Zugangsdaten
assert.equal(safeHref('https://beispiel-elektro.de@evil.example/'), '') // sähe nach dem Betrieb aus, führt aber woandershin
})
test('phoneLabel: Nummer nur mit Quelle, Heilberuf nie', () => {
assert.equal(phoneLabel({ phone: '0631 111', phoneSource: 'https://x.de/i', isHealth: false }), '0631 111')
assert.equal(phoneLabel({ phone: '0631 111', phoneSource: '', isHealth: false }), 'nicht verfügbar')
assert.equal(phoneLabel({ phone: '', phoneSource: '', isHealth: true }), 'bewusst nicht gespeichert')
assert.equal(phoneLabel({ phone: '0631 111', phoneSource: 'https://x.de/i', isHealth: true }), 'bewusst nicht gespeichert')
assert.equal(phoneLabel({ phone: '', phoneSource: 'https://x.de/i', isHealth: false }), 'nicht verfügbar')
assert.equal(phoneLabel({}), 'nicht verfügbar')
assert.equal(phoneLabel(null), 'nicht verfügbar')
})
test('botleadRow: alle zehn Felder', () => {
const item = { id: 'a', room: 'handwerk', roomLabel: 'Handwerk', name: 'Elektro Beispiel GmbH', postalCode: '67655', city: 'Kaiserslautern', score: 82, reason: 'keine veröffentlichte Mail', sourceUrl: 'javascript:x', phone: '', phoneSource: '', isHealth: false, foundAt: '2026-10-19T20:00:00Z', gekuerzt: false }
const r = botleadRow(item)
assert.deepEqual(Object.keys(r), ['id', 'title', 'place', 'roomLabel', 'scoreLabel', 'reason', 'phone', 'sourceHref', 'found', 'gekuerzt'])
assert.deepEqual(r, { id: 'a', title: 'Elektro Beispiel GmbH', place: '67655 Kaiserslautern', roomLabel: 'Handwerk', scoreLabel: '82 Punkte', reason: 'keine veröffentlichte Mail', phone: 'nicht verfügbar', sourceHref: '', found: '19.10.2026', gekuerzt: false })
// sichere Quelle, Nummer mit Quelle, gekürzt
const b = botleadRow({ ...item, sourceUrl: 'https://beispiel-elektro.de/impressum', phone: '0631 111', phoneSource: 'https://beispiel-elektro.de/impressum', gekuerzt: true })
assert.deepEqual([b.sourceHref, b.phone, b.gekuerzt], ['https://beispiel-elektro.de/impressum', '0631 111', true])
// Heilberuf: nie die Nummer, auch wenn der Dienst eine mitschickt
assert.equal(botleadRow({ ...item, isHealth: true, phone: '0631 111', phoneSource: 'https://x.de/i' }).phone, 'bewusst nicht gespeichert')
})
test('botleadRow: fehlende Werte ergeben Platzhalter, nie undefined oder NaN', () => {
const r = botleadRow({ id: 'b' })
assert.deepEqual(r, { id: 'b', title: 'nicht verfügbar', place: '', roomLabel: '–', scoreLabel: '–', reason: '–', phone: 'nicht verfügbar', sourceHref: '', found: '–', gekuerzt: false })
assert.equal(botleadRow({ id: 'c', name: ' ', postalCode: '', city: 'Mainz' }).title, 'nicht verfügbar')
assert.equal(botleadRow({ id: 'c', city: 'Mainz' }).place, 'Mainz')
assert.equal(botleadRow({ id: 'c', postalCode: '55116' }).place, '55116')
assert.equal(botleadRow({ id: 'c', score: 0 }).scoreLabel, '0 Punkte')
assert.equal(botleadRow({ id: 'c', score: 'x' }).scoreLabel, '–')
assert.equal(botleadRow({ id: 'c', foundAt: 'kein Datum' }).found, '–')
assert.equal(botleadRow(null).id, '')
})
test('botleadRow: das Funddatum zählt in Berlin (Mitternacht und Zeitumstellung)', () => {
assert.equal(botleadRow({ id: 'a', foundAt: '2026-10-19T22:30:00Z' }).found, '20.10.2026') // 00:30 Uhr am nächsten Tag
assert.equal(botleadRow({ id: 'a', foundAt: '2026-12-31T23:30:00Z' }).found, '01.01.2027')
assert.equal(botleadRow({ id: 'a', foundAt: '2026-10-25T00:30:00Z' }).found, '25.10.2026') // Nacht der Zeitumstellung
})
test('ROOM_FILTERS: Reihenfolge, Werte und Beschriftungen', () => {
assert.deepEqual(ROOM_FILTERS, [
{ value: '', label: 'Alle Räume' },
{ value: 'aerzte', label: 'Ärzte & Praxen' },
{ value: 'handwerk', label: 'Handwerk' },
{ value: 'selbst', label: 'Selbstständige' },
{ value: 'laden', label: 'Läden & Gastro' },
])
})
test('pageInfo: Seiten, Blättern und Beschriftung', () => {
assert.deepEqual(pageInfo({ total: 120, page: 2, pageSize: 50 }), { pages: 3, hasPrev: true, hasNext: true, label: 'Seite 2 von 3 · 120 Einträge' })
assert.deepEqual(pageInfo({ total: 120, page: 1, pageSize: 50 }), { pages: 3, hasPrev: false, hasNext: true, label: 'Seite 1 von 3 · 120 Einträge' })
assert.deepEqual(pageInfo({ total: 120, page: 3, pageSize: 50 }), { pages: 3, hasPrev: true, hasNext: false, label: 'Seite 3 von 3 · 120 Einträge' })
assert.equal(pageInfo({ total: 0, page: 1, pageSize: 50 }).label, 'Keine Einträge')
assert.deepEqual(pageInfo({ total: 0, page: 1, pageSize: 50 }), { pages: 1, hasPrev: false, hasNext: false, label: 'Keine Einträge' })
assert.equal(pageInfo({ total: 1, page: 1, pageSize: 50 }).label, 'Seite 1 von 1 · 1 Eintrag') // Einzahl
assert.equal(pageInfo({ total: 50, page: 1, pageSize: 50 }).pages, 1)
assert.equal(pageInfo({ total: 51, page: 1, pageSize: 50 }).pages, 2)
assert.equal(pageInfo({ total: 1234, page: 1, pageSize: 50 }).label, 'Seite 1 von 25 · 1.234 Einträge') // deutsche Tausenderpunkte
// der Dienst schickt pageSize mit; fehlt es oder ist es unbrauchbar, gilt 50
assert.equal(pageInfo({ total: 120, page: 1 }).pages, 3)
for (const bad of [0, -5, NaN, 'x', null]) assert.equal(pageInfo({ total: 120, page: 1, pageSize: bad }).pages, 3, String(bad))
assert.equal(pageInfo({ total: 120, page: 1, pageSize: 25 }).pages, 5)
// unbrauchbare Zahlen ergeben nie NaN
assert.deepEqual(pageInfo({ total: 'x', page: 'y', pageSize: 50 }), { pages: 1, hasPrev: false, hasNext: false, label: 'Keine Einträge' })
assert.deepEqual(pageInfo({}), { pages: 1, hasPrev: false, hasNext: false, label: 'Keine Einträge' })
})
test('BotLeadList.jsx: Links nur aus der Zeilen-Ableitung, extern mit rel noopener, kein HTML-Einschub, Knöpfe mit type="button"', () => {
const src = read('src/components/workers/BotLeadList.jsx')
assert.match(src, /botleadRow\(/)
assert.match(src, /rel="noopener noreferrer"/); assert.match(src, /target="_blank"/)
// jeder href kommt aus row.sourceHref (botleadRow → safeHref), nie aus dem Eintrag selbst
assert.match(src, /href=\{row\.sourceHref\}/)
assert.doesNotMatch(src, /\bhref=\{(?!row\.sourceHref\})/)
assert.doesNotMatch(src, /\bhref="/)
assert.doesNotMatch(src, /dangerouslySetInnerHTML|innerHTML/)
// kein Anruf-Link, kein Mailversand
assert.doesNotMatch(src, /tel:|mailto:/)
// WOMS-Button hat keinen Standard-type: jeder Knopf nennt ihn
assert.equal((src.match(/<(?:button|Button)\b/g) || []).length > 0, true)
assert.equal((src.match(/<(?:button|Button)\b/g) || []).length, (src.match(/<(?:button|Button)\b[^>]*type="button"/g) || []).length)
// kein Polling
assert.doesNotMatch(src, /setInterval/)
})