Completely remove legacy skill system and tests
This commit is contained in:
@@ -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).');
|
||||
Reference in New Issue
Block a user