Hustler-Pixelwelt T10: Keller (Szene mit Arne nach Status, Register-Buch, Akte, Gründe)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec63e5a43d
commit
1b3fdf5d97
14 files changed
+2247
-312
No files matched your search
@@ -0,0 +1,101 @@
|
||||
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/)
|
||||
})
|
||||
Reference in new issue
Block a user