import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync, readdirSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' // Die Seite und die Routen sind unter Node nicht ladbar (JSX, Router): geprüft wird der Quelltext (Muster workersComponents.test.mjs) test('App: /hustler lazy hinter ProtectedRoute, /leads und /workers leiten mit erhaltener Query auf /hustler um, kein statischer Import', () => { const s = readFileSync(new URL('../../src/App.jsx', import.meta.url), 'utf8') assert.match(s, /const WorkersPage = lazy\(\(\) => import\('\.\/pages\/WorkersPage'\)\)/); assert.doesNotMatch(s, /import WorkersPage from/) assert.match(s, /<\/Suspense><\/ProtectedRoute>\} \/>/) assert.match(s, /\} \/>/); assert.match(s, /\} \/>/) assert.match(s, /function HustlerRedirect\(\)[\s\S]*?useLocation\(\)[\s\S]*?/) // /workers?tab=botleads&bot=x → /hustler?tab=botleads&bot=x assert.ok(s.indexOf('path="/hustler"') < s.indexOf('path="*"')); assert.ok(s.indexOf('path="/workers"') < s.indexOf('path="*"')) }) test('WorkersPage: Tab-Titel Hustler, drei Bildschirme in fester Reihenfolge, keine Seitenüberschrift und keine Reiterleiste mehr, api-Instanz, Admin-Weitergabe ans Hochhaus', () => { const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8') assert.match(s, /document\.title = 'Hustler'/); assert.match(s, /return \(\) => \{ document\.title = previous \}/) // der alte Titel kommt beim Verlassen zurück assert.match(s, /id: 'hochhaus', label: 'Hochhaus'/); assert.match(s, /id: 'freigabe', label: 'Freigabe'/); assert.match(s, /id: 'botleads', label: 'Bot-Leads'/) assert.ok(s.indexOf("id: 'hochhaus'") < s.indexOf("id: 'freigabe'") && s.indexOf("id: 'freigabe'") < s.indexOf("id: 'botleads'")) assert.doesNotMatch(s, /title="Hustler"/); assert.doesNotMatch(s, /PageHeader/); assert.doesNotMatch(s, / 1/) assert.match(s, /const api = workersApi/); assert.match(s, /pickTab\(WORKERS_TABS, params\.get\('tab'\)\)/); assert.match(s, /useAuth\(\)/) assert.match(s, /]*\bapi=\{api\}/); assert.match(s, /]*isAdmin=\{isAdmin\}/) assert.match(s, /
/) // Vollhöhen-Rahmen, data-tab schaltet den Übergang für Pult und Keller assert.match(s, /import Hochhaus from '\.\.\/components\/workers\/hochhaus\/Hochhaus'/) assert.doesNotMatch(s, /WorkersBoard/) // die Kanvas-Oberfläche wird nicht mehr benutzt }) test('WorkersPage: der Feed läuft einmal pro Seite und füllt die Hustler-Tabelle; Ereignisse gehen zusätzlich an die Regie', () => { const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8') assert.equal(s.match(/useWorkersFeed\(/g).length, 1) assert.match(s, /const \{ state, error, loaded, refresh \} = useWorkersFeed\(api, \{ onEvents \}\)/) assert.match(s, /indexWorkers\(state\)/); assert.match(s, /reduceEvents\(w, events\)/) assert.match(s, /const regieRef = useRef\(null\)/); assert.match(s, /regieRef\.current\.push\(events\)/); assert.match(s, /]*regieRef=\{regieRef\}/) assert.match(s, /feed=\{\{ state, error, loaded, refresh \}\}/); assert.match(s, /]*workers=\{workers\}/) for (const f of ['Hochhaus', 'PultList', 'BotLeadList']) assert.equal(readFileSync(new URL(`../../src/components/workers/${f === 'Hochhaus' ? 'hochhaus/' : ''}${f}.jsx`, import.meta.url), 'utf8').includes('useWorkersFeed('), false, `${f} startet keinen eigenen Feed`) }) test('WorkersPage: freigabe zeigt die PultList, botleads die BotLeadList (api, bot aus der Adresse, onClearBot behält den Reiter); Wechsel per setParams lässt bot weg; Zurück ins Hochhaus', () => { const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8') assert.match(s, /import PultList from '\.\.\/components\/workers\/PultList'/); assert.match(s, /import BotLeadList from '\.\.\/components\/workers\/BotLeadList'/) assert.match(s, /active\.id === 'freigabe' && \([\s\S]*?]*\bapi=\{api\}/); assert.match(s, /]*me=\{\{ id: user\?\.\$id, name: user\?\.name \}\}/) assert.match(s, /active\.id === 'botleads' && \([\s\S]*?]*\bapi=\{api\}/) assert.match(s, /params\.get\('bot'\)/); assert.match(s, /]*\bbot=\{bot\}/); assert.match(s, /]*onClearBot=\{\(\) => setParams\(\{ tab: 'botleads' \}\)\}/) assert.match(s, /onOpen=\{\(tab\) => setParams\(\{ tab \}\)\}/) // setParams ersetzt alle Parameter: bot fällt beim Wechsel weg assert.equal(s.match(/onBack=\{\(\) => setParams\(\{ tab: 'hochhaus' \}\)\}/g).length, 2) // „◂ Hochhaus“ in Pult und Keller for (const name of ['PultList', 'BotLeadList']) assert.match(s, new RegExp(`<${name}[\\s\\S]*?kpis=\\{state\\?\\.kpis\\}[\\s\\S]*?workers=\\{workers\\}[\\s\\S]*?/>`)) assert.doesNotMatch(s, /Später kommt botleads/) }) test('CSS-Importe der Hustler-Oberfläche stehen nur in WorkersPage, nicht in den Komponenten (sonst nicht unter Node mit fakeReact ladbar)', () => { const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8') for (const css of ['workers', 'hustler/schrift', 'hustler/hochhaus', 'hustler/keller', 'hustler/pult', 'hustler/hustler-ui', 'hustler/hustler-welt', 'hustler/hustler-kopf', 'hustler/hustler-detail', 'hustler/hustler-regie', 'hustler/hustler-pult-ui', 'hustler/hustler-keller-ui']) assert.ok(s.includes(`import '../styles/${css}.css'`), css) const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)])) const root = fileURLToPath(new URL('../../src/components/workers/', import.meta.url)) const files = walk(root).filter((f) => /\.(jsx?|mjs)$/.test(f)) assert.ok(files.length > 5) for (const f of files) { const text = readFileSync(f, 'utf8') assert.doesNotMatch(text, /^\s*import\s+(?:[^'"\n]*\sfrom\s+)?['"][^'"]+\.(css|png|svg|jpe?g|webp|gif)['"]/m, `${path.relative(root, f)} importiert CSS oder Bilder`) } }) test('App: RouteErrorBoundary um die Routen im Inhaltsbereich (fehlender lazy-Chunk in altem Tab lässt nicht die ganze Oberfläche verschwinden), Pfad als resetKey', () => { const s = readFileSync(new URL('../../src/App.jsx', import.meta.url), 'utf8') assert.match(s, /import RouteErrorBoundary from '\.\/components\/RouteErrorBoundary'/) assert.match(s, /
<\/RouteErrorBoundary><\/main>/) // das Menü (Sidebar) steht außerhalb der Boundary und bleibt bei einem Fehler benutzbar assert.ok(s.indexOf('