fix: resolve all TypeScript compilation errors
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m17s

- Fixed DisciplinesAttunementType enum usage in discipline data files
- Fixed EquipmentSlot import in equipment/types.ts
- Fixed enchantment-effects.ts export/import chain
- Fixed safe-persist.ts StateStorage type compatibility
- Fixed store persist partial return types for all stores
- Fixed gameStore.ts ElementState type and error handling
- Fixed useGameDerived.ts missing properties on GameCoordinatorStore
- Added SkillUpgradeChoice type to types.ts
- Fixed ActionButtons.tsx optional currentStudyTarget prop
- Fixed GameToast.tsx Toast type compatibility
- Fixed EnchantmentDesigner sub-component type mismatches
- Fixed SpireCombatPage equippedInstances/equipmentInstances types
- Fixed page.tsx computeClickMana argument
- Added baseCastTime to SpellDef type
- Fixed golem/types.ts and loot-drops.ts import paths
- Fixed craftingStore.ts missing lastError in initial state and actions
- Fixed store-actions-combat-prestige.test.ts Memory type usage
- Fixed floor-utils.upgraded.test.ts array type annotation
- Fixed computed-stats.test.ts state type assertions
- Fixed activity-log.test.ts state type annotation
- Fixed discipline-math.test.ts enum value usage
- Fixed game-loop.test.ts vitest mock import
- Various other test file type fixes
This commit is contained in:
2026-05-24 14:34:49 +02:00
parent 14f25fffda
commit 23a83a04cf
44 changed files with 191 additions and 142 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { addActivityLogEntry } from '../utils/activity-log';
import type { ActivityLogEntry } from '../types';
import type { ActivityEventType, ActivityLogEntry } from '../types';
// ─── addActivityLogEntry ──────────────────────────────────────────────────────
@@ -106,8 +106,8 @@ describe('addActivityLogEntry', () => {
});
it('should handle various event types', () => {
const state = { activityLog: [] };
const types = ['combat', 'crafting', 'prestige', 'discovery', 'achievement'] as const;
const state: { activityLog: ActivityLogEntry[] } = { activityLog: [] };
const types: ActivityEventType[] = ['combat', 'damage_dealt', 'enemy_defeated', 'floor_cleared', 'spell_cast'];
let current = state;
for (const eventType of types) {
const result = addActivityLogEntry(current, eventType, `Event: ${eventType}`);
+12 -12
View File
@@ -164,7 +164,7 @@ describe('deductSpellCost', () => {
describe('computeMaxMana', () => {
it('should return base 100 with no skills or upgrades', () => {
const state = {
skills: {},
skills: {} as Record<string, number>,
prestigeUpgrades: {},
skillUpgrades: {},
skillTiers: {},
@@ -176,7 +176,7 @@ describe('computeMaxMana', () => {
it('should include manaWell prestige upgrade', () => {
const state = {
skills: {},
skills: {} as Record<string, number>,
prestigeUpgrades: { manaWell: 5 },
skillUpgrades: {},
skillTiers: {},
@@ -188,7 +188,7 @@ describe('computeMaxMana', () => {
it('should apply multiplier from effects', () => {
const state = {
skills: {},
skills: {} as Record<string, number>,
prestigeUpgrades: {},
skillUpgrades: {},
skillTiers: {},
@@ -200,7 +200,7 @@ describe('computeMaxMana', () => {
it('should apply bonus from effects', () => {
const state = {
skills: {},
skills: {} as Record<string, number>,
prestigeUpgrades: {},
skillUpgrades: {},
skillTiers: {},
@@ -214,14 +214,14 @@ describe('computeMaxMana', () => {
describe('computeRegen', () => {
it('should return base regen with no skills', () => {
const state = {
skills: {},
prestigeUpgrades: {},
skillUpgrades: {},
skillTiers: {},
attunements: {},
skills: {} as Record<string, number>,
prestigeUpgrades: {} as Record<string, number>,
skillUpgrades: {} as Record<string, string[]>,
skillTiers: {} as Record<string, number>,
attunements: {} as Record<string, { active: boolean; level: number; experience: number }>,
};
const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 } as any;
const result = computeRegen(state, effects);
const result = computeRegen(state as any, effects);
// Base regen is 2 (this test provides effects, so no attunement bonus)
expect(result).toBe(2);
});
@@ -230,10 +230,10 @@ describe('computeRegen', () => {
describe('computeClickMana', () => {
it('should return base click mana with no skills', () => {
const state = {
skills: {},
skills: {} as Record<string, number>,
};
const discipline = { bonuses: {}, multipliers: {} };
const result = computeClickMana(state, discipline);
const result = computeClickMana(state.skills, discipline);
expect(result).toBeGreaterThanOrEqual(1);
});
});
@@ -3,6 +3,7 @@ import {
canEquipInSlot,
isTwoHanded,
} from '../crafting-utils';
import type { EquipmentSlot } from '../types/equipmentSlot';
function makeInstance(overrides = {}): any {
return {
@@ -12,12 +13,15 @@ function makeInstance(overrides = {}): any {
enchantments: [],
totalCapacity: 100,
usedCapacity: 0,
rarity: 'common',
quality: 100,
tags: [],
...overrides,
};
}
describe('canEquipInSlot', () => {
const baseSlot: Record<string, string | null> = {
const baseSlot: Record<EquipmentSlot, string | null> = {
head: null,
body: null,
hands: null,
@@ -72,7 +76,7 @@ describe('canEquipInSlot', () => {
});
it('should block two-handed weapon if mainHand is occupied', () => {
const slot = { ...baseSlot, mainHand: 'something' };
const slot: Record<EquipmentSlot, string | null> = { ...baseSlot, mainHand: 'something' };
const instance = makeInstance({ instanceId: 'th_1', typeId: 'oakStaff' });
// Even if type is not two-handed, the slot check for mainHand+offHand applies
const result = canEquipInSlot(instance, 'mainHand', slot, {});
@@ -34,7 +34,7 @@ describe('checkRecipeMaterials', () => {
});
it('should return canCraft false when materials are empty', () => {
const result = checkRecipeMaterials(makeRecipe(), {});
const result = checkRecipeMaterials(makeRecipe(), {} as any);
expect(result.canCraft).toBe(false);
expect(result.missingMaterials).toEqual({ manaCrystalDust: 5, arcaneShard: 2 });
});
@@ -52,8 +52,8 @@ describe('checkRecipeMaterials', () => {
});
it('should handle recipe with no materials', () => {
const emptyRecipe = makeRecipe({});
const result = checkRecipeMaterials(emptyRecipe, {});
const emptyRecipe = makeRecipe({} as any);
const result = checkRecipeMaterials(emptyRecipe, {} as any);
expect(result.canCraft).toBe(true);
expect(result.missingMaterials).toEqual({});
});
@@ -89,7 +89,7 @@ describe('deductRecipeMaterials', () => {
});
it('should handle empty materials', () => {
const result = deductRecipeMaterials(makeRecipe(), {});
const result = deductRecipeMaterials(makeRecipe(), {} as any);
expect(result).toEqual({});
});
});
@@ -129,13 +129,13 @@ describe('refundCraftMaterials', () => {
it('should floor fractional refunds', () => {
// Recipe with manaCrystalDust: 7
// 50% of 7 = 3.5 → floor = 3
const recipeWithOdd = makeRecipe({ manaCrystalDust: 7 });
const recipeWithOdd = makeRecipe({ manaCrystalDust: 7 } as any);
const result = refundCraftMaterials(recipeWithOdd, 0.5);
expect(result.manaCrystalDust).toBe(3);
});
it('should handle empty recipe materials', () => {
const emptyRecipe = makeRecipe({});
const emptyRecipe = makeRecipe({} as any);
const result = refundCraftMaterials(emptyRecipe);
expect(result).toEqual({});
});
@@ -8,6 +8,7 @@ import {
getUnlockedPerks,
calculateDisciplineStats,
} from '../utils/discipline-math';
import { DisciplinesAttunementType } from '../types/disciplines';
import type { DisciplineDefinition, DisciplineState } from '../types/disciplines';
// ─── Test Fixtures ────────────────────────────────────────────────────────────
@@ -15,7 +16,7 @@ import type { DisciplineDefinition, DisciplineState } from '../types/disciplines
const rawMastery: DisciplineDefinition = {
id: 'raw-mastery',
name: 'Raw Mana Mastery',
attunement: 'base',
attunement: DisciplinesAttunementType.BASE,
manaType: 'raw',
baseCost: 5,
description: 'Learn to harness raw mana more efficiently.',
@@ -44,7 +45,7 @@ const rawMastery: DisciplineDefinition = {
const elementalAttunement: DisciplineDefinition = {
id: 'elemental-attunement',
name: 'Elemental Attunement',
attunement: 'base',
attunement: DisciplinesAttunementType.BASE,
manaType: 'fire',
baseCost: 10,
description: 'Begin focusing raw mana into fire.',
@@ -66,7 +67,7 @@ const elementalAttunement: DisciplineDefinition = {
const cappedPerkDiscipline: DisciplineDefinition = {
id: 'capped-test',
name: 'Capped Perk Test',
attunement: 'base',
attunement: DisciplinesAttunementType.BASE,
manaType: 'raw',
baseCost: 1,
description: 'Test discipline with capped perk.',
@@ -128,7 +128,8 @@ describe('getFloorElement - Enhanced Edge Cases', () => {
});
it('should handle negative floors', () => {
expect(getFloorElement(-10)).toBe('water'); // (-10-1) % 7 = -11 % 7 = 3, earth? Check actual formula
// ((-10-1) % 7 + 7) % 7 = (-11 % 7 + 7) % 7 = (-4 + 7) % 7 = 3 => earth
expect(getFloorElement(-10)).toBe('earth' as string);
});
it('should return only valid element names', () => {
@@ -141,7 +142,7 @@ describe('getFloorElement - Enhanced Edge Cases', () => {
it('should maintain consistent cycling for sequential calls', () => {
// Ensure the cycle is consistent across multiple calls
const elements = [];
const elements: string[] = [];
for (let i = 1; i <= 21; i++) {
elements.push(getFloorElement(i));
}
@@ -2,7 +2,8 @@ import { describe, it, expect } from 'vitest';
import { generateFloorState } from '../utils/room-utils';
import { PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG } from '../constants';
import { getGuardianForFloor } from '../data/guardian-encounters';
import { getFloorMaxHP, getDodgeChance } from '../utils/floor-utils';
import { getFloorMaxHP } from '../utils/floor-utils';
import { getDodgeChance } from '../utils/room-utils';
// ─── generateFloorState ───────────────────────────────────────────────────────
@@ -72,8 +73,8 @@ describe('generateFloorState', () => {
Math.random = () => 0.19;
const state = generateFloorState(7);
expect(state.roomType).toBe('puzzle');
expect(state.puzzleAttunements.length).toBeGreaterThan(0);
expect(typeof state.puzzleAttunements[0]).toBe('string');
expect(state.puzzleAttunements!.length).toBeGreaterThan(0);
expect(typeof state.puzzleAttunements![0]).toBe('string');
Math.random = originalRandom;
});
@@ -14,7 +14,7 @@ function resetCombatStore() {
currentAction: 'meditate',
castProgress: 0,
spireMode: false,
currentRoom: { roomType: 'combat', enemies: [], cleared: false },
currentRoom: { roomType: 'combat', enemies: [] },
clearedFloors: {},
climbDirection: null,
isDescending: false,
@@ -222,32 +222,32 @@ describe('PrestigeStore', () => {
describe('addMemory / removeMemory', () => {
it('should add a memory when slots available', () => {
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3 });
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3, tier: 1, upgrades: [] });
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 });
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3, tier: 1, upgrades: [] });
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 5, tier: 1, upgrades: [] });
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 });
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 1, tier: 1, upgrades: [] });
usePrestigeStore.getState().addMemory({ skillId: 'manaSpring', level: 1, tier: 1, upgrades: [] });
expect(usePrestigeStore.getState().memories.length).toBe(1);
});
it('should remove memory by skillId', () => {
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3 });
usePrestigeStore.getState().addMemory({ skillId: 'manaFlow', level: 3, tier: 1, upgrades: [] });
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().addMemory({ skillId: 'manaFlow', level: 1, tier: 1, upgrades: [] });
usePrestigeStore.getState().addMemory({ skillId: 'manaSpring', level: 1, tier: 1, upgrades: [] });
usePrestigeStore.getState().clearMemories();
expect(usePrestigeStore.getState().memories.length).toBe(0);
});
+1 -1
View File
@@ -26,7 +26,7 @@ function resetCombatStore() {
currentAction: 'meditate',
castProgress: 0,
spireMode: false,
currentRoom: { roomType: 'combat', enemies: [], cleared: false },
currentRoom: { roomType: 'combat', enemies: [] },
clearedFloors: {},
climbDirection: null,
isDescending: false,
@@ -42,7 +42,7 @@ function resetAllStores() {
currentAction: 'meditate',
castProgress: 0,
spireMode: false,
currentRoom: { roomType: 'combat', enemies: [], cleared: false },
currentRoom: { roomType: 'combat', enemies: [] },
clearedFloors: {},
climbDirection: null,
isDescending: false,