fix(turn): log reorder + deathSave (BUG-7)
reorderParticipants returned log:null → handler skipped logAction → drag
invisible in combat log, no undo payload. Now returns
log:{ message, undo:{ participants, turnOrderIds, currentTurnParticipantId }}.
App.js handleDrop calls logAction on logged reorders.
Found second gap: deathSave also returned null log (both branches).
Fixed — message + undo (participants snapshot).
Added logging contract test (turn.logging.test.js):
- all mutating ops logged (start/next/pause/add/remove/toggle/hp/deathsave/
condition/reorder/end)
- no-ops return null log (same-id, cross-init, cross-pointer blocks)
- undo payloads valid + restore prior state
- documents addParticipants + updateParticipant gaps (null log, intentional)
235 tests green.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// BUG-7: reorderParticipants not logged.
|
||||
// Every combat op returns { patch, log }. Handler calls logAction(log.message,
|
||||
// ctx, { updates: log.undo }) → writes entry to logs collection.
|
||||
// reorderParticipants returns log: null → handler skips logAction → drag
|
||||
// invisible in combat log + no undo payload (siblings all have one).
|
||||
//
|
||||
// RED: prove log is null (bug). Fix: return { message, undo }.
|
||||
|
||||
'use strict';
|
||||
|
||||
const shared = require('@ttrpg/shared');
|
||||
const { makeParticipant, reorderParticipants } = shared;
|
||||
|
||||
function p(id, init) {
|
||||
return makeParticipant({ id, name: id, type: 'monster',
|
||||
initiative: init, maxHp: 100, currentHp: 100 });
|
||||
}
|
||||
function enc(ps) {
|
||||
return { name:'t', participants:ps, isStarted:false, isPaused:false,
|
||||
round:0, currentTurnParticipantId:null, turnOrderIds:[] };
|
||||
}
|
||||
|
||||
describe('BUG-7: reorderParticipants logged', () => {
|
||||
test('returns log object (not null) with message + undo', () => {
|
||||
const e = enc([p('a', 10), p('b', 10), p('c', 10)]);
|
||||
const r = reorderParticipants(e, 'c', 'b');
|
||||
expect(r.patch).not.toBeNull();
|
||||
expect(r.log).not.toBeNull();
|
||||
expect(typeof r.log.message).toBe('string');
|
||||
expect(r.log.message.length).toBeGreaterThan(0);
|
||||
expect(r.log.undo).toBeDefined();
|
||||
});
|
||||
|
||||
test('undo restores original participants order', () => {
|
||||
const e = enc([p('a', 10), p('b', 10), p('c', 10)]);
|
||||
const orig = e.participants.map(p => p.id);
|
||||
const r = reorderParticipants(e, 'c', 'b');
|
||||
const restored = { ...e, ...r.log.undo };
|
||||
expect(restored.participants.map(p => p.id)).toEqual(orig);
|
||||
});
|
||||
|
||||
test('no-op (same id) returns null log', () => {
|
||||
const e = enc([p('a', 10), p('b', 10)]);
|
||||
const r = reorderParticipants(e, 'a', 'a');
|
||||
expect(r.log).toBeNull();
|
||||
});
|
||||
|
||||
test('no-op (cross-init blocked) returns null log', () => {
|
||||
const e = enc([p('a', 10), p('b', 5)]);
|
||||
const r = reorderParticipants(e, 'a', 'b');
|
||||
expect(r.patch).toBeNull();
|
||||
expect(r.log).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user