Der Test "die Szene zeigt Arne nach seinem Status" prüfte bisher nur die Zahl im Schild und die Abruf-Anzahl. Er prüft jetzt bei Stufe 3 data-hk-arne, hk-arne-weg, die drei hk-kl-ruht, den Marker hk-alarm und hk-steht für arbeitet, leerlauf, fehler und angehalten. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
703 lines
38 KiB
JavaScript
703 lines
38 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { loadModule } from './load.mjs'
|
|
import {
|
|
ITEM, KUERZ, PRAXIS, buttons, byClass, fakeApi, fakeFocus, nodes, settle, start, textOf,
|
|
} from './helpers/kellerRunner.mjs'
|
|
|
|
// BotLeadList (Hustler-Pixelwelt T10: der Keller) läuft wirklich: im Mini-Läufer anstelle von React und DOM (Muster workersPultUi.test.mjs, Baustein helpers/fakeReact.mjs,
|
|
// gemeinsamer Läufer helpers/kellerRunner.mjs). Gerendert wird in einen Baum aus { tag, props, children }; Eingaben und Klicks ruft der Test von Hand auf, die Zeit
|
|
// (window.setTimeout) läuft von Hand, der Dienst ist eine Attrappe mit offenen Versprechen.
|
|
// Die fachlichen Zusicherungen der alten Tabelle gelten unverändert (Abruf, Suche mit Verzögerung, Seiten, Fokus beim Blättern, „Erneut versuchen“, Einzelansicht, Telefonregel,
|
|
// sichere Links, Leer- und Ladezustände). Umgestellt ist nur, wo sie stehen: Zeilen sind Knöpfe des Buchs (hk-zeile) statt Tabellenzeilen, der Raumfilter sind Reiter statt einer
|
|
// Auswahlliste, die Blätterknöpfe heißen „Vorherige Seite“ / „Nächste Seite“ (◂ ▸), Quelle und Raum-Beschriftung stehen in der Akte. Kommentare „UMGESTELLT“ nennen jede Änderung.
|
|
const { ROOM_FILTERS } = await loadModule('src/lib/botleadsView.js')
|
|
|
|
const PREV = 'Vorherige Seite'
|
|
const NEXT = 'Nächste Seite'
|
|
const ROOM_VALUES = ROOM_FILTERS.map((f) => f.value) // ['', 'aerzte', 'handwerk', 'selbst', 'laden']
|
|
const idx = (room) => ROOM_VALUES.indexOf(room)
|
|
|
|
test('lädt beim Start (Raum alle, keine Suche, Seite 1), zeigt zuerst „Wird geladen“, dann die Zeilen', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
assert.deepEqual(api.calls.map((c) => c.params), [{ room: '', q: '', page: 1 }])
|
|
assert.ok(ui.text().includes('Reine Liste: keine Anrufe, kein Mailversand.'))
|
|
assert.ok(ui.text().includes('Wird geladen'))
|
|
assert.equal(ui.rows().length, 0)
|
|
await ui.answer(0, { items: [ITEM, PRAXIS], total: 2, pageSize: 50 })
|
|
assert.ok(!ui.text().includes('Wird geladen'))
|
|
// UMGESTELLT: statt der Tabellenköpfe (Betrieb, Raum, Punkte, Grund, Telefon, Quelle, Gefunden) die Kopfzeile des Buchs; Raum steht als Symbol in der Zeile, Quelle in der Akte
|
|
assert.deepEqual(byClass(ui.tree(), 'hk-kopfzeile')[0].children.map(textOf), ['', 'Betrieb · Ort · gefunden', 'Punkte', 'Grund', 'Telefon'])
|
|
const rows = ui.rows()
|
|
assert.equal(rows.length, 2)
|
|
// UMGESTELLT: Ort und Funddatum in einer Zeile unter dem Namen, Punkte als Zahl neben dem Balken („82 Punkte“ steht in der Akte und im Label)
|
|
assert.deepEqual(ui.parts(rows[0]), {
|
|
name: 'Elektro Beispiel GmbH', sub: '67655 Kaiserslautern · 19.10.2026', score: '82', reason: 'keine veröffentlichte Mail', phone: '0631 111',
|
|
raumIcon: 'hk-ic hk-ic-raum-handwerk', label: 'Elektro Beispiel GmbH, 67655 Kaiserslautern, 82 Punkte, keine veröffentlichte Mail, gefunden am 19.10.2026',
|
|
})
|
|
// Heilberuf: nie eine Nummer
|
|
assert.equal(ui.parts(rows[1]).phone, 'bewusst nicht gespeichert')
|
|
assert.equal(ui.parts(rows[1]).raumIcon, 'hk-ic hk-ic-raum-aerzte')
|
|
assert.equal(ui.parts(rows[1]).sub, '67655 Kaiserslautern · 18.10.2026')
|
|
assert.equal(rows[0].props['aria-current'], undefined)
|
|
// Zeilen sind Knöpfe (öffnen die Akte), keine Links
|
|
assert.ok(rows.every((r) => r.props.type === 'button'))
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Quelle: Link nur über die geprüfte Adresse (jetzt in der Akte), neuer Tab ohne Rückbezug; kein Anruf-Link', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM, PRAXIS, { ...ITEM, id: 'p4', sourceUrl: 'https://user:pw@evil.example/' }], total: 3 })
|
|
// UMGESTELLT: die Liste selbst hat gar keine Links mehr (die Zeilen sind Knöpfe); die Quelle öffnet die Akte
|
|
assert.equal(nodes(ui.tree(), (n) => n.tag === 'a').length, 0)
|
|
await ui.openRow(0)
|
|
let links = nodes(ui.tree(), (n) => n.tag === 'a')
|
|
assert.equal(links.length, 1)
|
|
assert.deepEqual([links[0].props.href, links[0].props.target, links[0].props.rel], ['https://beispiel-elektro.de/impressum', '_blank', 'noopener noreferrer'])
|
|
// zweiter Eintrag: javascript:-Adresse ist kein Link; dritter: Adresse mit Zugangsdaten ebenfalls nicht
|
|
await ui.click('Nächste Akte')
|
|
assert.equal(nodes(ui.tree(), (n) => n.tag === 'a').length, 0)
|
|
await ui.click('Nächste Akte')
|
|
links = nodes(ui.tree(), (n) => n.tag === 'a')
|
|
assert.equal(links.length, 0)
|
|
assert.equal(nodes(ui.tree(), (n) => n.props && typeof n.props.href === 'string' && n.props.href.startsWith('tel:')).length, 0)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('gekürzte Einträge sind grau und tragen „gekürzt“', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM, KUERZ], total: 2 })
|
|
const [normal, cut] = ui.rows()
|
|
// UMGESTELLT: Klassen des Buchs (hk-zeile-alt statt wk-bl-cut)
|
|
assert.equal(normal.props.className.split(' ').includes('hk-zeile-alt'), false)
|
|
assert.equal(cut.props.className.split(' ').includes('hk-zeile-alt'), true)
|
|
assert.ok(textOf(cut).includes('gekürzt'))
|
|
assert.ok(!textOf(normal).includes('gekürzt'))
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Raumfilter: Reiter aus ROOM_FILTERS, neue Anfrage mit Seite 1; nur die neueste Anfrage zählt', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
// UMGESTELLT: fünf Reiter mit aria-pressed (statt der Optionen einer Auswahlliste), Beschriftung = ROOM_FILTERS (aria-label), kurze sichtbare Namen, ein Symbol je Reiter
|
|
const reiter = ui.reiter()
|
|
assert.deepEqual(reiter.map((r) => r.props['aria-label']), ROOM_FILTERS.map((f) => f.label))
|
|
assert.deepEqual(reiter.map((r) => textOf(r)), ['Alle', 'Ärzte', 'Handwerk', 'Selbstständige', 'Läden'])
|
|
assert.deepEqual(reiter.map((r) => r.props['aria-pressed']), [true, false, false, false, false])
|
|
assert.ok(reiter.every((r) => r.props.type === 'button'))
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
assert.deepEqual(api.calls[1].params, { room: '', q: '', page: 2 })
|
|
await ui.answer(1, { items: [PRAXIS], total: 120, pageSize: 50 })
|
|
// Raum wechseln: zurück auf Seite 1, genau eine neue Anfrage
|
|
await ui.pick(idx('handwerk'))
|
|
assert.equal(api.calls.length, 3)
|
|
assert.deepEqual(api.calls[2].params, { room: 'handwerk', q: '', page: 1 })
|
|
assert.equal(ui.activeIndex(), idx('handwerk')) // UMGESTELLT: der gedrückte Reiter statt select.value
|
|
// noch einmal gewechselt, bevor die erste Antwort kommt; die späte Antwort der älteren Anfrage darf nichts überschreiben
|
|
await ui.pick(idx('laden'))
|
|
assert.deepEqual(api.calls[3].params, { room: 'laden', q: '', page: 1 })
|
|
await ui.answer(2, { items: [ITEM], total: 1 })
|
|
assert.equal(ui.rows().length, 0) // „laden“ lädt noch, der alte Raum zeigt nichts
|
|
assert.ok(ui.text().includes('Wird geladen'))
|
|
await ui.answer(3, { items: [PRAXIS], total: 1 })
|
|
assert.deepEqual(ui.rows().map((r) => ui.parts(r).name), ['Praxis Dr. Muster'])
|
|
// derselbe Raum noch einmal gewählt: keine neue Anfrage
|
|
await ui.pick(idx('laden'))
|
|
assert.equal(api.calls.length, 4)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Raum-Reiter: die Zahl steht nur am aktiven Reiter (= total der Antwort), erst wenn die Antwort da ist', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
assert.deepEqual(ui.reiter().map((r) => r.props['aria-label']), ROOM_FILTERS.map((f) => f.label)) // beim Laden keine Zahl
|
|
await ui.answer(0, { items: [ITEM], total: 1234, pageSize: 50 })
|
|
const zahlen = ui.reiter().map((r) => byClass(r, 'hk-kl-reiter-zahl').map(textOf))
|
|
assert.deepEqual(zahlen, [['1.234'], [], [], [], []])
|
|
assert.equal(ui.reiter()[0].props['aria-label'], 'Alle Räume, 1.234 Einträge')
|
|
await ui.pick(idx('aerzte'))
|
|
assert.deepEqual(ui.reiter().map((r) => byClass(r, 'hk-kl-reiter-zahl').length), [0, 0, 0, 0, 0]) // die neue Anfrage läuft: keine Zahl
|
|
await ui.answer(1, { items: [PRAXIS], total: 1 })
|
|
assert.deepEqual(ui.reiter().map((r) => byClass(r, 'hk-kl-reiter-zahl').map(textOf)), [[], ['1'], [], [], []])
|
|
assert.equal(ui.reiter()[1].props['aria-label'], 'Ärzte & Praxen, 1 Eintrag')
|
|
// bei einem Fehler steht keine Zahl
|
|
await ui.pick(idx('selbst'))
|
|
await ui.fail(2, new Error('weg'))
|
|
assert.deepEqual(ui.reiter().map((r) => byClass(r, 'hk-kl-reiter-zahl').length), [0, 0, 0, 0, 0])
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Suche: 300 ms Verzögerung, nur die letzte Eingabe wird abgefragt, Seite springt auf 1', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
await ui.answer(1, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.equal(api.calls.length, 2)
|
|
for (const value of ['b', 'bä', ' bäckerei ']) await ui.type(value)
|
|
assert.equal(ui.search().props.value, ' bäckerei ') // das Feld zeigt sofort, was getippt wurde
|
|
assert.equal(api.calls.length, 2) // noch keine Anfrage
|
|
assert.deepEqual([...ui.timers.values()].map((t) => t.ms), [300]) // nur der letzte Zeitgeber lebt
|
|
ui.fireTimers(); await settle()
|
|
assert.equal(api.calls.length, 3)
|
|
assert.deepEqual(api.calls[2].params, { room: '', q: 'bäckerei', page: 1 }) // getrimmt, Seite 1
|
|
// derselbe Suchbegriff noch einmal: keine neue Anfrage
|
|
await ui.type('bäckerei'); ui.fireTimers(); await settle()
|
|
assert.equal(api.calls.length, 3)
|
|
assert.equal(ui.search().props.maxLength, 100)
|
|
assert.equal(ui.search().props['aria-label'], 'Betriebe suchen')
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Fehler: der Satz aus der API steht als Meldung, keine Liste; der nächste Erfolg räumt ihn weg', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.fail(0, Object.assign(new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'), { status: 0 }))
|
|
assert.deepEqual(ui.errors(), [{ text: 'Der Hustler-Dienst ist gerade nicht erreichbar.', role: 'alert' }])
|
|
assert.equal(ui.rows().length, 0)
|
|
assert.ok(!ui.text().includes('Noch keine Bot-Leads.'))
|
|
await ui.pick(idx('laden'))
|
|
await ui.answer(1, { items: [ITEM], total: 1 })
|
|
assert.deepEqual(ui.errors(), [])
|
|
assert.equal(ui.rows().length, 1)
|
|
// ein Fehler ohne Satz zeigt trotzdem einen deutschen Text
|
|
await ui.pick(idx('selbst'))
|
|
await ui.fail(2, {})
|
|
assert.deepEqual(ui.errors().map((e) => e.text), ['Unbekannter Fehler'])
|
|
// ein später Fehler einer älteren Anfrage zählt nicht
|
|
await ui.pick(idx('aerzte')); await ui.pick(idx('handwerk'))
|
|
await ui.fail(3, new Error('zu spät'))
|
|
assert.deepEqual(ui.errors(), [])
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Leerzustände: ohne Filter „Noch keine Bot-Leads.“, mit Filter oder Suche „Keine Treffer.“', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [], total: 0 })
|
|
assert.ok(ui.text().includes('Noch keine Bot-Leads.'))
|
|
assert.equal(byClass(ui.tree(), 'hk-liste').length, 0) // UMGESTELLT: keine Liste statt keine Tabelle
|
|
assert.ok(!ui.text().includes('Keine Einträge')) // keine Seitenzeile
|
|
await ui.pick(idx('laden'))
|
|
await ui.answer(1, { items: [], total: 0 })
|
|
assert.ok(ui.text().includes('Keine Treffer.'))
|
|
assert.ok(!ui.text().includes('Noch keine Bot-Leads.'))
|
|
await ui.pick(idx(''))
|
|
await ui.type('xyz'); ui.fireTimers(); await settle()
|
|
await ui.answer(3, { items: [], total: 0 })
|
|
assert.ok(ui.text().includes('Keine Treffer.'))
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Blättern: Seitenzeile, Zurück und Weiter (◂ ▸), pageSize aus der Antwort', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.ok(ui.text().includes('Seite 1 von 3 · 120 Einträge'))
|
|
assert.deepEqual([ui.disabled(PREV), ui.disabled(NEXT)], [[true], [false]])
|
|
await ui.click(PREV) // gesperrt: nichts passiert
|
|
assert.equal(api.calls.length, 1)
|
|
await ui.click(NEXT)
|
|
await ui.answer(1, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(api.calls[1].params, { room: '', q: '', page: 2 })
|
|
assert.ok(ui.text().includes('Seite 2 von 3 · 120 Einträge'))
|
|
await ui.click(NEXT)
|
|
await ui.answer(2, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual([ui.disabled(PREV), ui.disabled(NEXT)], [[false], [true]])
|
|
await ui.click(PREV)
|
|
assert.deepEqual(api.calls[3].params, { room: '', q: '', page: 2 })
|
|
// während einer Anfrage ist die Seitenzeile weg: kein zweiter Klick auf eine veraltete Seitenzahl
|
|
assert.equal(buttons(ui.tree(), NEXT).length, 0)
|
|
// die Knöpfe sind echte Knöpfe mit Beschriftung (nur das Symbol ist zu sehen)
|
|
await ui.answer(3, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.ok(buttons(ui.tree(), NEXT).every((b) => b.props.type === 'button' && b.props['aria-label'] === NEXT))
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Blättern: eine Seite genügt ⇒ nur die Zahl, kein Blättern; Einzahl bei einem Eintrag', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 1, pageSize: 50 })
|
|
assert.ok(ui.text().includes('Seite 1 von 1 · 1 Eintrag'))
|
|
assert.equal(buttons(ui.tree(), NEXT).length + buttons(ui.tree(), PREV).length, 0)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Blättern: ist die Seite nach einem Kürzen leer, springt die Liste auf die letzte Seite', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT); await ui.answer(1, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
assert.deepEqual(api.calls[2].params, { room: '', q: '', page: 3 })
|
|
await ui.answer(2, { items: [], total: 60, pageSize: 50 }) // inzwischen nur noch zwei Seiten
|
|
assert.deepEqual(api.calls[3].params, { room: '', q: '', page: 2 })
|
|
assert.ok(ui.text().includes('Wird geladen'))
|
|
await ui.answer(3, { items: [ITEM], total: 60, pageSize: 50 })
|
|
assert.ok(ui.text().includes('Seite 2 von 2 · 60 Einträge'))
|
|
assert.equal(api.calls.length, 4)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('bot: ruft die API mit id ab, hebt die Zeile hervor und bietet „Alle anzeigen“ statt der Filter an', async () => {
|
|
const api = fakeApi()
|
|
let cleared = 0
|
|
const ui = await start(api, { bot: 'p1', onClearBot: () => { cleared += 1 } })
|
|
try {
|
|
assert.deepEqual(api.calls.map((c) => c.params), [{ id: 'p1' }])
|
|
assert.equal(nodes(ui.tree(), (n) => n.tag === 'input' || n.props['aria-pressed'] !== undefined).length, 0) // Filter (Reiter) und Suche wirken auf einen Einzeleintrag nicht
|
|
assert.ok(ui.text().includes('Reine Liste: keine Anrufe, kein Mailversand.'))
|
|
await ui.answer(0, { items: [ITEM], total: 1, pageSize: 50 })
|
|
const rows = ui.rows()
|
|
assert.equal(rows.length, 1)
|
|
assert.equal(rows[0].props['aria-current'], 'true')
|
|
assert.equal(buttons(ui.tree(), NEXT).length, 0) // keine Seitenzeile bei einem Einzeleintrag
|
|
await ui.click('Alle anzeigen')
|
|
assert.equal(cleared, 1)
|
|
// die Seite nimmt bot aus der Adresse: die Liste lädt wieder alle (Filter und Suche zurück)
|
|
ui.setProps({ bot: '' })
|
|
await settle()
|
|
assert.deepEqual(api.calls[1].params, { room: '', q: '', page: 1 })
|
|
assert.equal(ui.activeIndex(), idx('')) // UMGESTELLT: „Alle Räume“ ist der gedrückte Reiter statt select.value ''
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('bot: ein anderer Eintrag über die Adresse lädt neu; nur die neueste Anfrage zählt; nicht gefunden ⇒ Hinweis mit „Alle anzeigen“', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api, { bot: 'p1', onClearBot: () => {} })
|
|
try {
|
|
ui.setProps({ bot: 'p2' }); await settle()
|
|
assert.deepEqual(api.calls.map((c) => c.params), [{ id: 'p1' }, { id: 'p2' }])
|
|
await ui.answer(0, { items: [ITEM], total: 1 }) // späte Antwort für p1
|
|
assert.equal(ui.rows().length, 0)
|
|
await ui.answer(1, { items: [], total: 0 })
|
|
assert.ok(ui.text().includes('Dieser Eintrag ist nicht (mehr) in der Liste.'))
|
|
assert.ok(!ui.text().includes('Noch keine Bot-Leads.'))
|
|
assert.equal(buttons(ui.tree(), 'Alle anzeigen').length, 1)
|
|
// ein Fehler (ungültige Kennung) lässt den Weg zurück offen
|
|
ui.setProps({ bot: 'x y' }); await settle()
|
|
await ui.fail(2, Object.assign(new Error('Ungültige Kennung.'), { status: 400 }))
|
|
assert.deepEqual(ui.errors().map((e) => e.text), ['Ungültige Kennung.'])
|
|
assert.equal(buttons(ui.tree(), 'Alle anzeigen').length, 1)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('nach dem Abbau setzt eine späte Antwort nichts mehr, ein offener Zeitgeber der Suche wird gelöscht', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
await ui.type('abc')
|
|
assert.equal(ui.timers.size, 1)
|
|
ui.end()
|
|
assert.equal(ui.timers.size, 0)
|
|
api.calls[0].resolve({ items: [ITEM], total: 1 })
|
|
await settle()
|
|
assert.equal(ui.lateUpdates(), 0)
|
|
})
|
|
|
|
test('Blättern (C5): nach dem Laden der neuen Seite bekommt die Seitenzeile den Tastaturfokus zurück, ohne dass die Seite scrollt (N5); ist der Knopf gesperrt, der andere', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
const ui = await start(api)
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, []) // nicht schon beim ersten Laden
|
|
await dom.press(NEXT)
|
|
assert.deepEqual(dom.focused, []) // während der Anfrage ist die Seitenzeile weg
|
|
assert.equal(dom.doc.activeElement.isConnected, false) // und mit ihr der Knopf, der den Fokus hatte: der Fokus ist verloren
|
|
await ui.answer(1, { items: [ITEM], total: 120, pageSize: 50 }) // Seite 2 von 3
|
|
assert.deepEqual(dom.focused, [NEXT])
|
|
await dom.press(NEXT)
|
|
await ui.answer(2, { items: [ITEM], total: 120, pageSize: 50 }) // Seite 3 von 3: „Nächste Seite“ ist gesperrt ⇒ „Vorherige Seite“
|
|
assert.deepEqual(dom.focused, [NEXT, PREV])
|
|
await dom.press(PREV)
|
|
await ui.answer(3, { items: [ITEM], total: 120, pageSize: 50 }) // Seite 2
|
|
assert.deepEqual(dom.focused, [NEXT, PREV, PREV])
|
|
await dom.press(PREV)
|
|
await ui.answer(4, { items: [ITEM], total: 120, pageSize: 50 }) // Seite 1: „Vorherige Seite“ ist gesperrt ⇒ „Nächste Seite“
|
|
assert.deepEqual(dom.focused, [NEXT, PREV, PREV, NEXT])
|
|
// N5: ohne { preventScroll: true } sprängen Chromium und Firefox ans Ende der neuen Seite
|
|
assert.deepEqual(dom.options, [{ preventScroll: true }, { preventScroll: true }, { preventScroll: true }, { preventScroll: true }])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('Blättern (N5): hat der Nutzer den Fokus inzwischen selbst woandershin gesetzt, wird er nicht gestohlen; liegt er auf body oder nirgends, kommt er zurück', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
const ui = await start(api)
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 400, pageSize: 50 })
|
|
// 1. Der Nutzer klickt während des Ladens ins Suchfeld (Fokus dort, aber die Zeitgeber der Suche laufen noch nicht)
|
|
await dom.press(NEXT)
|
|
const search = dom.other('Suchfeld')
|
|
search.focus()
|
|
await ui.answer(1, { items: [ITEM], total: 400, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [])
|
|
assert.equal(dom.doc.activeElement, search)
|
|
// 2. … oder er wählt den Raum-Reiter mit der Tastatur an, ohne etwas zu ändern (UMGESTELLT: Reiter statt Auswahlliste)
|
|
await dom.press(NEXT)
|
|
dom.made.get('reiter').focus()
|
|
assert.deepEqual(dom.focused, ['reiter'])
|
|
await ui.answer(2, { items: [ITEM], total: 400, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, ['reiter']) // kein weiterer Fokus auf die Seitenzeile
|
|
assert.equal(dom.doc.activeElement.key, 'reiter')
|
|
// 3. Fokus auf body: zurück an die Seitenzeile
|
|
dom.focused.length = 0; dom.options.length = 0
|
|
await dom.press(NEXT)
|
|
dom.doc.activeElement = dom.body
|
|
await ui.answer(3, { items: [ITEM], total: 400, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [NEXT])
|
|
assert.deepEqual(dom.options, [{ preventScroll: true }])
|
|
// 4. Fokus auf nichts (activeElement null, etwa ein Dokument ohne Fokus): ebenfalls zurück
|
|
await dom.press(NEXT)
|
|
dom.doc.activeElement = null
|
|
await ui.answer(4, { items: [ITEM], total: 400, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [NEXT, NEXT])
|
|
// 5. ohne document (Server, Node): nichts geschieht, nichts bricht
|
|
delete globalThis.document
|
|
await ui.click(NEXT)
|
|
await ui.answer(5, { items: [ITEM], total: 400, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [NEXT, NEXT])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('Blättern (C5): der Fokus springt nicht, wenn der Nutzer inzwischen Raum oder Suche bedient hat oder die Anfrage scheitert', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
const ui = await start(api)
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
await ui.pick(idx('handwerk')) // während die Seite lädt, wechselt der Raum
|
|
await ui.answer(1, { items: [ITEM], total: 120, pageSize: 50 }) // späte Antwort der älteren Anfrage
|
|
await ui.answer(2, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [])
|
|
await ui.click(NEXT)
|
|
await ui.type('bäckerei') // die Suche wird getippt, bevor die Seite da ist
|
|
await ui.answer(3, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [])
|
|
// Raum und Suche selbst verschieben den Fokus nie
|
|
ui.fireTimers(); await settle()
|
|
await ui.answer(4, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [])
|
|
// scheitert die Anfrage, gibt es keine Seitenzeile zum Fokussieren; die Merkung bleibt nicht für später hängen
|
|
await ui.click(NEXT)
|
|
await ui.fail(5, new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'))
|
|
assert.deepEqual(dom.focused, [])
|
|
await ui.pick(idx('laden'))
|
|
await ui.answer(6, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('Fehler beim Blättern (N6): „Erneut versuchen“ wiederholt genau die Seitenanfrage; Fehler und Knopf verschwinden, die Seitenzeile ist wieder da', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
assert.deepEqual(api.calls[1].params, { room: '', q: '', page: 2 })
|
|
assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0) // beim Laden gibt es keinen Knopf
|
|
await ui.fail(1, Object.assign(new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'), { status: 0 }))
|
|
assert.deepEqual(ui.errors(), [{ text: 'Der Hustler-Dienst ist gerade nicht erreichbar.', role: 'alert' }]) // der Satz bleibt unverändert im Alarm
|
|
const retry = buttons(ui.tree(), 'Erneut versuchen')
|
|
assert.equal(retry.length, 1)
|
|
assert.equal(retry[0].props.type, 'button')
|
|
assert.equal(retry[0].props.disabled, undefined)
|
|
assert.equal(api.calls.length, 2)
|
|
await ui.click('Erneut versuchen')
|
|
assert.equal(api.calls.length, 3) // genau eine neue Anfrage, dieselbe Seite
|
|
assert.deepEqual(api.calls[2].params, { room: '', q: '', page: 2 })
|
|
assert.ok(ui.text().includes('Wird geladen'))
|
|
assert.deepEqual(ui.errors(), [])
|
|
assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0)
|
|
await ui.answer(2, { items: [PRAXIS], total: 120, pageSize: 50 })
|
|
assert.equal(api.calls.length, 3)
|
|
assert.deepEqual(ui.errors(), [])
|
|
assert.deepEqual(ui.rows().map((r) => ui.parts(r).name), ['Praxis Dr. Muster'])
|
|
assert.ok(ui.text().includes('Seite 2 von 3 · 120 Einträge'))
|
|
assert.deepEqual([ui.disabled(PREV), ui.disabled(NEXT)], [[false], [false]])
|
|
assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Fehler beim Blättern (N6): scheitert auch der zweite Versuch, bleibt der Knopf; Raumfilter und Suche gehen weiterhin, mit Seite 1', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await ui.click(NEXT)
|
|
await ui.fail(1, new Error('zu langsam'))
|
|
await ui.click('Erneut versuchen')
|
|
await ui.fail(2, new Error('immer noch nicht'))
|
|
assert.deepEqual(ui.errors().map((e) => e.text), ['immer noch nicht'])
|
|
assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 1)
|
|
await ui.click('Erneut versuchen')
|
|
assert.deepEqual(api.calls[3].params, { room: '', q: '', page: 2 })
|
|
// statt eines weiteren Versuchs ändert der Nutzer den Raum: neue Anfrage mit Seite 1, die späte Antwort des Versuchs zählt nicht
|
|
await ui.pick(idx('handwerk'))
|
|
assert.deepEqual(api.calls[4].params, { room: 'handwerk', q: '', page: 1 })
|
|
await ui.answer(3, { items: [PRAXIS], total: 120, pageSize: 50 })
|
|
assert.equal(ui.rows().length, 0)
|
|
await ui.answer(4, { items: [ITEM], total: 1, pageSize: 50 })
|
|
assert.deepEqual(ui.errors(), [])
|
|
assert.deepEqual(ui.rows().map((r) => ui.parts(r).name), ['Elektro Beispiel GmbH'])
|
|
assert.equal(buttons(ui.tree(), 'Erneut versuchen').length, 0)
|
|
} finally { ui.end() }
|
|
})
|
|
|
|
test('Fehler beim Blättern (N6): „Erneut versuchen“ gibt es auch beim ersten Laden und in der Einzelansicht und wiederholt dort die jeweilige Anfrage', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
await ui.fail(0, new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'))
|
|
await ui.click('Erneut versuchen')
|
|
assert.deepEqual(api.calls[1].params, { room: '', q: '', page: 1 })
|
|
await ui.answer(1, { items: [ITEM], total: 1, pageSize: 50 })
|
|
assert.equal(ui.rows().length, 1)
|
|
} finally { ui.end() }
|
|
const api2 = fakeApi()
|
|
const one = await start(api2, { bot: 'p1', onClearBot: () => {} })
|
|
try {
|
|
await one.fail(0, Object.assign(new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'), { status: 0 }))
|
|
assert.equal(buttons(one.tree(), 'Alle anzeigen').length, 1) // der Weg zurück bleibt offen
|
|
await one.click('Erneut versuchen')
|
|
assert.deepEqual(api2.calls.map((c) => c.params), [{ id: 'p1' }, { id: 'p1' }])
|
|
await one.answer(1, { items: [ITEM], total: 1 })
|
|
assert.equal(one.rows().length, 1)
|
|
assert.equal(one.rows()[0].props['aria-current'], 'true')
|
|
} finally { one.end() }
|
|
})
|
|
|
|
test('Fehler beim Blättern (N6): nach einem gelungenen Wiederholen bekommt die Seitenzeile den Fokus zurück (ohne Scrollen), wenn zuvor geblättert wurde', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
const ui = await start(api)
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 120, pageSize: 50 })
|
|
await dom.press(NEXT)
|
|
await ui.fail(1, new Error('Der Hustler-Dienst ist gerade nicht erreichbar.'))
|
|
assert.deepEqual(dom.focused, []) // keine Seitenzeile, nichts zu fokussieren
|
|
await dom.press('Erneut versuchen') // der Knopf verschwindet beim Laden wie vorher die Seitenzeile
|
|
assert.deepEqual(dom.focused, [])
|
|
await ui.answer(2, { items: [ITEM], total: 120, pageSize: 50 })
|
|
assert.deepEqual(dom.focused, [NEXT])
|
|
assert.deepEqual(dom.options, [{ preventScroll: true }])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('Alle anzeigen (W1): nach dem Verlassen der Einzelansicht bekommt der aktive Raum-Reiter den Fokus, nicht beim Einhängen und nicht bei einem anderen Weg zur Liste', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
let cleared = 0
|
|
const ui = await start(api, { bot: 'p1', onClearBot: () => { cleared += 1 } })
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 1 })
|
|
assert.deepEqual(dom.focused, []) // nicht beim Einhängen (das öffnete am Handy die Tastatur oder die Auswahl)
|
|
await ui.click('Alle anzeigen')
|
|
assert.equal(cleared, 1)
|
|
assert.deepEqual(dom.focused, []) // der Knopf steht noch, bis die Seite bot aus der Adresse nimmt
|
|
ui.setProps({ bot: '' }); await settle()
|
|
assert.deepEqual(dom.focused, ['reiter']) // UMGESTELLT: der aktive Reiter („Alle Räume“) statt der Auswahlliste
|
|
assert.equal(ui.reiter()[ui.activeIndex()].props['aria-label'].startsWith('Alle Räume'), true)
|
|
// ein weiterer Wechsel in die Einzelansicht und zurück ohne den Knopf (Zurück-Taste des Browsers): kein Fokussprung
|
|
ui.setProps({ bot: 'p2' }); await settle()
|
|
ui.setProps({ bot: '' }); await settle()
|
|
assert.deepEqual(dom.focused, ['reiter'])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('Alle anzeigen (W1): ohne bot (Liste von Anfang an) bleibt der Fokus unberührt', async () => {
|
|
const api = fakeApi()
|
|
const dom = fakeFocus()
|
|
const ui = await start(api)
|
|
dom.attach(ui)
|
|
try {
|
|
await ui.answer(0, { items: [ITEM], total: 1 })
|
|
ui.setProps({ bot: 'p1', onClearBot: () => {} }); await settle()
|
|
ui.setProps({ bot: '' }); await settle()
|
|
assert.deepEqual(dom.focused, [])
|
|
} finally { dom.restore(); ui.end() }
|
|
})
|
|
|
|
test('aria-current (C5): nur die Zeile mit der Kennung aus bot, nie eine Zeile ohne Kennung', async () => {
|
|
const api = fakeApi()
|
|
const ui = await start(api)
|
|
try {
|
|
// Liste (bot leer): eine Zeile ohne Kennung (botleadRow macht daraus '') gilt nicht als „aktuell“
|
|
await ui.answer(0, { items: [{ ...ITEM, id: undefined }, { ...PRAXIS, id: 5 }, ITEM], total: 3 })
|
|
assert.equal(ui.rows().length, 3)
|
|
assert.deepEqual(ui.rows().map((r) => r.props['aria-current']), [undefined, undefined, undefined])
|
|
} finally { ui.end() }
|
|
const api2 = fakeApi()
|
|
const one = await start(api2, { bot: 'p1', onClearBot: () => {} })
|
|
try {
|
|
await one.answer(0, { items: [{ ...ITEM, id: '' }, ITEM, PRAXIS], total: 3 })
|
|
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 arne = (status) => ({ arne: { status, task: '', lastTs: null, connected: true, counters: {} } })
|
|
const stand = (ui) => ({
|
|
attr: nodes(ui.tree(), (n) => n.props && n.props['data-hk-arne'] !== undefined).map((n) => n.props['data-hk-arne']),
|
|
weg: byClass(ui.tree(), 'hk-arne-weg').length, ruht: byClass(ui.tree(), 'hk-kl-ruht').length,
|
|
alarm: byClass(ui.tree(), 'hk-alarm').length, steht: byClass(ui.tree(), 'hk-steht').length,
|
|
})
|
|
const ui = await start(api, { stufe: 3, kpis: { botLeads: 0 }, workers: arne('arbeitet') })
|
|
try {
|
|
assert.equal(textOf(byClass(ui.tree(), 'hk-kl-zahl')[0]), '0')
|
|
// arbeitet: Arnes Ablauf-Schleife läuft, nichts ruht, kein Alarm
|
|
assert.deepEqual(stand(ui), { attr: ['arbeitet'], weg: 1, ruht: 0, alarm: 0, steht: 0 })
|
|
// leerlauf: Arne sitzt am Pult, die drei Dinge (Stempel, Rohrpost, Schublade) ruhen
|
|
ui.setProps({ workers: arne('leerlauf') }); await settle()
|
|
assert.deepEqual(stand(ui), { attr: ['leerlauf'], weg: 0, ruht: 3, alarm: 0, steht: 0 })
|
|
// fehler: roter Marker
|
|
ui.setProps({ workers: arne('fehler') }); await settle()
|
|
assert.deepEqual(stand(ui), { attr: ['fehler'], weg: 0, ruht: 3, alarm: 1, steht: 0 })
|
|
// Hustler angehalten: die ganze Szene steht still
|
|
ui.setProps({ paused: true }); await settle()
|
|
assert.equal(stand(ui).steht, 1)
|
|
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() }
|
|
})
|