# Test Rewrite Spec — async turn.js writes own logs ## New turn.js API ALL mutating funcs now `async`, take `ctx` last param, write encounter + log internally, return `newEnc`. ```js // OLD const r = nextTurn(enc); enc = { ...enc, ...r.patch }; // caller had to write log separately // NEW enc = await nextTurn(enc, ctx); // encounter + log already written inside func ``` ### Func signatures (all async unless noted) - `startEncounter(enc, ctx)` → newEnc - `nextTurn(enc, ctx)` → newEnc - `togglePause(enc, ctx)` → newEnc - `addParticipant(enc, participant, ctx)` → newEnc - `addParticipants(enc, newParticipants[], ctx)` → newEnc - `updateParticipant(enc, id, updatedData, ctx)` → newEnc - `removeParticipant(enc, id, ctx)` → newEnc - `toggleParticipantActive(enc, id, ctx)` → newEnc - `applyHpChange(enc, id, changeType, amount, ctx)` → newEnc (no-op = returns enc unchanged, no write) - `deathSave(enc, id, type, n, ctx)` → `{ enc, status, isDying }` (object now!) - `toggleCondition(enc, id, conditionId, ctx)` → newEnc - `reorderParticipants(enc, draggedId, targetId, ctx)` → newEnc (no-op = returns enc, no write) - `endEncounter(enc, ctx)` → newEnc ### Pure helpers (sync, unchanged) - `makeParticipant`, `buildCharacterParticipant`, `buildMonsterParticipant` - `generateId`, `rollD20`, `formatInitMod` - `sortParticipantsByInitiative`, `syncTurnOrder`, `computeTurnOrderAfterRemoval` - `snapshotOf`, `buildEntry` ### Display lifecycle (async, no combat log) - `activateDisplay({campaignId, encounterId}, ctx)` → void - `clearDisplay(ctx)` → void - `toggleHidePlayerHp(currentValue, ctx)` → void ## Test helper: shared/tests/_helpers.js ```js const { createMockStorage, mockCtx, readyEnc } = require('./_helpers'); const { storage, ctx } = mockCtx(); // ctx = {storage, encPath:'encounters/e1', logPath:'logs', displayPath:'activeDisplay/status'} const enc = readyEnc(); // {id:'e1', name:'TestEnc', round:0, ... 2 participants p1(Bob,init10) p2(Mob,init5)} ``` ### Mock storage methods - `storage.calls` — array of `{fn, path, data}` ordered - `storage.logs()` — array of log entries written (addDoc data) - `storage.updatesFor(prefix)` — updateDoc calls matching path prefix - `storage.docs` — Map path→data ## Transform patterns ### Pattern 1: state assertion ```js // OLD const r = startEncounter(enc); expect(r.patch.round).toBe(1); // NEW const newEnc = await startEncounter(enc, ctx); expect(newEnc.round).toBe(1); ``` ### Pattern 2: log assertion ```js // OLD const r = startEncounter(enc); expect(r.log.type).toBe('start_encounter'); // NEW const newEnc = await startEncounter(enc, ctx); expect(storage.logs()).toHaveLength(1); expect(storage.logs()[0].type).toBe('start_encounter'); ``` ### Pattern 3: chained calls (sequence) ```js // OLD let enc = readyEnc(); enc = { ...enc, ...startEncounter(enc).patch }; enc = { ...enc, ...nextTurn(enc).patch }; // NEW let enc = readyEnc(); enc = await startEncounter(enc, ctx); enc = await nextTurn(enc, ctx); ``` ### Pattern 4: no-op ```js // OLD (returned {patch:null, log:null}) const r = applyHpChange(enc, 'p1', 'damage', 0); expect(r.patch).toBeNull(); // NEW (returns enc unchanged, no write) const newEnc = await applyHpChange(enc, 'p1', 'damage', 0, ctx); expect(newEnc).toBe(enc); // same ref = no write expect(storage.calls.filter(c => c.fn === 'updateDoc')).toHaveLength(0); ``` ### Pattern 5: deathSave (returns object now) ```js // OLD const r = deathSave(enc, 'p1', 'success', 1); expect(r.status).toBe('pending'); // NEW const { enc: newEnc, status, isDying } = await deathSave(enc, 'p1', 'success', 1, ctx); expect(status).toBe('pending'); ``` ## Static guards - `static.no-sort.test.js` — KEEP. Still valid (scans turn.js for stray `.sort(`). - `static.no-unlogged.test.js` — DELETE. Old `{patch, log}` shape gone. Every func writes log internally via `commit()`. No way to "forget". - `static.eslint.test.js` — KEEP. ## Constraints - KEEP test names (describe/it blocks) — same coverage intent. - KEEP assertions — same values checked, just via newEnc or storage.logs(). - Fresh `mockCtx()` per test (isolation). - For seeded-RNG combat tests: keep the LCG seed + rand helpers, just swap call pattern. - `await` every mutating func call.