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:
ClaudeandClaude Opus 5.5 committed 2026-10-22 09:47:57 +00:00
1 parent 0924a8d2ae
commit 5e6698f5e0
14 files changed
+994 -52

No files matched your search

+88
View File
@@ -609,6 +609,94 @@ test('Fokus-Rückgabe: Kennungen mit Sonderzeichen gehen nie in einen Selektor',
// ---------------------------------------------------------------------------------------------------------------------------------------------
// Quelltext und Stil
// ---------------------------------------------------------------------------------------------------------------------------------------------
// Korrekturen der Endprüfung (T13): Ansage beim Blättern, Pause, Handy quer, Überlagerung, weniger Bewegung
test('M6 Blättern wird angesagt: Live-Region (polite) ist beim Öffnen leer, nach ◂ ▸ und ←/→ steht Name, Status und Nummer darin; Öffnen sagt nichts an', async () => {
const workers = { lea: EINTRAG('arbeitet'), bea: EINTRAG('wartet'), sven: EINTRAG('fehler'), otto: EINTRAG('leerlauf'), arne: EINTRAG('grau', { connected: false }) }
await lauf({ props: { workerId: 'lea', workers } }, (ui) => {
const region = () => ui.one((n) => n.props['aria-live'] === 'polite', 'Live-Region')
assert.equal(region().props['aria-atomic'], 'true'); assert.ok(hasClass(region(), 'hk-nur-sr'))
assert.equal(textOf(region()), '', 'beim Öffnen nichts ansagen')
ui.knopf('Nächster Hustler').props.onClick({})
assert.equal(textOf(region()), 'Bea, Wartet auf Auftrag, 10 von 19')
assert.deepEqual(ui.wechsel, ['bea'])
ui.setProps({ workerId: 'bea' })
assert.equal(textOf(region()), 'Bea, Wartet auf Auftrag, 10 von 19', 'bleibt stehen, bis neu geblättert wird')
ui.taste('ArrowLeft')
assert.equal(textOf(region()), 'Lea, Arbeitet, 9 von 19')
ui.setProps({ workerId: 'lea' })
ui.taste('ArrowLeft')
assert.equal(textOf(region()), 'Sven, Fehler, 8 von 19')
ui.setProps({ workerId: 'otto' })
ui.knopf('Vorheriger Hustler').props.onClick({})
assert.equal(textOf(region()), 'Arne, Noch nicht angeschlossen, 19 von 19', 'Umlauf')
// die Ansage ist eine Momentaufnahme: ein späterer Statuswechsel meldet sich nicht von selbst
ui.setProps({ workerId: 'arne', workers: { ...workers, arne: EINTRAG('arbeitet') } })
assert.equal(textOf(region()), 'Arne, Noch nicht angeschlossen, 19 von 19')
// der Dialog selbst kennt den neuen Namen
assert.equal(ui.dialog().props['aria-label'], 'Hustler Arne')
// ein Hustler ohne Eintrag in der Tabelle: Leerlauf
ui.setProps({ workerId: 'lea', workers: {} })
ui.knopf('Nächster Hustler').props.onClick({})
assert.equal(textOf(region()), 'Bea, Leerlauf, 10 von 19')
// Dialog zu und neu auf: wieder leer
ui.setProps({ workerId: null })
ui.setProps({ workerId: 'kim', workers })
assert.equal(textOf(region()), '')
})
})
test('R3 Pause: bei paused trägt die Überlagerung hk-steht (alle Animationen im Dialog stehen), sonst nicht', async () => {
await lauf({ props: { workerId: 'lea', workers: { lea: EINTRAG('arbeitet') }, paused: false } }, (ui) => {
assert.ok(!hasClass(ui.rahmen(), 'hk-steht'))
ui.setProps({ paused: true })
assert.ok(hasClass(ui.rahmen(), 'hk-steht')); assert.ok(hasClass(ui.rahmen(), 'hk-dialog-ueber'))
ui.setProps({ paused: 'ja' })
assert.ok(!hasClass(ui.rahmen(), 'hk-steht'), 'nur echtes true')
ui.setProps({ paused: undefined })
assert.ok(!hasClass(ui.rahmen(), 'hk-steht'))
})
// die globale Regel hält Animationen unter .hk-steht an, und die Detailansicht liegt unter der Seiten-Wurzel
const regel = fs.readFileSync(path.join(ROOT, 'src/styles/hustler/hustler-ui.css'), 'utf8')
assert.match(regel, /\.hk-seite \.hk-steht \*[^{]*\{\s*animation-play-state:\s*paused !important/)
})
test('M5 Handy quer: Vollbild auch bei niedriger Höhe (max-width 719 ODER max-height 500), die Zeilen-Anordnung gilt nur quer; Überlagerung unter der Handy-Navigation', () => {
const css = fs.readFileSync(path.join(ROOT, 'src/styles/hustler/hustler-detail.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
// Vollbild-Block: beide Bedingungen, durch Komma verbunden
const vollbild = css.match(/@media \(max-width: 719px\), \(max-height: 500px\) \{([\s\S]*?)\n\}/)
assert.ok(vollbild, 'Media-Abfrage für Vollbild')
assert.match(vollbild[1], /\.hk-hochhaus \.hk-dialog-ueber \{ padding: 0; \}/)
assert.match(vollbild[1], /width: 100%; max-width: 100%; height: 100%; max-height: 100%/)
assert.match(vollbild[1], /container-type: inline-size/)
// Quer: Szene links, Daten rechts, nur im Querformat und niedrig
const quer = css.match(/@media \(max-height: 500px\) and \(orientation: landscape\) \{([\s\S]*?)\n\}/)
assert.ok(quer, 'Media-Abfrage für quer')
assert.match(quer[1], /flex-direction: row/); assert.match(quer[1], /45cqw/)
// die alte Bedingung (nur schmale Handys) ist weg
assert.doesNotMatch(css, /@media \(max-width: 719px\) and \(max-height: 520px\)/)
// R4: z-index unter Seitenleiste (20) und Abdunkler (15)
assert.match(css, /\.hk-hochhaus \.hk-dialog-ueber \{[^}]*z-index: 9;/)
assert.doesNotMatch(css, /z-index:\s*(1[0-9]|[2-9][0-9])/)
})
test('M4 weniger Bewegung: alle Sprites der Detailansicht (Figur, Monitor, Dampf, Zzz, Wirbel) tragen hk-spr und stehen damit auf dem ersten Bild; der Fehler-Marker hat seine eigene Regel', async () => {
const css = fs.readFileSync(path.join(ROOT, 'src/styles/hustler/hustler-ui.css'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
const reduziert = css.slice(css.indexOf('@media (prefers-reduced-motion: reduce)'))
assert.match(reduziert, /\.hk-seite \.hk-hochhaus \.hk-spr \{ animation: none; \}/)
assert.match(reduziert, /\.hk-seite \.hk-alarm \{ animation: none; \}/)
const vorlage = fs.readFileSync(path.join(ROOT, 'src/styles/hustler/hochhaus.css'), 'utf8')
const animiert = new Set([...vorlage.replace(/@media \(prefers-reduced-motion: reduce\)\{[\s\S]*?\n\}\n/, '').matchAll(/\.hk-hochhaus (\.hk-[\w-]+)\{[^}]*animation:/g)].map((m) => m[1].slice(1)))
for (const [id, status] of [['lea', 'arbeitet'], ['hanna', 'wartet'], ['bea', 'leerlauf'], ['kim', 'grau'], ['otto', 'wartet'], ['pia', 'fehler']]) {
await lauf({ props: { workerId: id, workers: { [id]: EINTRAG(status, { connected: status !== 'grau' }) } } }, (ui) => {
const bewegt = ui.find((n) => typeof n.tag === 'string' && classes(n).some((c) => animiert.has(c)))
assert.ok(bewegt.length >= 1, `${id}: mindestens die Figur`)
for (const n of bewegt) assert.ok(classes(n).includes('hk-spr') || classes(n).includes('hk-alarm'), `${id}: ${classes(n).join(' ')} nicht von einer Reduziert-Regel erfasst`)
})
}
})
test('Komponente: kein innerHTML, kein CSS- oder Bild-Import, Links nur über safeLink', () => {
const quelle = fs.readFileSync(path.join(ROOT, 'src/components/workers/hochhaus/HustlerDialog.jsx'), 'utf8')
const src = quelle.replace(/(^|\s)\/\/.*$/gm, '$1') // Zeilenkommentare dürfen das Wort erwähnen