Files
DndGamePlayer/app/main/initiative/initiativeSessionStore.test.ts
T
Ivan Fontosh 86d6d157f0 feat(control): трекер инициативы в отдельном окне
Очередь NPC/игроков с DnD, курсором на сцене, состояниями и запасом; сброс при смене сцены.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 10:09:51 +08:00

106 lines
3.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { InitiativeSessionStore } from './initiativeSessionStore';
import {
initiativeNpcEntryId,
initiativePlayerEntryId,
} from '../../shared/types/initiative';
void test('initiative: addManual + next cycles order', () => {
const store = new InitiativeSessionStore();
store.dispatch({ kind: 'addManual', name: 'A' });
store.dispatch({ kind: 'addManual', name: 'B' });
const a = store.getState().order[0]!;
const b = store.getState().order[1]!;
assert.equal(store.getState().activeId, a);
store.dispatch({ kind: 'next' });
assert.equal(store.getState().activeId, b);
store.dispatch({ kind: 'next' });
assert.equal(store.getState().activeId, a);
});
void test('initiative: syncPresence adds npcs and players', () => {
const store = new InitiativeSessionStore();
store.dispatch({
kind: 'syncPresence',
npcs: [{ placementId: 'p1', npcId: 'n1', name: 'Goblin', avatarAssetId: null }],
players: [{ playerId: 'pl1', name: 'Hero' }],
playersVisible: true,
});
const st = store.getState();
assert.ok(st.entries[initiativeNpcEntryId('p1')]);
assert.ok(st.entries[initiativePlayerEntryId('pl1')]);
assert.deepEqual(st.order, [initiativeNpcEntryId('p1'), initiativePlayerEntryId('pl1')]);
});
void test('initiative: hide players moves them to reserve', () => {
const store = new InitiativeSessionStore();
store.dispatch({
kind: 'syncPresence',
npcs: [],
players: [{ playerId: 'pl1', name: 'Hero' }],
playersVisible: true,
});
const pid = initiativePlayerEntryId('pl1');
assert.ok(store.getState().order.includes(pid));
store.dispatch({
kind: 'syncPresence',
npcs: [],
players: [{ playerId: 'pl1', name: 'Hero' }],
playersVisible: false,
});
assert.ok(!store.getState().order.includes(pid));
assert.ok(store.getState().reserve.includes(pid));
store.dispatch({
kind: 'syncPresence',
npcs: [],
players: [{ playerId: 'pl1', name: 'Hero' }],
playersVisible: true,
});
assert.ok(store.getState().reserve.includes(pid));
assert.ok(!store.getState().order.includes(pid));
});
void test('initiative: conditions unique + reserve/restore', () => {
const store = new InitiativeSessionStore();
store.dispatch({ kind: 'addManual', name: 'X' });
const id = store.getState().order[0]!;
store.dispatch({ kind: 'addCondition', id, conditionId: 'poisoned' });
store.dispatch({ kind: 'addCondition', id, conditionId: 'poisoned' });
assert.deepEqual(store.getState().conditions[id], ['poisoned']);
store.dispatch({ kind: 'moveToReserve', id });
assert.deepEqual(store.getState().order, []);
assert.deepEqual(store.getState().reserve, [id]);
store.dispatch({ kind: 'restoreFromReserve', id });
assert.deepEqual(store.getState().order, [id]);
assert.deepEqual(store.getState().conditions[id], ['poisoned']);
});
void test('initiative: clear keeps enabled', () => {
const store = new InitiativeSessionStore();
store.dispatch({ kind: 'setEnabled', enabled: true });
store.dispatch({ kind: 'addManual', name: 'Z' });
store.clear();
assert.equal(store.getState().enabled, true);
assert.deepEqual(store.getState().order, []);
});
void test('initiative: despawn removes npc entry', () => {
const store = new InitiativeSessionStore();
store.dispatch({
kind: 'syncPresence',
npcs: [{ placementId: 'p1', npcId: 'n1', name: 'Goblin', avatarAssetId: null }],
players: [],
playersVisible: false,
});
store.dispatch({
kind: 'syncPresence',
npcs: [],
players: [],
playersVisible: false,
});
assert.deepEqual(store.getState().order, []);
assert.deepEqual(Object.keys(store.getState().entries), []);
});