Rework backend #1
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user