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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user