2026-07-06 10:31:46 -04:00
|
|
|
// Test helper: in-memory mock storage. Records all writes (addDoc/updateDoc).
|
|
|
|
|
// Captures encounter patches + log entries so tests assert persistence.
|
|
|
|
|
function createMockStorage() {
|
|
|
|
|
const docs = new Map(); // path -> data
|
|
|
|
|
const calls = []; // ordered {fn, path, data}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
calls,
|
|
|
|
|
docs,
|
|
|
|
|
async getDoc(path) { return docs.has(path) ? { ...docs.get(path) } : null; },
|
|
|
|
|
async setDoc(path, data) {
|
|
|
|
|
calls.push({ fn: 'setDoc', path, data });
|
|
|
|
|
docs.set(path, { ...data });
|
|
|
|
|
},
|
|
|
|
|
async addDoc(path, data) {
|
|
|
|
|
calls.push({ fn: 'addDoc', path, data });
|
|
|
|
|
},
|
|
|
|
|
async updateDoc(path, patch) {
|
|
|
|
|
calls.push({ fn: 'updateDoc', path, data: patch });
|
|
|
|
|
if (!docs.has(path)) docs.set(path, {});
|
|
|
|
|
docs.set(path, { ...docs.get(path), ...patch });
|
|
|
|
|
},
|
|
|
|
|
async deleteDoc(path) {
|
|
|
|
|
calls.push({ fn: 'deleteDoc', path });
|
|
|
|
|
docs.delete(path);
|
|
|
|
|
},
|
|
|
|
|
async getCollection() { return []; },
|
|
|
|
|
async batchWrite(ops) {
|
|
|
|
|
for (const op of ops) {
|
|
|
|
|
if (op.type === 'delete') await this.deleteDoc(op.path);
|
|
|
|
|
else await this.updateDoc(op.path, op.data);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async undo() {},
|
|
|
|
|
// filters
|
|
|
|
|
updatesFor(prefix) { return calls.filter(c => c.fn === 'updateDoc' && c.path.startsWith(prefix)); },
|
|
|
|
|
logs() { return calls.filter(c => c.fn === 'addDoc').map(c => c.data); },
|
2026-07-06 16:39:11 -04:00
|
|
|
getLogs() { return calls.filter(c => c.fn === 'addDoc').map(c => c.data); },
|
2026-07-06 10:31:46 -04:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard ctx for tests. encPath + logPath + displayPath preset.
|
|
|
|
|
// Returns { storage, ctx } so tests destructure both in one call:
|
|
|
|
|
// const { storage, ctx } = mockCtx();
|
|
|
|
|
function mockCtx(storage) {
|
|
|
|
|
storage = storage || createMockStorage();
|
|
|
|
|
const ctx = {
|
|
|
|
|
storage,
|
|
|
|
|
encPath: 'encounters/e1',
|
|
|
|
|
logPath: 'logs',
|
|
|
|
|
displayPath: 'activeDisplay/status',
|
|
|
|
|
};
|
|
|
|
|
return { storage, ctx };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard 2-participant ready encounter.
|
|
|
|
|
function readyEnc(opts = {}) {
|
|
|
|
|
return {
|
|
|
|
|
id: 'e1',
|
|
|
|
|
name: opts.name || 'TestEnc',
|
|
|
|
|
round: 0,
|
|
|
|
|
isStarted: false,
|
|
|
|
|
isPaused: false,
|
|
|
|
|
currentTurnParticipantId: null,
|
|
|
|
|
turnOrderIds: [],
|
|
|
|
|
participants: opts.participants || [
|
2026-07-06 16:39:11 -04:00
|
|
|
{ id: 'p1', name: 'Bob', type: 'character', initiative: 10, maxHp: 10, currentHp: 10, isActive: true, conditions: [], status: 'conscious', deathSaveSuccesses: 0, deathSaveFailures: 0 },
|
|
|
|
|
{ id: 'p2', name: 'Mob', type: 'monster', initiative: 5, maxHp: 10, currentHp: 10, isActive: true, conditions: [], status: 'conscious', deathSaveSuccesses: 0, deathSaveFailures: 0 },
|
2026-07-06 10:31:46 -04:00
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { createMockStorage, mockCtx, readyEnc };
|