tests: undo roundtrip (10 green) + reorderParticipants BUG-7 candidate

turn.undo.test.js: every op with log.undo roundtrips to prior state.
startEncounter, nextTurn, togglePause, applyHpChange, toggleCondition,
toggleParticipantActive, addParticipant, removeParticipant, endEncounter.

Found: reorderParticipants returns log:null. Cannot undo. Documents as
BUG-7 candidate (test green now, asserts current behavior).
This commit is contained in:
david raistrick
2026-06-30 13:50:48 -04:00
parent bac94d85ff
commit be481767f0
+123
View File
@@ -0,0 +1,123 @@
// Undo roundtrip: every op that returns log.undo must restore prior state.
// Apply op → patch → apply undo → assert deepEqual original.
const shared = require('@ttrpg/shared');
const {
makeParticipant, startEncounter, nextTurn, togglePause,
addParticipant, removeParticipant, toggleParticipantActive,
applyHpChange, toggleCondition, reorderParticipants, endEncounter,
} = shared;
function p(id, init, extra = {}) {
return makeParticipant({
id, name: id, type: 'monster',
initiative: init, maxHp: 100, currentHp: 100,
...extra,
});
}
function enc(ps) {
return { name:'t', participants:ps, isStarted:false, isPaused:false,
round:0, currentTurnParticipantId:null, turnOrderIds:[] };
}
const snap = (e) => JSON.parse(JSON.stringify(e));
describe('undo roundtrip', () => {
test('startEncounter undo restores pre-start', () => {
const before = enc([p('a',10),p('b',20)]);
const r = startEncounter(before);
expect(r.log.undo).toBeTruthy();
const after = { ...before, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(snap(before));
});
test('nextTurn undo restores prior currentTurn/round', () => {
let e = enc([p('a',10),p('b',20),p('c',5)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = nextTurn(e);
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('togglePause undo restores prior paused state', () => {
let e = enc([p('a',10),p('b',20)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = togglePause(e);
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('applyHpChange undo restores prior participants', () => {
let e = enc([p('a',10,{maxHp:100,currentHp:100}),p('b',20)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = applyHpChange(e, 'a', 'damage', 20);
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('toggleCondition undo restores prior participants', () => {
let e = enc([p('a',10),p('b',20)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = toggleCondition(e, 'a', 'stunned');
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('toggleParticipantActive undo restores prior participants + turn order', () => {
let e = enc([p('a',10),p('b',20),p('c',5)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = toggleParticipantActive(e, 'b');
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('addParticipant undo restores prior participants', () => {
let e = enc([p('a',10),p('b',20)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const np = makeParticipant({ id:'z', name:'z', type:'monster', initiative:15, maxHp:50, currentHp:50 });
const r = addParticipant(e, np);
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('removeParticipant undo restores prior participants + turn order', () => {
let e = enc([p('a',10),p('b',20),p('c',5)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = removeParticipant(e, 'b');
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('endEncounter undo restores prior state', () => {
let e = enc([p('a',10),p('b',20)]);
e = { ...e, ...startEncounter(e).patch };
const before = snap(e);
const r = endEncounter(e);
expect(r.log.undo).toBeTruthy();
const after = { ...e, ...r.patch, ...r.log.undo };
expect(snap(after)).toEqual(before);
});
test('reorderParticipants has no undo (log: null) — BUG candidate', () => {
const ps = [p('a',10),p('b',20),p('c',20)]; // b,c tie
let e = enc(ps);
e = { ...e, ...startEncounter(e).patch };
const r = reorderParticipants(e, 'c', 'b');
// Documents: reorderParticipants returns log: null. Cannot undo.
// If undo expected here, this is BUG-7.
expect(r.log).toBeNull();
});
});