Hustler-Pixelwelt T13: Korrekturen Pult und Keller (Sperre laufender Aktionen, Fokus, Kontrast, Handy-Tippflächen, Pause)

Pult: laufende Anfragen je Eintrags-Id in PultList (laufRef, Brief meldet Beginn/Ende): Brief schließen und denselben Eintrag
wieder öffnen sperrt alle Aktionsknöpfe, kein zweites reply/paste/start-build (P1); spätes Ergebnis nennt den Titel (P1b);
Original und Entwurf pre-wrap (P3); Überschrift nimmt beim Einhängen den Fokus (M2); „Pult zuklappen“ mit aria-expanded (K4);
paused -> hk-steht an der Szene (R3); Brief z-index 9 (R4); Textlinks am Handy 40 px (K7).
Keller: Überschrift-Fokus (M2), Reiter-Fokusring innen (M3), Seitenzeile/Punkte/Reiterzahl in Pixelify Sans 13 px (K5), Suchfeld und
Akte-Link 40 px am Handy (K7), Ortszeile mit title (W2), paused -> hk-steht (R3), Akte z-index 9 (R4); Arne ohne Eintrag neutral (R5).
Tests: Sperre laufender Aktionen, Titel im späten Ergebnis, Fokus, aria-expanded, paused, pre-wrap, z-index, Tippflächen, Reiter-Fokus,
Schrift, Kontrast (WCAG) von .hk-zeile-alt/.hk-kl-tag auf Papier und Hover, Arne neutral (alte „Arne schläft ohne Eintrag“-Prüfung ersetzt).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 09:40:06 +00:00
1 parent ea1485c27c
commit 0924a8d2ae
13 files changed
+597 -70

No files matched your search

