Files
tickte-system/tests/unit/workersRoute.test.mjs
ClaudeandClaude Sonnet 5.5 1ab760be0c Hustler-Keller: unter Stufe 6 leeres Register ohne Abruf, mit ehrlichem Text
BotLeadList bekommt die Stufe (WorkersPage reicht sie durch). Unterhalb von BOTLEADS_AB
(Stufe 6) ruft sie weder api.botleads noch ein JWT, zeigt keine Ladeanzeige und keinen
Fehlerzustand: Szene mit Arne, Register-Buch, Reiter und Suche stehen da, das Register ist leer
("Noch keine Bot-Leads. Hier legt Arne Betriebe ohne Website ab, die Bea nicht fuer eine Mail
ausgewaehlt hat. Die Branchen-Raeume ... suchen noch nicht."), ohne Zahl am Reiter. Reiter,
Suche, Zuklappen und Alle anzeigen bleiben bedienbar und loesen nie einen Abruf aus. Ab Stufe 6
unveraendert (Live-Daten); der Test-Laeufer startet den Keller standardmaessig mit Stufe 6.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 12:23:03 +00:00

70 lines
7.7 KiB
JavaScript

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, /<Route path="\/hustler" element=\{<ProtectedRoute><Suspense[\s\S]*?<WorkersPage \/><\/Suspense><\/ProtectedRoute>\} \/>/)
assert.match(s, /<Route path="\/leads" element=\{<HustlerRedirect \/>\} \/>/); assert.match(s, /<Route path="\/workers" element=\{<HustlerRedirect \/>\} \/>/)
assert.match(s, /function HustlerRedirect\(\)[\s\S]*?useLocation\(\)[\s\S]*?<Navigate to=\{`\/hustler\$\{search\}`\} replace \/>/) // /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, /<Tabs\b/); assert.doesNotMatch(s, /WORKERS_TABS\.length > 1/)
assert.match(s, /const api = workersApi/); assert.match(s, /pickTab\(WORKERS_TABS\.filter\(\(t\) => tabErlaubt\(t\.id, stufe\)\), params\.get\('tab'\)\)/); assert.match(s, /useAuth\(\)/) // Stufen-Sperre: der Keller ist ab Stufe 3 ein Bildschirm, die Bot-Leads ruft er erst ab Stufe 6 ab (hustlerStufe.test.mjs, botleadList.test.mjs)
assert.match(s, /<Hochhaus[^>]*\bapi=\{api\}/); assert.match(s, /<Hochhaus[^>]*isAdmin=\{isAdmin\}/)
assert.match(s, /<div className="hk-seite" data-tab=\{active\.id\}>/) // 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\(t\.workers, events\)/) // die Tabelle merkt sich ihren Schnappschuss (R5) und wird bei Ereignissen fortgeschrieben
assert.match(s, /const regieRef = useRef\(null\)/); assert.match(s, /regieRef\.current\.push\(events\)/); assert.match(s, /<Hochhaus[^>]*regieRef=\{regieRef\}/)
assert.match(s, /feed=\{\{ state, error, loaded, refresh \}\}/); assert.match(s, /<Hochhaus[^>]*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]*?<PultList[^>]*\bapi=\{api\}/); assert.match(s, /<PultList[^>]*me=\{\{ id: user\?\.\$id, name: user\?\.name \}\}/)
assert.match(s, /active\.id === 'botleads' && \([\s\S]*?<BotLeadList[^>]*\bapi=\{api\}/)
assert.match(s, /params\.get\('bot'\)/); assert.match(s, /<BotLeadList[^>]*\bstufe=\{stufe\}/); assert.match(s, /<BotLeadList[^>]*\bbot=\{bot\}/); assert.match(s, /<BotLeadList[^>]*onClearBot=\{\(\) => setParams\(\{ tab: 'botleads' \}\)\}/)
assert.match(s, /onOpen=\{\(tab\) => \{ if \(tabErlaubt\(tab, stufe\)\) setParams\(\{ tab \}\) \}\}/) // setParams ersetzt alle Parameter: bot fällt beim Wechsel weg; ein gesperrter Bildschirm öffnet sich nie
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]*?paused=\\{paused\\}[\\s\\S]*?/>`)) // Pause (R3) geht an Pult und Keller
assert.match(s, /const paused = state\?\.paused === true/)
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, /<main id="inhalt"><RouteErrorBoundary resetKey=\{location\.pathname\}><AppRoutes \/><\/RouteErrorBoundary><\/main>/)
// das Menü (Sidebar) steht außerhalb der Boundary und bleibt bei einem Fehler benutzbar
assert.ok(s.indexOf('<Sidebar') < s.indexOf('<RouteErrorBoundary'))
const b = readFileSync(new URL('../../src/components/RouteErrorBoundary.jsx', import.meta.url), 'utf8')
assert.match(b, /window\.location\.reload\(\)/); assert.match(b, /Seite neu laden/); assert.match(b, /getDerivedStateFromError/)
})