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

109 lines
6.6 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { loadModule } from './load.mjs'
// Gründe der Bot-Lead-Liste (src/lib/botleadGruende.js, Hustler-Pixelwelt T10, Spec E12): Grund-Text des Dienstes -> Symbol und Erklärung für die Akte.
// Die Fixture tests/fixtures/botlead-gruende.json trägt alle 14 Texte des Dienstes (workers-dev-p6/src/scoring/business.js, GRUND, Stand d0d66f9); sie ist unabhängig vom Code unter Test.
const { grundInfo, grundBekannt, ALLGEMEIN, GRUND_SYMBOLE } = await loadModule('src/lib/botleadGruende.js')
const fixture = JSON.parse(readFileSync(new URL('../fixtures/botlead-gruende.json', import.meta.url), 'utf8'))
const read = (p) => readFileSync(new URL('../../' + p, import.meta.url), 'utf8')
// Symbol je Grund (Tabelle GRUENDE der Zeichenfläche, keller/entwurf/logik.js; „Lauf unterbrochen“ bekommt das Pausen-Symbol)
const SYMBOL = {
KEINE_MAIL: 'brief-x', MX: 'brief-zurueck', KETTE: 'kette', WEBSITE_GUT: 'haken', SCHWELLE: 'pfeil-runter', VERALTET: 'uhr', BEKANNT: 'doppelt',
BEKANNT_NAME: 'doppelt', GESPERRT: 'stopp', LESEN: 'seite-kaputt', IMPRESSUM_FREMD: 'fremd', ABGEBROCHEN: 'pause', BEWERTUNG_AUSGEFALLEN: 'waage-kaputt', NAME_FEHLT: 'etikett-leer',
}
// Klassen aus keller.css und hustler-keller-ui.css (ohne Kommentare und url(...))
const cssKlassen = (datei) => new Set((read(datei).replace(/\/\*[\s\S]*?\*\//g, '').replace(/url\([^)]*\)/g, '').match(/\.hk-[A-Za-z0-9_-]+/g) ?? []).map((k) => k.slice(1)))
const CSS = new Set([...cssKlassen('src/styles/hustler/keller.css'), ...cssKlassen('src/styles/hustler/hustler-keller-ui.css')])
test('Fixture: 14 Texte des Dienstes mit Quelle und Stand', () => {
assert.match(fixture.quelle, /scoring\/business\.js/)
assert.match(fixture.quelle, /d0d66f9/)
assert.equal(fixture.stand, 'd0d66f9')
const texte = Object.values(fixture.gruende)
assert.equal(texte.length, 14)
assert.equal(new Set(texte).size, 14)
assert.ok(texte.every((t) => typeof t === 'string' && t.trim() === t && t !== ''))
assert.deepEqual(Object.keys(fixture.gruende).sort(), Object.keys(SYMBOL).sort())
assert.equal(fixture.gruende.SCHWELLE, 'Punktzahl unter 55') // so, wie der Dienst den Text mit der Grenze bildet
})
test('grundInfo: jeder Grund der Fixture hat sein Symbol und eine eigene Erklärung (nie der allgemeine Satz)', () => {
const saetze = new Set()
for (const [name, text] of Object.entries(fixture.gruende)) {
assert.equal(grundBekannt(text), true, name)
const info = grundInfo(text)
assert.equal(info.icon, `hk-ic hk-ic-${SYMBOL[name]}`, name)
assert.equal(typeof info.erklaerung, 'string')
assert.ok(info.erklaerung.length > 15 && info.erklaerung.endsWith('.'), `${name}: ${info.erklaerung}`)
assert.notEqual(info.erklaerung, ALLGEMEIN.erklaerung, name)
assert.deepEqual(Object.keys(info).sort(), ['erklaerung', 'icon'])
saetze.add(info.erklaerung)
}
assert.equal(saetze.size, 14) // auch die beiden „schon bekannt“ haben je einen eigenen Satz
})
test('grundInfo: Wortlaut der Erklärungen aus der Zeichenfläche, „Lauf unterbrochen“ im selben Ton', () => {
assert.equal(grundInfo('keine veröffentlichte Mail').erklaerung, 'Auf der Website steht keine E-Mail-Adresse. Ohne veröffentlichte Adresse schreibt der Hustler nie.')
assert.equal(grundInfo('Punktzahl unter 55').erklaerung, 'Zu wenige Punkte für eine Mail (Grenze 55).')
assert.equal(grundInfo('Sperrliste').erklaerung, 'Hat widersprochen oder steht auf der Sperrliste.')
assert.equal(grundInfo('Bewertung ausgefallen').erklaerung, 'Bea konnte den Betrieb nicht bewerten.')
const abbruch = grundInfo('Lauf unterbrochen')
assert.equal(abbruch.icon, 'hk-ic hk-ic-pause')
assert.match(abbruch.erklaerung, /unterbrochen/)
assert.match(abbruch.erklaerung, /keine Mail/)
})
test('grundInfo: die Grenze in „Punktzahl unter N“ kommt aus dem Text des Dienstes (ändert er sie, stimmt die Erklärung weiter)', () => {
assert.equal(grundInfo('Punktzahl unter 60').erklaerung, 'Zu wenige Punkte für eine Mail (Grenze 60).')
assert.equal(grundInfo('Punktzahl unter 60').icon, 'hk-ic hk-ic-pfeil-runter')
assert.equal(grundBekannt('Punktzahl unter 60'), true)
for (const nein of ['Punktzahl unter', 'Punktzahl unter x', 'Punktzahl unter 5 5', 'Punktzahl unter 0055', 'Punktzahl unter 55 Punkte']) assert.equal(grundBekannt(nein), false, nein)
})
test('grundInfo: Leerraum am Rand zählt nicht, Groß- und Kleinschreibung schon', () => {
assert.deepEqual(grundInfo(' Sperrliste \n'), grundInfo('Sperrliste'))
assert.equal(grundBekannt('sperrliste'), false)
})
test('grundInfo: unbekannter Text ergibt das neutrale Symbol und einen allgemeinen Satz, nie einen Fehler', () => {
for (const unbekannt of ['irgendein neuer Grund', '', ' ', null, undefined, 42, {}, [], 'constructor', '__proto__', 'toString', 'hasOwnProperty']) {
const info = grundInfo(unbekannt)
assert.deepEqual(info, { icon: 'hk-ic hk-ic-akte', erklaerung: ALLGEMEIN.erklaerung }, String(unbekannt))
assert.equal(grundBekannt(unbekannt), false)
}
assert.match(ALLGEMEIN.erklaerung, /^Der Hustler schreibt diesem Betrieb nicht\./)
})
test('Symbole: jede Klasse, die grundInfo liefern kann, steht in keller.css oder hustler-keller-ui.css', () => {
const klassen = new Set()
for (const text of [...Object.values(fixture.gruende), 'Punktzahl unter 70', 'unbekannt']) {
const { icon } = grundInfo(text)
assert.match(icon, /^hk-ic hk-ic-[a-z-]+$/)
for (const k of icon.split(' ')) klassen.add(k)
}
assert.ok(klassen.size >= 14)
for (const k of klassen) assert.ok(CSS.has(k), `${k} fehlt in keller.css / hustler-keller-ui.css`)
// GRUND_SYMBOLE nennt genau diese Symbole
assert.deepEqual([...GRUND_SYMBOLE].sort(), [...new Set([...Object.values(SYMBOL), 'akte'])].sort())
for (const s of GRUND_SYMBOLE) assert.ok(CSS.has(`hk-ic-${s}`), s)
})
test('botleadGruende.js: rein (kein React, kein DOM, keine Importe), keine Beispieldaten', () => {
const src = read('src/lib/botleadGruende.js').replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|\s)\/\/.*$/gm, '$1')
assert.doesNotMatch(src, /^\s*import\b/m)
assert.doesNotMatch(src, /\b(?:window|document|fetch)\b/)
assert.doesNotMatch(src, /innerHTML/)
})
test('„veraltet (nicht ausgewählt)“ erklärt die 72-Stunden-Regel des Dienstes (night.js, WAHL_STUNDEN), nicht eine veraltete Website', async () => {
const { grundInfo } = await loadModule('src/lib/botleadGruende.js')
const info = grundInfo('veraltet (nicht ausgewählt)')
assert.match(info.erklaerung, /72 Stunden/)
assert.doesNotMatch(info.erklaerung, /Website ist veraltet/)
})