Completely remove legacy skill system and tests

This commit is contained in:
2026-05-16 11:20:11 +02:00
parent 1a688394e4
commit fe0f2a079c
125 changed files with 7 additions and 14459 deletions
@@ -1,17 +0,0 @@
/**
* Store Method Tests - Main Index
*
* This file re-exports all individual store method test files.
* Each test file is focused on a specific store's methods.
*
* Original file: store-methods.test.ts (588 lines)
* Refactored into 5 smaller test files (~2,900 total lines across files).
*/
import '../store-method-tests/skill-store.test';
import '../store-method-tests/mana-store.test';
import '../store-method-tests/combat-store.test';
import '../store-method-tests/prestige-store.test';
import '../store-method-tests/ui-store.test';
console.log('✅ All store method tests complete (refactored from 588 lines to 5 focused test files).');
@@ -1,25 +0,0 @@
/**
* Stores Tests (stores/__tests__/stores.test.ts) - Main Index
*
* This file re-exports all individual test files from stores/__tests__/stores.test.ts
* Each test file is focused on a specific area of functionality.
*
* Original file: stores.test.ts (458 lines)
* Refactored into 12 smaller test files.
*/
import '../stores-tests/formatting.test';
import '../stores-tests/floor.test';
import '../stores-tests/mana-calculation.test';
import '../stores-tests/damage-calculation.test';
import '../stores-tests/insight-calculation.test';
import '../stores-tests/meditation.test';
import '../stores-tests/incursion.test';
import '../stores-tests/spell-cost.test';
import '../stores-tests/study-speed.test';
import '../stores-tests/guardians.test';
import '../stores-tests/skill-definitions.test';
import '../stores-tests/prestige-upgrades.test';
import '../stores-tests/spell-definitions.test';
console.log('✅ All stores tests from stores/__tests__/stores.test.ts complete (refactored from 458 lines to 12 focused test files).');
@@ -1,492 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
computeStats,
BASE_STATS,
SKILLS_V2,
getBaseSkillId,
hasPrerequisites,
} from '../../constants/skills-v2';
import type { ComputedStats } from '../../constants/skills-v2-types';
// Helper to create a minimal prestige state
const emptyPrestige = {};
describe('computeStats()', () => {
describe('base stats with no skills', () => {
it('should return base stats when no skills are provided', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBe(100);
expect(result.manaRegen).toBe(2);
expect(result.clickMana).toBe(1);
expect(result.baseDamage).toBe(5);
expect(result.elementCap).toBe(10);
});
it('should have all base values correct', () => {
const result = computeStats({}, emptyPrestige);
expect(result).toMatchObject({
maxMana: 100,
manaRegen: 2,
clickMana: 1,
elementCap: 10,
studySpeed: 1,
studyCostMult: 1,
meditationEfficiency: 1,
enchantCapacity: 100,
enchantSpeed: 1,
enchantPower: 1,
disenchantRecovery: 1,
baseDamage: 5,
damageMultiplier: 1,
attackSpeed: 1,
critChance: 0,
critMultiplier: 1.5,
armorPierce: 0,
insightGain: 1,
golemDamage: 1,
golemDuration: 1,
pactMultiplier: 1,
spellDamage: 1,
guardianDamage: 1,
craftSpeed: 1,
repairSpeed: 1,
elementalDamage: 1,
});
});
});
describe('Mana Well skill', () => {
it('should add 100 max mana per level', () => {
const result = computeStats({ manaWell: 5 }, emptyPrestige);
expect(result.maxMana).toBe(100 + 5 * 100);
});
it('should be 100 at level 0', () => {
const result = computeStats({ manaWell: 0 }, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should be 1100 at max level 10', () => {
const result = computeStats({ manaWell: 10 }, emptyPrestige);
expect(result.maxMana).toBe(1100);
});
});
describe('Mana Flow skill', () => {
it('should add 1 mana regen per level', () => {
const result = computeStats({ manaFlow: 5 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 5);
});
it('should be 2 at level 0', () => {
const result = computeStats({ manaFlow: 0 }, emptyPrestige);
expect(result.manaRegen).toBe(2);
});
it('should be 12 at max level 10', () => {
const result = computeStats({ manaFlow: 10 }, emptyPrestige);
expect(result.manaRegen).toBe(12);
});
});
describe('Mana Tap skill', () => {
it('should add 1 click mana at level 1', () => {
const result = computeStats({ manaTap: 1 }, emptyPrestige);
expect(result.clickMana).toBe(2);
});
it('should be 1 at level 0', () => {
const result = computeStats({ manaTap: 0 }, emptyPrestige);
expect(result.clickMana).toBe(1);
});
});
describe('Mana Surge skill', () => {
it('should add 3 click mana per level', () => {
const result = computeStats({ manaSurge: 1 }, emptyPrestige);
expect(result.clickMana).toBe(1 + 3);
});
it('should stack with manaTap', () => {
const result = computeStats({ manaTap: 1, manaSurge: 1 }, emptyPrestige);
expect(result.clickMana).toBe(1 + 1 + 3);
});
});
describe('Mana Spring skill', () => {
it('should add 2 mana regen at level 1', () => {
const result = computeStats({ manaSpring: 1 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 2);
});
it('should stack with manaFlow', () => {
const result = computeStats({ manaFlow: 5, manaSpring: 1 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 5 + 2);
});
});
describe('Mana Overflow skill', () => {
it('should multiply click mana by compounding 1.25 per level', () => {
// multiply effects compound per level: 1 * 1.25^2 = 1.5625
const result = computeStats({ manaOverflow: 2 }, emptyPrestige);
expect(result.clickMana).toBeCloseTo(1.5625, 2);
});
it('should be 1 at level 0', () => {
const result = computeStats({ manaOverflow: 0 }, emptyPrestige);
expect(result.clickMana).toBe(1);
});
});
describe('Quick Learner skill', () => {
it('should multiply study speed by compounding 1.10 per level', () => {
// 1.1^5 = 1.61051
const result = computeStats({ quickLearner: 5 }, emptyPrestige);
expect(result.studySpeed).toBeCloseTo(1.61051, 2);
});
it('should be ~2.5937 at max level 10', () => {
const result = computeStats({ quickLearner: 10 }, emptyPrestige);
expect(result.studySpeed).toBeCloseTo(2.593742, 2);
});
});
describe('Focused Mind skill', () => {
it('should multiply study cost by compounding 0.95 per level', () => {
// 0.95^2 = 0.9025
const result = computeStats({ focusedMind: 2 }, emptyPrestige);
expect(result.studyCostMult).toBeCloseTo(0.9025, 2);
});
});
describe('Meditation Focus skill', () => {
it('should multiply meditation efficiency at level 1', () => {
// BASE (1) * (1 + 1.5) = 2.5
const result = computeStats({ meditation: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(2.5);
});
});
describe('Deep Trance skill', () => {
it('should multiply meditation efficiency further', () => {
// meditation: 1 * (1+1.5) = 2.5, deepTrance: 2.5 * (1+1.8) = 7.0
const result = computeStats({ meditation: 1, deepTrance: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(7.0);
});
});
describe('Void Meditation skill', () => {
it('should multiply meditation efficiency to max', () => {
// 1 * 2.5 * 2.8 * 3.5 = 24.5
const result = computeStats({ meditation: 1, deepTrance: 1, voidMeditation: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(24.5);
});
});
describe('Combat skills', () => {
it('Arcane Fury should multiply damage', () => {
const result = computeStats({ arcaneFury: 5 }, emptyPrestige);
expect(result.damageMultiplier).toBeCloseTo(1.61051, 2);
});
it('Combat Training should add base damage', () => {
const result = computeStats({ combatTraining: 3 }, emptyPrestige);
expect(result.baseDamage).toBe(5 + 3 * 5);
});
it('Precision should add crit chance', () => {
const result = computeStats({ precision: 4 }, emptyPrestige);
expect(result.critChance).toBe(0.2);
});
it('Precision should cap at 1.0', () => {
const result = computeStats({ precision: 25 }, emptyPrestige);
expect(result.critChance).toBe(1.0);
});
it('Elemental Mastery should multiply elemental damage', () => {
// 1 * 1.15^4 = ~1.749
const result = computeStats({ elementalMastery: 4 }, emptyPrestige);
expect(result.elementalDamage).toBeCloseTo(1.749, 2);
});
it('Attack Speed should multiply attack speed (compounding)', () => {
// 1 * 0.9^3 = 0.729
const result = computeStats({ attackSpeed: 3 }, emptyPrestige);
expect(result.attackSpeed).toBeCloseTo(0.729, 2);
});
it('Armor Piercing should add armor pierce', () => {
const result = computeStats({ armorPiercing: 4 }, emptyPrestige);
expect(result.armorPierce).toBe(0.2);
});
});
describe('Enchanting skills', () => {
it('Enchanting should affect enchantCapacity and enchantSpeed', () => {
const result = computeStats({ enchanting: 5 }, emptyPrestige);
expect(result.enchantCapacity).toBe(100 + 5 * 10); // add 10 per level
expect(result.enchantSpeed).toBeCloseTo(0.9, 2); // multiply by 0.98^5
});
it('Essence Refining should multiply enchantPower', () => {
const result = computeStats({ enchanting: 4, essenceRefining: 1 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.1, 2);
});
});
describe('Golemancy skills', () => {
it('Golem Mastery should multiply golemDamage', () => {
const result = computeStats({ golemMastery: 5 }, emptyPrestige);
expect(result.golemDamage).toBeCloseTo(1.61051, 2);
});
it('Golem Longevity should add golemDuration', () => {
const result = computeStats({ golemLongevity: 3 }, emptyPrestige);
expect(result.golemDuration).toBe(1 + 3);
});
it('Golem Efficiency should multiply attackSpeed', () => {
const result = computeStats({ golemEfficiency: 2 }, emptyPrestige);
expect(result.attackSpeed).toBeCloseTo(0.9025, 2); // 0.95^2
});
});
describe('Invocation / Pact skills', () => {
it('Invocation should multiply spellDamage', () => {
const result = computeStats({ invocation: 5 }, emptyPrestige);
expect(result.spellDamage).toBeCloseTo(1.27628, 2); // 1.05^5
});
it('Pact Mastery should multiply pactMultiplier', () => {
const result = computeStats({ pactMastery: 5 }, emptyPrestige);
expect(result.pactMultiplier).toBeCloseTo(1.61051, 2); // 1.1^5
});
it('Guardian Lore should multiply guardianDamage', () => {
const result = computeStats({ guardianLore: 3 }, emptyPrestige);
expect(result.guardianDamage).toBeCloseTo(1.728, 2); // 1.2^3
});
});
describe('Element capacity skills', () => {
it('Fire Mana Cap should increase fireCap', () => {
const result = computeStats({ fireManaCap: 5 }, emptyPrestige);
expect(result.fireCap).toBe(5 * 10);
});
it('Multiple element caps should contribute to elementCap', () => {
const result = computeStats({ fireManaCap: 3, waterManaCap: 2 }, emptyPrestige);
// fireCap=30, waterCap=20 -> elementCap = 10 + 30 + 20 = 60
expect(result.elementCap).toBe(60);
});
it('All base element caps should contribute', () => {
const result = computeStats({
fireManaCap: 10, waterManaCap: 10, airManaCap: 10, earthManaCap: 10,
}, emptyPrestige);
// Each adds 10*10=100, total 400 + base 10 = 410
expect(result.elementCap).toBe(410);
});
});
describe('Hybrid skills', () => {
it('Pact-Weaving should multiply enchantPower', () => {
const result = computeStats({ pactWeaving: 3 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.331, 2); // 1.1^3
});
it('Guardian Constructs should affect golemDamage and golemDuration', () => {
const result = computeStats({ guardianConstructs: 2 }, emptyPrestige);
expect(result.golemDamage).toBeCloseTo(1.3225, 2); // 1.15^2
expect(result.golemDuration).toBe(1.5); // add 0.25*2=0.5, but cap floor is 1, so 1 + 0.5 = 1.5
});
it('Enchanted Golemancy should affect enchantPower and golemDamage', () => {
const result = computeStats({ enchantedGolemancy: 3 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.157625, 2); // 1.05^3
expect(result.golemDamage).toBeCloseTo(1.331, 2); // 1.1^3
});
});
describe('Prestige upgrades', () => {
it('manaWell prestige should increase maxMana', () => {
const result = computeStats({}, { manaWell: 3 });
expect(result.maxMana).toBe(100 + 3 * 500);
});
it('manaFlow prestige should increase manaRegen', () => {
const result = computeStats({}, { manaFlow: 2 });
expect(result.manaRegen).toBe(2 + 2 * 0.5);
});
it('elementalAttune prestige should increase elementCap', () => {
const result = computeStats({}, { elementalAttune: 4 });
expect(result.elementCap).toBe(10 + 4 * 25);
});
it('pactBinding prestige should increase pactMultiplier', () => {
const result = computeStats({}, { pactBinding: 2 });
expect(result.pactMultiplier).toBe(1 + 2 * 0.1);
});
it('insightAmp prestige should multiply insightGain', () => {
const result = computeStats({}, { insightAmp: 2 });
expect(result.insightGain).toBe(1 + 1 * 0.25 * 2);
});
it('prestige should stack with skills', () => {
const result = computeStats({ manaWell: 5 }, { manaWell: 3 });
expect(result.maxMana).toBe(100 + 5 * 100 + 3 * 500);
});
});
describe('Skill stacking', () => {
it('should correctly stack multiple skills', () => {
const result = computeStats({
manaWell: 3,
manaFlow: 2,
manaTap: 1,
precision: 4,
}, emptyPrestige);
expect(result.maxMana).toBe(100 + 300);
expect(result.manaRegen).toBe(2 + 2);
expect(result.clickMana).toBe(1 + 1);
expect(result.critChance).toBe(0.2);
});
it('should handle all skills with effects at once without interference', () => {
// Only test skills that have effects defined (skip research skills with empty effects)
const allSkillsWithEffects: Record<string, number> = {};
for (const [id, def] of Object.entries(SKILLS_V2)) {
if (def.effects.length > 0) {
allSkillsWithEffects[id] = 1;
}
}
const result = computeStats(allSkillsWithEffects, emptyPrestige);
// Should not throw and should produce reasonable values
expect(result.maxMana).toBeGreaterThan(0);
expect(result.manaRegen).toBeGreaterThan(0);
expect(result.baseDamage).toBeGreaterThan(0);
expect(result.elementCap).toBeGreaterThanOrEqual(10);
});
});
describe('edge cases', () => {
it('should ignore negative levels (no effect applied)', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should ignore unknown skill IDs', () => {
const result = computeStats({ unknownSkill: 5 } as any, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should clamp critChance to 1.0', () => {
const result = computeStats({ precision: 100 } as any, emptyPrestige);
expect(result.critChance).toBe(1.0);
});
it('should clamp armorPierce to 1.0', () => {
const result = computeStats({ armorPiercing: 100 } as any, emptyPrestige);
expect(result.armorPierce).toBe(1.0);
});
it('should clamp attackSpeed minimum to 0.1', () => {
const result = computeStats({ attackSpeed: 100 } as any, emptyPrestige);
expect(result.attackSpeed).toBe(0.1);
});
it('should clamp maxMana minimum to 1', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBeGreaterThanOrEqual(1);
});
it('should clamp baseDamage minimum to 1', () => {
const result = computeStats({}, emptyPrestige);
expect(result.baseDamage).toBeGreaterThanOrEqual(1);
});
});
});
describe('getBaseSkillId()', () => {
it('should strip _tN suffix for tiered skills', () => {
expect(getBaseSkillId('manaWell_t2')).toBe('manaWell');
expect(getBaseSkillId('manaWell_t5')).toBe('manaWell');
expect(getBaseSkillId('quickLearner_t3')).toBe('quickLearner');
});
it('should return same ID for non-tiered skills', () => {
expect(getBaseSkillId('manaWell')).toBe('manaWell');
expect(getBaseSkillId('fireManaCap')).toBe('fireManaCap');
});
});
describe('hasPrerequisites()', () => {
it('should return true when no prerequisites', () => {
expect(hasPrerequisites({}, undefined)).toBe(true);
expect(hasPrerequisites({}, {})).toBe(true);
});
it('should return true when prerequisites are met', () => {
expect(hasPrerequisites({ manaWell: 5 }, { manaWell: 3 })).toBe(true);
});
it('should return false when prerequisites are not met', () => {
expect(hasPrerequisites({ manaWell: 2 }, { manaWell: 3 })).toBe(false);
expect(hasPrerequisites({}, { manaWell: 1 })).toBe(false);
});
it('should check multiple prerequisites', () => {
expect(hasPrerequisites({ a: 2, b: 3 }, { a: 1, b: 2 })).toBe(true);
expect(hasPrerequisites({ a: 2, b: 1 }, { a: 1, b: 2 })).toBe(false);
});
});
describe('SKILLS_V2', () => {
it('should have manaWell defined', () => {
expect(SKILLS_V2.manaWell).toBeDefined();
expect(SKILLS_V2.manaWell.id).toBe('manaWell');
expect(SKILLS_V2.manaWell.maxLevel).toBe(10);
expect(SKILLS_V2.manaWell.effects).toHaveLength(1);
});
it('should have all core skills defined', () => {
const coreSkills = ['manaWell', 'manaFlow', 'quickLearner', 'focusedMind', 'meditation'];
for (const id of coreSkills) {
expect(SKILLS_V2[id]).toBeDefined();
expect(SKILLS_V2[id].effects.length).toBeGreaterThan(0);
}
});
it('should have correct manaWell effect', () => {
const effect = SKILLS_V2.manaWell.effects[0];
expect(effect.stat).toBe('maxMana');
expect(effect.mode).toBe('add');
expect(effect.valuePerLevel).toBe(100);
});
it('should have correct manaFlow effect', () => {
const effect = SKILLS_V2.manaFlow.effects[0];
expect(effect.stat).toBe('manaRegen');
expect(effect.mode).toBe('add');
expect(effect.valuePerLevel).toBe(1);
});
it('should handle prerequisite fields', () => {
expect(SKILLS_V2.manaOverflow.prerequisites).toEqual({ manaWell: 3 });
expect(SKILLS_V2.deepTrance.prerequisites).toEqual({ meditation: 1 });
expect(SKILLS_V2.manaTap.prerequisites).toBeUndefined();
});
it('should handle attunementRequired fields', () => {
expect(SKILLS_V2.enchanting.attunementRequired).toBe('enchanter');
expect(SKILLS_V2.invocation.attunementRequired).toBe('invoker');
expect(SKILLS_V2.golemMastery.attunementRequired).toBe('fabricator');
expect(SKILLS_V2.manaWell.attunementRequired).toBeUndefined();
});
});
console.log('✅ computeStats() and skill v2 tests defined.');
@@ -1,106 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useCraftingStore } from '@/lib/game/stores';
describe('useCraftingStore - Equipment Actions', () => {
beforeEach(() => {
// Reset to initial state
useCraftingStore.setState({
equipmentInstances: {},
equippedInstances: {},
});
});
it('equipItem sets equippedInstances[slot] to instanceId', () => {
const instanceId = 'test-instance-1';
const slot = 'mainHand';
// First, add the instance to equipmentInstances
useCraftingStore.setState((state) => ({
equipmentInstances: {
...state.equipmentInstances,
[instanceId]: {
id: instanceId,
equipmentId: 'test-equip',
name: 'Test Sword',
rarity: 'common',
level: 1,
upgrades: [],
createdAt: Date.now(),
} as any,
},
}));
// Equip the item - note: equipItem might take (slot, instanceId)
const state = useCraftingStore.getState();
if (state.equipItem) {
state.equipItem(slot, instanceId);
expect(useCraftingStore.getState().equippedInstances[slot]).toBe(instanceId);
}
});
it('unequipItem sets equippedInstances[slot] to null', () => {
const instanceId = 'test-instance-1';
const slot = 'mainHand';
// First equip the item
useCraftingStore.setState((state) => ({
equipmentInstances: {
...state.equipmentInstances,
[instanceId]: {
id: instanceId,
equipmentId: 'test-equip',
name: 'Test Sword',
rarity: 'common',
level: 1,
upgrades: [],
createdAt: Date.now(),
} as any,
},
equippedInstances: {
...state.equippedInstances,
[slot]: instanceId,
},
}));
// Unequip the item
const state = useCraftingStore.getState();
if (state.unequipItem) {
state.unequipItem(slot);
expect(useCraftingStore.getState().equippedInstances[slot]).toBeNull();
}
});
it('deleteEquipmentInstance removes from both equippedInstances and equipmentInstances', () => {
const instanceId = 'test-instance-1';
const slot = 'mainHand';
// Add and equip the item
useCraftingStore.setState((state) => ({
equipmentInstances: {
...state.equipmentInstances,
[instanceId]: {
id: instanceId,
equipmentId: 'test-equip',
name: 'Test Sword',
rarity: 'common',
level: 1,
upgrades: [],
createdAt: Date.now(),
} as any,
},
equippedInstances: {
...state.equippedInstances,
[slot]: instanceId,
},
}));
// Delete the item
const state = useCraftingStore.getState();
if (state.deleteEquipmentInstance) {
state.deleteEquipmentInstance(instanceId);
const newState = useCraftingStore.getState();
expect(newState.equipmentInstances[instanceId]).toBeUndefined();
expect(newState.equippedInstances[slot]).toBeNull();
}
});
});
@@ -1,147 +0,0 @@
/**
* Combat Calculation Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { calcDamage, getFloorMaxHP, getFloorElement } from '@/lib/game/stores/index';
import { GUARDIANS } from '@/lib/game/constants';
import type { GameState } from '../../../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning'].forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
defeatedGuardians: [],
signedPacts: [],
pactSlots: 1,
pactRitualFloor: null,
pactRitualProgress: 0,
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
spells: {
manaBolt: { learned: true, level: 1, studyProgress: 0 },
fireball: { learned: true, level: 1, studyProgress: 0 },
waterJet: { learned: true, level: 1, studyProgress: 0 },
},
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
paidStudySkills: {},
currentStudyTarget: null,
parallelStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null },
inventory: [],
blueprints: {},
schedule: [],
autoSchedule: false,
studyQueue: [],
craftQueue: [],
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
invoker: { id: 'invoker', active: false, level: 1, experience: 0 },
fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 },
},
golemancy: {
enabledGolems: [],
summonedGolems: [],
lastSummonFloor: 0,
},
equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null },
equipmentInstances: {},
...overrides,
} as GameState;
}
describe('Combat Calculations', () => {
describe('calcDamage', () => {
it('should return spell base damage with no bonuses', () => {
const state = createMockState();
const dmg = calcDamage(state, 'manaBolt');
expect(dmg).toBeGreaterThanOrEqual(5); // Base damage (can be higher with crit)
});
it('should have elemental bonuses', () => {
const state = createMockState({
spells: {
manaBolt: { learned: true, level: 1 },
fireball: { learned: true, level: 1 },
waterJet: { learned: true, level: 1 },
}
});
// Test elemental bonus by comparing same spell vs different elements
// Fireball vs fire floor (same element, +25%) vs vs air floor (neutral)
let fireVsFire = 0, fireVsAir = 0;
for (let i = 0; i < 100; i++) {
fireVsFire += calcDamage(state, 'fireball', 'fire');
fireVsAir += calcDamage(state, 'fireball', 'air');
}
const sameAvg = fireVsFire / 100;
const neutralAvg = fireVsAir / 100;
// Same element should do more damage
expect(sameAvg).toBeGreaterThan(neutralAvg * 1.1);
});
});
describe('getFloorMaxHP', () => {
it('should return guardian HP for guardian floors', () => {
expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp);
expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp);
});
it('should scale HP for non-guardian floors', () => {
expect(getFloorMaxHP(1)).toBeGreaterThan(0);
expect(getFloorMaxHP(5)).toBeGreaterThan(getFloorMaxHP(1));
});
});
describe('getFloorElement', () => {
it('should cycle through elements in order', () => {
// FLOOR_ELEM_CYCLE has 7 elements: fire, water, air, earth, light, dark, death
expect(getFloorElement(1)).toBe('fire');
expect(getFloorElement(2)).toBe('water');
expect(getFloorElement(3)).toBe('air');
expect(getFloorElement(4)).toBe('earth');
expect(getFloorElement(5)).toBe('light');
expect(getFloorElement(6)).toBe('dark');
expect(getFloorElement(7)).toBe('death');
});
it('should wrap around after 7 floors', () => {
// Floor 8 should be fire (wraps around)
expect(getFloorElement(8)).toBe('fire');
expect(getFloorElement(9)).toBe('water');
expect(getFloorElement(15)).toBe('fire'); // (15-1) % 7 = 0
expect(getFloorElement(16)).toBe('water'); // (16-1) % 7 = 1
});
});
});
console.log('✅ Combat calculation tests defined.');
@@ -1,98 +0,0 @@
/**
* Skill and Prestige Definition Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF, PRESTIGE_DEF, GUARDIANS } from '@/lib/game/constants';
import type { GameState } from '../../../types';
describe('Skill Definitions', () => {
it('all skills should have valid categories', () => {
const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant',
'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'craft', 'hybrid'];
Object.values(SKILLS_DEF).forEach(skill => {
expect(validCategories).toContain(skill.cat);
});
});
it('all skills should have reasonable study times', () => {
Object.values(SKILLS_DEF).forEach(skill => {
expect(skill.studyTime).toBeGreaterThan(0);
expect(skill.studyTime).toBeLessThanOrEqual(72);
});
});
it('all prerequisite skills should exist', () => {
Object.entries(SKILLS_DEF).forEach(([id, skill]) => {
if (skill.req) {
Object.keys(skill.req).forEach(reqId => {
expect(SKILLS_DEF[reqId]).toBeDefined();
});
}
});
});
it('all prerequisite levels should be within skill max', () => {
Object.entries(SKILLS_DEF).forEach(([id, skill]) => {
if (skill.req) {
Object.entries(skill.req).forEach(([reqId, reqLevel]) => {
expect(reqLevel).toBeLessThanOrEqual(SKILLS_DEF[reqId].max);
});
}
});
});
});
describe('Prestige Upgrades', () => {
it('all prestige upgrades should have valid costs', () => {
Object.entries(PRESTIGE_DEF).forEach(([id, upgrade]) => {
expect(upgrade.cost).toBeGreaterThan(0);
expect(upgrade.max).toBeGreaterThan(0);
});
});
it('Mana Well prestige should add 500 starting max mana', () => {
// Need to import compute functions - this test is simpler in the actual refactored files
// For now, just test the definition
expect(PRESTIGE_DEF.manaWell).toBeDefined();
expect(PRESTIGE_DEF.manaWell.cost).toBeGreaterThan(0);
});
it('Elemental Attunement prestige should add 25 element cap', () => {
expect(PRESTIGE_DEF.elementalAttune).toBeDefined();
expect(PRESTIGE_DEF.elementalAttune.cost).toBeGreaterThan(0);
});
});
describe('Guardian Definitions', () => {
it('should have guardians on expected floors (no floor 70)', () => {
// Floor 70 was removed from the game
[10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => {
expect(GUARDIANS[floor]).toBeDefined();
});
});
it('should have increasing HP', () => {
let prevHP = 0;
[10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => {
expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP);
prevHP = GUARDIANS[floor].hp;
});
});
it('should have boons defined', () => {
Object.values(GUARDIANS).forEach(guardian => {
expect(guardian.boons).toBeDefined();
expect(guardian.boons.length).toBeGreaterThan(0);
});
});
it('should have pact costs defined', () => {
Object.values(GUARDIANS).forEach(guardian => {
expect(guardian.pactCost).toBeGreaterThan(0);
expect(guardian.pactTime).toBeGreaterThan(0);
});
});
});
console.log('✅ Skill, prestige, and guardian definition tests defined.');
@@ -1,164 +0,0 @@
/**
* Mana Calculation Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { computeMaxMana, computeRegen, computeClickMana, computeElementMax } from '@/lib/game/stores/index';
import type { GameState } from '../../../types';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
defeatedGuardians: [],
signedPacts: [],
pactSlots: 1,
pactRitualFloor: null,
pactRitualProgress: 0,
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
paidStudySkills: {},
currentStudyTarget: null,
parallelStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
invoker: { id: 'invoker', active: false, level: 1, experience: 0 },
fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 },
},
golemancy: {
enabledGolems: [],
summonedGolems: [],
lastSummonFloor: 0,
},
equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null },
equipmentInstances: {},
...overrides,
} as GameState;
}
describe('Mana Calculations', () => {
describe('computeMaxMana', () => {
it('should return base mana with no upgrades', () => {
const state = createMockState();
expect(computeMaxMana(state)).toBe(100);
});
it('should add mana from manaWell skill', () => {
const state = createMockState({ skills: { manaWell: 5 } });
expect(computeMaxMana(state)).toBe(100 + 5 * 100);
});
it('should add mana from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { manaWell: 3 } });
expect(computeMaxMana(state)).toBe(100 + 3 * 500);
});
it('should stack manaWell skill and prestige', () => {
const state = createMockState({
skills: { manaWell: 5 },
prestigeUpgrades: { manaWell: 2 },
});
expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500);
});
});
describe('computeRegen', () => {
it('should return base regen with no upgrades', () => {
// Base regen is 2 (attunement regen is added separately in the store)
const state = createMockState();
expect(computeRegen(state)).toBe(2);
});
it('should add regen from manaFlow skill', () => {
// Base 2 + manaFlow 5
const state = createMockState({ skills: { manaFlow: 5 } });
expect(computeRegen(state)).toBe(2 + 5 * 1);
});
it('should add regen from manaSpring skill', () => {
// Base 2 + manaSpring 2
const state = createMockState({ skills: { manaSpring: 1 } });
expect(computeRegen(state)).toBe(2 + 2);
});
it('should multiply by temporal echo prestige', () => {
const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } });
// Base 2 * 1.2 = 2.4
expect(computeRegen(state)).toBe(2 * 1.2);
});
});
describe('computeClickMana', () => {
it('should return base click mana with no upgrades', () => {
const state = createMockState();
expect(computeClickMana(state)).toBe(1);
});
it('should add mana from manaTap skill', () => {
const state = createMockState({ skills: { manaTap: 1 } });
expect(computeClickMana(state)).toBe(1 + 1);
});
it('should add mana from manaSurge skill', () => {
const state = createMockState({ skills: { manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 3);
});
it('should stack manaTap and manaSurge', () => {
const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 1 + 3);
});
});
describe('computeElementMax', () => {
it('should return base element cap with no upgrades', () => {
const state = createMockState();
expect(computeElementMax(state)).toBe(10);
});
it('should add cap from elemAttune skill', () => {
const state = createMockState({ skills: { elemAttune: 5 } });
expect(computeElementMax(state)).toBe(10 + 5 * 50);
});
it('should add cap from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } });
expect(computeElementMax(state)).toBe(10 + 3 * 25);
});
});
});
console.log('✅ Mana calculation tests defined.');
@@ -1,180 +0,0 @@
/**
* Meditation, Insight, and Incursion Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { getMeditationBonus, calcInsight, getIncursionStrength } from '@/lib/game/stores/index';
import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants';
import type { GameState } from '../../../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference'].forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
defeatedGuardians: [],
signedPacts: [],
pactSlots: 1,
pactRitualFloor: null,
pactRitualProgress: 0,
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
paidStudySkills: {},
currentStudyTarget: null,
parallelStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null },
inventory: [],
blueprints: {},
schedule: [],
autoSchedule: false,
studyQueue: [],
craftQueue: [],
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
},
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
equippedInstances: {},
...overrides,
} as GameState;
}
// ─── Meditation Tests ──────────────────────────────────────────────
describe('Meditation Bonus', () => {
describe('getMeditationBonus', () => {
it('should start at 1x with no meditation', () => {
expect(getMeditationBonus(0, {})).toBe(1);
});
it('should ramp up over time', () => {
const bonus1hr = getMeditationBonus(25, {}); // 1 hour of ticks
expect(bonus1hr).toBeGreaterThan(1);
const bonus4hr = getMeditationBonus(100, {}); // 4 hours
expect(bonus4hr).toBeGreaterThan(bonus1hr);
});
it('should cap at 1.5x without meditation skill', () => {
const bonus = getMeditationBonus(200, {}); // 8 hours
expect(bonus).toBe(1.5);
});
it('should give 2.5x with meditation skill after 4 hours', () => {
const bonus = getMeditationBonus(100, { meditation: 1 });
expect(bonus).toBe(2.5);
});
it('should give 3.0x with deepTrance skill after 6 hours', () => {
const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 });
expect(bonus).toBe(3.0);
});
it('should give 5.0x with voidMeditation skill after 8 hours', () => {
const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 });
expect(bonus).toBe(5.0);
});
});
});
// ─── Insight Tests ────────────────────────────────────────────────
describe('Insight Calculations', () => {
describe('calcInsight', () => {
it('should calculate insight from floor progress', () => {
const state = createMockState({ maxFloorReached: 10 });
const insight = calcInsight(state);
expect(insight).toBe(10 * 15);
});
it('should calculate insight from mana gathered', () => {
const state = createMockState({ totalManaGathered: 5000 });
const insight = calcInsight(state);
// 1*15 + 5000/500 + 0 = 25
expect(insight).toBe(25);
});
it('should calculate insight from signed pacts', () => {
const state = createMockState({ signedPacts: [10, 20] });
const insight = calcInsight(state);
// 1*15 + 0 + 2*150 = 315
expect(insight).toBe(315);
});
it('should multiply by insightAmp prestige', () => {
const state = createMockState({
maxFloorReached: 10,
prestigeUpgrades: { insightAmp: 2 },
});
const insight = calcInsight(state);
expect(insight).toBe(Math.floor(10 * 15 * 1.5));
});
it('should multiply by insightHarvest skill', () => {
const state = createMockState({
maxFloorReached: 10,
skills: { insightHarvest: 3 },
});
const insight = calcInsight(state);
expect(insight).toBe(Math.floor(10 * 15 * 1.3));
});
});
});
// ─── Incursion Tests ──────────────────────────────────────────────
describe('Incursion Strength', () => {
describe('getIncursionStrength', () => {
it('should be 0 before incursion start day', () => {
expect(getIncursionStrength(19, 0)).toBe(0);
expect(getIncursionStrength(19, 23)).toBe(0);
});
it('should start at incursion start day', () => {
expect(getIncursionStrength(INCURSION_START_DAY, 1)).toBeGreaterThan(0);
});
it('should increase over time', () => {
const early = getIncursionStrength(INCURSION_START_DAY, 12);
const late = getIncursionStrength(25, 12);
expect(late).toBeGreaterThan(early);
});
it('should cap at 95%', () => {
const strength = getIncursionStrength(MAX_DAY, 23);
expect(strength).toBeLessThanOrEqual(0.95);
});
});
});
console.log('✅ Meditation, insight, and incursion tests defined.');
@@ -1,53 +0,0 @@
/**
* Spell Cost Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { canAffordSpellCost } from '@/lib/game/stores/index';
import { rawCost, elemCost } from '@/lib/game/constants';
describe('Spell Cost System', () => {
describe('rawCost', () => {
it('should create a raw mana cost', () => {
const cost = rawCost(10);
expect(cost.type).toBe('raw');
expect(cost.amount).toBe(10);
});
});
describe('elemCost', () => {
it('should create an elemental mana cost', () => {
const cost = elemCost('fire', 5);
expect(cost.type).toBe('element');
expect(cost.element).toBe('fire');
});
});
describe('canAffordSpellCost', () => {
it('should allow raw mana costs when enough raw mana', () => {
const cost = { type: 'raw' as const, amount: 10 };
const elements = { fire: { current: 0, max: 10, unlocked: true } };
expect(canAffordSpellCost(cost, 100, elements)).toBe(true);
});
it('should deny raw mana costs when not enough raw mana', () => {
const cost = { type: 'raw' as const, amount: 100 };
const elements = { fire: { current: 0, max: 10, unlocked: true } };
expect(canAffordSpellCost(cost, 50, elements)).toBe(false);
});
it('should allow elemental costs when enough element mana', () => {
const cost = { type: 'element' as const, element: 'fire', amount: 5 };
const elements = { fire: { current: 10, max: 10, unlocked: true } };
expect(canAffordSpellCost(cost, 0, elements)).toBe(true);
});
it('should deny elemental costs when element not unlocked', () => {
const cost = { type: 'element' as const, element: 'fire', amount: 5 };
const elements = { fire: { current: 10, max: 10, unlocked: false } };
expect(canAffordSpellCost(cost, 100, elements)).toBe(false);
});
});
});
console.log('✅ Spell cost tests defined.');
@@ -1,34 +0,0 @@
/**
* Study Speed Function Tests for Stores Index
*/
import { describe, it, expect } from 'vitest';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores/index';
describe('Study Speed Functions', () => {
describe('getStudySpeedMultiplier', () => {
it('should return 1 with no quickLearner skill', () => {
expect(getStudySpeedMultiplier({})).toBe(1);
});
it('should increase by 10% per level', () => {
expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1);
expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5);
expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0);
});
});
describe('getStudyCostMultiplier', () => {
it('should return 1 with no focusedMind skill', () => {
expect(getStudyCostMultiplier({})).toBe(1);
});
it('should decrease by 5% per level', () => {
expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95);
expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75);
expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5);
});
});
});
console.log('✅ Study speed function tests defined.');
@@ -1,39 +0,0 @@
/**
* Utility Function Tests for Stores
*/
import { describe, it, expect } from 'vitest';
import { fmt, fmtDec } from '@/lib/game/stores/index';
describe('Utility Functions', () => {
describe('fmt', () => {
it('should format small numbers', () => {
expect(fmt(0)).toBe('0');
expect(fmt(1)).toBe('1');
expect(fmt(999)).toBe('999');
});
it('should format thousands', () => {
expect(fmt(1000)).toBe('1.0K');
expect(fmt(1500)).toBe('1.5K');
});
it('should format millions', () => {
expect(fmt(1000000)).toBe('1.00M');
expect(fmt(1500000)).toBe('1.50M');
});
it('should format billions', () => {
expect(fmt(1000000000)).toBe('1.00B');
});
});
describe('fmtDec', () => {
it('should format decimals', () => {
expect(fmtDec(1.234, 2)).toBe('1.23');
expect(fmtDec(1.5, 1)).toBe('1.5');
});
});
});
console.log('✅ Utility function tests defined.');
@@ -1,59 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../manaStore';
import { useGameStore } from '../gameStore';
import { useAttunementStore } from '../attunementStore';
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
describe('Mana Conversion Fix - Attunements deduct from regen, not pool', () => {
beforeEach(() => {
// Reset all stores
useManaStore.setState({
rawMana: 100,
elements: Object.fromEntries(
Object.keys(useManaStore.getState().elements).map(k => [
k,
{ current: 0, max: 10, unlocked: k === 'transference' }
])
),
});
useAttunementStore.setState({
attunements: {
enchanter: { active: true, level: 1 }
}
});
});
it('should deduct conversion cost from regen, not mana pool', () => {
const initialState = useManaStore.getState();
const initialRawMana = initialState.rawMana;
// Run a few ticks
for (let i = 0; i < 10; i++) {
useGameStore.getState().tick();
}
const finalState = useManaStore.getState();
// Mana pool should not be drained by conversion (only regen is reduced)
expect(finalState.rawMana).toBeGreaterThan(initialRawMana - 50); // Should not drop significantly
});
it('should reduce effective regen by conversion rate', () => {
// The conversion rate is subtracted from effective regen in gameStore.ts
// This is tested implicitly in the tick tests
expect(true).toBe(true);
});
it('should not get stuck below mana cap', () => {
useManaStore.setState({ rawMana: 99, elements: { ...useManaStore.getState().elements } });
// Run many ticks to approach mana cap
for (let i = 0; i < 1000; i++) {
useGameStore.getState().tick();
}
const state = useManaStore.getState();
// Should be able to reach mana cap (not stuck at cap -1)
expect(state.rawMana).toBeGreaterThan(98); // Should be near cap, not stuck at 99
});
});
@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '@/lib/game/stores';
describe('useManaStore', () => {
beforeEach(() => {
// Reset to initial state by setting known initial values
useManaStore.setState({
rawMana: 10,
meditateTicks: 0,
totalManaGathered: 0,
elements: {},
});
});
it('spendRawMana reduces rawMana correctly', () => {
useManaStore.setState({ rawMana: 100 });
const result = useManaStore.getState().spendRawMana(30);
expect(result).toBe(true);
expect(useManaStore.getState().rawMana).toBe(70);
});
it('spendRawMana returns false if insufficient mana', () => {
useManaStore.setState({ rawMana: 20 });
const result = useManaStore.getState().spendRawMana(50);
expect(result).toBe(false);
expect(useManaStore.getState().rawMana).toBe(20); // unchanged
});
it('rawMana never goes below 0', () => {
useManaStore.setState({ rawMana: 10 });
const result = useManaStore.getState().spendRawMana(20);
expect(result).toBe(false);
expect(useManaStore.getState().rawMana).toBeGreaterThanOrEqual(0);
});
});
@@ -1,51 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { computeRegen } from '@/lib/game/store-modules/computed-stats';
import { getIncursionStrength } from '@/lib/game/store-modules/computed-stats';
import { useSkillStore } from '@/lib/game/stores';
describe('computeRegen', () => {
it('Returns base regen when no skills', () => {
const result = computeRegen({
skills: {},
skillTiers: {},
skillUpgrades: {},
prestigeUpgrades: {},
});
// Base regen is positive (not zero) due to base game settings
expect(result).toBeGreaterThanOrEqual(0);
});
it('Increases with manaWell skill level', () => {
const base = computeRegen({
skills: { manaWell: 0 },
skillTiers: {},
skillUpgrades: {},
prestigeUpgrades: {},
});
const withSkill = computeRegen({
skills: { manaWell: 5 },
skillTiers: {},
skillUpgrades: {},
prestigeUpgrades: {},
});
// With higher skill, regen should be greater
// With skill, regen should be at least base (may not increase if skill effect is different)
expect(withSkill).toBeGreaterThanOrEqual(base);
});
});
describe('effectiveRegen', () => {
it('effectiveRegen = baseRegen * (1 - incursionStrength)', () => {
const baseRegen = 10;
const day = 20; // After incursion start
const hour = 12;
const incursionStrength = getIncursionStrength(day, hour);
const effectiveRegen = baseRegen * (1 - incursionStrength);
expect(effectiveRegen).toBeLessThan(baseRegen);
expect(effectiveRegen).toBeGreaterThanOrEqual(0);
});
});
@@ -1,65 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useSkillStore } from '@/lib/game/stores';
import { useManaStore } from '@/lib/game/stores';
describe('useSkillStore', () => {
beforeEach(() => {
// Reset skill store
useSkillStore.setState({
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
paidStudySkills: {},
currentStudyTarget: null,
parallelStudyTarget: null,
});
// Reset mana store
useManaStore.setState({
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements: {},
});
});
it('startStudyingSkill returns { started: false } if rawMana < cost', () => {
useManaStore.setState({ rawMana: 0 });
const result = useSkillStore.getState().startStudyingSkill('manaWell', false);
expect(result.started).toBe(false);
});
it('startStudyingSkill deducts mana when started', () => {
const initialMana = useManaStore.getState().rawMana;
const result = useSkillStore.getState().startStudyingSkill('manaWell', false);
if (result.started && result.cost > 0) {
expect(useManaStore.getState().rawMana).toBeLessThan(initialMana);
}
});
it('startStudyingSkill does NOT deduct if isAlreadyPaid', () => {
const initialMana = useManaStore.getState().rawMana;
const result = useSkillStore.getState().startStudyingSkill('manaWell', true);
if (result.started) {
expect(result.cost).toBe(0);
// Mana should not be deducted
expect(useManaStore.getState().rawMana).toBe(initialMana);
}
});
it.skip('cancelStudy clears currentStudyTarget', () => {
// This test is skipped due to time constraints
// First start studying
useManaStore.setState({ rawMana: 100 });
useSkillStore.getState().startStudyingSkill('manaWell', false);
expect(useSkillStore.getState().currentStudyTarget).not.toBeNull();
// Cancel study
useSkillStore.getState().cancelStudy(0);
expect(useSkillStore.getState().currentStudyTarget).toBeNull();
});
});
@@ -1,68 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { canAffordSpellCost } from '@/lib/game/stores';
import { formatSpellCost } from '@/lib/game/formatting';
import { useManaStore } from '@/lib/game/stores';
import type { SpellCost } from '@/lib/game/types';
describe('canAffordSpellCost', () => {
beforeEach(() => {
// Reset to initial state
useManaStore.setState({
rawMana: 10,
elements: {},
});
});
it('returns true when rawMana >= cost.amount (type: raw)', () => {
useManaStore.setState({ rawMana: 100 });
const cost: SpellCost = { type: 'raw', amount: 50 };
const state = useManaStore.getState();
expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(true);
});
it('returns false when insufficient rawMana', () => {
useManaStore.setState({ rawMana: 20 });
const cost: SpellCost = { type: 'raw', amount: 50 };
const state = useManaStore.getState();
expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(false);
});
it('returns true when has enough element mana', () => {
// Set the Fire element with enough amount
useManaStore.setState((state) => ({
elements: {
...state.elements,
Fire: { current: 100, max: 200, unlocked: true } as any,
},
}));
const cost: SpellCost = { type: 'element', element: 'Fire', amount: 50 };
const state = useManaStore.getState();
expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(true);
});
});
describe('formatSpellCost', () => {
it('returns a non-empty string for raw cost', () => {
const cost: SpellCost = { type: 'raw', amount: 50 };
const result = formatSpellCost(cost);
expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
it('returns a non-empty string for element cost', () => {
const cost: SpellCost = { type: 'element', element: 'Fire', amount: 30 };
const result = formatSpellCost(cost);
expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
it('does NOT throw when cost.type is element', () => {
const cost: SpellCost = { type: 'element', element: 'Water', amount: 25 };
expect(() => formatSpellCost(cost)).not.toThrow();
});
it('handles edge case: zero amount', () => {
const cost: SpellCost = { type: 'raw', amount: 0 };
expect(() => formatSpellCost(cost)).not.toThrow();
});
});
@@ -1,64 +0,0 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { useCombatStore } from '../combatStore';
import { useUIStore } from '../uiStore';
describe('Spire Exit Action — Bug 1 Fix', () => {
beforeEach(() => {
// Reset combat store to a known state
useCombatStore.setState({
currentFloor: 5,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 5,
activeSpell: 'manaBolt',
currentAction: 'climb' as const,
castProgress: 0,
spireMode: true,
climbDirection: 'down' as const,
isDescending: true,
});
});
afterEach(() => {
useCombatStore.setState({
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
spireMode: false,
climbDirection: null,
isDescending: false,
});
useUIStore.setState({ logs: [] });
});
it('should reset currentAction to "meditate" when exitSpireMode is called', () => {
// Pre-condition: action is "climb" while in spire
expect(useCombatStore.getState().currentAction).toBe('climb');
expect(useCombatStore.getState().spireMode).toBe(true);
// Exit spire mode
useCombatStore.getState().exitSpireMode();
// Post-condition: currentAction must be "meditate"
expect(useCombatStore.getState().currentAction).toBe('meditate');
});
it('should set spireMode to false when exitSpireMode is called', () => {
useCombatStore.getState().exitSpireMode();
expect(useCombatStore.getState().spireMode).toBe(false);
});
it('should clear climbDirection when exitSpireMode is called', () => {
useCombatStore.getState().exitSpireMode();
expect(useCombatStore.getState().climbDirection).toBeNull();
});
it('should clear isDescending when exitSpireMode is called', () => {
useCombatStore.getState().exitSpireMode();
expect(useCombatStore.getState().isDescending).toBe(false);
});
});
@@ -1,94 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from '../combatStore';
import { useManaStore } from '../manaStore';
import { useCraftingStore } from '../craftingStore';
describe('SpireTab Refresh & Casting Fixes', () => {
beforeEach(() => {
// Reset stores
useCombatStore.setState({
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
activeSpell: 'manaBolt',
currentAction: 'climb',
castProgress: 0,
spireMode: false,
equipmentSpellStates: [
{ spellId: 'manaBolt', sourceEquipment: 'test-staff', castProgress: 0 }
],
});
useManaStore.setState({
rawMana: 100,
elements: {
fire: { current: 50, max: 100, unlocked: true },
transference: { current: 20, max: 100, unlocked: true },
},
});
});
it('should update equipment spell cast progress', () => {
const initialState = useCombatStore.getState();
expect(initialState.castProgress).toBe(0);
// Simulate combat tick (simplified)
const newProgress = 0.5;
useCombatStore.setState({ castProgress: newProgress });
const updatedState = useCombatStore.getState();
expect(updatedState.castProgress).toBe(newProgress);
});
it('should deduct mana when casting spells', () => {
const initialMana = useManaStore.getState().rawMana;
expect(initialMana).toBeGreaterThan(0);
// Simulate spell cast (simplified - mana should decrease)
const spellCost = 10;
useManaStore.setState({ rawMana: initialMana - spellCost });
const updatedMana = useManaStore.getState().rawMana;
expect(updatedMana).toBe(initialMana - spellCost);
});
it('should have exitSpireMode function', () => {
const state = useCombatStore.getState();
expect(typeof state.exitSpireMode).toBe('function');
});
it('should set spireMode to false when exitSpireMode is called', () => {
// Enter spire mode first
useCombatStore.setState({ spireMode: true });
expect(useCombatStore.getState().spireMode).toBe(true);
// Exit spire mode
useCombatStore.getState().exitSpireMode();
expect(useCombatStore.getState().spireMode).toBe(false);
});
it('should NOT show study components when spireMode is true', () => {
// This is a UI test - we can only verify the state
useCombatStore.setState({ spireMode: true });
const state = useCombatStore.getState();
expect(state.spireMode).toBe(true);
// Study target should be null or ignored when in spire mode
// (UI conditionally renders based on spireMode)
});
it('should process equipment spell states in combat tick', () => {
const state = useCombatStore.getState();
expect(state.equipmentSpellStates.length).toBeGreaterThan(0);
// Simulate equipment spell progress
const updatedStates = state.equipmentSpellStates.map(s =>
({ ...s, castProgress: 0.5 })
);
useCombatStore.setState({ equipmentSpellStates: updatedStates });
const updatedState = useCombatStore.getState();
expect(updatedState.equipmentSpellStates[0].castProgress).toBe(0.5);
});
});
@@ -1,93 +0,0 @@
/**
* CombatStore Method Tests
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from '@/lib/game/stores/combatStore';
// Reset stores before each test
beforeEach(() => {
useCombatStore.getState().resetCombat(1);
});
describe('CombatStore', () => {
describe('initial state', () => {
it('should start with manaBolt learned', () => {
const state = useCombatStore.getState();
expect(state.spells.manaBolt.learned).toBe(true);
});
it('should start at floor 1', () => {
const state = useCombatStore.getState();
expect(state.currentFloor).toBe(1);
});
});
describe('setAction', () => {
it('should change current action', () => {
useCombatStore.getState().setAction('climb');
const state = useCombatStore.getState();
expect(state.currentAction).toBe('climb');
});
});
describe('setSpell', () => {
it('should change active spell if learned', () => {
// Learn another spell
useCombatStore.getState().learnSpell('fireball');
useCombatStore.getState().setSpell('fireball');
const state = useCombatStore.getState();
expect(state.activeSpell).toBe('fireball');
});
it('should not change to unlearned spell', () => {
useCombatStore.getState().setSpell('fireball');
const state = useCombatStore.getState();
expect(state.activeSpell).toBe('manaBolt'); // Still manaBolt
});
});
describe('learnSpell', () => {
it('should add spell to learned spells', () => {
useCombatStore.getState().learnSpell('fireball');
const state = useCombatStore.getState();
expect(state.spells.fireball.learned).toBe(true);
});
});
describe('advanceFloor', () => {
it('should increment floor', () => {
useCombatStore.getState().advanceFloor();
const state = useCombatStore.getState();
expect(state.currentFloor).toBe(2);
});
it('should not exceed floor 100', () => {
useCombatStore.setState({ currentFloor: 100 });
useCombatStore.getState().advanceFloor();
const state = useCombatStore.getState();
expect(state.currentFloor).toBe(100);
});
it('should update maxFloorReached', () => {
useCombatStore.setState({ maxFloorReached: 1 });
useCombatStore.getState().advanceFloor();
const state = useCombatStore.getState();
expect(state.maxFloorReached).toBe(2);
});
});
});
console.log('✅ CombatStore method tests defined.');
@@ -1,140 +0,0 @@
/**
* ManaStore Method Tests
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '@/lib/game/stores/manaStore';
// Reset stores before each test
beforeEach(() => {
useManaStore.getState().resetMana({}, {}, {}, {});
});
describe('ManaStore', () => {
describe('initial state', () => {
it('should have base elements unlocked', () => {
const state = useManaStore.getState();
// Only transference should be unlocked at start
expect(state.elements.transference.unlocked).toBe(true);
// Base elements should be locked
expect(state.elements.fire.unlocked).toBe(false);
expect(state.elements.water.unlocked).toBe(false);
expect(state.elements.air.unlocked).toBe(false);
expect(state.elements.earth.unlocked).toBe(false);
});
it('should have exotic elements locked', () => {
const state = useManaStore.getState();
expect(state.elements.void.unlocked).toBe(false);
expect(state.elements.stellar.unlocked).toBe(false);
});
});
describe('convertMana', () => {
it('should convert raw mana to elemental', () => {
useManaStore.setState({ rawMana: 200 });
// Transference is unlocked at start
const result = useManaStore.getState().convertMana('transference', 1);
expect(result).toBe(true);
const state = useManaStore.getState();
expect(state.rawMana).toBe(100);
expect(state.elements.transference.current).toBe(1);
});
it('should not convert when not enough raw mana', () => {
useManaStore.setState({ rawMana: 50 });
const result = useManaStore.getState().convertMana('fire', 1);
expect(result).toBe(false);
});
it('should not convert when element at max', () => {
useManaStore.setState({
rawMana: 500,
elements: {
...useManaStore.getState().elements,
fire: { current: 10, max: 10, unlocked: true }
}
});
const result = useManaStore.getState().convertMana('fire', 1);
expect(result).toBe(false);
});
it('should not convert to locked element', () => {
useManaStore.setState({ rawMana: 500 });
const result = useManaStore.getState().convertMana('void', 1);
expect(result).toBe(false);
});
});
describe('unlockElement', () => {
it('should unlock element when have enough mana', () => {
useManaStore.setState({ rawMana: 500 });
const result = useManaStore.getState().unlockElement('light', 500);
expect(result).toBe(true);
const state = useManaStore.getState();
expect(state.elements.light.unlocked).toBe(true);
});
it('should not unlock when not enough mana', () => {
useManaStore.setState({ rawMana: 100 });
const result = useManaStore.getState().unlockElement('light', 500);
expect(result).toBe(false);
});
});
describe('craftComposite', () => {
it('should craft composite element with correct ingredients', () => {
// Set up ingredients for metal (fire + earth)
useManaStore.setState({
elements: {
...useManaStore.getState().elements,
fire: { current: 5, max: 10, unlocked: true },
earth: { current: 5, max: 10, unlocked: true },
}
});
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
expect(result).toBe(true);
const state = useManaStore.getState();
expect(state.elements.fire.current).toBe(4);
expect(state.elements.earth.current).toBe(4);
expect(state.elements.metal.current).toBe(1);
expect(state.elements.metal.unlocked).toBe(true);
});
it('should not craft without ingredients', () => {
useManaStore.setState({
elements: {
...useManaStore.getState().elements,
fire: { current: 0, max: 10, unlocked: true },
earth: { current: 0, max: 10, unlocked: true },
}
});
const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']);
expect(result).toBe(false);
});
});
});
console.log('✅ ManaStore method tests defined.');
@@ -1,150 +0,0 @@
/**
* PrestigeStore Method Tests
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { usePrestigeStore } from '@/lib/game/stores/prestigeStore';
import { useManaStore } from '../manaStore';
// Reset stores before each test
beforeEach(() => {
usePrestigeStore.getState().resetPrestige();
useManaStore.getState().resetMana({}, {}, {}, {});
});
describe('PrestigeStore', () => {
describe('initial state', () => {
it('should start with 0 insight', () => {
const state = usePrestigeStore.getState();
expect(state.insight).toBe(0);
});
it('should start with 3 memory slots', () => {
const state = usePrestigeStore.getState();
expect(state.memorySlots).toBe(3);
});
it('should start with 1 pact slot', () => {
const state = usePrestigeStore.getState();
expect(state.pactSlots).toBe(1);
});
});
describe('doPrestige', () => {
it('should deduct insight and add upgrade', () => {
usePrestigeStore.setState({ insight: 1000 });
usePrestigeStore.getState().doPrestige('manaWell');
const state = usePrestigeStore.getState();
expect(state.prestigeUpgrades.manaWell).toBe(1);
expect(state.insight).toBeLessThan(1000);
});
it('should not upgrade without enough insight', () => {
usePrestigeStore.setState({ insight: 100 });
usePrestigeStore.getState().doPrestige('manaWell');
const state = usePrestigeStore.getState();
expect(state.prestigeUpgrades.manaWell).toBeUndefined();
});
});
describe('addMemory', () => {
it('should add memory within slot limit', () => {
const memory = { skillId: 'manaWell', level: 5, tier: 1, upgrades: [] };
usePrestigeStore.getState().addMemory(memory);
const state = usePrestigeStore.getState();
expect(state.memories.length).toBe(1);
});
it('should not add duplicate memory', () => {
const memory = { skillId: 'manaWell', level: 5, tier: 1, upgrades: [] };
usePrestigeStore.getState().addMemory(memory);
usePrestigeStore.getState().addMemory(memory);
const state = usePrestigeStore.getState();
expect(state.memories.length).toBe(1);
});
it('should not exceed memory slots', () => {
// Fill memory slots
for (let i = 0; i < 5; i++) {
usePrestigeStore.getState().addMemory({
skillId: `skill${i}`,
level: 5,
tier: 1,
upgrades: []
});
}
const state = usePrestigeStore.getState();
expect(state.memories.length).toBe(3); // Default 3 slots
});
});
describe('startPactRitual', () => {
it('should start ritual for defeated guardian', () => {
usePrestigeStore.setState({
defeatedGuardians: [10],
signedPacts: []
});
useManaStore.setState({ rawMana: 1000 });
const result = usePrestigeStore.getState().startPactRitual(10, 1000);
expect(result).toBe(true);
const state = usePrestigeStore.getState();
expect(state.pactRitualFloor).toBe(10);
});
it('should not start ritual for undefeated guardian', () => {
usePrestigeStore.setState({
defeatedGuardians: [],
signedPacts: []
});
useManaStore.setState({ rawMana: 1000 });
const result = usePrestigeStore.getState().startPactRitual(10, 1000);
expect(result).toBe(false);
});
it('should not start ritual without enough mana', () => {
usePrestigeStore.setState({
defeatedGuardians: [10],
signedPacts: []
});
useManaStore.setState({ rawMana: 100 });
const result = usePrestigeStore.getState().startPactRitual(10, 100);
expect(result).toBe(false);
});
});
describe('addSignedPact', () => {
it('should add pact to signed list', () => {
usePrestigeStore.getState().addSignedPact(10);
const state = usePrestigeStore.getState();
expect(state.signedPacts).toContain(10);
});
});
describe('addDefeatedGuardian', () => {
it('should add guardian to defeated list', () => {
usePrestigeStore.getState().addDefeatedGuardian(10);
const state = usePrestigeStore.getState();
expect(state.defeatedGuardians).toContain(10);
});
});
});
console.log('✅ PrestigeStore method tests defined.');
@@ -1,172 +0,0 @@
/**
* SkillStore Method Tests
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useSkillStore } from '@/lib/game/stores/skillStore';
// Reset stores before each test
beforeEach(() => {
useSkillStore.getState().resetSkills();
});
describe('SkillStore', () => {
describe('startStudyingSkill', () => {
it('should start studying a skill when have enough mana', () => {
const skillStore = useSkillStore.getState();
const result = skillStore.startStudyingSkill('manaWell', 100);
expect(result.started).toBe(true);
expect(result.cost).toBe(100); // base cost for level 1
const newState = useSkillStore.getState();
expect(newState.currentStudyTarget).not.toBeNull();
expect(newState.currentStudyTarget?.type).toBe('skill');
expect(newState.currentStudyTarget?.id).toBe('manaWell');
});
it('should not start studying when not enough mana', () => {
const skillStore = useSkillStore.getState();
const result = skillStore.startStudyingSkill('manaWell', 50);
expect(result.started).toBe(false);
const newState = useSkillStore.getState();
expect(newState.currentStudyTarget).toBeNull();
});
it('should not start studying skill at max level', () => {
// Set skill to max level
useSkillStore.setState({ skills: { manaWell: 10 } });
const skillStore = useSkillStore.getState();
const result = skillStore.startStudyingSkill('manaWell', 1000);
expect(result.started).toBe(false);
});
it('should not start studying without prerequisites', () => {
const skillStore = useSkillStore.getState();
// manaOverflow requires manaWell level 3
const result = skillStore.startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(false);
});
it('should start studying with prerequisites met', () => {
useSkillStore.setState({ skills: { manaWell: 3 } });
const skillStore = useSkillStore.getState();
const result = skillStore.startStudyingSkill('manaOverflow', 1000);
expect(result.started).toBe(true);
});
it('should be free to resume if already paid', () => {
// First, start studying (which marks as paid)
const skillStore = useSkillStore.getState();
skillStore.startStudyingSkill('manaWell', 100);
// Cancel study
skillStore.cancelStudy(0);
// Resume should be free
const newState = useSkillStore.getState();
const result = newState.startStudyingSkill('manaWell', 0);
expect(result.started).toBe(true);
expect(result.cost).toBe(0);
});
});
describe('updateStudyProgress', () => {
it('should progress study target', () => {
// Start studying
useSkillStore.getState().startStudyingSkill('manaWell', 100);
// Update progress
const result = useSkillStore.getState().updateStudyProgress(1);
expect(result.completed).toBe(false);
const state = useSkillStore.getState();
expect(state.currentStudyTarget?.progress).toBe(1);
});
it('should complete study when progress reaches required', () => {
// Start studying manaWell (4 hours study time)
useSkillStore.getState().startStudyingSkill('manaWell', 100);
// Update with enough progress
const result = useSkillStore.getState().updateStudyProgress(4);
expect(result.completed).toBe(true);
const state = useSkillStore.getState();
expect(state.currentStudyTarget).toBeNull();
});
});
describe('incrementSkillLevel', () => {
it('should increment skill level', () => {
useSkillStore.setState({ skills: { manaWell: 0 } });
useSkillStore.getState().incrementSkillLevel('manaWell');
const state = useSkillStore.getState();
expect(state.skills.manaWell).toBe(1);
});
it('should clear skill progress', () => {
useSkillStore.setState({
skills: { manaWell: 0 },
skillProgress: { manaWell: 2 }
});
useSkillStore.getState().incrementSkillLevel('manaWell');
const state = useSkillStore.getState();
expect(state.skillProgress.manaWell).toBe(0);
});
});
describe('cancelStudy', () => {
it('should clear study target', () => {
useSkillStore.getState().startStudyingSkill('manaWell', 100);
useSkillStore.getState().cancelStudy(0);
const state = useSkillStore.getState();
expect(state.currentStudyTarget).toBeNull();
});
it('should save progress with retention bonus', () => {
useSkillStore.getState().startStudyingSkill('manaWell', 100);
useSkillStore.getState().updateStudyProgress(2); // 2 hours progress
// Cancel with 50% retention bonus
// Retention bonus limits how much of the *required* time can be saved
// Required = 4 hours, so 50% = 2 hours max
// Progress = 2 hours, so we save all of it (within limit)
useSkillStore.getState().cancelStudy(0.5);
const state = useSkillStore.getState();
// Saved progress should be min(progress, required * retentionBonus) = min(2, 4*0.5) = min(2, 2) = 2
expect(state.skillProgress.manaWell).toBe(2);
});
it('should limit saved progress to retention bonus cap', () => {
useSkillStore.getState().startStudyingSkill('manaWell', 100);
useSkillStore.getState().updateStudyProgress(3); // 3 hours progress (out of 4 required)
// Cancel with 50% retention bonus
// Cap is 4 * 0.5 = 2 hours, progress is 3, so we save 2 (the cap)
useSkillStore.getState().cancelStudy(0.5);
const state = useSkillStore.getState();
expect(state.skillProgress.manaWell).toBe(2);
});
});
});
console.log('✅ SkillStore method tests defined.');
@@ -1,65 +0,0 @@
/**
* UIStore Method Tests
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useUIStore } from '@/lib/game/stores/uiStore';
// Reset stores before each test
beforeEach(() => {
useUIStore.getState().resetUI();
});
describe('UIStore', () => {
describe('addLog', () => {
it('should add message to logs', () => {
useUIStore.getState().addLog('Test message');
const state = useUIStore.getState();
expect(state.logs[0]).toBe('Test message');
});
it('should limit log size', () => {
for (let i = 0; i < 100; i++) {
useUIStore.getState().addLog(`Message ${i}`);
}
const state = useUIStore.getState();
expect(state.logs.length).toBeLessThanOrEqual(50);
});
});
describe('togglePause', () => {
it('should toggle pause state', () => {
const initial = useUIStore.getState().paused;
useUIStore.getState().togglePause();
expect(useUIStore.getState().paused).toBe(!initial);
useUIStore.getState().togglePause();
expect(useUIStore.getState().paused).toBe(initial);
});
});
describe('setGameOver', () => {
it('should set game over state', () => {
useUIStore.getState().setGameOver(true, false);
const state = useUIStore.getState();
expect(state.gameOver).toBe(true);
expect(state.victory).toBe(false);
});
it('should set victory state', () => {
useUIStore.getState().setGameOver(true, true);
const state = useUIStore.getState();
expect(state.gameOver).toBe(true);
expect(state.victory).toBe(true);
});
});
});
console.log('✅ UIStore method tests defined.');
@@ -1,96 +0,0 @@
/**
* Damage Calculation Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { calcDamage } from '@/lib/game/utils';
import type { GameState } from '../../../types';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
signedPacts: [],
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
currentRoom: {
roomType: 'combat',
enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }],
},
spells: {
manaBolt: { learned: true, level: 1, studyProgress: 0 },
},
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
parallelStudyTarget: null,
equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null },
inventory: [],
blueprints: {},
schedule: [],
autoSchedule: false,
studyQueue: [],
craftQueue: [],
currentStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
},
equippedInstances: {},
equipmentInstances: {},
enchantmentDesigns: [],
designProgress: null,
preparationProgress: null,
applicationProgress: null,
equipmentCraftingProgress: null,
unlockedEffects: [],
equipmentSpellStates: [],
lootInventory: { materials: {}, blueprints: [] },
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
achievements: { unlocked: [], progress: {} },
totalSpellsCast: 0,
totalDamageDealt: 0,
totalCraftsCompleted: 0,
...overrides,
} as GameState;
}
describe('Damage Calculation', () => {
describe('calcDamage', () => {
it('should return spell base damage with no bonuses', () => {
const state = createMockState();
const dmg = calcDamage(state, 'manaBolt');
expect(dmg).toBeGreaterThanOrEqual(5);
});
});
});
console.log('✅ Damage calculation tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,40 +0,0 @@
/**
* Floor Function Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { getFloorMaxHP, getFloorElement } from '@/lib/game/utils';
import { GUARDIANS } from '@/lib/game/constants';
describe('Floor Functions', () => {
describe('getFloorMaxHP', () => {
it('should return guardian HP for guardian floors', () => {
expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp);
expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp);
});
it('should scale HP for non-guardian floors', () => {
expect(getFloorMaxHP(1)).toBeGreaterThan(0);
expect(getFloorMaxHP(5)).toBeGreaterThan(getFloorMaxHP(1));
});
});
describe('getFloorElement', () => {
it('should cycle through elements in order', () => {
expect(getFloorElement(1)).toBe('fire');
expect(getFloorElement(2)).toBe('water');
expect(getFloorElement(3)).toBe('air');
expect(getFloorElement(4)).toBe('earth');
});
it('should wrap around after cycle', () => {
// FLOOR_ELEM_CYCLE has 7 elements: fire, water, air, earth, light, dark, death
// Floor 1 = index 0 = fire, Floor 7 = index 6 = death, Floor 8 = index 0 = fire
expect(getFloorElement(7)).toBe('death');
expect(getFloorElement(8)).toBe('fire');
expect(getFloorElement(9)).toBe('water');
});
});
});
console.log('✅ Floor function tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,49 +0,0 @@
/**
* Formatting Function Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { fmt, fmtDec } from '@/lib/game/utils';
describe('Formatting Functions', () => {
describe('fmt (format number)', () => {
it('should format numbers less than 1000 as integers', () => {
expect(fmt(0)).toBe('0');
expect(fmt(1)).toBe('1');
expect(fmt(999)).toBe('999');
});
it('should format thousands with K suffix', () => {
expect(fmt(1000)).toBe('1.0K');
expect(fmt(1500)).toBe('1.5K');
});
it('should format millions with M suffix', () => {
expect(fmt(1000000)).toBe('1.00M');
expect(fmt(1500000)).toBe('1.50M');
});
it('should format billions with B suffix', () => {
expect(fmt(1000000000)).toBe('1.00B');
});
it('should handle non-finite numbers', () => {
expect(fmt(Infinity)).toBe('0');
expect(fmt(NaN)).toBe('0');
});
});
describe('fmtDec (format decimal)', () => {
it('should format numbers with specified decimal places', () => {
expect(fmtDec(1.234, 2)).toBe('1.23');
expect(fmtDec(1.567, 1)).toBe('1.6');
});
it('should handle non-finite numbers', () => {
expect(fmtDec(Infinity, 2)).toBe('0');
expect(fmtDec(NaN, 2)).toBe('0');
});
});
});
console.log('✅ Formatting function tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,26 +0,0 @@
/**
* Guardian Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { GUARDIANS } from '@/lib/game/constants';
describe('Guardians', () => {
it('should have guardians on expected floors', () => {
const guardianFloors = [10, 20, 30, 40, 50, 60, 80, 90, 100];
guardianFloors.forEach(floor => {
expect(GUARDIANS[floor]).toBeDefined();
});
});
it('should have increasing HP across guardians', () => {
const guardianFloors = [10, 20, 30, 40, 50, 60, 80, 90, 100];
let prevHP = 0;
guardianFloors.forEach(floor => {
expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP);
prevHP = GUARDIANS[floor].hp;
});
});
});
console.log('✅ Guardian tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,26 +0,0 @@
/**
* Incursion Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { getIncursionStrength } from '@/lib/game/utils';
import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants';
describe('Incursion Strength', () => {
describe('getIncursionStrength', () => {
it('should be 0 before incursion start day', () => {
expect(getIncursionStrength(19, 0)).toBe(0);
});
it('should start at incursion start day', () => {
expect(getIncursionStrength(INCURSION_START_DAY, 1)).toBeGreaterThan(0);
});
it('should cap at 95%', () => {
const strength = getIncursionStrength(MAX_DAY, 23);
expect(strength).toBeLessThanOrEqual(0.95);
});
});
});
console.log('✅ Incursion tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,96 +0,0 @@
/**
* Insight Calculation Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { calcInsight } from '@/lib/game/utils';
import type { GameState } from '../../../types';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
signedPacts: [],
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
parallelStudyTarget: null,
equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null },
inventory: [],
blueprints: {},
schedule: [],
autoSchedule: false,
studyQueue: [],
craftQueue: [],
currentStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
},
equippedInstances: {},
equipmentInstances: {},
enchantmentDesigns: [],
designProgress: null,
preparationProgress: null,
applicationProgress: null,
equipmentCraftingProgress: null,
unlockedEffects: [],
equipmentSpellStates: [],
lootInventory: { materials: {}, blueprints: [] },
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
achievements: { unlocked: [], progress: {} },
totalSpellsCast: 0,
totalDamageDealt: 0,
totalCraftsCompleted: 0,
...overrides,
} as GameState;
}
describe('Insight Calculation', () => {
describe('calcInsight', () => {
it('should calculate insight from floor progress', () => {
const state = createMockState({ maxFloorReached: 10 });
const insight = calcInsight(state);
expect(insight).toBe(10 * 15);
});
it('should calculate insight from signed pacts', () => {
const state = createMockState({ signedPacts: [10, 20] });
const insight = calcInsight(state);
expect(insight).toBeGreaterThan(0);
});
});
});
console.log('✅ Insight calculation tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,188 +0,0 @@
/**
* Mana Calculation Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { computeMaxMana, computeRegen, computeClickMana } from '@/lib/game/utils';
import { computeElementMax } from '@/lib/game/stores/index';
import type { GameState } from '../../../types';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) };
});
return {
day: 1,
hour: 0,
loopCount: 0,
gameOver: false,
victory: false,
paused: false,
rawMana: 100,
meditateTicks: 0,
totalManaGathered: 0,
elements,
currentFloor: 1,
floorHP: 100,
floorMaxHP: 100,
maxFloorReached: 1,
signedPacts: [],
activeSpell: 'manaBolt',
currentAction: 'meditate',
castProgress: 0,
currentRoom: {
roomType: 'combat',
enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }],
},
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
skills: {},
skillProgress: {},
skillUpgrades: {},
skillTiers: {},
parallelStudyTarget: null,
equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null },
inventory: [],
blueprints: {},
schedule: [],
autoSchedule: false,
studyQueue: [],
craftQueue: [],
currentStudyTarget: null,
insight: 0,
totalInsight: 0,
prestigeUpgrades: {},
memorySlots: 3,
memories: [],
incursionStrength: 0,
containmentWards: 0,
log: [],
loopInsight: 0,
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 },
},
equippedInstances: {},
equipmentInstances: {},
enchantmentDesigns: [],
designProgress: null,
preparationProgress: null,
applicationProgress: null,
equipmentCraftingProgress: null,
unlockedEffects: [],
equipmentSpellStates: [],
lootInventory: { materials: {}, blueprints: [] },
golemancy: {
enabledGolems: [],
summonedGolems: [],
lastSummonFloor: 0,
},
achievements: {
unlocked: [],
progress: {},
},
totalSpellsCast: 0,
totalDamageDealt: 0,
totalCraftsCompleted: 0,
...overrides,
} as GameState;
}
describe('Mana Calculation Functions', () => {
describe('computeMaxMana', () => {
it('should return base mana with no upgrades', () => {
const state = createMockState();
expect(computeMaxMana(state)).toBe(100);
});
it('should add mana from manaWell skill', () => {
const state = createMockState({ skills: { manaWell: 5 } });
expect(computeMaxMana(state)).toBe(100 + 5 * 100);
});
it('should add mana from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { manaWell: 3 } });
expect(computeMaxMana(state)).toBe(100 + 3 * 500);
});
it('should stack manaWell skill and prestige', () => {
const state = createMockState({
skills: { manaWell: 5 },
prestigeUpgrades: { manaWell: 2 },
});
expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500);
});
});
describe('computeRegen', () => {
// Base regen is 2, but computeRegen now includes attunement regen
// Enchanter (active, level 1) adds rawManaRegen * 1.5^0 = rawManaRegen
// Default enchanter rawManaRegen is 0.5, so base with enchanter = 2 + 0.5 = 2.5
it('should return base regen with no upgrades (includes attunement regen)', () => {
const state = createMockState();
// Base 2 + enchanter regen (0.5 * 1 = 0.5) = 2.5
expect(computeRegen(state)).toBeCloseTo(2.5, 1);
});
it('should add regen from manaFlow skill', () => {
const state = createMockState({ skills: { manaFlow: 5 } });
// Base 2 + manaFlow 5 + enchanter 0.5 = 7.5
expect(computeRegen(state)).toBeCloseTo(2 + 5 * 1 + 0.5, 1);
});
it('should add regen from manaSpring skill', () => {
const state = createMockState({ skills: { manaSpring: 1 } });
// Base 2 + manaSpring 2 + enchanter 0.5 = 4.5
expect(computeRegen(state)).toBeCloseTo(2 + 2 + 0.5, 1);
});
it('should multiply by temporal echo prestige', () => {
const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } });
// (2 + 0.5 enchanter) * 1.2 = 3.0
expect(computeRegen(state)).toBeCloseTo(2.5 * 1.2, 1);
});
});
describe('computeClickMana', () => {
it('should return base click mana with no upgrades', () => {
const state = createMockState();
expect(computeClickMana(state)).toBe(1);
});
it('should add mana from manaTap skill', () => {
const state = createMockState({ skills: { manaTap: 1 } });
expect(computeClickMana(state)).toBe(1 + 1);
});
it('should add mana from manaSurge skill', () => {
const state = createMockState({ skills: { manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 3);
});
it('should stack manaTap and manaSurge', () => {
const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 1 + 3);
});
});
describe('computeElementMax', () => {
it('should return base element cap with no upgrades', () => {
const state = createMockState();
expect(computeElementMax(state)).toBe(10);
});
it('should add cap from elemAttune skill', () => {
const state = createMockState({ skills: { elemAttune: 5 } });
expect(computeElementMax(state)).toBe(10 + 5 * 50);
});
it('should add cap from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } });
expect(computeElementMax(state)).toBe(10 + 3 * 25);
});
});
});
console.log('✅ Mana calculation tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,36 +0,0 @@
/**
* Meditation Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { getMeditationBonus } from '@/lib/game/utils';
describe('Meditation Bonus', () => {
describe('getMeditationBonus', () => {
it('should start at 1x with no meditation', () => {
expect(getMeditationBonus(0, {})).toBe(1);
});
it('should cap at 1.5x without meditation skill', () => {
const bonus = getMeditationBonus(200, {}); // 8 hours
expect(bonus).toBe(1.5);
});
it('should give 2.5x with meditation skill after 4 hours', () => {
const bonus = getMeditationBonus(100, { meditation: 1 });
expect(bonus).toBe(2.5);
});
it('should give 3.0x with deepTrance skill after 6 hours', () => {
const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 });
expect(bonus).toBe(3.0);
});
it('should give 5.0x with voidMeditation skill after 8 hours', () => {
const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 });
expect(bonus).toBe(5.0);
});
});
});
console.log('✅ Meditation tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,17 +0,0 @@
/**
* Prestige Upgrade Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { PRESTIGE_DEF } from '@/lib/game/constants';
describe('Prestige Upgrades', () => {
it('should have prestige upgrades with valid costs', () => {
Object.values(PRESTIGE_DEF).forEach(def => {
expect(def.cost).toBeGreaterThan(0);
expect(def.max).toBeGreaterThan(0);
});
});
});
console.log('✅ Prestige upgrade tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,24 +0,0 @@
/**
* Skill Definition Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Skill Definitions', () => {
it('should have skills with valid categories', () => {
const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant', 'effectResearch', 'craft', 'golemancy', 'invocation', 'pact', 'hybrid'];
Object.values(SKILLS_DEF).forEach(skill => {
expect(validCategories).toContain(skill.cat);
});
});
it('should have reasonable study times', () => {
Object.values(SKILLS_DEF).forEach(skill => {
expect(skill.studyTime).toBeGreaterThan(0);
expect(skill.studyTime).toBeLessThanOrEqual(72);
});
});
});
console.log('✅ Skill definition tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,41 +0,0 @@
/**
* Spell Cost Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { canAffordSpellCost } from '@/lib/game/utils';
import { rawCost, elemCost } from '@/lib/game/constants';
describe('Spell Cost System', () => {
describe('rawCost', () => {
it('should create a raw mana cost', () => {
const cost = rawCost(10);
expect(cost.type).toBe('raw');
expect(cost.amount).toBe(10);
});
});
describe('elemCost', () => {
it('should create an elemental mana cost', () => {
const cost = elemCost('fire', 5);
expect(cost.type).toBe('element');
expect(cost.element).toBe('fire');
});
});
describe('canAffordSpellCost', () => {
it('should allow raw mana costs when enough raw mana', () => {
const cost = rawCost(10);
const elements = { fire: { current: 0, max: 10, unlocked: true } };
expect(canAffordSpellCost(cost, 100, elements)).toBe(true);
});
it('should deny raw mana costs when not enough raw mana', () => {
const cost = rawCost(100);
const elements = { fire: { current: 0, max: 10, unlocked: true } };
expect(canAffordSpellCost(cost, 50, elements)).toBe(false);
});
});
});
console.log('✅ Spell cost tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,22 +0,0 @@
/**
* Spell Definition Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { SPELLS_DEF } from '@/lib/game/constants';
describe('Spell Definitions', () => {
it('should have manaBolt as a basic spell', () => {
expect(SPELLS_DEF.manaBolt).toBeDefined();
expect(SPELLS_DEF.manaBolt.tier).toBe(0);
expect(SPELLS_DEF.manaBolt.cost.type).toBe('raw');
});
it('should have increasing damage for higher tiers', () => {
const tier0Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 0).reduce((a, s) => a + s.dmg, 0);
const tier1Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 1).reduce((a, s) => a + s.dmg, 0);
expect(tier1Avg).toBeGreaterThan(tier0Avg);
});
});
console.log('✅ Spell definition tests defined (from stores/__tests__/stores.test.ts).');
@@ -1,40 +0,0 @@
/**
* Study Speed Tests - stores.test.ts
*/
import { describe, it, expect } from 'vitest';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/constants';
describe('Study Speed Functions', () => {
describe('getStudySpeedMultiplier', () => {
it('should return 1 with no quickLearner skill', () => {
expect(getStudySpeedMultiplier({})).toBe(1);
});
it('should increase by 10% per level', () => {
expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1);
expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5);
});
it('should increase by 10% per level up to max', () => {
expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0);
});
});
describe('getStudyCostMultiplier', () => {
it('should return 1 with no focusedMind skill', () => {
expect(getStudyCostMultiplier({})).toBe(1);
});
it('should decrease by 5% per level', () => {
expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95);
expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75);
});
it('should decrease by 5% per level up to max', () => {
expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5);
});
});
});
console.log('✅ Study speed function tests defined (from stores/__tests__/stores.test.ts).');