// 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(); }); });