Pult: laufende Anfragen je Eintrags-Id in PultList (laufRef, Brief meldet Beginn/Ende): Brief schließen und denselben Eintrag wieder öffnen sperrt alle Aktionsknöpfe, kein zweites reply/paste/start-build (P1); spätes Ergebnis nennt den Titel (P1b); Original und Entwurf pre-wrap (P3); Überschrift nimmt beim Einhängen den Fokus (M2); „Pult zuklappen“ mit aria-expanded (K4); paused -> hk-steht an der Szene (R3); Brief z-index 9 (R4); Textlinks am Handy 40 px (K7). Keller: Überschrift-Fokus (M2), Reiter-Fokusring innen (M3), Seitenzeile/Punkte/Reiterzahl in Pixelify Sans 13 px (K5), Suchfeld und Akte-Link 40 px am Handy (K7), Ortszeile mit title (W2), paused -> hk-steht (R3), Akte z-index 9 (R4); Arne ohne Eintrag neutral (R5). Tests: Sperre laufender Aktionen, Titel im späten Ergebnis, Fokus, aria-expanded, paused, pre-wrap, z-index, Tippflächen, Reiter-Fokus, Schrift, Kontrast (WCAG) von .hk-zeile-alt/.hk-kl-tag auf Papier und Hover, Arne neutral (alte „Arne schläft ohne Eintrag“-Prüfung ersetzt). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
254 lines
17 KiB
JavaScript
254 lines
17 KiB
JavaScript
import test from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { readFileSync } from 'node:fs'
|
||
import { loadModule } from './load.mjs'
|
||
|
||
// Reine Daten und Ableitungen des Kellers (src/lib/hustlerKeller.js, Hustler-Pixelwelt T10, Spec E12/E13): Einpassen der Szene, Lage der Dinge, Arne nach Status,
|
||
// Raum-Reiter, Scout, Punkte-Anzeige, Zeilenmodell eines Bot-Leads.
|
||
|
||
const K = await loadModule('src/lib/hustlerKeller.js')
|
||
const V = await loadModule('src/lib/botleadsView.js')
|
||
const { SZENE, kellerEinpassen, FENSTER, DAMPF, BUCH_STEMPEL, ROHRPOST, SCHUBLADE, MAUS_STREIFEN, ARNE_RUHE, LEDS, REITER, raumIcon, scoutFuer, punkteInfo, eintragModell, arneAnsicht } = K
|
||
const { ROOM_FILTERS } = V
|
||
|
||
const read = (p) => readFileSync(new URL('../../' + p, import.meta.url), 'utf8')
|
||
const ohneKommentare = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '')
|
||
const kellerCss = ohneKommentare(read('src/styles/hustler/keller.css'))
|
||
const uiCss = ohneKommentare(read('src/styles/hustler/hustler-keller-ui.css'))
|
||
const klassen = (css) => new Set((css.replace(/url\([^)]*\)/g, '').match(/\.hk-[A-Za-z0-9_-]+/g) ?? []).map((k) => k.slice(1)))
|
||
const CSS = new Set([...klassen(kellerCss), ...klassen(uiCss)])
|
||
const KEYFRAMES = new Set([...kellerCss.matchAll(/@keyframes\s+([\w-]+)/g)].map((m) => m[1]))
|
||
const px = (s) => Number.parseFloat(s)
|
||
|
||
function tiefEingefroren(wert, pfad) {
|
||
if (wert === null || typeof wert !== 'object') return
|
||
assert.ok(Object.isFrozen(wert), `${pfad} ist nicht eingefroren`)
|
||
for (const [k, v] of Object.entries(wert)) tiefEingefroren(v, `${pfad}.${k}`)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------------------------
|
||
// Szene
|
||
|
||
test('kellerEinpassen: „cover“ links verankert, Maßstab = größeres Seitenverhältnis, Vorgabe ohne Messung', () => {
|
||
assert.deepEqual(SZENE, { breite: 640, hoehe: 360 })
|
||
assert.deepEqual(kellerEinpassen(1280, 720), { s: 2, x: 0, y: 0 })
|
||
assert.deepEqual(kellerEinpassen(1920, 1080), { s: 3, x: 0, y: 0 })
|
||
// breiter als hoch: der Maßstab folgt der Höhe, links bleibt verankert (x nie positiv, nur ein Teil des Überstands wandert)
|
||
const schmal = kellerEinpassen(1280, 900)
|
||
assert.equal(schmal.s, 2.5)
|
||
assert.equal(schmal.x, -48)
|
||
assert.equal(schmal.y, 0)
|
||
// sehr breit: der Maßstab folgt der Breite, die Szene steht tiefer (der untere Rand bleibt, höchstens 36 Szenen-Pixel gehen unten verloren)
|
||
const breit = kellerEinpassen(2560, 720)
|
||
assert.equal(breit.s, 4)
|
||
assert.equal(breit.x, 0)
|
||
assert.equal(breit.y, -576)
|
||
// Handy hochkant
|
||
const handy = kellerEinpassen(390, 844)
|
||
assert.ok(Math.abs(handy.s - 844 / 360) < 1e-9)
|
||
assert.ok(handy.x < 0 && handy.x > -(640 * handy.s - 390))
|
||
assert.equal(handy.y, 0)
|
||
// ohne brauchbare Größe: Vorgabe der Vorlage
|
||
for (const [b, h] of [[0, 0], [NaN, 720], [1280, NaN], [-1, 700], [1280, 0], [undefined, undefined], ['1280', '720']]) {
|
||
assert.deepEqual(kellerEinpassen(b, h), { s: 2, x: 0, y: 0 }, `${b} x ${h}`)
|
||
}
|
||
})
|
||
|
||
test('kellerEinpassen: die Szene deckt den Rahmen immer ganz (cover) und liegt nie rechts vom linken Rand', () => {
|
||
for (const [b, h] of [[1280, 720], [1920, 1080], [1048, 720], [390, 844], [768, 1024], [2560, 1440], [3440, 1440], [320, 568], [1000, 300]]) {
|
||
const { s, x, y } = kellerEinpassen(b, h)
|
||
assert.ok(640 * s >= b - 1e-9 && 360 * s >= h - 1e-9, `${b} x ${h}: Szene kleiner als der Rahmen`)
|
||
assert.ok(x <= 0 && x + 640 * s >= b - 1e-9, `${b} x ${h}: Lücke links oder rechts (x ${x})`)
|
||
assert.ok(y <= 0 && y + 360 * s >= h - 1e-9, `${b} x ${h}: Lücke oben oder unten (y ${y})`)
|
||
}
|
||
})
|
||
|
||
test('Lage der Dinge: alles liegt in der 640 x 360 großen Szene, Arne am Pult steht auf der Laufline, Daten sind eingefroren', () => {
|
||
for (const [name, o] of Object.entries({ FENSTER, DAMPF, BUCH_STEMPEL, ROHRPOST, SCHUBLADE, MAUS_STREIFEN, ARNE_RUHE })) {
|
||
assert.equal(o.position, 'absolute', name)
|
||
assert.ok(px(o.left) >= 0 && px(o.top) >= 0 && px(o.left) < 640 && px(o.top) < 360, name)
|
||
assert.match(o.left, /^\d+px$/); assert.match(o.top, /^\d+px$/)
|
||
}
|
||
// Maße der Vorlage (BotLeads.dc.html)
|
||
assert.deepEqual([FENSTER.left, FENSTER.top, FENSTER.width, FENSTER.height, FENSTER.overflow], ['23px', '34px', '46px', '29px', 'hidden'])
|
||
assert.deepEqual([ROHRPOST.left, ROHRPOST.top], ['130px', '224px'])
|
||
assert.deepEqual([SCHUBLADE.left, SCHUBLADE.top], ['214px', '279px'])
|
||
assert.deepEqual([BUCH_STEMPEL.left, BUCH_STEMPEL.top], ['60px', '252px'])
|
||
assert.deepEqual([DAMPF.left, DAMPF.top], ['26px', '247px'])
|
||
assert.deepEqual([MAUS_STREIFEN.left, MAUS_STREIFEN.top, MAUS_STREIFEN.width, MAUS_STREIFEN.height], ['30px', '336px', '238px', '24px'])
|
||
assert.equal(px(MAUS_STREIFEN.top) + px(MAUS_STREIFEN.height), 360)
|
||
// Haus-Figur 24 x 32: die Füße stehen auf der Laufline (318), Mitte beim Halt am Pult (128)
|
||
assert.equal(px(ARNE_RUHE.top) + 32, 318)
|
||
assert.equal(px(ARNE_RUHE.left) + 12, 128)
|
||
for (const [name, o] of Object.entries({ SZENE, FENSTER, DAMPF, BUCH_STEMPEL, ROHRPOST, SCHUBLADE, MAUS_STREIFEN, ARNE_RUHE, LEDS, REITER })) tiefEingefroren(o, name)
|
||
})
|
||
|
||
test('Blinkpunkte: sechs, jeder mit Keyframes aus keller.css (hk-k-k-led-…), Maße und Takte der Vorlage', () => {
|
||
assert.deepEqual(LEDS.map((l) => l.name), ['weiche-gruen', 'weiche-bernstein', 'sicherung-1', 'sicherung-2', 'sicherung-3', 'verteiler'])
|
||
for (const l of LEDS) {
|
||
const m = /^(hk-k-k-led-[a-z0-9-]+) (\d+)ms steps\(1,end\) infinite$/.exec(l.stil.animation)
|
||
assert.ok(m, l.stil.animation)
|
||
assert.equal(m[1], `hk-k-k-led-${l.name}`)
|
||
assert.ok(KEYFRAMES.has(m[1]), `${m[1]} fehlt in keller.css`)
|
||
assert.equal(l.stil.height, '2px')
|
||
assert.match(l.stil.background, /^#[0-9a-f]{6}$/)
|
||
assert.ok(px(l.stil.left) < 640 && px(l.stil.top) < 360)
|
||
}
|
||
assert.deepEqual(LEDS.map((l) => px(l.stil.animation.split(' ')[1])), [1800, 2600, 3000, 2200, 3400, 2000])
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------------------------------
|
||
// Arne
|
||
|
||
const eintrag = (status) => (status === 'grau' ? { status: 'grau', task: '', lastTs: null, connected: false, counters: {} } : { status, task: '', lastTs: null, connected: true, counters: {} })
|
||
|
||
test('arneAnsicht: arbeitet ⇒ Ablauf-Schleife; sonst Haus-Figur nach Status (warten, schlafen, nicht), Fehler mit Marker', () => {
|
||
const arbeit = arneAnsicht({ arne: eintrag('arbeitet') })
|
||
assert.deepEqual(arbeit, { status: 'arbeitet', arbeitet: true, pose: 'arbeiten', alarm: false, punkte: false, zzz: false, label: 'Arne, Haiku, arbeitet' })
|
||
assert.deepEqual(arneAnsicht({ arne: eintrag('wartet') }), { status: 'wartet', arbeitet: false, pose: 'warten', alarm: false, punkte: true, zzz: false, label: 'Arne, Haiku, wartet auf Auftrag' })
|
||
assert.deepEqual(arneAnsicht({ arne: eintrag('leerlauf') }), { status: 'leerlauf', arbeitet: false, pose: 'schlafen', alarm: false, punkte: false, zzz: true, label: 'Arne, Haiku, Leerlauf' })
|
||
assert.deepEqual(arneAnsicht({ arne: eintrag('grau') }), { status: 'grau', arbeitet: false, pose: 'nicht', alarm: false, punkte: false, zzz: false, label: 'Arne, Haiku, noch nicht angeschlossen' })
|
||
assert.deepEqual(arneAnsicht({ arne: eintrag('fehler') }), { status: 'fehler', arbeitet: false, pose: 'nicht', alarm: true, punkte: false, zzz: false, label: 'Arne, Haiku, Fehler' })
|
||
})
|
||
|
||
test('arneAnsicht: nur „arbeitet“ arbeitet; ein Eintrag mit unbrauchbarem Status schläft (Leerlauf)', () => {
|
||
for (const workers of [{ arne: { status: 'irgendwas' } }, { arne: {} }]) {
|
||
const a = arneAnsicht(workers)
|
||
assert.equal(a.arbeitet, false, JSON.stringify(workers))
|
||
assert.equal(a.status, 'leerlauf')
|
||
assert.equal(a.pose, 'schlafen')
|
||
assert.equal(a.alarm, false)
|
||
}
|
||
// ein Eintrag anderer Hustler ändert nichts: nur arne zählt
|
||
assert.equal(arneAnsicht({ arne: eintrag('wartet'), otto: eintrag('arbeitet'), ali: eintrag('fehler') }).status, 'wartet')
|
||
// arbeitet und gleichzeitig nicht angeschlossen: grau gewinnt (figurZustand)
|
||
assert.equal(arneAnsicht({ arne: { ...eintrag('arbeitet'), connected: false } }).status, 'grau')
|
||
})
|
||
|
||
// T13 R5: ohne Eintrag für Arne (Tabelle leer, die Seite hat den ersten Zustand noch nicht) ist nichts über ihn bekannt: neutral, kein Schlaf, keine „nicht angeschlossen“-Pose
|
||
test('arneAnsicht (T13 R5): ohne Tabelle oder ohne Eintrag für Arne steht er neutral da – nicht arbeitend, ohne Zzz, ohne Punkte, ohne Marker, nicht „nicht angeschlossen“', () => {
|
||
const neutral = { status: 'unbekannt', arbeitet: false, pose: 'warten', alarm: false, punkte: false, zzz: false, label: 'Arne, Haiku' }
|
||
for (const workers of [undefined, null, {}, [], 'x', 5, { otto: eintrag('arbeitet') }, { ali: eintrag('fehler') }, { arne: null }, { arne: undefined }, { arne: 'arbeitet' }, { arne: 7 }, { arne: [] }]) {
|
||
assert.deepEqual(arneAnsicht(workers), neutral, JSON.stringify(workers))
|
||
}
|
||
// neutral ist weder einer der Status des Dienstes noch „grau“
|
||
for (const status of ['arbeitet', 'wartet', 'leerlauf', 'fehler', 'grau']) assert.notEqual(arneAnsicht({ arne: eintrag(status) }).status, 'unbekannt', status)
|
||
assert.equal(arneAnsicht({ arne: eintrag('grau') }).status, 'grau')
|
||
// die Pose „warten“ gibt es als Sprite und als Klasse im CSS (sonst stünde Arne unsichtbar da)
|
||
assert.ok(CSS.has('hk-kl-fig-warten'))
|
||
})
|
||
|
||
test('REITER: Reihenfolge, Werte und Beschriftungen von ROOM_FILTERS, kurze sichtbare Namen, Symbol und Farbe je Reiter aus keller.css', () => {
|
||
assert.deepEqual(REITER.map((r) => r.value), ROOM_FILTERS.map((f) => f.value))
|
||
assert.deepEqual(REITER.map((r) => r.label), ROOM_FILTERS.map((f) => f.label))
|
||
assert.deepEqual(REITER.map((r) => r.kurz), ['Alle', 'Ärzte', 'Handwerk', 'Selbstständige', 'Läden'])
|
||
assert.deepEqual(REITER.map((r) => r.icon), ['hk-ic hk-ic-alle', 'hk-ic hk-ic-raum-aerzte', 'hk-ic hk-ic-raum-handwerk', 'hk-ic hk-ic-raum-selbst', 'hk-ic hk-ic-raum-laden'])
|
||
assert.deepEqual(REITER.map((r) => r.klasse), ['hk-reiter hk-reiter-alle', 'hk-reiter hk-reiter-aerzte', 'hk-reiter hk-reiter-handwerk', 'hk-reiter hk-reiter-selbst', 'hk-reiter hk-reiter-laden'])
|
||
for (const r of REITER) for (const k of `${r.icon} ${r.klasse}`.split(' ')) assert.ok(CSS.has(k), `${k} fehlt im CSS`)
|
||
})
|
||
|
||
test('raumIcon: Symbol je Raum, ein unbekannter Raum zeigt den Aktenstapel', () => {
|
||
assert.equal(raumIcon('aerzte'), 'hk-ic hk-ic-raum-aerzte')
|
||
assert.equal(raumIcon('handwerk'), 'hk-ic hk-ic-raum-handwerk')
|
||
assert.equal(raumIcon('selbst'), 'hk-ic hk-ic-raum-selbst')
|
||
assert.equal(raumIcon('laden'), 'hk-ic hk-ic-raum-laden')
|
||
for (const unbekannt of ['', 'netz', 'xyz', 'constructor', '__proto__', undefined, null, 3, {}]) assert.equal(raumIcon(unbekannt), 'hk-ic hk-ic-alle', String(unbekannt))
|
||
for (const room of ['aerzte', 'handwerk', 'selbst', 'laden', '']) for (const k of raumIcon(room).split(' ')) assert.ok(CSS.has(k), k)
|
||
})
|
||
|
||
test('scoutFuer: Raum → Hustler der Etage „Ohne Website“ (aerzte → Ali, handwerk → Hanna, selbst → Sven, laden → Lea), sonst leer', () => {
|
||
assert.equal(scoutFuer('aerzte'), 'Ali')
|
||
assert.equal(scoutFuer('handwerk'), 'Hanna')
|
||
assert.equal(scoutFuer('selbst'), 'Sven')
|
||
assert.equal(scoutFuer('laden'), 'Lea')
|
||
// Räume anderer Etagen (Rita im Netz, Arne im Archiv) und alles Unbekannte haben keinen Scout
|
||
for (const room of ['netz', 'archiv', 'bruecke', 'waage', '', 'xyz', 'constructor', undefined, null, 7]) assert.equal(scoutFuer(room), '', String(room))
|
||
})
|
||
|
||
test('punkteInfo: Zahl, Balkenbreite (48 und 96 px) und Farbe (ab 75 gut, ab 55 mittel, darunter schlecht, ohne Zahl leer)', () => {
|
||
assert.deepEqual(punkteInfo(82), { text: '82', breite: 39, breiteGross: 79, farbe: 'gut' })
|
||
assert.deepEqual(punkteInfo(75), { text: '75', breite: 36, breiteGross: 72, farbe: 'gut' })
|
||
assert.equal(punkteInfo(74).farbe, 'mittel')
|
||
assert.equal(punkteInfo(55).farbe, 'mittel')
|
||
assert.equal(punkteInfo(54).farbe, 'schlecht')
|
||
assert.deepEqual(punkteInfo(0), { text: '0', breite: 0, breiteGross: 0, farbe: 'schlecht' })
|
||
assert.deepEqual(punkteInfo(100), { text: '100', breite: 48, breiteGross: 96, farbe: 'gut' })
|
||
assert.equal(punkteInfo(81.6).text, '82') // gerundet wie scoreLabel in botleadRow
|
||
// der Balken bleibt im Rahmen, der Text nennt die Zahl des Dienstes
|
||
assert.deepEqual(punkteInfo(130), { text: '130', breite: 48, breiteGross: 96, farbe: 'gut' })
|
||
assert.deepEqual(punkteInfo(-5), { text: '-5', breite: 0, breiteGross: 0, farbe: 'schlecht' })
|
||
for (const nichts of [undefined, null, NaN, Infinity, 'x', '82', {}]) assert.deepEqual(punkteInfo(nichts), { text: '–', breite: 0, breiteGross: 0, farbe: 'leer' }, String(nichts))
|
||
})
|
||
|
||
const ITEM = {
|
||
id: 'p1', room: 'handwerk', roomLabel: 'Handwerk', name: 'Elektro Beispiel GmbH', postalCode: '67655', city: 'Kaiserslautern', score: 82,
|
||
reason: 'keine veröffentlichte Mail', sourceUrl: 'https://beispiel-elektro.de/impressum', phone: '0631 111', phoneSource: 'https://beispiel-elektro.de/impressum',
|
||
isHealth: false, foundAt: '2026-10-19T20:00:00Z', gekuerzt: false,
|
||
}
|
||
|
||
test('eintragModell: Zeilenmodell von botleadRow plus Raum-Symbol, Scout, Punkte, Grund mit Erklärung, Telefon-Symbol und Label', () => {
|
||
const m = eintragModell(ITEM)
|
||
assert.deepEqual({ ...m, punkte: undefined }, {
|
||
...V.botleadRow(ITEM),
|
||
raum: 'handwerk', raumIcon: 'hk-ic hk-ic-raum-handwerk', scout: 'Hanna', punkte: undefined,
|
||
grundIcon: 'hk-ic hk-ic-brief-x', erklaerung: 'Auf der Website steht keine E-Mail-Adresse. Ohne veröffentlichte Adresse schreibt der Hustler nie.',
|
||
telefonIcon: 'hk-ic hk-ic-telefon',
|
||
label: 'Elektro Beispiel GmbH, 67655 Kaiserslautern, 82 Punkte, keine veröffentlichte Mail, gefunden am 19.10.2026',
|
||
})
|
||
assert.deepEqual(m.punkte, punkteInfo(82))
|
||
assert.equal(m.sourceHref, 'https://beispiel-elektro.de/impressum')
|
||
assert.equal(m.phone, '0631 111')
|
||
})
|
||
|
||
test('eintragModell: Heilberuf nie mit Nummer (Schloss), unsichere Quelle ohne Link, ohne Quelle der Nummer „nicht verfügbar“, gekürzt mit Schere', () => {
|
||
const praxis = eintragModell({ ...ITEM, id: 'p2', room: 'aerzte', roomLabel: 'Ärzte & Praxen', isHealth: true, sourceUrl: 'javascript:alert(1)' })
|
||
assert.equal(praxis.phone, 'bewusst nicht gespeichert')
|
||
assert.equal(praxis.telefonIcon, 'hk-ic hk-ic-schloss')
|
||
assert.equal(praxis.sourceHref, '')
|
||
assert.equal(praxis.scout, 'Ali')
|
||
// ein Altbestand mit Nummer und Quelle verrät sie bei einem Heilberuf trotzdem nicht
|
||
assert.equal(eintragModell({ ...ITEM, isHealth: true }).phone, 'bewusst nicht gespeichert')
|
||
assert.equal(eintragModell({ ...ITEM, phoneSource: '' }).phone, 'nicht verfügbar')
|
||
assert.equal(eintragModell({ ...ITEM, phoneSource: '' }).telefonIcon, 'hk-ic hk-ic-telefon')
|
||
assert.equal(eintragModell({ ...ITEM, sourceUrl: 'https://user:pw@evil.example/' }).sourceHref, '')
|
||
assert.equal(eintragModell({ ...ITEM, sourceUrl: 'http://alt.example/kontakt' }).sourceHref, 'http://alt.example/kontakt')
|
||
const kuerz = eintragModell({ ...ITEM, name: '[gekürzt]', gekuerzt: true, phone: '', phoneSource: '', sourceUrl: '' })
|
||
assert.equal(kuerz.gekuerzt, true)
|
||
assert.equal(kuerz.telefonIcon, 'hk-ic hk-ic-schere')
|
||
assert.ok(kuerz.label.startsWith('[gekürzt] (gekürzt), '))
|
||
// jedes Symbol des Modells gibt es im CSS
|
||
for (const m of [eintragModell(ITEM), praxis, kuerz]) for (const k of `${m.raumIcon} ${m.grundIcon} ${m.telefonIcon}`.split(' ')) assert.ok(CSS.has(k), k)
|
||
})
|
||
|
||
test('eintragModell: fehlende oder kaputte Werte ergeben Platzhalter, nie undefined, NaN oder einen Fehler', () => {
|
||
for (const kaputt of [null, undefined, 42, 'x', [], {}, { room: 5, score: 'x', reason: null, name: 7 }]) {
|
||
const m = eintragModell(kaputt)
|
||
assert.equal(m.title, 'nicht verfügbar')
|
||
assert.equal(m.scoreLabel, '–')
|
||
assert.equal(m.reason, '–')
|
||
assert.equal(m.raum, '')
|
||
assert.equal(m.raumIcon, 'hk-ic hk-ic-alle')
|
||
assert.equal(m.scout, '')
|
||
assert.deepEqual(m.punkte, { text: '–', breite: 0, breiteGross: 0, farbe: 'leer' })
|
||
assert.equal(m.grundIcon, 'hk-ic hk-ic-akte') // Grund „–“ ist nicht bekannt: neutrales Symbol
|
||
assert.equal(m.sourceHref, '')
|
||
assert.ok(!/undefined|NaN/.test(JSON.stringify(m)), JSON.stringify(m))
|
||
assert.equal(typeof m.label, 'string')
|
||
}
|
||
// unbekannter Raum und unbekannter Grund
|
||
const m = eintragModell({ ...ITEM, room: 'xyz', reason: 'irgendein neuer Grund' })
|
||
assert.equal(m.scout, '')
|
||
assert.equal(m.raumIcon, 'hk-ic hk-ic-alle')
|
||
assert.equal(m.grundIcon, 'hk-ic hk-ic-akte')
|
||
assert.match(m.erklaerung, /Der Hustler schreibt diesem Betrieb nicht/)
|
||
})
|
||
|
||
test('hustlerKeller.js: rein (kein React, kein DOM, kein CSS und keine Bilder), nur Importe aus src/lib', () => {
|
||
const src = read('src/lib/hustlerKeller.js').replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|\s)\/\/.*$/gm, '$1')
|
||
const importe = [...src.matchAll(/^\s*import\b[^'"]*['"]([^'"]+)['"]/gm)].map((m) => m[1])
|
||
assert.deepEqual(importe.sort(), ['./botleadGruende.js', './botleadsView.js', './hustlerSicht.js', './workersRoster.js'])
|
||
assert.doesNotMatch(src, /\b(?:window|document|fetch|localStorage)\b/)
|
||
assert.doesNotMatch(src, /innerHTML/)
|
||
assert.doesNotMatch(src, /\.(?:css|png|svg|jpg)['"]/)
|
||
})
|