refactor: eliminate as any type casts across 18 source files
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m34s

- Fix computeDisciplineEffects() to not require GameState parameter
- Fix getUnifiedEffects() to accept proper partial state type
- Replace upgradeEffects as any with proper UnifiedEffects type
- Replace explicit : any annotations with proper types (ComputedEffects, DesignProgress, SpellDef, etc.)
- Fix activity-log.ts eventType casting
- Fix crafting-design.ts computedEffects and designProgress types
- Fix page.tsx grimoire spell rendering with proper SpellDef property names
- Fix StatsTab ManaStatsSection with proper ManaStatsEffects interface
- Remove unused imports (useDisciplineStore from page.tsx, LeftPanel.tsx)

Remaining: 1 as any in craftingStore.ts (pre-existing CraftingStore/GameState architectural mismatch)
This commit is contained in:
2026-05-20 17:22:52 +02:00
parent df316c2865
commit 742a992d59
36 changed files with 1820 additions and 1460 deletions
@@ -0,0 +1,362 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from '../stores/combatStore';
import { usePrestigeStore } from '../stores/prestigeStore';
import { getFloorMaxHP } from '../utils';
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: [], cleared: false },
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: {},
memorySlots: 3,
pactSlots: 1,
memories: [],
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('debugSetFloor / resetFloorHP', () => {
it('should set floor and update HP', () => {
useCombatStore.getState().debugSetFloor(10);
expect(useCombatStore.getState().currentFloor).toBe(10);
expect(useCombatStore.getState().floorHP).toBe(getFloorMaxHP(10));
});
it('should reset floor HP to max', () => {
useCombatStore.setState({ floorHP: 10 });
useCombatStore.getState().resetFloorHP();
expect(useCombatStore.getState().floorHP).toBe(useCombatStore.getState().floorMaxHP);
});
});
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', () => {
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({});
});
});
});