99 lines
7.6 KiB
JavaScript
99 lines
7.6 KiB
JavaScript
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/)
|
||
})
|