e0e7beb495
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
- Remove debugSetFloor and resetFloorHP actions from combatStore.ts - Remove their type definitions from combat-state.types.ts - Remove Skip to Floor 100 and Reset Floor HP buttons from GameStateDebugSection.tsx - Remove same buttons from legacy GameStateDebug.tsx - Remove floor quick-jump and Reset Floor HP from SpireDebugSection.tsx - Remove associated tests from DebugTab.test.ts, store-actions.test.ts, store-actions-combat-prestige.test.ts - Add missing src/test/setup.ts required by vitest config These debug buttons created inconsistent game states by teleporting players to floors without proper initialization (no spireMode, no room state, no clearedFloors, no guardian encounters). resetFloorHP could be spammed to infinitely retry any floor for free.
328 lines
12 KiB
TypeScript
328 lines
12 KiB
TypeScript
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({
|
|
currentFloor: 1,
|
|
floorHP: getFloorMaxHP(1),
|
|
floorMaxHP: getFloorMaxHP(1),
|
|
maxFloorReached: 1,
|
|
activeSpell: 'manaBolt',
|
|
currentAction: 'meditate',
|
|
castProgress: 0,
|
|
spireMode: false,
|
|
currentRoom: { roomType: 'combat', enemies: [] },
|
|
clearedFloors: {},
|
|
climbDirection: null,
|
|
isDescending: false,
|
|
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
|
|
equipmentSpellStates: [],
|
|
comboHitCount: 0,
|
|
floorHitCount: 0,
|
|
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
|
|
activityLog: [],
|
|
achievements: { unlocked: [], progress: {} },
|
|
totalSpellsCast: 0,
|
|
totalDamageDealt: 0,
|
|
totalCraftsCompleted: 0,
|
|
});
|
|
}
|
|
|
|
function resetPrestigeStore() {
|
|
usePrestigeStore.setState({
|
|
loopCount: 0,
|
|
insight: 500,
|
|
totalInsight: 500,
|
|
loopInsight: 0,
|
|
prestigeUpgrades: {},
|
|
pactSlots: 1,
|
|
defeatedGuardians: [],
|
|
signedPacts: [],
|
|
signedPactDetails: {},
|
|
pactRitualFloor: null,
|
|
pactRitualProgress: 0,
|
|
});
|
|
}
|
|
|
|
describe('CombatStore', () => {
|
|
beforeEach(resetCombatStore);
|
|
|
|
describe('setCurrentFloor', () => {
|
|
it('should set floor and update HP', () => {
|
|
useCombatStore.getState().setCurrentFloor(5);
|
|
expect(useCombatStore.getState().currentFloor).toBe(5);
|
|
expect(useCombatStore.getState().floorHP).toBe(getFloorMaxHP(5));
|
|
expect(useCombatStore.getState().floorMaxHP).toBe(getFloorMaxHP(5));
|
|
});
|
|
});
|
|
|
|
describe('advanceFloor', () => {
|
|
it('should increment floor and update HP', () => {
|
|
useCombatStore.getState().advanceFloor();
|
|
expect(useCombatStore.getState().currentFloor).toBe(2);
|
|
expect(useCombatStore.getState().floorHP).toBe(getFloorMaxHP(2));
|
|
});
|
|
|
|
it('should cap at floor 100', () => {
|
|
useCombatStore.setState({ currentFloor: 100 });
|
|
useCombatStore.getState().advanceFloor();
|
|
expect(useCombatStore.getState().currentFloor).toBe(100);
|
|
});
|
|
|
|
it('should update maxFloorReached', () => {
|
|
useCombatStore.getState().advanceFloor();
|
|
expect(useCombatStore.getState().maxFloorReached).toBe(2);
|
|
});
|
|
|
|
it('should reset cast progress', () => {
|
|
useCombatStore.setState({ castProgress: 0.5 });
|
|
useCombatStore.getState().advanceFloor();
|
|
expect(useCombatStore.getState().castProgress).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('setFloorHP', () => {
|
|
it('should set floor HP', () => {
|
|
useCombatStore.getState().setFloorHP(50);
|
|
expect(useCombatStore.getState().floorHP).toBe(50);
|
|
});
|
|
|
|
it('should clamp negative to 0', () => {
|
|
useCombatStore.getState().setFloorHP(-10);
|
|
expect(useCombatStore.getState().floorHP).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('setMaxFloorReached', () => {
|
|
it('should update max floor reached', () => {
|
|
useCombatStore.getState().setMaxFloorReached(10);
|
|
expect(useCombatStore.getState().maxFloorReached).toBe(10);
|
|
});
|
|
|
|
it('should only increase, never decrease', () => {
|
|
useCombatStore.setState({ maxFloorReached: 10 });
|
|
useCombatStore.getState().setMaxFloorReached(5);
|
|
expect(useCombatStore.getState().maxFloorReached).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe('setAction / setSpell', () => {
|
|
it('should set current action', () => {
|
|
useCombatStore.getState().setAction('climb');
|
|
expect(useCombatStore.getState().currentAction).toBe('climb');
|
|
});
|
|
|
|
it('should set active spell when learned', () => {
|
|
useCombatStore.getState().setSpell('manaBolt');
|
|
expect(useCombatStore.getState().activeSpell).toBe('manaBolt');
|
|
});
|
|
|
|
it('should not set spell when not learned', () => {
|
|
useCombatStore.getState().setSpell('fireball');
|
|
expect(useCombatStore.getState().activeSpell).toBe('manaBolt');
|
|
});
|
|
});
|
|
|
|
describe('learnSpell', () => {
|
|
it('should add a new learned spell', () => {
|
|
useCombatStore.getState().learnSpell('fireball');
|
|
expect(useCombatStore.getState().spells.fireball.learned).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('resetCombat', () => {
|
|
it('should reset to starting floor', () => {
|
|
useCombatStore.setState({ currentFloor: 50, maxFloorReached: 50 });
|
|
useCombatStore.getState().resetCombat(1);
|
|
expect(useCombatStore.getState().currentFloor).toBe(1);
|
|
expect(useCombatStore.getState().maxFloorReached).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('climbDownFloor', () => {
|
|
it('should decrement floor', () => {
|
|
useCombatStore.setState({ currentFloor: 5 });
|
|
useCombatStore.getState().climbDownFloor();
|
|
expect(useCombatStore.getState().currentFloor).toBe(4);
|
|
});
|
|
|
|
it('should not go below floor 1', () => {
|
|
useCombatStore.setState({ currentFloor: 1 });
|
|
useCombatStore.getState().climbDownFloor();
|
|
expect(useCombatStore.getState().currentFloor).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('exitSpireMode', () => {
|
|
it('should reset spire state', () => {
|
|
useCombatStore.setState({ spireMode: true, climbDirection: 'up', currentAction: 'climb' });
|
|
useCombatStore.getState().exitSpireMode();
|
|
expect(useCombatStore.getState().spireMode).toBe(false);
|
|
expect(useCombatStore.getState().climbDirection).toBeNull();
|
|
expect(useCombatStore.getState().currentAction).toBe('meditate');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('PrestigeStore', () => {
|
|
beforeEach(resetPrestigeStore);
|
|
|
|
describe('doPrestige', () => {
|
|
it('should purchase upgrade when affordable', () => {
|
|
usePrestigeStore.setState({ insight: 2000 });
|
|
const result = usePrestigeStore.getState().doPrestige('insightAmp');
|
|
expect(result.success).toBe(true);
|
|
expect(usePrestigeStore.getState().prestigeUpgrades.insightAmp).toBe(1);
|
|
expect(usePrestigeStore.getState().insight).toBeLessThan(2000);
|
|
});
|
|
|
|
it('should return false when cannot afford', () => {
|
|
usePrestigeStore.setState({ insight: 0 });
|
|
const result = usePrestigeStore.getState().doPrestige('insightAmp');
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.INSUFFICIENT_INSIGHT);
|
|
}
|
|
});
|
|
|
|
it('should fail for invalid upgrade id', () => {
|
|
const result = usePrestigeStore.getState().doPrestige('nonexistent');
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.INVALID_PRESTIGE_ID);
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
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.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.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.GUARDIAN_NOT_DEFEATED);
|
|
}
|
|
});
|
|
|
|
it('should fail when already signed', () => {
|
|
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [10] });
|
|
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.PACT_ALREADY_SIGNED);
|
|
}
|
|
});
|
|
|
|
it('should fail when pact slots full', () => {
|
|
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [20], pactSlots: 1 });
|
|
const result = usePrestigeStore.getState().startPactRitual(10, 10000);
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.PACT_SLOTS_FULL);
|
|
}
|
|
});
|
|
|
|
it('should fail when insufficient mana', () => {
|
|
usePrestigeStore.setState({ defeatedGuardians: [10], signedPacts: [] });
|
|
const result = usePrestigeStore.getState().startPactRitual(10, 0);
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.code).toBe(ErrorCode.INSUFFICIENT_MANA);
|
|
}
|
|
});
|
|
});
|
|
|
|
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, { insightAmp: 2 });
|
|
expect(usePrestigeStore.getState().insight).toBe(200);
|
|
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({ insightAmp: 2 });
|
|
|
|
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('resetPrestige', () => {
|
|
it('should reset everything to initial state', () => {
|
|
usePrestigeStore.setState({ insight: 1000, loopCount: 5, prestigeUpgrades: { insightAmp: 3 } });
|
|
usePrestigeStore.getState().resetPrestige();
|
|
expect(usePrestigeStore.getState().insight).toBe(0);
|
|
expect(usePrestigeStore.getState().loopCount).toBe(0);
|
|
expect(usePrestigeStore.getState().prestigeUpgrades).toEqual({});
|
|
});
|
|
});
|
|
});
|