refactor: complete error handling standardization (issue #101)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m26s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m26s
Prestige Store: - Convert doPrestige() to return Result<void> with specific error codes (INVALID_PRESTIGE_ID, PRESTIGE_MAX_LEVEL, INSUFFICIENT_INSIGHT) - Convert startPactRitual() to return Result<void> with specific error codes (GUARDIAN_NOT_DEFEATED, PACT_ALREADY_SIGNED, PACT_SLOTS_FULL, INSUFFICIENT_MANA, RITUAL_IN_PROGRESS) Combat Actions: - Add try/catch wrapper inside processCombatTick with safe fallback defaults - Add makeDefaultCombatTickResult helper for error recovery LocalStorage Error Handling: - Create safe-persist.ts utility wrapping localStorage with error handling (corrupted JSON, quota exceeded, unexpected failures) - Update all 8 Zustand stores to use createSafeStorage() in persist middleware UI Updates: - Update GuardianPactsTab to use Result pattern for ritual error messages Tests: - Update store-actions-combat-prestige.test.ts for Result return types - Update store-actions.test.ts ManaStore tests for Result pattern - Remove duplicate Prestige/Discipline sections from store-actions.test.ts - All files under 400 line limit 601 tests pass (3 pre-existing failures in spire-utils.test.ts)
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useCombatStore } from '../stores/combatStore';
|
||||
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
import { ErrorCode } from '../utils/result';
|
||||
|
||||
function resetCombatStore() {
|
||||
useCombatStore.setState({
|
||||
@@ -188,7 +189,7 @@ describe('PrestigeStore', () => {
|
||||
describe('doPrestige', () => {
|
||||
it('should purchase upgrade when affordable', () => {
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades.manaWell).toBe(1);
|
||||
expect(usePrestigeStore.getState().insight).toBeLessThan(500);
|
||||
});
|
||||
@@ -196,18 +197,25 @@ describe('PrestigeStore', () => {
|
||||
it('should return false when cannot afford', () => {
|
||||
usePrestigeStore.setState({ insight: 0 });
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_INSIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false for invalid upgrade id', () => {
|
||||
it('should fail for invalid upgrade id', () => {
|
||||
const result = usePrestigeStore.getState().doPrestige('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INVALID_PRESTIGE_ID);
|
||||
}
|
||||
});
|
||||
|
||||
it('should increase memorySlots with deepMemory', () => {
|
||||
usePrestigeStore.setState({ insight: 2000 });
|
||||
const before = usePrestigeStore.getState().memorySlots;
|
||||
usePrestigeStore.getState().doPrestige('deepMemory');
|
||||
const deepResult = usePrestigeStore.getState().doPrestige('deepMemory');
|
||||
expect(deepResult.success).toBe(true);
|
||||
expect(usePrestigeStore.getState().memorySlots).toBe(before + 1);
|
||||
});
|
||||
});
|
||||
@@ -280,31 +288,43 @@ describe('PrestigeStore', () => {
|
||||
it('should start ritual when conditions met', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [], insight: 10000 });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBe(10);
|
||||
});
|
||||
|
||||
it('should return false when guardian not defeated', () => {
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.GUARDIAN_NOT_DEFEATED);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when already signed', () => {
|
||||
it('should fail when already signed', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [10] });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.PACT_ALREADY_SIGNED);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when pact slots full', () => {
|
||||
it('should fail when pact slots full', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [20], pactSlots: 1 });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.PACT_SLOTS_FULL);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when insufficient mana', () => {
|
||||
it('should fail when insufficient mana', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [] });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 0);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useManaStore, makeInitialElements } from '../stores/manaStore';
|
||||
import { MANA_PER_ELEMENT } from '../constants';
|
||||
import { ErrorCode } from '../utils/result';
|
||||
|
||||
function resetManaStore() {
|
||||
useManaStore.setState({
|
||||
@@ -74,47 +75,65 @@ describe('ManaStore', () => {
|
||||
it('should convert raw mana to element mana', () => {
|
||||
useManaStore.setState({ rawMana: 500 });
|
||||
const result = useManaStore.getState().convertMana('transference', 2);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.converted).toBe(2);
|
||||
}
|
||||
expect(useManaStore.getState().rawMana).toBe(500 - 2 * MANA_PER_ELEMENT);
|
||||
expect(useManaStore.getState().elements.transference.current).toBe(2);
|
||||
});
|
||||
|
||||
it('should return false for locked element', () => {
|
||||
it('should fail for locked element', () => {
|
||||
const result = useManaStore.getState().convertMana('fire', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.ELEMENT_NOT_UNLOCKED);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when insufficient raw mana', () => {
|
||||
it('should fail when insufficient raw mana', () => {
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
const result = useManaStore.getState().convertMana('transference', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when element is at max', () => {
|
||||
it('should fail when element is at max', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
elements.transference.current = elements.transference.max;
|
||||
useManaStore.setState({ elements });
|
||||
const result = useManaStore.getState().convertMana('transference', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.ELEMENT_MAX_CAPACITY);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockElement', () => {
|
||||
it('should unlock element and deduct cost', () => {
|
||||
const result = useManaStore.getState().unlockElement('fire', 50);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().rawMana).toBe(50);
|
||||
expect(useManaStore.getState().elements.fire.unlocked).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when already unlocked', () => {
|
||||
it('should fail when already unlocked', () => {
|
||||
const result = useManaStore.getState().unlockElement('transference', 0);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INVALID_INPUT);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when insufficient mana', () => {
|
||||
it('should fail when insufficient mana', () => {
|
||||
const result = useManaStore.getState().unlockElement('fire', 200);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,13 +151,16 @@ describe('ManaStore', () => {
|
||||
it('should deduct element mana when sufficient', () => {
|
||||
useManaStore.getState().addElementMana('transference', 20, 50);
|
||||
const result = useManaStore.getState().spendElementMana('transference', 10);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().elements.transference.current).toBe(10);
|
||||
});
|
||||
|
||||
it('should return false when insufficient element mana', () => {
|
||||
it('should fail when insufficient element mana', () => {
|
||||
const result = useManaStore.getState().spendElementMana('transference', 10);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -149,16 +171,19 @@ describe('ManaStore', () => {
|
||||
useManaStore.getState().addElementMana('fire', 5, 50);
|
||||
useManaStore.getState().addElementMana('earth', 5, 50);
|
||||
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().elements.fire.current).toBe(4);
|
||||
expect(useManaStore.getState().elements.earth.current).toBe(4);
|
||||
expect(useManaStore.getState().elements.metal.current).toBe(1);
|
||||
expect(useManaStore.getState().elements.metal.unlocked).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when missing ingredients', () => {
|
||||
it('should fail when missing ingredients', () => {
|
||||
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useManaStore, makeInitialElements } from '../stores/manaStore';
|
||||
import { useCombatStore } from '../stores/combatStore';
|
||||
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||
import { useDisciplineStore } from '../stores/discipline-slice';
|
||||
import { MANA_PER_ELEMENT } from '../constants';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
import { ErrorCode } from '../utils/result';
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -44,37 +43,6 @@ function resetCombatStore() {
|
||||
});
|
||||
}
|
||||
|
||||
function resetPrestigeStore() {
|
||||
usePrestigeStore.setState({
|
||||
loopCount: 0,
|
||||
insight: 500,
|
||||
totalInsight: 500,
|
||||
loopInsight: 0,
|
||||
prestigeUpgrades: {},
|
||||
memorySlots: 3,
|
||||
pactSlots: 1,
|
||||
memories: [],
|
||||
defeatedGuardians: [],
|
||||
signedPacts: [],
|
||||
signedPactDetails: {},
|
||||
pactRitualFloor: null,
|
||||
pactRitualProgress: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function resetDisciplineStore() {
|
||||
useDisciplineStore.setState({
|
||||
disciplines: {},
|
||||
activeIds: [],
|
||||
concurrentLimit: 1,
|
||||
totalXP: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// MANA STORE
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
describe('ManaStore', () => {
|
||||
beforeEach(resetManaStore);
|
||||
|
||||
@@ -138,47 +106,65 @@ describe('ManaStore', () => {
|
||||
it('should convert raw mana to element mana', () => {
|
||||
useManaStore.setState({ rawMana: 500 });
|
||||
const result = useManaStore.getState().convertMana('transference', 2);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.converted).toBe(2);
|
||||
}
|
||||
expect(useManaStore.getState().rawMana).toBe(500 - 2 * MANA_PER_ELEMENT);
|
||||
expect(useManaStore.getState().elements.transference.current).toBe(2);
|
||||
});
|
||||
|
||||
it('should return false for locked element', () => {
|
||||
it('should fail for locked element', () => {
|
||||
const result = useManaStore.getState().convertMana('fire', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.ELEMENT_NOT_UNLOCKED);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when insufficient raw mana', () => {
|
||||
it('should fail when insufficient raw mana', () => {
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
const result = useManaStore.getState().convertMana('transference', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when element is at max', () => {
|
||||
it('should fail when element is at max', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
elements.transference.current = elements.transference.max;
|
||||
useManaStore.setState({ elements });
|
||||
const result = useManaStore.getState().convertMana('transference', 1);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.ELEMENT_MAX_CAPACITY);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockElement', () => {
|
||||
it('should unlock element and deduct cost', () => {
|
||||
const result = useManaStore.getState().unlockElement('fire', 50);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().rawMana).toBe(50);
|
||||
expect(useManaStore.getState().elements.fire.unlocked).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when already unlocked', () => {
|
||||
it('should fail when already unlocked', () => {
|
||||
const result = useManaStore.getState().unlockElement('transference', 0);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INVALID_INPUT);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false when insufficient mana', () => {
|
||||
it('should fail when insufficient mana', () => {
|
||||
const result = useManaStore.getState().unlockElement('fire', 200);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,13 +182,16 @@ describe('ManaStore', () => {
|
||||
it('should deduct element mana when sufficient', () => {
|
||||
useManaStore.getState().addElementMana('transference', 20, 50);
|
||||
const result = useManaStore.getState().spendElementMana('transference', 10);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().elements.transference.current).toBe(10);
|
||||
});
|
||||
|
||||
it('should return false when insufficient element mana', () => {
|
||||
it('should fail when insufficient element mana', () => {
|
||||
const result = useManaStore.getState().spendElementMana('transference', 10);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -213,16 +202,19 @@ describe('ManaStore', () => {
|
||||
useManaStore.getState().addElementMana('fire', 5, 50);
|
||||
useManaStore.getState().addElementMana('earth', 5, 50);
|
||||
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
|
||||
expect(result).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
expect(useManaStore.getState().elements.fire.current).toBe(4);
|
||||
expect(useManaStore.getState().elements.earth.current).toBe(4);
|
||||
expect(useManaStore.getState().elements.metal.current).toBe(1);
|
||||
expect(useManaStore.getState().elements.metal.unlocked).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when missing ingredients', () => {
|
||||
it('should fail when missing ingredients', () => {
|
||||
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
|
||||
expect(result).toBe(false);
|
||||
expect(result.success).toBe(false);
|
||||
if (!result.success) {
|
||||
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -402,273 +394,3 @@ describe('CombatStore', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// PRESTIGE STORE
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
describe('PrestigeStore', () => {
|
||||
beforeEach(resetPrestigeStore);
|
||||
|
||||
describe('doPrestige', () => {
|
||||
it('should purchase upgrade when affordable', () => {
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
expect(result).toBe(true);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades.manaWell).toBe(1);
|
||||
expect(usePrestigeStore.getState().insight).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it('should return false when cannot afford', () => {
|
||||
usePrestigeStore.setState({ insight: 0 });
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for invalid upgrade id', () => {
|
||||
const result = usePrestigeStore.getState().doPrestige('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should increase memorySlots with deepMemory', () => {
|
||||
usePrestigeStore.setState({ insight: 2000 });
|
||||
const before = usePrestigeStore.getState().memorySlots;
|
||||
usePrestigeStore.getState().doPrestige('deepMemory');
|
||||
expect(usePrestigeStore.getState().memorySlots).toBe(before + 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addMemory / removeMemory', () => {
|
||||
it('should add a memory when slots available', () => {
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3 });
|
||||
expect(usePrestigeStore.getState().memories.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not add duplicate memory', () => {
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3 });
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 5 });
|
||||
expect(usePrestigeStore.getState().memories.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not exceed memory slots', () => {
|
||||
usePrestigeStore.setState({ memorySlots: 1 });
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 1 });
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaSpring', level: 1 });
|
||||
expect(usePrestigeStore.getState().memories.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should remove memory by skillId', () => {
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3 });
|
||||
usePrestigeStore.getState().removeMemory('manaFlow');
|
||||
expect(usePrestigeStore.getState().memories.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should clear all memories', () => {
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 1 });
|
||||
usePrestigeStore.getState().addMemory({ skillId: 'manaSpring', level: 1 });
|
||||
usePrestigeStore.getState().clearMemories();
|
||||
expect(usePrestigeStore.getState().memories.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defeatGuardian / signedPacts', () => {
|
||||
it('should add defeated guardian', () => {
|
||||
usePrestigeStore.getState().defeatGuardian(10);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toContain(10);
|
||||
});
|
||||
|
||||
it('should not duplicate defeated guardian', () => {
|
||||
usePrestigeStore.getState().defeatGuardian(10);
|
||||
usePrestigeStore.getState().defeatGuardian(10);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians.filter(f => f === 10).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not defeat already signed guardian', () => {
|
||||
usePrestigeStore.setState({ signedPacts: [10] });
|
||||
usePrestigeStore.getState().defeatGuardian(10);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).not.toContain(10);
|
||||
});
|
||||
|
||||
it('should add signed pact', () => {
|
||||
usePrestigeStore.getState().addSignedPact(10);
|
||||
expect(usePrestigeStore.getState().signedPacts).toContain(10);
|
||||
});
|
||||
|
||||
it('should remove pact', () => {
|
||||
usePrestigeStore.setState({ signedPacts: [10, 20] });
|
||||
usePrestigeStore.getState().removePact(10);
|
||||
expect(usePrestigeStore.getState().signedPacts).not.toContain(10);
|
||||
expect(usePrestigeStore.getState().signedPacts).toContain(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('startPactRitual', () => {
|
||||
it('should start ritual when conditions met', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [], insight: 10000 });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(true);
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBe(10);
|
||||
});
|
||||
|
||||
it('should return false when guardian not defeated', () => {
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when already signed', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [10] });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when pact slots full', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [20], pactSlots: 1 });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when insufficient mana', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [] });
|
||||
const result = usePrestigeStore.getState().startPactRitual(10, 0);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelPactRitual', () => {
|
||||
it('should cancel active ritual', () => {
|
||||
usePrestigeStore.setState({ pactRitualFloor: 10, pactRitualProgress: 1 });
|
||||
usePrestigeStore.getState().cancelPactRitual();
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBeNull();
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('startNewLoop', () => {
|
||||
it('should increment loop count and add insight', () => {
|
||||
usePrestigeStore.setState({ insight: 100, totalInsight: 100 });
|
||||
usePrestigeStore.getState().startNewLoop(50);
|
||||
expect(usePrestigeStore.getState().loopCount).toBe(1);
|
||||
expect(usePrestigeStore.getState().insight).toBe(150);
|
||||
expect(usePrestigeStore.getState().totalInsight).toBe(150);
|
||||
});
|
||||
|
||||
it('should reset loop-specific state', () => {
|
||||
usePrestigeStore.setState({
|
||||
defeatedGuardians: [10],
|
||||
signedPacts: [20],
|
||||
pactRitualFloor: 10,
|
||||
pactRitualProgress: 5,
|
||||
});
|
||||
usePrestigeStore.getState().startNewLoop(0);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
||||
expect(usePrestigeStore.getState().signedPacts).toEqual([]);
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPrestigeForNewLoop', () => {
|
||||
it('should preserve insight and upgrades, reset loop state', () => {
|
||||
usePrestigeStore.getState().resetPrestigeForNewLoop(200, { manaWell: 2 }, [], 4);
|
||||
expect(usePrestigeStore.getState().insight).toBe(200);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({ manaWell: 2 });
|
||||
expect(usePrestigeStore.getState().memorySlots).toBe(4);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPrestige', () => {
|
||||
it('should reset everything to initial state', () => {
|
||||
usePrestigeStore.setState({ insight: 1000, loopCount: 5, prestigeUpgrades: { manaWell: 3 } });
|
||||
usePrestigeStore.getState().resetPrestige();
|
||||
expect(usePrestigeStore.getState().insight).toBe(0);
|
||||
expect(usePrestigeStore.getState().loopCount).toBe(0);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// DISCIPLINE STORE
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
describe('DisciplineStore', () => {
|
||||
beforeEach(resetDisciplineStore);
|
||||
|
||||
describe('activate', () => {
|
||||
it('should activate raw discipline', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
|
||||
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(false);
|
||||
});
|
||||
|
||||
it('should not activate same discipline twice', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
expect(useDisciplineStore.getState().activeIds.filter(id => id === 'raw-mastery').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not activate when concurrent limit reached', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().activate('elemental-attunement');
|
||||
expect(useDisciplineStore.getState().activeIds.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should activate when no prior discipline state (optimistic)', () => {
|
||||
useDisciplineStore.getState().activate('elemental-attunement', {
|
||||
elements: { fire: { unlocked: false } },
|
||||
});
|
||||
expect(useDisciplineStore.getState().activeIds).toContain('elemental-attunement');
|
||||
});
|
||||
|
||||
it('should not activate when existing state has insufficient mana', () => {
|
||||
useDisciplineStore.setState({
|
||||
disciplines: { 'raw-mastery': { id: 'raw-mastery', xp: 1000, paused: false } },
|
||||
});
|
||||
useDisciplineStore.getState().activate('raw-mastery', { elements: {} });
|
||||
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
|
||||
});
|
||||
|
||||
it('should activate when required element is unlocked', () => {
|
||||
useDisciplineStore.getState().activate('elemental-attunement', {
|
||||
elements: { fire: { unlocked: true } },
|
||||
});
|
||||
expect(useDisciplineStore.getState().activeIds).toContain('elemental-attunement');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deactivate', () => {
|
||||
it('should remove discipline from active list', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().deactivate('raw-mastery');
|
||||
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
|
||||
});
|
||||
});
|
||||
|
||||
describe('processTick', () => {
|
||||
it('should accrue XP for active discipline', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(1);
|
||||
expect(useDisciplineStore.getState().totalXP).toBe(1);
|
||||
});
|
||||
|
||||
it('should drain raw mana for raw discipline', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
const result = useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||
expect(result.rawMana).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
it('should pause discipline when insufficient mana', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().processTick({ rawMana: 0, elements: {} });
|
||||
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
|
||||
});
|
||||
|
||||
it('should increase concurrent limit at 500 total XP', () => {
|
||||
useDisciplineStore.setState({ totalXP: 499 });
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
|
||||
expect(useDisciplineStore.getState().totalXP).toBe(500);
|
||||
expect(useDisciplineStore.getState().concurrentLimit).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user