const { test } = require('node:test'); const assert = require('node:assert/strict'); const { createWeaponEvidenceRecorder } = require('./weapon-evidence-recorder.cjs'); function fixture() { let state = { combatEvents: [], mobs: {}, auras: [] }; const listeners = new Set(); return { getState: () => state, subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); }, commit(next) { state = next; for (const fn of listeners) fn(state); }, listenerCount: () => listeners.size }; } test('retains source shout parent and child timers without recording unrelated auras', () => { const store = fixture(), recorder = createWeaponEvidenceRecorder(store, { clock: () => 123 }); const aura = (id) => ({ instanceId: id, definition: { id }, sourceId: 'boss', targetId: 'player', appliedAt: 100, expiresAt: 5_100, stacks: 1 }); store.commit({ combatEvents: [], mobs: {}, auras: [ aura('enemy-control:source-spell-5246'), aura('enemy-control:source-spell-20511'), aura('unrelated'), ] }); assert.deepEqual(recorder.stop().auraSamples.at(-1).auras.map((entry) => entry.definitionId), [ 'enemy-control:source-spell-5246', 'enemy-control:source-spell-20511', ]); }); test('retains consecutive results beyond the HUD ring and unsubscribes without changing state', () => { const store = fixture(), recorder = createWeaponEvidenceRecorder(store); let history = []; for (let id = 1; id <= 350; id++) { history = [...history, { id, attackHand: 'offhand', rawAmount: 30 }].slice(-200); store.commit({ combatEvents: history, mobs: {}, auras: [] }); } const before = store.getState(), result = recorder.stop(); assert.equal(store.getState(), before); assert.equal(result.events.length, 350); assert.deepEqual(result.gaps, []); assert.equal(result.events[349].attackHand, 'offhand'); assert.equal(store.listenerCount(), 0); store.commit({ ...before, combatEvents: [...history, { id: 351 }] }); assert.equal(recorder.stop().events.length, 350); }); test('reports dropped IDs and capacity overflow rather than claiming complete evidence', () => { const store = fixture(), recorder = createWeaponEvidenceRecorder(store, { capacity: 2 }); store.commit({ combatEvents: [{ id: 3 }, { id: 4 }, { id: 5 }], mobs: {}, auras: [] }); const result = recorder.stop(); assert.deepEqual(result.gaps, [{ afterId: 0, beforeId: 3 }]); assert.equal(result.discardedEvents, 1); assert.equal(result.events.length, 2); }); test('copies transient weapon queues and party debuffs between checkpoints', () => { const store = fixture(), recorder = createWeaponEvidenceRecorder(store, { clock: () => 123 }); const pending = [{ id: 'thrash', remaining: 2, sourceSpellId: 3391 }]; const state = { combatEvents: [], mobs: { boss: { id: 'boss', name: 'Boss', engaged: true, dualWieldEnabled: true, nextEnemyOffhandAttackAt: 200, pendingExtraAttacks: pending } }, auras: [{ instanceId: 'tank-wound', definition: { id: 'enemy-source-spell-17230', dispelCategory: 'disease' }, targetId: 'tank', sourceId: 'boss', appliedAt: 123, expiresAt: 300123, stacks: 1 }] }; store.commit(state); pending[0].remaining = 0; store.commit({ ...state, mobs: { boss: { ...state.mobs.boss, pendingExtraAttacks: [], nextEnemyOffhandAttackAt: 400 } }, auras: [] }); const result = recorder.stop(); assert.equal(result.mobSamples[0].pendingExtraAttacks[0].remaining, 2); assert.equal(result.mobSamples[1].nextEnemyOffhandAttackAt, 400); assert.equal(result.auraSamples[1].auras[0].targetId, 'tank'); assert.deepEqual(result.auraSamples[2].auras, []); });