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>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 12:23:03 +00:00
1 parent 6eb4dd3c51
commit 1ab760be0c
5 files changed
+161 -15

No files matched your search

+128
View File
@@ -555,3 +555,131 @@ test('aria-current (C5): nur die Zeile mit der Kennung aus bot, nie eine Zeile o
assert.deepEqual(one.rows().map((r) => r.props['aria-current']), [undefined, 'true', undefined])
} finally { one.end() }
})
// ---------- Stufen-Sperre (hustlerStufe.js): unterhalb von Stufe 6 ist der Keller begehbar, aber leer und ohne Abruf ----------
// Der Läufer baut sonst mit stufe: 6 (Live-Daten, alle Tests oben). Hier ausdrücklich Stufe 3 (Produktion: Dienst P3, GET /botleads gibt es noch nicht).
const OHNE_DIENST_TITEL = 'Noch keine Bot-Leads.'
const OHNE_DIENST_TEXT = 'Hier legt Arne Betriebe ohne Website ab, die Bea nicht für eine Mail ausgewählt hat. Die Branchen-Räume (Ärzte, Handwerk, Selbstständige, Läden) suchen noch nicht.'
test('Stufe 3: der Keller steht mit Szene, Register-Buch, Reitern und Suche da, zeigt den ehrlichen Leer-Text, fragt nichts ab und zeigt weder Laden noch Fehler', async () => {
for (const stufe of [3, 4, 5]) {
const api = fakeApi()
const ui = await start(api, { stufe })
try {
assert.equal(api.calls.length, 0, `Stufe ${stufe}: kein Abruf`)
assert.equal(byClass(ui.tree(), 'hk-kl-szene').length, 1, 'Szene mit Arne')
assert.equal(textOf(byClass(ui.tree(), 'hk-kl-titel')[0]), 'Bot-Leads')
assert.equal(textOf(byClass(ui.tree(), 'hk-kl-buchtitel')[0]), 'Nie-doppelt-Register')
assert.equal(ui.reiter().length, ROOM_VALUES.length); assert.equal(ui.activeIndex(), 0)
assert.ok(ui.search(), 'Suchfeld')
const leer = byClass(ui.tree(), 'hk-kl-leer')
assert.equal(leer.length, 1)
assert.equal(textOf(byClass(leer[0], 'hk-kl-leer-titel')[0]), OHNE_DIENST_TITEL)
assert.equal(textOf(byClass(leer[0], 'hk-kl-leer-text')[0]), OHNE_DIENST_TEXT)
// kein Fehlerzustand, keine Ladeanzeige, kein „Erneut versuchen“, keine Zeilen, keine Kopfzeile, keine Seitenzeile, keine Zahl am Reiter
assert.ok(!ui.text().includes('Wird geladen')); assert.equal(byClass(ui.tree(), 'hk-kl-laden').length, 0)
assert.equal(ui.errors().length, 0); assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0)
assert.equal(nodes(ui.tree(), (n) => n.props && (n.props.role === 'alert' || n.props.role === 'status')).length, 0)
assert.equal(ui.rows().length, 0); assert.equal(byClass(ui.tree(), 'hk-kopfzeile').length, 0); assert.equal(byClass(ui.tree(), 'hk-kl-fuss').length, 0)
assert.equal(byClass(ui.tree(), 'hk-kl-reiter-zahl').length, 0)
assert.ok(!ui.text().includes('Keine Treffer'))
} finally { ui.end() }
}
})
test('Stufe 3: ohne Stufen-Angabe gilt HUSTLER_STUFE (Produktion = 3): leeres Register, kein Abruf', async () => {
const { HUSTLER_STUFE, botleadsAbrufbar } = await loadModule('src/lib/hustlerStufe.js')
const api = fakeApi()
const ui = await start(api, { stufe: undefined })
try {
assert.equal(api.calls.length, botleadsAbrufbar(HUSTLER_STUFE) ? 1 : 0)
if (!botleadsAbrufbar(HUSTLER_STUFE)) assert.ok(ui.text().includes(OHNE_DIENST_TITEL) && ui.text().includes(OHNE_DIENST_TEXT))
} finally { ui.end() }
})
test('Stufe 3: Reiter und Suche bleiben bedienbar, ändern nur die Anzeige und lösen nie einen Abruf aus; die Leer-Auskunft bleibt dieselbe', async () => {
const api = fakeApi()
const dom = fakeFocus()
const ui = await start(api, { stufe: 3 })
dom.attach(ui)
try {
await ui.pick(idx('handwerk'))
assert.equal(ui.activeIndex(), idx('handwerk')); assert.equal(dom.focused.length, 0)
await ui.type('Elektro'); ui.fireTimers(); await settle()
assert.equal(ui.search().props.value, 'Elektro')
await ui.pick(idx('laden')); await ui.pick(idx(''))
assert.equal(ui.activeIndex(), 0)
assert.equal(api.calls.length, 0, 'weder Reiter noch Suche rufen etwas ab')
assert.equal(ui.timers.size, 0)
assert.ok(ui.text().includes(OHNE_DIENST_TITEL) && ui.text().includes(OHNE_DIENST_TEXT))
assert.ok(!ui.text().includes('Keine Treffer') && !ui.text().includes('Wird geladen'))
assert.equal(ui.errors().length, 0); assert.equal(byClass(ui.tree(), 'hk-kl-reiter-zahl').length, 0)
// keine Fokusfalle: nichts wird dem Nutzer weggenommen oder von der Komponente an sich gezogen (nur die Überschrift beim Einhängen, ohne Seitensprung)
assert.ok(dom.focused.every((k) => k !== 'reiter'))
} finally { dom.restore(); ui.end() }
})
test('Stufe 3: Register zuklappen und aufklappen funktionieren wie mit Daten; ◂ Hochhaus ruft onBack', async () => {
const api = fakeApi(); let zurueck = 0
const ui = await start(api, { stufe: 3, onBack: () => { zurueck += 1 } })
try {
await ui.click('Register zuklappen')
assert.equal(byClass(ui.tree(), 'hk-buch').length, 0); assert.equal(byClass(ui.tree(), 'hk-kl-leer').length, 0); assert.equal(byClass(ui.tree(), 'hk-kl-szene').length, 1)
await ui.click('Register aufklappen')
assert.equal(byClass(ui.tree(), 'hk-kl-leer').length, 1)
await ui.click('Zurück zum Hochhaus'); assert.equal(zurueck, 1)
assert.equal(api.calls.length, 0)
} finally { ui.end() }
})
test('Stufe 3: ?bot= (Einzelansicht) ruft nichts ab, zeigt dieselbe ehrliche Auskunft und lässt „Alle anzeigen“ zu', async () => {
const api = fakeApi(); let leer = 0
const ui = await start(api, { stufe: 3, bot: 'p1', onClearBot: () => { leer += 1 } })
try {
assert.equal(api.calls.length, 0)
assert.ok(ui.text().includes(OHNE_DIENST_TITEL) && ui.text().includes(OHNE_DIENST_TEXT))
assert.ok(!ui.text().includes('Wird geladen') && !ui.text().includes('nicht (mehr) in der Liste'))
assert.equal(ui.errors().length, 0); assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0)
await ui.click('Alle anzeigen'); assert.equal(leer, 1)
ui.setProps({ bot: '' }); await settle()
assert.equal(api.calls.length, 0); assert.ok(ui.text().includes(OHNE_DIENST_TEXT))
} finally { ui.end() }
})
test('Stufe 3: die Zahl im Schild kommt weiter aus den Kennzahlen (kpis.botLeads); die Szene zeigt Arne nach seinem Status', async () => {
const api = fakeApi()
const ui = await start(api, { stufe: 3, kpis: { botLeads: 0 }, workers: { arne: { status: 'arbeitet', task: '', lastTs: null, connected: true, counters: {} } } })
try {
assert.equal(textOf(byClass(ui.tree(), 'hk-kl-zahl')[0]), '0')
assert.equal(api.calls.length, 0)
} finally { ui.end() }
})
test('Stufe 6 (und höher): Abruf wie bisher, der Live-Leer-Text bleibt der alte (nicht der der Stufe 3), Lade- und Fehlerzustand sind zurück', async () => {
for (const stufe of [6, 9]) {
const api = fakeApi()
const ui = await start(api, { stufe })
try {
assert.equal(api.calls.length, 1, `Stufe ${stufe}`); assert.ok(ui.text().includes('Wird geladen'))
await ui.answer(0, { items: [], total: 0 })
assert.ok(ui.text().includes('Noch keine Bot-Leads.')); assert.ok(ui.text().includes('Betriebe, die nicht angeschrieben werden, erscheinen hier mit ihrem Grund.'))
assert.ok(!ui.text().includes(OHNE_DIENST_TEXT)); assert.equal(byClass(ui.tree(), 'hk-kl-reiter-zahl').length, 1)
await ui.pick(idx('handwerk'))
assert.equal(api.calls.length, 2); await ui.fail(1, new Error('Der Dienst antwortet nicht.'))
assert.deepEqual(ui.errors().map((e) => e.text), ['Der Dienst antwortet nicht.']); assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 1)
} finally { ui.end() }
}
})
test('Ändert sich die Stufe von 3 auf 6 (im Betrieb nur über einen neuen Bau, hier als Prop): ab dann Abruf, das Register füllt sich', async () => {
const api = fakeApi()
const ui = await start(api, { stufe: 3 })
try {
assert.equal(api.calls.length, 0)
ui.setProps({ stufe: 6 }); await settle()
assert.equal(api.calls.length, 1); assert.ok(ui.text().includes('Wird geladen'))
await ui.answer(0, { items: [ITEM], total: 1 })
assert.equal(ui.rows().length, 1); assert.ok(!ui.text().includes(OHNE_DIENST_TEXT))
} finally { ui.end() }
})