Workers P4 Endprüfung W4: Esc und „Abbrechen“ schließen die Felder und geben den Fokus an den Knopf zurück
„Als Mensch antworten“ und „Antwort des Kunden einfügen“ ließen sich nur durch erneuten Klick auf den Auslöser schließen. Esc (aus dem ganzen Feld, mit preventDefault für die Handy-Leiste) und der neue Knopf „Abbrechen“ schließen das Feld, der Text bleibt in der Liste erhalten, der Fokus geht an den Auslöser zurück (data-opens am Knopf, gesucht in der Aktionsgruppe: Button reicht kein ref durch). Während einer Anfrage sind beide gesperrt. Neue Gruppe .wk-pult-panel-btns, auf dem Handy mit dem Hauptknopf oben. Test-Läufer: ref-Objekte an Tags und eine document-Attrappe für den Fokus. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c7061837fb
commit
ff3d042c0e
4 files changed
+141
-11
No files matched your search
@@ -1606,7 +1606,7 @@ async function handyP4(browser, base) {
|
||||
const reach2 = await reachOf(page, '.wk-pult-reply textarea, .wk-pult-reply .wk-pult-panel-foot .btn')
|
||||
const area = panel.locator('textarea')
|
||||
const areaBox = await area.evaluate((el) => { const r = el.getBoundingClientRect(); return { left: Math.round(r.left), right: Math.round(r.right), height: Math.round(r.height) } })
|
||||
check('handy-p4: Antwortfeld: Textfeld und „Jetzt senden“ im Fenster, nicht verdeckt, Knopf mindestens 40 px hoch, Feld mindestens 100 px hoch', reach2.length === 2 && badReach(reach2).length === 0 && areaBox.left >= 0 && areaBox.right <= 390 && areaBox.height >= 100, { reach2, areaBox })
|
||||
check('handy-p4: Antwortfeld: Textfeld, „Abbrechen“ und „Jetzt senden“ im Fenster, nicht verdeckt, Knöpfe mindestens 40 px hoch, Feld mindestens 100 px hoch', reach2.length === 3 && badReach(reach2).length === 0 && areaBox.left >= 0 && areaBox.right <= 390 && areaBox.height >= 100, { reach2, areaBox })
|
||||
await panel.scrollIntoViewIfNeeded()
|
||||
await shoot(page, 'handy-p4-antwort-offen.png')
|
||||
await area.fill('x'.repeat(400) + ' https://www.sv-namborn.example/' + 'lang/'.repeat(30))
|
||||
@@ -1676,7 +1676,7 @@ async function handyP4(browser, base) {
|
||||
await setPultFault(page, 'paste', null)
|
||||
await noScroll('beide Einfüge-Felder offen, 413 sichtbar')
|
||||
const reach6 = await reachOf(page, '.wk-pult-paste textarea, .wk-pult-paste .wk-pult-panel-foot .btn, li.wk-pult-row .wk-pult-actions > button')
|
||||
check('handy-p4: Einfüge-Felder, „Einfügen“ und „Antwort des Kunden einfügen“ sind erreichbar (im Fenster, nicht verdeckt, Knöpfe mindestens 40 px hoch)', reach6.length === 6 && badReach(reach6).length === 0, { count: reach6.length, bad: badReach(reach6) })
|
||||
check('handy-p4: Einfüge-Felder, „Abbrechen“, „Einfügen“ und „Antwort des Kunden einfügen“ sind erreichbar (im Fenster, nicht verdeckt, Knöpfe mindestens 40 px hoch)', reach6.length === 8 && badReach(reach6).length === 0, { count: reach6.length, bad: badReach(reach6) })
|
||||
await plat.scrollIntoViewIfNeeded()
|
||||
await shoot(page, 'handy-p4-einfuegen-413.png')
|
||||
await shoot(page, 'handy-p4-einfuegen-ganz.png', { fullPage: true })
|
||||
|
||||
@@ -47,16 +47,28 @@ function rowName(label, title, joiner = ': ') {
|
||||
}
|
||||
|
||||
// Aufgeklapptes Feld unter den Aktionen: „Als Mensch antworten“ (.wk-pult-reply) und „Antwort des Kunden einfügen“ (.wk-pult-paste).
|
||||
// Textfeld, Zähler (Zeichen wie der Dienst sie zählt, höchstens REPLY_TEXT_MAX) und ein Knopf; die Prüfung macht die Zeile beim Absenden.
|
||||
function TextPanel({ className, label, hint, value, onChange, submitLabel, onSubmit, busy }) {
|
||||
// Textfeld, Zähler (Zeichen wie der Dienst sie zählt, höchstens REPLY_TEXT_MAX) und zwei Knöpfe; die Prüfung macht die Zeile beim Absenden.
|
||||
// Esc (aus dem ganzen Feld heraus, nicht nur aus dem Textfeld) und „Abbrechen“ schließen das Feld, der eingegebene Text bleibt in der Liste erhalten.
|
||||
// Während einer Anfrage ist beides gesperrt: das Feld soll nicht mitten im Senden verschwinden.
|
||||
function TextPanel({ className, label, hint, value, onChange, submitLabel, onSubmit, onClose, busy }) {
|
||||
const count = charCount(value)
|
||||
// preventDefault: die Handy-Leiste (App.jsx) schließt bei Esc nur, wenn das Ereignis nicht behandelt wurde
|
||||
const onKeyDown = (e) => {
|
||||
if (e.key !== 'Escape' || busy) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onClose()
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={className} onKeyDown={onKeyDown}>
|
||||
{hint && <p className="wk-hint">{hint}</p>}
|
||||
<textarea className="textarea" value={value} onChange={(e) => onChange(e.target.value)} aria-label={label} autoFocus />
|
||||
<div className="wk-pult-panel-foot">
|
||||
<span className={count > REPLY_TEXT_MAX ? 'wk-pult-count over' : 'wk-pult-count'}>{count} / {REPLY_TEXT_MAX}</span>
|
||||
<Button type="button" variant="primary" onClick={onSubmit} disabled={busy}>{submitLabel}</Button>
|
||||
<div className="wk-pult-panel-btns">
|
||||
<Button type="button" onClick={onClose} disabled={busy}>Abbrechen</Button>
|
||||
<Button type="button" variant="primary" onClick={onSubmit} disabled={busy}>{submitLabel}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -72,6 +84,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
const [note, setNote] = useState(null) // { kind: 'ok' | 'err', text }
|
||||
const busyRef = useRef(false) // sperrt schon im selben Takt, in dem busy noch nicht greift (Doppelklick)
|
||||
const aliveRef = useRef(true)
|
||||
const actionsRef = useRef(null) // die Aktionsgruppe: darin sucht close() den Knopf, der ein Feld geöffnet hat
|
||||
|
||||
useEffect(() => {
|
||||
aliveRef.current = true
|
||||
@@ -173,6 +186,12 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
const replyValue = fields.reply ?? text
|
||||
const pasteValue = fields.paste ?? ''
|
||||
const toggle = (which) => setOpen((current) => (current === which ? null : which))
|
||||
// Feld schließen (Esc, „Abbrechen“) und den Fokus an den Knopf zurückgeben, der es geöffnet hat: der bleibt eingehängt, der Fokus kann sofort dorthin.
|
||||
// Der Knopf trägt seine Kennung in data-opens (Button reicht sie weiter; ein ref ginge an Button vorbei).
|
||||
const close = (which) => {
|
||||
setOpen(null)
|
||||
actionsRef.current?.querySelector(`[data-opens="${which}"]`)?.focus()
|
||||
}
|
||||
|
||||
const sendReply = () => {
|
||||
const check = validateReplyText(replyValue)
|
||||
@@ -254,7 +273,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
)}
|
||||
|
||||
{hasActions && (
|
||||
<div className="row-actions wk-pult-actions" role="group" aria-label={rowName('Aktionen', row.title)}>
|
||||
<div className="row-actions wk-pult-actions" role="group" aria-label={rowName('Aktionen', row.title)} ref={actionsRef}>
|
||||
{row.canSend && row.kind === 'mail_antwort' && (
|
||||
mail
|
||||
? <a className="btn btn-primary" href={mail}>E-Mail schreiben</a>
|
||||
@@ -276,7 +295,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
</Button>
|
||||
)}
|
||||
{row.canReply && (
|
||||
<Button type="button" onClick={() => toggle('reply')} aria-expanded={open === 'reply'} disabled={busy}>
|
||||
<Button type="button" onClick={() => toggle('reply')} aria-expanded={open === 'reply'} data-opens="reply" disabled={busy}>
|
||||
Als Mensch antworten
|
||||
</Button>
|
||||
)}
|
||||
@@ -299,7 +318,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
</>
|
||||
)}
|
||||
{row.canPaste && (
|
||||
<Button type="button" onClick={() => toggle('paste')} aria-expanded={open === 'paste'} disabled={busy}>
|
||||
<Button type="button" onClick={() => toggle('paste')} aria-expanded={open === 'paste'} data-opens="paste" disabled={busy}>
|
||||
Antwort des Kunden einfügen
|
||||
</Button>
|
||||
)}
|
||||
@@ -328,6 +347,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
onChange={(value) => onField(row.id, 'reply', value)}
|
||||
submitLabel="Jetzt senden"
|
||||
onSubmit={sendReply}
|
||||
onClose={() => close('reply')}
|
||||
busy={busy}
|
||||
/>
|
||||
)}
|
||||
@@ -340,6 +360,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
|
||||
onChange={(value) => onField(row.id, 'paste', value)}
|
||||
submitLabel="Einfügen"
|
||||
onSubmit={sendPaste}
|
||||
onClose={() => close('paste')}
|
||||
busy={busy}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -212,6 +212,8 @@
|
||||
.wk-pult-reply .wk-hint, .wk-pult-paste .wk-hint { margin: 0; }
|
||||
.wk-pult-reply .textarea, .wk-pult-paste .textarea { display: block; width: 100%; min-height: 140px; }
|
||||
.wk-pult-panel-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
|
||||
/* „Abbrechen“ links vom Hauptknopf */
|
||||
.wk-pult-panel-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
||||
.wk-pult-count { color: var(--text-2); font-size: 13px; font-variant-numeric: tabular-nums; }
|
||||
.wk-pult-count.over { color: var(--red-fg); }
|
||||
|
||||
@@ -254,6 +256,8 @@
|
||||
.wk-pult-row .wk-pult-actions .btn, .wk-pult-row .wk-pult-actions .link-danger { width: 100%; min-height: 40px; justify-content: center; text-align: center; }
|
||||
/* Felder: Zähler und Knopf untereinander, der Knopf über die volle Breite */
|
||||
.wk-pult-panel-foot { flex-direction: column; align-items: stretch; }
|
||||
/* Hauptknopf oben, „Abbrechen“ darunter */
|
||||
.wk-pult-panel-btns { flex-direction: column-reverse; align-items: stretch; }
|
||||
.wk-pult-panel-foot .btn { width: 100%; min-height: 40px; justify-content: center; text-align: center; }
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ export function __mount(element) {
|
||||
return out === null || out === undefined || out === false ? null : expand(out, path + '/c', visited)
|
||||
}
|
||||
const children = flat(props.children).map((c, i) => expand(c, path + '/' + (c && c.key != null ? 'k:' + c.key : i), visited))
|
||||
// ref-Objekt an einem Tag: wie React vor den Effekten gesetzt; das Element liefert der Test (globalThis.__fakeRef), sonst null
|
||||
if (typeof type === 'string' && props.ref && typeof props.ref === 'object') props.ref.current = globalThis.__fakeRef ? globalThis.__fakeRef(type, props) : null
|
||||
return { tag: type === Fragment ? 'fragment' : type, props, children }
|
||||
}
|
||||
|
||||
@@ -485,7 +487,7 @@ test('PultList (P4-T32): Quelltext – Aktionen über pultAction, Prüfung mit v
|
||||
"'reply'", "'approve-preview'", "'paste'", "'start-build'", 'validateReplyText', 'canReply', 'canApprovePreview', 'canStartBuild', 'canPaste', 'previewHref',
|
||||
'actionErrorText', 'pasteDoneText', 'rel="noopener noreferrer"', 'target="_blank"',
|
||||
'Als Mensch antworten', 'Jetzt senden', 'Vorschau ansehen', 'Freigeben & senden', 'Wirklich freigeben?', 'Vorschau bauen', 'Wirklich bauen?',
|
||||
'Antwort des Kunden einfügen', 'Einfügen', 'wk-pult-reply', 'wk-pult-paste',
|
||||
'Antwort des Kunden einfügen', 'Einfügen', 'wk-pult-reply', 'wk-pult-paste', 'Abbrechen', 'data-opens', 'Escape',
|
||||
'Kopiere die Antwort aus der Plattform hierher. Nora sortiert sie ein; bei einer Frage schreibt Clara den nächsten Entwurf.',
|
||||
]) assert.ok(s.includes(t), t)
|
||||
assert.doesNotMatch(s, /dangerouslySetInnerHTML/); assert.doesNotMatch(s, /innerHTML/)
|
||||
@@ -517,9 +519,11 @@ test('CSS (P4-T32): .wk-pult-reply und .wk-pult-paste mit Haarlinie, volle Breit
|
||||
assert.match(css, /\.wk-pult-reply/); assert.match(css, /\.wk-pult-paste/)
|
||||
const rule = css.match(/\.wk-pult-row \.wk-pult-reply[^{]*\{[^}]*\}/)?.[0] ?? ''
|
||||
assert.match(rule, /\.wk-pult-paste/); assert.match(rule, /border-top: 1px solid var\(--line\)/); assert.match(rule, /width: 100%/)
|
||||
// schmale Bildschirme: die Fußzeile des Feldes (Zähler, Knopf) steht untereinander
|
||||
// schmale Bildschirme: die Fußzeile des Feldes (Zähler, Knöpfe) steht untereinander
|
||||
const phone = css.slice(css.indexOf('@media (max-width: 860px)'))
|
||||
assert.match(phone, /\.wk-pult-panel-foot/); assert.match(phone, /flex-direction: column/)
|
||||
// Endprüfung W4: „Abbrechen“ und „Jetzt senden“ stehen nebeneinander (Gruppe .wk-pult-panel-btns), auf dem Handy untereinander über die volle Breite
|
||||
assert.match(css, /\.wk-pult-panel-btns\s*\{[^}]*display: flex/); assert.match(phone, /\.wk-pult-panel-btns\s*\{[^}]*flex-direction: column-reverse/)
|
||||
})
|
||||
|
||||
test('PultList (gerendert, P4-T32): Aktionen je Art – Antworten, Freigeben, Bauen, Einfügen; Felder bleiben zu, Fremdtext nur als Text', async () => {
|
||||
@@ -664,6 +668,15 @@ async function startList(items, api) {
|
||||
if (!found[0].props.disabled) found[0].props.onClick?.({ preventDefault() {} })
|
||||
await settle()
|
||||
},
|
||||
// Taste im Feld mit dieser Klasse (wk-pult-reply, wk-pult-paste); Ergebnis: das Ereignis, damit der Test preventDefault sehen kann
|
||||
async key(panelClass, key = 'Escape') {
|
||||
const panel = nodes(view.tree(), (n) => n.tag === 'div' && hasClass(n, panelClass))[0]
|
||||
assert.ok(panel, 'Feld ' + panelClass)
|
||||
const event = { key, defaultPrevented: false, stopped: false, preventDefault() { this.defaultPrevented = true }, stopPropagation() { this.stopped = true } }
|
||||
panel.props.onKeyDown?.(event)
|
||||
await settle()
|
||||
return event
|
||||
},
|
||||
async tab(label) {
|
||||
const found = clickable(inFilters(view.tree()), label)
|
||||
assert.equal(found.length, 1, `Reiter „${label}“`)
|
||||
@@ -703,6 +716,30 @@ function scriptedApi(...outcomes) {
|
||||
}
|
||||
const failure = (message, status) => Object.assign(new Error(message), { status })
|
||||
|
||||
// document-Attrappe für den Fokus (nur für die Dauer eines Tests): activeElement stellt der Test, focus() eines Elements setzt es. Elemente, die der Bauteil über
|
||||
// ref anfasst (Zeile, Aktionsgruppe, Meldung), liefert __fakeRef je Klasse; querySelector antwortet aus f.found (Selektor -> Element), sonst null.
|
||||
function fakeFocus() {
|
||||
const body = { name: 'body' }
|
||||
const doc = { body, documentElement: { name: 'html' }, activeElement: body }
|
||||
const f = { doc, body, found: new Map(), made: new Map() }
|
||||
f.el = (name, extra = {}) => ({ name, isConnected: true, disabled: false, focusCalls: 0, focus() { this.focusCalls += 1; doc.activeElement = this }, querySelector: (selector) => f.found.get(selector) ?? null, ...extra })
|
||||
f.ref = (key) => f.made.get(key) // das Element hinter ref an Tag/Klasse key
|
||||
const had = Object.hasOwn(globalThis, 'document')
|
||||
const saved = globalThis.document
|
||||
globalThis.document = doc
|
||||
globalThis.__fakeRef = (tag, props) => {
|
||||
const key = String(props.className ?? tag).split(' ')[0]
|
||||
if (!f.made.has(key)) f.made.set(key, f.el(key))
|
||||
return f.made.get(key)
|
||||
}
|
||||
f.restore = () => {
|
||||
if (had) globalThis.document = saved
|
||||
else delete globalThis.document
|
||||
delete globalThis.__fakeRef
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
const unsicher = { $id: 'e1', kind: 'unsicher', status: 'offen', title: 'Unklare Antwort', draft: 'Entwurf von Clara', createdAt: new Date().toISOString(), prospectId: 'p1', prospect: { $id: 'p1', name: 'Verein', email: 'x@verein.example' } }
|
||||
|
||||
test('PultList (bedient, P4-T32): „Als Mensch antworten“ klappt auf, ist mit dem Entwurf vorbelegt, prüft den Text und zeigt die Sätze des Dienstes', async () => {
|
||||
@@ -773,6 +810,74 @@ test('PultList (bedient, P4-T32): ein „unsicher“-Eintrag ohne Entwurf öffne
|
||||
} finally { ui.end() }
|
||||
})
|
||||
|
||||
// Endprüfung P4 (W4): die aufgeklappten Felder schließen mit Esc und mit „Abbrechen“; der Fokus geht an den Knopf zurück, der sie öffnete, der Text bleibt.
|
||||
// Während einer Anfrage bleiben beide gesperrt (das Feld soll nicht mitten im Senden verschwinden).
|
||||
test('PultList (bedient, W4): Esc und „Abbrechen“ schließen „Als Mensch antworten“, geben den Fokus an den Knopf zurück und behalten den Text', async () => {
|
||||
const f = fakeFocus()
|
||||
let finish
|
||||
const api = scriptedApi(new Promise((resolve) => { finish = resolve }))
|
||||
const ui = await startList([unsicher], api)
|
||||
try {
|
||||
const opener = f.el('opener')
|
||||
f.found.set('[data-opens="reply"]', opener)
|
||||
assert.equal(clickable(inList(ui.tree()), 'Als Mensch antworten')[0].props['data-opens'], 'reply') // der Auslöser trägt seine Kennung
|
||||
assert.ok(!ui.has('Abbrechen'))
|
||||
await ui.click('Als Mensch antworten')
|
||||
assert.ok(ui.has('Abbrechen'))
|
||||
await ui.type('Text der Antwort', 'Mein Text')
|
||||
// eine andere Taste ändert nichts
|
||||
let event = await ui.key('wk-pult-reply', 'a')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), ['Mein Text']); assert.equal(event.defaultPrevented, false)
|
||||
// Esc: Feld zu, Fokus auf dem Auslöser, das Ereignis ist behandelt (die Handy-Leiste schließt sonst mit)
|
||||
event = await ui.key('wk-pult-reply')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), []); assert.equal(opener.focusCalls, 1); assert.equal(f.doc.activeElement, opener)
|
||||
assert.ok(event.defaultPrevented); assert.ok(event.stopped)
|
||||
assert.equal(nodes(ui.tree(), (n) => n.tag === 'button' && textOf(n) === 'Als Mensch antworten')[0].props['aria-expanded'], false)
|
||||
// der Text bleibt erhalten
|
||||
await ui.click('Als Mensch antworten')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), ['Mein Text'])
|
||||
// Abbrechen: wie Esc
|
||||
await ui.click('Abbrechen')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), []); assert.equal(opener.focusCalls, 2); assert.equal(api.calls.length, 0)
|
||||
// ein zweiter Klick auf den Auslöser schließt wie bisher, ohne den Fokus zu bewegen (er liegt schon dort)
|
||||
await ui.click('Als Mensch antworten'); await ui.click('Als Mensch antworten')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), []); assert.equal(opener.focusCalls, 2)
|
||||
// während der Anfrage sind Esc und Abbrechen gesperrt
|
||||
await ui.click('Als Mensch antworten')
|
||||
await ui.click('Jetzt senden')
|
||||
assert.equal(api.calls.length, 1); assert.deepEqual(ui.disabled('Abbrechen'), [true])
|
||||
event = await ui.key('wk-pult-reply')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), ['Mein Text']); assert.equal(event.defaultPrevented, false); assert.equal(opener.focusCalls, 2)
|
||||
await ui.click('Abbrechen')
|
||||
assert.deepEqual(ui.value('Text der Antwort'), ['Mein Text'])
|
||||
finish({ item: { $id: 'e1' } }); await settle()
|
||||
} finally { ui.end(); f.restore() }
|
||||
})
|
||||
|
||||
test('PultList (bedient, W4): Esc und „Abbrechen“ schließen auch „Antwort des Kunden einfügen“ und kehren an dessen Knopf zurück', async () => {
|
||||
const f = fakeFocus()
|
||||
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 api = scriptedApi()
|
||||
const ui = await startList([plattform], api)
|
||||
try {
|
||||
const opener = f.el('opener')
|
||||
f.found.set('[data-opens="paste"]', opener)
|
||||
assert.equal(clickable(inList(ui.tree()), 'Antwort des Kunden einfügen')[0].props['data-opens'], 'paste')
|
||||
await ui.click('Antwort des Kunden einfügen')
|
||||
await ui.type('Antwort des Kunden', 'Noch nicht gesendet')
|
||||
await ui.key('wk-pult-paste')
|
||||
assert.deepEqual(ui.value('Antwort des Kunden'), []); assert.equal(opener.focusCalls, 1)
|
||||
await ui.click('Antwort des Kunden einfügen')
|
||||
assert.deepEqual(ui.value('Antwort des Kunden'), ['Noch nicht gesendet']) // der Text bleibt
|
||||
await ui.click('Abbrechen')
|
||||
assert.deepEqual(ui.value('Antwort des Kunden'), []); assert.equal(opener.focusCalls, 2); assert.equal(api.calls.length, 0)
|
||||
// „Abbrechen“ liegt vor dem Absenden (Einfügen bleibt der Hauptknopf)
|
||||
await ui.click('Antwort des Kunden einfügen')
|
||||
const names = nodes(ui.tree(), (n) => n.tag === 'div' && hasClass(n, 'wk-pult-panel-btns'))[0].children.map(textOf)
|
||||
assert.deepEqual(names, ['Abbrechen', 'Einfügen'])
|
||||
} finally { ui.end(); f.restore() }
|
||||
})
|
||||
|
||||
// Endprüfung P4 (W2): „Vorschau bauen“ startet einen Opus-Lauf und lässt sich in der Oberfläche nicht zurücknehmen: wie Freigeben und Verwerfen
|
||||
// fragt der Knopf zuerst nach (4 s). Ein Fehlklick (Handy: der Knopf liegt in voller Breite zwischen anderen) startet keinen Bau mehr.
|
||||
test('PultList (bedient, W2): „Vorschau bauen“ – erster Klick fragt nach (4 s, Hinweis), zweiter startet den Bau; Fehler und Erfolg wie bisher', async () => {
|
||||
|
||||
Reference in new issue
Block a user