Initial commit
This commit is contained in:
Executable
+494
@@ -0,0 +1,494 @@
|
||||
/**
|
||||
* Tests for the split store architecture
|
||||
*
|
||||
* Tests each store in isolation and integration between stores
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||
import {
|
||||
useManaStore,
|
||||
useSkillStore,
|
||||
usePrestigeStore,
|
||||
useCombatStore,
|
||||
useUIStore,
|
||||
fmt,
|
||||
fmtDec,
|
||||
computeMaxMana,
|
||||
computeElementMax,
|
||||
computeRegen,
|
||||
computeClickMana,
|
||||
calcDamage,
|
||||
calcInsight,
|
||||
getMeditationBonus,
|
||||
getIncursionStrength,
|
||||
canAffordSpellCost,
|
||||
} from './stores';
|
||||
import {
|
||||
ELEMENTS,
|
||||
GUARDIANS,
|
||||
SPELLS_DEF,
|
||||
SKILLS_DEF,
|
||||
PRESTIGE_DEF,
|
||||
getStudySpeedMultiplier,
|
||||
getStudyCostMultiplier,
|
||||
HOURS_PER_TICK,
|
||||
} from './constants';
|
||||
import type { GameState } from './types';
|
||||
|
||||
// ─── Test Fixtures ───────────────────────────────────────────────────────────
|
||||
|
||||
// Reset all stores before each test
|
||||
beforeEach(() => {
|
||||
useManaStore.setState({
|
||||
rawMana: 10,
|
||||
meditateTicks: 0,
|
||||
totalManaGathered: 0,
|
||||
elements: (() => {
|
||||
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 elements;
|
||||
})(),
|
||||
});
|
||||
|
||||
useSkillStore.setState({
|
||||
skills: {},
|
||||
skillProgress: {},
|
||||
skillUpgrades: {},
|
||||
skillTiers: {},
|
||||
paidStudySkills: {},
|
||||
currentStudyTarget: null,
|
||||
parallelStudyTarget: null,
|
||||
});
|
||||
|
||||
usePrestigeStore.setState({
|
||||
loopCount: 0,
|
||||
insight: 0,
|
||||
totalInsight: 0,
|
||||
loopInsight: 0,
|
||||
prestigeUpgrades: {},
|
||||
memorySlots: 3,
|
||||
pactSlots: 1,
|
||||
memories: [],
|
||||
defeatedGuardians: [],
|
||||
signedPacts: [],
|
||||
pactRitualFloor: null,
|
||||
pactRitualProgress: 0,
|
||||
});
|
||||
|
||||
useCombatStore.setState({
|
||||
currentFloor: 1,
|
||||
floorHP: 151,
|
||||
floorMaxHP: 151,
|
||||
maxFloorReached: 1,
|
||||
activeSpell: 'manaBolt',
|
||||
currentAction: 'meditate',
|
||||
castProgress: 0,
|
||||
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
|
||||
});
|
||||
|
||||
useUIStore.setState({
|
||||
logs: [],
|
||||
paused: false,
|
||||
gameOver: false,
|
||||
victory: false,
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Mana Store Tests ─────────────────────────────────────────────────────────
|
||||
|
||||
describe('ManaStore', () => {
|
||||
describe('initial state', () => {
|
||||
it('should have correct initial values', () => {
|
||||
const state = useManaStore.getState();
|
||||
expect(state.rawMana).toBe(10);
|
||||
expect(state.meditateTicks).toBe(0);
|
||||
expect(state.totalManaGathered).toBe(0);
|
||||
});
|
||||
|
||||
it('should have base elements unlocked', () => {
|
||||
const state = useManaStore.getState();
|
||||
expect(state.elements.fire.unlocked).toBe(true);
|
||||
expect(state.elements.water.unlocked).toBe(true);
|
||||
expect(state.elements.air.unlocked).toBe(true);
|
||||
expect(state.elements.earth.unlocked).toBe(true);
|
||||
expect(state.elements.light.unlocked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('raw mana operations', () => {
|
||||
it('should add raw mana', () => {
|
||||
useManaStore.getState().addRawMana(50, 100);
|
||||
expect(useManaStore.getState().rawMana).toBe(60);
|
||||
});
|
||||
|
||||
it('should cap at max mana', () => {
|
||||
useManaStore.getState().addRawMana(200, 100);
|
||||
expect(useManaStore.getState().rawMana).toBe(100);
|
||||
});
|
||||
|
||||
it('should spend raw mana', () => {
|
||||
const result = useManaStore.getState().spendRawMana(5);
|
||||
expect(result).toBe(true);
|
||||
expect(useManaStore.getState().rawMana).toBe(5);
|
||||
});
|
||||
|
||||
it('should fail to spend more than available', () => {
|
||||
const result = useManaStore.getState().spendRawMana(50);
|
||||
expect(result).toBe(false);
|
||||
expect(useManaStore.getState().rawMana).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('element operations', () => {
|
||||
it('should convert raw mana to element', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000); // Have 100 mana
|
||||
const result = useManaStore.getState().convertMana('fire', 1);
|
||||
expect(result).toBe(true);
|
||||
expect(useManaStore.getState().elements.fire.current).toBe(1);
|
||||
});
|
||||
|
||||
it('should unlock new element', () => {
|
||||
useManaStore.getState().addRawMana(490, 1000); // Have 500 mana
|
||||
const result = useManaStore.getState().unlockElement('light', 500);
|
||||
expect(result).toBe(true);
|
||||
expect(useManaStore.getState().elements.light.unlocked).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Skill Store Tests ───────────────────────────────────────────────────────
|
||||
|
||||
describe('SkillStore', () => {
|
||||
describe('study skill', () => {
|
||||
it('should start studying a skill', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000); // Have 100 mana
|
||||
|
||||
const result = useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
|
||||
expect(result.started).toBe(true);
|
||||
expect(result.cost).toBe(100);
|
||||
expect(useSkillStore.getState().currentStudyTarget).not.toBeNull();
|
||||
expect(useSkillStore.getState().currentStudyTarget?.type).toBe('skill');
|
||||
expect(useSkillStore.getState().currentStudyTarget?.id).toBe('manaWell');
|
||||
});
|
||||
|
||||
it('should not start studying without enough mana', () => {
|
||||
const result = useSkillStore.getState().startStudyingSkill('manaWell', 50);
|
||||
|
||||
expect(result.started).toBe(false);
|
||||
expect(result.cost).toBe(100);
|
||||
expect(useSkillStore.getState().currentStudyTarget).toBeNull();
|
||||
});
|
||||
|
||||
it('should track paid study skills', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
|
||||
useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
|
||||
expect(useSkillStore.getState().paidStudySkills['manaWell']).toBe(0);
|
||||
});
|
||||
|
||||
it('should resume studying for free after payment', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
|
||||
// First study attempt
|
||||
const result1 = useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
expect(result1.cost).toBe(100);
|
||||
|
||||
// Cancel study (simulated)
|
||||
useSkillStore.getState().cancelStudy(0);
|
||||
|
||||
// Resume should be free
|
||||
const result2 = useSkillStore.getState().startStudyingSkill('manaWell', 0);
|
||||
expect(result2.started).toBe(true);
|
||||
expect(result2.cost).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update study progress', () => {
|
||||
it('should update study progress', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
|
||||
const result = useSkillStore.getState().updateStudyProgress(1);
|
||||
|
||||
expect(result.completed).toBe(false);
|
||||
expect(useSkillStore.getState().currentStudyTarget?.progress).toBe(1);
|
||||
});
|
||||
|
||||
it('should complete study when progress reaches required', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
|
||||
// manaWell requires 4 hours
|
||||
const result = useSkillStore.getState().updateStudyProgress(4);
|
||||
|
||||
expect(result.completed).toBe(true);
|
||||
expect(result.target?.id).toBe('manaWell');
|
||||
expect(useSkillStore.getState().currentStudyTarget).toBeNull();
|
||||
});
|
||||
|
||||
it('should apply study speed multiplier', () => {
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
useSkillStore.getState().setSkillLevel('quickLearner', 5); // 50% faster
|
||||
|
||||
useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
|
||||
// The caller should calculate progress with speed multiplier
|
||||
const speedMult = getStudySpeedMultiplier(useSkillStore.getState().skills);
|
||||
const result = useSkillStore.getState().updateStudyProgress(3 * speedMult); // 3 * 1.5 = 4.5
|
||||
|
||||
expect(result.completed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('skill level operations', () => {
|
||||
it('should set skill level', () => {
|
||||
useSkillStore.getState().setSkillLevel('manaWell', 5);
|
||||
expect(useSkillStore.getState().skills['manaWell']).toBe(5);
|
||||
});
|
||||
|
||||
it('should increment skill level', () => {
|
||||
useSkillStore.getState().setSkillLevel('manaWell', 5);
|
||||
useSkillStore.getState().incrementSkillLevel('manaWell');
|
||||
expect(useSkillStore.getState().skills['manaWell']).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerequisites', () => {
|
||||
it('should not start studying without prerequisites', () => {
|
||||
useManaStore.getState().addRawMana(990, 1000);
|
||||
|
||||
// deepReservoir requires manaWell 5
|
||||
const result = useSkillStore.getState().startStudyingSkill('deepReservoir', 1000);
|
||||
|
||||
expect(result.started).toBe(false);
|
||||
});
|
||||
|
||||
it('should start studying with prerequisites met', () => {
|
||||
useManaStore.getState().addRawMana(990, 1000);
|
||||
useSkillStore.getState().setSkillLevel('manaWell', 5);
|
||||
|
||||
const result = useSkillStore.getState().startStudyingSkill('deepReservoir', 1000);
|
||||
|
||||
expect(result.started).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Prestige Store Tests ────────────────────────────────────────────────────
|
||||
|
||||
describe('PrestigeStore', () => {
|
||||
describe('prestige upgrades', () => {
|
||||
it('should purchase prestige upgrade', () => {
|
||||
usePrestigeStore.getState().startNewLoop(1000); // Add 1000 insight
|
||||
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1);
|
||||
expect(usePrestigeStore.getState().insight).toBe(500); // 1000 - 500 cost
|
||||
});
|
||||
|
||||
it('should not purchase without enough insight', () => {
|
||||
usePrestigeStore.getState().startNewLoop(100);
|
||||
|
||||
const result = usePrestigeStore.getState().doPrestige('manaWell');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loop management', () => {
|
||||
it('should increment loop count', () => {
|
||||
usePrestigeStore.getState().incrementLoopCount();
|
||||
expect(usePrestigeStore.getState().loopCount).toBe(1);
|
||||
|
||||
usePrestigeStore.getState().incrementLoopCount();
|
||||
expect(usePrestigeStore.getState().loopCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should reset for new loop', () => {
|
||||
usePrestigeStore.getState().startNewLoop(1000);
|
||||
usePrestigeStore.getState().doPrestige('manaWell');
|
||||
|
||||
usePrestigeStore.getState().resetPrestigeForNewLoop(
|
||||
500, // total insight
|
||||
{ manaWell: 1 }, // prestige upgrades
|
||||
[], // memories
|
||||
3 // memory slots
|
||||
);
|
||||
|
||||
expect(usePrestigeStore.getState().insight).toBe(500);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
||||
expect(usePrestigeStore.getState().signedPacts).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('guardian pacts', () => {
|
||||
it('should add signed pact', () => {
|
||||
usePrestigeStore.getState().addSignedPact(10);
|
||||
expect(usePrestigeStore.getState().signedPacts).toContain(10);
|
||||
});
|
||||
|
||||
it('should add defeated guardian', () => {
|
||||
usePrestigeStore.getState().addDefeatedGuardian(10);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toContain(10);
|
||||
});
|
||||
|
||||
it('should not add same guardian twice', () => {
|
||||
usePrestigeStore.getState().addDefeatedGuardian(10);
|
||||
usePrestigeStore.getState().addDefeatedGuardian(10);
|
||||
expect(usePrestigeStore.getState().defeatedGuardians.length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Combat Store Tests ───────────────────────────────────────────────────────
|
||||
|
||||
describe('CombatStore', () => {
|
||||
describe('floor operations', () => {
|
||||
it('should advance floor', () => {
|
||||
useCombatStore.getState().advanceFloor();
|
||||
expect(useCombatStore.getState().currentFloor).toBe(2);
|
||||
expect(useCombatStore.getState().maxFloorReached).toBe(2);
|
||||
});
|
||||
|
||||
it('should cap at floor 100', () => {
|
||||
useCombatStore.setState({ currentFloor: 100 });
|
||||
useCombatStore.getState().advanceFloor();
|
||||
expect(useCombatStore.getState().currentFloor).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('action management', () => {
|
||||
it('should set action', () => {
|
||||
useCombatStore.getState().setAction('climb');
|
||||
expect(useCombatStore.getState().currentAction).toBe('climb');
|
||||
|
||||
useCombatStore.getState().setAction('study');
|
||||
expect(useCombatStore.getState().currentAction).toBe('study');
|
||||
});
|
||||
});
|
||||
|
||||
describe('spell management', () => {
|
||||
it('should set active spell', () => {
|
||||
useCombatStore.getState().learnSpell('fireball');
|
||||
useCombatStore.getState().setSpell('fireball');
|
||||
expect(useCombatStore.getState().activeSpell).toBe('fireball');
|
||||
});
|
||||
|
||||
it('should learn spell', () => {
|
||||
useCombatStore.getState().learnSpell('fireball');
|
||||
expect(useCombatStore.getState().spells['fireball']?.learned).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── UI Store Tests ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('UIStore', () => {
|
||||
describe('log management', () => {
|
||||
it('should add log message', () => {
|
||||
useUIStore.getState().addLog('Test message');
|
||||
expect(useUIStore.getState().logs).toContain('Test message');
|
||||
});
|
||||
|
||||
it('should clear logs', () => {
|
||||
useUIStore.getState().addLog('Test message');
|
||||
useUIStore.getState().clearLogs();
|
||||
expect(useUIStore.getState().logs.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pause management', () => {
|
||||
it('should toggle pause', () => {
|
||||
expect(useUIStore.getState().paused).toBe(false);
|
||||
|
||||
useUIStore.getState().togglePause();
|
||||
expect(useUIStore.getState().paused).toBe(true);
|
||||
|
||||
useUIStore.getState().togglePause();
|
||||
expect(useUIStore.getState().paused).toBe(false);
|
||||
});
|
||||
|
||||
it('should set pause state', () => {
|
||||
useUIStore.getState().setPaused(true);
|
||||
expect(useUIStore.getState().paused).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('game over state', () => {
|
||||
it('should set game over', () => {
|
||||
useUIStore.getState().setGameOver(true);
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
|
||||
it('should set victory', () => {
|
||||
useUIStore.getState().setGameOver(true, true);
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
expect(useUIStore.getState().victory).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Integration Tests ─────────────────────────────────────────────────────────
|
||||
|
||||
describe('Store Integration', () => {
|
||||
describe('skill study flow', () => {
|
||||
it('should complete full study flow', () => {
|
||||
// Setup: give enough mana
|
||||
useManaStore.getState().addRawMana(90, 1000);
|
||||
|
||||
// Start studying
|
||||
const startResult = useSkillStore.getState().startStudyingSkill('manaWell', 100);
|
||||
expect(startResult.started).toBe(true);
|
||||
expect(startResult.cost).toBe(100);
|
||||
|
||||
// Deduct mana (simulating UI behavior)
|
||||
if (startResult.cost > 0) {
|
||||
useManaStore.getState().spendRawMana(startResult.cost);
|
||||
}
|
||||
|
||||
// Set action to study
|
||||
useCombatStore.getState().setAction('study');
|
||||
expect(useCombatStore.getState().currentAction).toBe('study');
|
||||
|
||||
// Update progress until complete
|
||||
const result = useSkillStore.getState().updateStudyProgress(4);
|
||||
expect(result.completed).toBe(true);
|
||||
|
||||
// Level up skill
|
||||
useSkillStore.getState().setSkillLevel('manaWell', 1);
|
||||
expect(useSkillStore.getState().skills['manaWell']).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mana and prestige interaction', () => {
|
||||
it('should apply prestige mana bonus', () => {
|
||||
// Get prestige upgrade
|
||||
usePrestigeStore.getState().startNewLoop(1000);
|
||||
usePrestigeStore.getState().doPrestige('manaWell');
|
||||
|
||||
// Check that prestige upgrade is recorded
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1);
|
||||
|
||||
// Mana well prestige gives +500 max mana per level
|
||||
const state = {
|
||||
skills: {},
|
||||
prestigeUpgrades: usePrestigeStore.getState().prestigeUpgrades,
|
||||
skillUpgrades: {},
|
||||
skillTiers: {},
|
||||
};
|
||||
|
||||
const maxMana = computeMaxMana(state);
|
||||
expect(maxMana).toBe(100 + 500); // Base 100 + 500 from prestige
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ All store tests defined. Run with: bun test src/lib/game/stores.test.ts');
|
||||
Reference in New Issue
Block a user