Hustler-Pixelwelt T13: Korrekturen Hochhaus (Fokus, Tastatur, weniger Bewegung, Handy-Tippflächen, Schnee per transform, Pause überall, Seite ohne Leerlauf-Blitz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0924a8d2ae
commit
5e6698f5e0
14 files changed
+994
-52
No files matched your search
@@ -215,9 +215,10 @@ const STUBS = {
|
||||
return hk.feed
|
||||
}
|
||||
`,
|
||||
hochhaus: "import { jsx } from 'react'\nexport default function Hochhaus(props) { globalThis.__hk.props.hochhaus = props; return jsx('div', { 'data-screen': 'hochhaus' }) }",
|
||||
pult: "import { jsx } from 'react'\nexport default function PultList(props) { globalThis.__hk.props.pult = props; return jsx('div', { 'data-screen': 'pult' }) }",
|
||||
keller: "import { jsx } from 'react'\nexport default function BotLeadList(props) { globalThis.__hk.props.keller = props; return jsx('div', { 'data-screen': 'keller' }) }",
|
||||
// renders: jede Darstellung eines Bildschirms (nicht nur die letzte), damit der Test auch Zwischenstände sieht (R5)
|
||||
hochhaus: "import { jsx } from 'react'\nexport default function Hochhaus(props) { globalThis.__hk.props.hochhaus = props; globalThis.__hk.renders?.hochhaus.push(props); return jsx('div', { 'data-screen': 'hochhaus' }) }",
|
||||
pult: "import { jsx } from 'react'\nexport default function PultList(props) { globalThis.__hk.props.pult = props; globalThis.__hk.renders?.pult.push(props); return jsx('div', { 'data-screen': 'pult' }) }",
|
||||
keller: "import { jsx } from 'react'\nexport default function BotLeadList(props) { globalThis.__hk.props.keller = props; globalThis.__hk.renders?.keller.push(props); return jsx('div', { 'data-screen': 'keller' }) }",
|
||||
}
|
||||
const stubPlugin = {
|
||||
name: 'seite-attrappen',
|
||||
@@ -255,6 +256,7 @@ async function seite(query, { admin = false, feed } = {}) {
|
||||
api: globalThis.__hkApi,
|
||||
feed: feed ?? { state: null, error: null, loaded: false, refresh: () => {} },
|
||||
feedCalls: [], feedMounts: 0, feedUnmounts: 0, props: {}, onEvents: null,
|
||||
renders: { hochhaus: [], pult: [], keller: [] },
|
||||
}
|
||||
const savedHk = globalThis.__hk
|
||||
const hadDocument = Object.hasOwn(globalThis, 'document')
|
||||
@@ -454,3 +456,176 @@ test('Seite: ohne angemeldeten Nutzer bleibt me ohne Id und Name (kein Absturz)'
|
||||
assert.deepEqual(ui.hk.props.pult.me, { id: undefined, name: undefined })
|
||||
} finally { ui.end() }
|
||||
})
|
||||
|
||||
// ---------- Korrekturen der Endprüfung (T13) ----------
|
||||
|
||||
test('R5 Seite: die Hustler-Tabelle liegt nie einen Render hinter dem Schnappschuss (kein „Leerlauf“-Blitz der Figuren, in keinem Bildschirm)', async () => {
|
||||
const state = makeState({ statuses: { lea: 'wartet', kim: 'fehler' }, connected: { pia: false } })
|
||||
const ui = await seite('tab=hochhaus', { feed: { state, error: null, loaded: true, refresh() {} } })
|
||||
try {
|
||||
// schon die allererste Darstellung mit Zustand hat die Tabelle daraus
|
||||
const renders = ui.hk.renders.hochhaus
|
||||
assert.ok(renders.length >= 1)
|
||||
for (const p of renders) {
|
||||
assert.equal(Object.keys(p.workers).length, 19)
|
||||
assert.equal(p.workers.lea.status, 'wartet'); assert.equal(p.workers.kim.status, 'fehler'); assert.equal(p.workers.pia.status, 'grau')
|
||||
}
|
||||
// neuer Schnappschuss mitten im Lauf: jede weitere Darstellung zeigt schon die neue Lage (nie die alte Tabelle zum neuen Zustand)
|
||||
renders.length = 0
|
||||
const zweiter = makeState({ statuses: { lea: 'leerlauf' } })
|
||||
ui.setFeed({ state: zweiter })
|
||||
assert.ok(renders.length >= 1)
|
||||
for (const p of renders) { assert.equal(p.feed.state, zweiter); assert.equal(p.workers.lea.status, 'leerlauf'); assert.equal(p.workers.kim.status, 'arbeitet') }
|
||||
// dasselbe für Pult und Keller (sie bekommen dieselbe Tabelle)
|
||||
ui.go('tab=freigabe')
|
||||
ui.hk.renders.pult.length = 0
|
||||
const dritter = makeState({ statuses: { lea: 'wartet' } })
|
||||
ui.setFeed({ state: dritter })
|
||||
for (const p of ui.hk.renders.pult) assert.equal(p.workers.lea.status, 'wartet')
|
||||
ui.go('tab=botleads')
|
||||
ui.hk.renders.keller.length = 0
|
||||
ui.setFeed({ state: makeState({ statuses: { lea: 'fehler' } }) })
|
||||
for (const p of ui.hk.renders.keller) assert.equal(p.workers.lea.status, 'fehler')
|
||||
assert.ok(ui.hk.renders.keller.length >= 1)
|
||||
// Ereignisse wirken weiter auf die Tabelle und überleben das Neu-Rendern desselben Schnappschusses
|
||||
ui.hk.onEvents([ev('leerlauf', 'lea', { text: 'Pause' })])
|
||||
assert.equal(ui.hk.props.keller.workers.lea.status, 'leerlauf')
|
||||
ui.setFeed({})
|
||||
assert.equal(ui.hk.props.keller.workers.lea.status, 'leerlauf')
|
||||
} finally { ui.end() }
|
||||
// Erster Schnappschuss kommt erst später: vorher leer, danach nie eine Darstellung mit Zustand und leerer Tabelle
|
||||
const spaet = await seite('tab=hochhaus')
|
||||
try {
|
||||
assert.deepEqual(spaet.hk.props.hochhaus.workers, {})
|
||||
spaet.hk.renders.hochhaus.length = 0
|
||||
spaet.setFeed({ state: makeState({ statuses: { lea: 'wartet' } }), loaded: true })
|
||||
for (const p of spaet.hk.renders.hochhaus) { assert.ok(p.feed.state); assert.equal(Object.keys(p.workers).length, 19) }
|
||||
assert.ok(spaet.hk.renders.hochhaus.length >= 1)
|
||||
} finally { spaet.end() }
|
||||
})
|
||||
|
||||
test('R3 Seite: Pult und Keller bekommen paused aus state.paused (nur ein echtes true), Pause schaltet mit dem Zustand um', async () => {
|
||||
const ui = await seite('tab=freigabe', { feed: { state: makeState({ paused: false }), error: null, loaded: true, refresh() {} } })
|
||||
try {
|
||||
assert.equal(ui.hk.props.pult.paused, false)
|
||||
ui.setFeed({ state: makeState({ paused: true }) })
|
||||
assert.equal(ui.hk.props.pult.paused, true)
|
||||
ui.go('tab=botleads')
|
||||
assert.equal(ui.hk.props.keller.paused, true)
|
||||
ui.setFeed({ state: makeState({ paused: false }) })
|
||||
assert.equal(ui.hk.props.keller.paused, false)
|
||||
ui.setFeed({ state: { ...makeState(), paused: 'ja' } })
|
||||
assert.equal(ui.hk.props.keller.paused, false)
|
||||
// vor dem ersten Schnappschuss: nicht angehalten
|
||||
ui.setFeed({ state: null, loaded: false })
|
||||
assert.equal(ui.hk.props.keller.paused, false)
|
||||
} finally { ui.end() }
|
||||
})
|
||||
|
||||
test('M2 Seite: das Hochhaus bekommt vonTab (der Bildschirm, von dem man zurückkommt); beim ersten Besuch null', async () => {
|
||||
const ui = await seite('tab=hochhaus', { feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
|
||||
try {
|
||||
assert.equal(ui.hk.props.hochhaus.vonTab, null)
|
||||
ui.go('tab=freigabe'); assert.deepEqual(ui.screen(), ['pult'])
|
||||
ui.go('tab=hochhaus'); assert.equal(ui.hk.props.hochhaus.vonTab, 'freigabe')
|
||||
// schon der erste Render nach dem Wechsel kennt vonTab (das Hochhaus fokussiert beim Einhängen)
|
||||
assert.ok(ui.hk.renders.hochhaus.every((p, i) => i === 0 || p.vonTab === null || p.vonTab === 'freigabe'))
|
||||
ui.go('tab=botleads'); assert.deepEqual(ui.screen(), ['keller'])
|
||||
ui.go('tab=hochhaus'); assert.equal(ui.hk.props.hochhaus.vonTab, 'botleads')
|
||||
// Neu-Rendern im Hochhaus ändert nichts
|
||||
ui.setFeed({}); assert.equal(ui.hk.props.hochhaus.vonTab, 'botleads')
|
||||
} finally { ui.end() }
|
||||
// direkt im Pult gestartet, dann zurück
|
||||
const direkt = await seite('tab=freigabe', { feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
|
||||
try {
|
||||
direkt.go('tab=hochhaus')
|
||||
assert.equal(direkt.hk.props.hochhaus.vonTab, 'freigabe')
|
||||
} finally { direkt.end() }
|
||||
})
|
||||
|
||||
// Hochhaus mit Wurzel-Attrappe (querySelector, focus) für den Rückweg und die Überschrift
|
||||
function wurzelAttrappe() {
|
||||
const gefragt = []
|
||||
const etagen = new Map()
|
||||
const wurzel = {
|
||||
querySelector(sel) {
|
||||
gefragt.push(sel)
|
||||
const m = /^\[data-hk-etage="([\w-]+)"\]$/.exec(sel)
|
||||
if (!m) return null
|
||||
if (!etagen.has(m[1])) etagen.set(m[1], { id: m[1], focusCalls: [], focus(o) { this.focusCalls.push(o) } })
|
||||
return etagen.get(m[1])
|
||||
},
|
||||
}
|
||||
const gehabt = Object.hasOwn(globalThis, '__fakeRef')
|
||||
const alt = globalThis.__fakeRef
|
||||
globalThis.__fakeRef = (tag, props) => (tag === 'div' && props.className === 'hk-hochhaus' ? wurzel : null)
|
||||
return { gefragt, etagen, restore() { if (gehabt) globalThis.__fakeRef = alt; else delete globalThis.__fakeRef } }
|
||||
}
|
||||
|
||||
test('M2 Hochhaus: nach dem Rückweg aus dem Pult fokussiert die Etage „freigabe“, aus dem Keller die Etage „keller“ (ohne Scrollen), erst wenn die Welt steht; sonst nichts', async () => {
|
||||
const ready = { state: makeState(), error: null, loaded: true, refresh() {} }
|
||||
for (const [von, etage] of [['freigabe', 'freigabe'], ['botleads', 'keller']]) {
|
||||
const w = wurzelAttrappe()
|
||||
const view = await hochhaus(ready, { vonTab: von })
|
||||
try {
|
||||
assert.deepEqual(w.gefragt, [`[data-hk-etage="${etage}"]`])
|
||||
assert.deepEqual(w.etagen.get(etage).focusCalls, [{ preventScroll: true }])
|
||||
// Neu-Rendern (Feed-Aktualisierung) holt den Fokus nicht noch einmal
|
||||
view.setFeed({ state: makeState() })
|
||||
assert.equal(w.etagen.get(etage).focusCalls.length, 1)
|
||||
} finally { view.end(); w.restore() }
|
||||
}
|
||||
// kein vonTab / unbekannter Wert: kein Fokus
|
||||
for (const von of [null, undefined, 'hochhaus', 'toString', '__proto__']) {
|
||||
const w = wurzelAttrappe()
|
||||
const view = await hochhaus(ready, { vonTab: von })
|
||||
try { assert.deepEqual(w.gefragt, [], String(von)) } finally { view.end(); w.restore() }
|
||||
}
|
||||
// noch nicht geladen: erst wenn der Zustand da ist
|
||||
const w = wurzelAttrappe()
|
||||
const view = await hochhaus({ state: null, error: null, loaded: false, refresh() {} }, { vonTab: 'freigabe' })
|
||||
try {
|
||||
assert.deepEqual(w.gefragt, [])
|
||||
view.setFeed({ state: makeState(), loaded: true })
|
||||
assert.deepEqual(w.etagen.get('freigabe').focusCalls, [{ preventScroll: true }])
|
||||
} finally { view.end(); w.restore() }
|
||||
})
|
||||
|
||||
test('M2/M7 Hochhaus: Überschrift „Hustler – Hochhaus“ (h1, nur für Screenreader) in jedem Zustand; die Kopfzeile steht im DOM vor der Welt', async () => {
|
||||
const ready = { state: makeState(), error: null, loaded: true, refresh() {} }
|
||||
const faelle = [
|
||||
['geladen', ready],
|
||||
['lädt', { state: null, error: null, loaded: false, refresh() {} }],
|
||||
['Dienst nicht erreichbar', { state: null, error: 'weg', loaded: false, refresh() {} }],
|
||||
]
|
||||
for (const [wo, feed] of faelle) {
|
||||
const view = await hochhaus(feed)
|
||||
try {
|
||||
const h1 = nodes(view.tree(), (n) => n.tag === 'h1')
|
||||
assert.equal(h1.length, 1, wo)
|
||||
assert.equal(textOf(h1[0]), 'Hustler – Hochhaus', wo); assert.equal(h1[0].props.className, 'hk-nur-sr', wo)
|
||||
assert.equal(view.tree().children[0], h1[0], `${wo}: die Überschrift steht zuerst`)
|
||||
} finally { view.end() }
|
||||
}
|
||||
const view = await hochhaus(ready)
|
||||
try {
|
||||
const reihenfolge = view.tree().children.filter((k) => k && k.props && k.props['data-teil']).map((k) => k.props['data-teil'])
|
||||
assert.deepEqual(reihenfolge, ['kopf', 'welt'], 'Tab-Reihenfolge: Kopfzeile vor der Welt')
|
||||
view.hh.welt.onZeige('lea')
|
||||
await settle()
|
||||
assert.deepEqual(view.tree().children.filter((k) => k && k.props && k.props['data-teil']).map((k) => k.props['data-teil']), ['kopf', 'welt', 'dialog'])
|
||||
} finally { view.end() }
|
||||
})
|
||||
|
||||
test('R3 Hochhaus: paused des Dienstes geht an die Detailansicht (nur ein echtes true)', async () => {
|
||||
const view = await hochhaus({ state: makeState({ paused: false }), error: null, loaded: true, refresh() {} })
|
||||
try {
|
||||
view.hh.welt.onZeige('lea')
|
||||
await settle()
|
||||
assert.equal(view.hh.dialog.paused, false)
|
||||
view.setFeed({ state: makeState({ paused: true }) })
|
||||
assert.equal(view.hh.dialog.paused, true)
|
||||
view.setFeed({ state: { ...makeState(), paused: 'ja' } })
|
||||
assert.equal(view.hh.dialog.paused, false)
|
||||
} finally { view.end() }
|
||||
})
|
||||
Reference in new issue
Block a user