diff --git a/src/lib/workersEvents.js b/src/lib/workersEvents.js index 43c0521..2a5e447 100644 --- a/src/lib/workersEvents.js +++ b/src/lib/workersEvents.js @@ -186,7 +186,9 @@ const task = (who, text) => ({ op: 'task', who, text }) const say = (who, text) => ({ op: 'say', who, text: cutBubble(text) }) const pose = (who, p) => ({ op: 'pose', who, pose: p }) const give = (from, to) => ({ op: 'give', from, to }) -const fx = (name, at, to) => (to === undefined ? { op: 'fx', fx: name, at } : { op: 'fx', fx: name, at, to }) +// `to` wird gesetzt, sobald ein drittes Argument übergeben wird, auch wenn es undefined ist (z. B. fehlende workerId); +// so bleibt ein Schritt mit unbekanntem Ziel erkennbar und wird verworfen, statt ohne Ziel zu bleiben +const fx = (name, at, ...rest) => (rest.length ? { op: 'fx', fx: name, at, to: rest[0] } : { op: 'fx', fx: name, at }) // Schritte der Tabelle (Übersicht §7) für ein Ereignis, noch ungeprüft gegen Roster und Plätze function rawSteps(e) { @@ -271,7 +273,7 @@ function stepIsValid(s) { case 'give': return isWorker(s.from) && isWorker(s.to) case 'fx': - return isPlace(s.at) && (s.to === undefined || isPlace(s.to)) + return isPlace(s.at) && (!Object.hasOwn(s, 'to') || isPlace(s.to)) default: return isWorker(s.who) } diff --git a/tests/unit/workersEvents.test.mjs b/tests/unit/workersEvents.test.mjs index 8e2555e..d999e70 100644 --- a/tests/unit/workersEvents.test.mjs +++ b/tests/unit/workersEvents.test.mjs @@ -219,6 +219,32 @@ test('eventToSteps: jede Art liefert gültige Schritte, Blasen ≤ 20 Zeichen, U assert.deepEqual(E.eventToSteps(ev('uebergabe', 'rita', { to: 'zed' })), [{ op: 'task', who: 'rita', text: 'Text' }]) }) +test('eventToSteps: fehlende oder fremde workerId erzeugt nirgends einen Schritt ohne Ziel (Review Focus 4)', () => { + const ids = new Set(ROLES.map((r) => r.workerId)) + const known = (v) => ids.has(v) || E.SPOTS.includes(v) + // eingang ohne workerId: weder der Umschlag ohne Ziel noch andere Schritte bleiben übrig + assert.deepEqual(E.eventToSteps({ kind: 'eingang', ts: TS, text: 'Text' }), []) + assert.deepEqual(E.eventToSteps({ kind: 'eingang', ts: TS, text: 'Text', workerId: null }), []) + assert.deepEqual(E.eventToSteps(ev('eingang', 'zed')), []) + assert.deepEqual(E.eventToSteps(ev('eingang', 'max')).map((s) => s.op), ['task', 'fx', 'say']) + // jede Art mit fehlender, leerer, falsch getypter und fremder workerId: jeder vorhandene Verweis (auch ein ausdrücklich undefinierter) muss bekannt sein + for (const kind of E.EVENT_KINDS) { + for (const w of [undefined, null, '', 7, {}, 'zed', 'system']) { + const e = { id: `${kind}-x`, ts: TS, kind, text: 'Text', meta: { count: 3, score: 4, round: 2 } } + if (w !== undefined) e.workerId = w + for (const s of E.eventToSteps(e)) { + const label = `${kind}/${String(w)}: ${JSON.stringify(s)}` + if (s.op === 'fx') { + assert.ok(known(s.at), label) + if ('to' in s) assert.ok(known(s.to), label) + } else { + for (const k of ['who', 'from', 'to']) if (k in s) assert.ok(ids.has(s[k]), label) + } + } + } + } +}) + test('eventToSteps: kaputte Ereignisse werfen nicht, Text wird gekürzt statt verworfen', () => { for (const bad of [null, undefined, 7, 'x', [], {}]) assert.deepEqual(E.eventToSteps(bad), []) assert.deepEqual(E.eventToSteps({ kind: 'gelesen', workerId: 'kim' }), [{ op: 'pose', who: 'kim', pose: 'type' }, { op: 'task', who: 'kim', text: '' }, { op: 'say', who: 'kim', text: 'Gelesen' }])