+102
View File
@@ -758,6 +758,108 @@ test('Pult (bedient, P4-T32): während einer Anfrage sind alle Knöpfe des Brief
} finally { ui.end() }
})
// T13 P1: die Sperre gehört dem Eintrag, nicht dem Brief. Wer den Brief während einer Anfrage schließt und denselben Eintrag wieder öffnet, darf nichts zweimal auslösen
// (sonst fügte „Antwort einfügen“ den Text doppelt ein). Aufruf der Knopf-Funktionen von Hand: ein gesperrter Knopf löst im Browser nichts aus, aber die Funktion dahinter
// darf auch auf anderem Weg (alter Stand, Hilfstechnik) nichts senden.
test('Pult (bedient, T13 P1): Brief schließen, während eine Anfrage läuft → denselben Eintrag öffnen → Knöpfe gesperrt, kein zweiter Aufruf → Anfrage endet → frei bzw. Brief/Meldung wie vorgesehen', async () => {
const plattform = { ...unsicher, $id: 'k1', kind: 'plattform_antwort', title: 'Suche Webdesigner', draft: 'Claras Entwurf', sourceUrl: 'https://www.kleinanzeigen.de/s-anzeige/x/1-2-3', prospect: { $id: 'p1', name: 'Verein' } }
const zweite = { ...unsicher, $id: 'e2', title: 'Zweite Rückfrage' }
const faelle = [
{ name: 'start-build', item: unsicher, aktion: 'start-build', schliesst: true, ok: 'wird gebaut.',
start: async (ui) => { await ui.click('Vorschau bauen'); await ui.click('Wirklich bauen?') },
gesperrt: ['Als Mensch antworten', 'Vorschau bauen', 'Erledigt', 'Verwerfen', 'Ich kümmere mich'] },
{ name: 'reply', item: unsicher, aktion: 'reply', schliesst: true, ok: 'gesendet.',
start: async (ui) => { await ui.click('Als Mensch antworten'); await ui.click('Jetzt senden') },
gesperrt: ['Als Mensch antworten', 'Vorschau bauen', 'Erledigt', 'Verwerfen', 'Ich kümmere mich'] },
{ name: 'paste', item: plattform, aktion: 'paste', schliesst: false, ok: 'Eingefügt. Nora sortiert die Antwort ein.',
start: async (ui) => { await ui.click('Antwort des Kunden einfügen'); await ui.type('Antwort des Kunden', 'Ja, gern'); await ui.click('Einfügen') },
gesperrt: ['Antwort des Kunden einfügen', 'Kopieren & öffnen', 'Als gesendet markieren', 'Verwerfen', 'Ich kümmere mich'] },
]
for (const fall of faelle) {
for (const ende of ['fehler', 'erfolg']) {
const wo = `${fall.name} / ${ende}`
let finish
let reject
const api = scriptedApi(new Promise((resolve, no) => { finish = resolve; reject = no }))
const ui = await startList([fall.item, zweite], api)
const stale = (label) => clickable(ui.brief(), label)[0].props.onClick?.({ preventDefault() {} }) // wie ein Klick auf einen Knopf, den der Browser nicht auslöst
try {
// 1. die Anfrage läuft, der Brief wird geschlossen
await ui.open(0)
await fall.start(ui)
assert.equal(api.calls.length, 1, wo); assert.equal(api.calls[0][1], fall.aktion, wo)
await ui.close()
assert.equal(ui.brief(), null, wo)
// 2. derselbe Eintrag, neuer Brief: alles gesperrt, ein Hinweis nennt den Grund
await ui.open(0)
for (const label of fall.gesperrt) assert.deepEqual(ui.disabled(label), [true], `${wo}: ${label}`)
assert.ok(ui.text().includes('Für diesen Eintrag läuft noch eine Anfrage.'), wo)
// 3. auch die Funktionen hinter den Knöpfen senden nichts (kein zweites reply, paste oder start-build, keine Rückfrage, kein Feld, kein Öffnen der Anzeige)
for (const label of fall.gesperrt) { stale(label); stale(label); await settle() }
assert.equal(api.calls.length, 1, `${wo}: zweiter Aufruf`)
assert.ok(!ui.has('Wirklich bauen?') && !ui.has('Wirklich verwerfen?') && !ui.has('Wirklich freigeben?'), `${wo}: Rückfrage`)
assert.deepEqual([ui.value('Text der Antwort'), ui.value('Antwort des Kunden')], [[], []], `${wo}: Feld`)
assert.equal(ui.opened.length, 0, `${wo}: Anzeige geöffnet`)
// 4. andere Einträge sind nicht betroffen
await ui.close(); await ui.open(1)
assert.deepEqual(ui.disabled('Erledigt'), [false], wo); assert.ok(!ui.text().includes('Für diesen Eintrag läuft noch eine Anfrage.'), wo)
await ui.close(); await ui.open(0)
assert.deepEqual(ui.disabled(fall.gesperrt[0]), [true], wo)
// 5. die Anfrage endet
if (ende === 'fehler') {
reject(failure('Wird schon von einem Kollegen bearbeitet.', 409)); await settle()
// der Brief bleibt offen, alles ist frei, der Hinweis ist weg; der späte Fehler steht bei der Liste und nennt den Eintrag
assert.ok(ui.brief(), wo)
for (const label of fall.gesperrt) assert.deepEqual(ui.disabled(label), [false], `${wo}: ${label}`)
assert.ok(!ui.text().includes('Für diesen Eintrag läuft noch eine Anfrage.'), wo)
assert.deepEqual(ui.messages(), [{ kind: 'err', text: `„${fall.item.title}“: Wird schon von einem Kollegen bearbeitet.`, role: 'alert' }], wo)
assert.equal(api.calls.length, 1, wo)
// wieder frei: die nächste Aktion geht hinaus
await ui.click('Ich kümmere mich')
assert.deepEqual(api.calls[1], [fall.item.$id, 'claim'], wo)
} else {
finish({ status: 'neu', item: { $id: fall.item.$id } }); await settle()
assert.equal(api.calls.length, 1, wo)
if (fall.schliesst) {
// der Eintrag hat die Liste verlassen: der wieder geöffnete Brief schließt, die Meldung nennt den Titel
assert.equal(ui.brief(), null, wo)
assert.deepEqual(ui.messages(), [{ kind: 'ok', text: `„${fall.item.title}“ ${fall.ok}`, role: 'status' }], wo)
} else {
// der Eintrag bleibt: der Brief ist offen und frei, das späte Ergebnis steht mit dem Titel in der Liste
assert.ok(ui.brief(), wo)
for (const label of fall.gesperrt) assert.deepEqual(ui.disabled(label), [false], `${wo}: ${label}`)
assert.deepEqual(ui.messages(), [{ kind: 'ok', text: `„${fall.item.title}“: ${fall.ok}`, role: 'status' }], wo)
}
}
} finally { ui.end() }
}
}
})
test('Pult (bedient, T13 P1): ohne Anfrage ist nichts gesperrt; die Sperre hält nur während der Anfrage und gilt je Eintrag (Brief zu, Brief auf, andere Einträge frei)', async () => {
const zweite = { ...unsicher, $id: 'e2', title: 'Zweite Rückfrage' }
let finish
const api = scriptedApi(new Promise((resolve) => { finish = resolve }))
const ui = await startList([unsicher, zweite], api)
try {
await ui.open(0)
for (const label of ['Als Mensch antworten', 'Vorschau bauen', 'Erledigt', 'Verwerfen', 'Ich kümmere mich']) assert.deepEqual(ui.disabled(label), [false], label)
assert.ok(!ui.text().includes('Für diesen Eintrag läuft noch eine Anfrage.'))
await ui.click('Ich kümmere mich') // Anfrage zu e1 läuft
assert.equal(api.calls.length, 1)
// der erste Brief sperrt sich selbst (wie bisher), der Hinweis erscheint dort nicht: die Sperre ist die eigene
assert.deepEqual(ui.disabled('Erledigt'), [true]); assert.ok(!ui.text().includes('Für diesen Eintrag läuft noch eine Anfrage.'))
// ein zweiter Eintrag kann währenddessen bearbeitet werden (eigene Id)
await ui.close(); await ui.open(1)
await ui.click('Ich kümmere mich')
assert.deepEqual(api.calls.map((c) => c[0]), ['e1', 'e2'])
finish({ item: { $id: 'e1' } }); await settle()
await ui.close(); await ui.open(0)
assert.deepEqual(ui.disabled('Erledigt'), [false]) // Anfrage zu e1 ist zu Ende
} finally { ui.end() }
})
test('Pult (bedient, P4-T32): ein „unsicher“-Eintrag ohne Entwurf öffnet „Als Mensch antworten“ mit leerem Feld', async () => {
const api = scriptedApi()
const ui = await startList([{ ...unsicher, draft: undefined }], api)