2026-07-04 17:22:52 -04:00
|
|
|
// BUG-7: reorderParticipants not logged.
|
2026-07-06 10:31:46 -04:00
|
|
|
// Every mutating func is now async, takes ctx last, and writes encounter +
|
|
|
|
|
// log internally via commit(). reorderParticipants must emit a log entry on a
|
|
|
|
|
// real move (so the drag shows up in the combat log + carries an undo payload)
|
|
|
|
|
// and stay a silent no-op (no write) when blocked.
|
2026-07-04 17:22:52 -04:00
|
|
|
//
|
2026-07-06 10:31:46 -04:00
|
|
|
// RED was: returned { patch, log } with log:null → handler skipped logAction.
|
|
|
|
|
// Now: a successful reorder writes exactly one log entry; no-ops write none.
|
2026-07-04 17:22:52 -04:00
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
2026-07-06 10:31:46 -04:00
|
|
|
const { makeParticipant, reorderParticipants, expandUndo } = require('@ttrpg/shared');
|
|
|
|
|
const { mockCtx } = require('./_helpers');
|
2026-07-04 17:22:52 -04:00
|
|
|
|
|
|
|
|
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', () => {
|
2026-07-06 10:31:46 -04:00
|
|
|
test('returns log object (not null) with message + undo', async () => {
|
|
|
|
|
const { storage, ctx } = mockCtx();
|
2026-07-04 17:22:52 -04:00
|
|
|
const e = enc([p('a', 10), p('b', 10), p('c', 10)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
await reorderParticipants(e, 'c', 'b', ctx);
|
|
|
|
|
expect(storage.logs()).toHaveLength(1);
|
|
|
|
|
const entry = storage.logs()[0];
|
|
|
|
|
expect(typeof entry.message).toBe('string');
|
|
|
|
|
expect(entry.message.length).toBeGreaterThan(0);
|
|
|
|
|
expect(entry.undo).toBeDefined();
|
2026-07-04 17:22:52 -04:00
|
|
|
});
|
|
|
|
|
|
2026-07-06 10:31:46 -04:00
|
|
|
test('undo restores original participants order', async () => {
|
|
|
|
|
const { storage, ctx } = mockCtx();
|
2026-07-04 17:22:52 -04:00
|
|
|
const e = enc([p('a', 10), p('b', 10), p('c', 10)]);
|
|
|
|
|
const orig = e.participants.map(p => p.id);
|
2026-07-06 10:31:46 -04:00
|
|
|
await reorderParticipants(e, 'c', 'b', ctx);
|
|
|
|
|
const entry = storage.logs()[0];
|
|
|
|
|
const restored = { ...e, ...expandUndo(entry, e).updates };
|
2026-07-04 17:22:52 -04:00
|
|
|
expect(restored.participants.map(p => p.id)).toEqual(orig);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-06 10:31:46 -04:00
|
|
|
test('no-op (same id) returns null log', async () => {
|
|
|
|
|
const { storage, ctx } = mockCtx();
|
2026-07-04 17:22:52 -04:00
|
|
|
const e = enc([p('a', 10), p('b', 10)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
const newEnc = await reorderParticipants(e, 'a', 'a', ctx);
|
|
|
|
|
expect(newEnc).toBe(e); // same ref = no write
|
|
|
|
|
expect(storage.logs()).toHaveLength(0);
|
2026-07-04 17:22:52 -04:00
|
|
|
});
|
|
|
|
|
|
2026-07-06 10:31:46 -04:00
|
|
|
test('no-op (cross-init blocked) returns null log', async () => {
|
|
|
|
|
const { storage, ctx } = mockCtx();
|
2026-07-04 17:22:52 -04:00
|
|
|
const e = enc([p('a', 10), p('b', 5)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
const newEnc = await reorderParticipants(e, 'a', 'b', ctx);
|
|
|
|
|
expect(newEnc).toBe(e); // same ref = no write
|
|
|
|
|
expect(storage.logs()).toHaveLength(0);
|
2026-07-04 17:22:52 -04:00
|
|
|
});
|
|
|
|
|
});
|