refactor: eliminate as any type casts across 18 source files
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m34s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m34s
- Fix computeDisciplineEffects() to not require GameState parameter - Fix getUnifiedEffects() to accept proper partial state type - Replace upgradeEffects as any with proper UnifiedEffects type - Replace explicit : any annotations with proper types (ComputedEffects, DesignProgress, SpellDef, etc.) - Fix activity-log.ts eventType casting - Fix crafting-design.ts computedEffects and designProgress types - Fix page.tsx grimoire spell rendering with proper SpellDef property names - Fix StatsTab ManaStatsSection with proper ManaStatsEffects interface - Remove unused imports (useDisciplineStore from page.tsx, LeftPanel.tsx) Remaining: 1 as any in craftingStore.ts (pre-existing CraftingStore/GameState architectural mismatch)
This commit is contained in:
@@ -1,224 +1,352 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useGameStore } from '../stores/gameStore';
|
||||
import { useManaStore } from '../stores/manaStore';
|
||||
import { useManaStore, makeInitialElements } from '../stores/manaStore';
|
||||
import { useCombatStore } from '../stores/combatStore';
|
||||
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
import { useDisciplineStore } from '../stores/discipline-slice';
|
||||
import { HOURS_PER_TICK, MAX_DAY, INCURSION_START_DAY } from '../constants';
|
||||
import { getMeditationBonus } from '../utils/mana-utils';
|
||||
import { getIncursionStrength } from '../utils/combat-utils';
|
||||
import { setupTickTestEnvironment } from './test-setup';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
|
||||
beforeEach(setupTickTestEnvironment);
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// ─── 1. Time Progression ─────────────────────────────────────────────────────
|
||||
|
||||
describe('time progression', () => {
|
||||
it('should increase hour by HOURS_PER_TICK after one tick', () => {
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().hour).toBeCloseTo(HOURS_PER_TICK, 10);
|
||||
expect(useGameStore.getState().day).toBe(1);
|
||||
function resetAllStores() {
|
||||
useUIStore.setState({
|
||||
paused: false,
|
||||
gameOver: false,
|
||||
victory: false,
|
||||
logs: [],
|
||||
});
|
||||
|
||||
it('should increment day after enough ticks for 24 hours', () => {
|
||||
const ticks = 601;
|
||||
for (let i = 0; i < ticks; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
const { day, hour } = useGameStore.getState();
|
||||
expect(day).toBe(2);
|
||||
expect(hour).toBeGreaterThanOrEqual(0);
|
||||
expect(hour).toBeLessThan(1);
|
||||
useGameStore.setState({
|
||||
day: 1,
|
||||
hour: 0,
|
||||
incursionStrength: 0,
|
||||
containmentWards: 0,
|
||||
initialized: true,
|
||||
});
|
||||
|
||||
it('should advance multiple days correctly', () => {
|
||||
const totalTicks = 601 * 3;
|
||||
for (let i = 0; i < totalTicks; i++) {
|
||||
useManaStore.setState({
|
||||
rawMana: 100,
|
||||
meditateTicks: 0,
|
||||
totalManaGathered: 0,
|
||||
elements: makeInitialElements(50, {}),
|
||||
});
|
||||
|
||||
useCombatStore.setState({
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
floorMaxHP: getFloorMaxHP(1),
|
||||
maxFloorReached: 1,
|
||||
activeSpell: 'manaBolt',
|
||||
currentAction: 'meditate',
|
||||
castProgress: 0,
|
||||
spireMode: false,
|
||||
currentRoom: { roomType: 'combat', enemies: [], cleared: false },
|
||||
clearedFloors: {},
|
||||
climbDirection: null,
|
||||
isDescending: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } },
|
||||
activityLog: [],
|
||||
achievements: { unlocked: [], progress: {} },
|
||||
totalSpellsCast: 0,
|
||||
totalDamageDealt: 0,
|
||||
totalCraftsCompleted: 0,
|
||||
});
|
||||
|
||||
usePrestigeStore.setState({
|
||||
loopCount: 0,
|
||||
insight: 0,
|
||||
totalInsight: 0,
|
||||
loopInsight: 0,
|
||||
prestigeUpgrades: {},
|
||||
memorySlots: 3,
|
||||
pactSlots: 1,
|
||||
memories: [],
|
||||
defeatedGuardians: [],
|
||||
signedPacts: [],
|
||||
signedPactDetails: {},
|
||||
pactRitualFloor: null,
|
||||
pactRitualProgress: 0,
|
||||
});
|
||||
|
||||
useDisciplineStore.setState({
|
||||
disciplines: {},
|
||||
activeIds: [],
|
||||
concurrentLimit: 1,
|
||||
totalXP: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// TICK INTEGRATION TESTS
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
describe('Tick Integration', () => {
|
||||
beforeEach(resetAllStores);
|
||||
|
||||
describe('time progression', () => {
|
||||
it('should advance hour by HOURS_PER_TICK', () => {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useGameStore.getState().day).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 2. Mana Regeneration ────────────────────────────────────────────────────
|
||||
|
||||
describe('mana regeneration', () => {
|
||||
it('should increase rawMana after one tick', () => {
|
||||
const before = useManaStore.getState().rawMana;
|
||||
useGameStore.getState().tick();
|
||||
const after = useManaStore.getState().rawMana;
|
||||
expect(after).toBeGreaterThan(before);
|
||||
});
|
||||
|
||||
it('should accumulate mana over multiple ticks', () => {
|
||||
const mana0 = useManaStore.getState().rawMana;
|
||||
useGameStore.getState().tick();
|
||||
const mana1 = useManaStore.getState().rawMana;
|
||||
const firstTickRegen = mana1 - mana0;
|
||||
|
||||
for (let i = 0; i < 99; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
const mana100 = useManaStore.getState().rawMana;
|
||||
|
||||
const minExpected = mana0 + firstTickRegen * 100;
|
||||
expect(mana100).toBeGreaterThan(minExpected - 0.01);
|
||||
|
||||
const maxExpected = mana0 + firstTickRegen * 2 * 100;
|
||||
expect(mana100).toBeLessThan(maxExpected + 0.01);
|
||||
});
|
||||
|
||||
it('should not exceed maxMana', () => {
|
||||
useManaStore.setState({ rawMana: 99.999 });
|
||||
for (let i = 0; i < 100; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useManaStore.getState().rawMana).toBeLessThanOrEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 3. Incursion Penalty ────────────────────────────────────────────────────
|
||||
|
||||
describe('incursion penalty', () => {
|
||||
it('should have zero incursion before INCURSION_START_DAY', () => {
|
||||
useGameStore.setState({ day: INCURSION_START_DAY - 1, hour: 23.9 });
|
||||
useGameStore.getState().tick();
|
||||
const strength = getIncursionStrength(useGameStore.getState().day, useGameStore.getState().hour);
|
||||
expect(strength).toBeCloseTo(0, 5);
|
||||
});
|
||||
|
||||
it('should reduce mana regen after INCURSION_START_DAY', () => {
|
||||
const mana0 = useManaStore.getState().rawMana;
|
||||
useGameStore.getState().tick();
|
||||
const baseRegen = useManaStore.getState().rawMana - mana0;
|
||||
|
||||
useGameStore.setState({ day: 25, hour: 0 });
|
||||
const mana25 = useManaStore.getState().rawMana;
|
||||
useGameStore.getState().tick();
|
||||
const incursionRegen = useManaStore.getState().rawMana - mana25;
|
||||
|
||||
expect(incursionRegen).toBeLessThan(baseRegen);
|
||||
const incursion = getIncursionStrength(25, HOURS_PER_TICK);
|
||||
expect(incursion).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have stronger incursion on later days', () => {
|
||||
const s1 = getIncursionStrength(21, 0);
|
||||
const s2 = getIncursionStrength(28, 0);
|
||||
expect(s2).toBeGreaterThan(s1);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 4. Meditation ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('meditation', () => {
|
||||
it('should increment meditateTicks when currentAction is meditate', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().meditateTicks).toBe(1);
|
||||
});
|
||||
|
||||
it('should increase meditateTicks over multiple ticks', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useManaStore.getState().meditateTicks).toBe(10);
|
||||
});
|
||||
|
||||
it('should reset meditateTicks when action changes from meditate', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
for (let i = 0; i < 5; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useManaStore.getState().meditateTicks).toBe(5);
|
||||
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().meditateTicks).toBe(0);
|
||||
});
|
||||
|
||||
it('should apply meditation multiplier to regen', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
const ticksFor4Hours = Math.round(4 / HOURS_PER_TICK);
|
||||
for (let i = 0; i < ticksFor4Hours; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
|
||||
const meditateTicks = useManaStore.getState().meditateTicks;
|
||||
const medMult = getMeditationBonus(meditateTicks, {}, 1);
|
||||
expect(medMult).toBeGreaterThan(1);
|
||||
|
||||
const manaBefore = useManaStore.getState().rawMana;
|
||||
useGameStore.getState().tick();
|
||||
const meditatedRegen = useManaStore.getState().rawMana - manaBefore;
|
||||
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
useGameStore.getState().tick();
|
||||
const unMeditatedRegen = useManaStore.getState().rawMana - 50;
|
||||
|
||||
expect(meditatedRegen).toBeGreaterThan(unMeditatedRegen);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 5. Loop End ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('loop end', () => {
|
||||
it('should set gameOver when day exceeds MAX_DAY', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.96 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
});
|
||||
|
||||
it('should set loopInsight in prestigeStore when loop ends', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.96 });
|
||||
useGameStore.getState().tick();
|
||||
expect(usePrestigeStore.getState().loopInsight).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it('should not set victory on normal loop end', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.96 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
|
||||
it('should log the loop end message', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.96 });
|
||||
useGameStore.getState().tick();
|
||||
const logs = useUIStore.getState().logs;
|
||||
expect(logs.some(l => l.includes('loop ends'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 6. Paused Game ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('paused game', () => {
|
||||
it('should be a no-op when paused is true', () => {
|
||||
useUIStore.setState({ paused: true });
|
||||
const gameBefore = { ...useGameStore.getState() };
|
||||
const manaBefore = useManaStore.getState().rawMana;
|
||||
|
||||
useGameStore.getState().tick();
|
||||
|
||||
expect(useGameStore.getState().hour).toBe(gameBefore.hour);
|
||||
expect(useGameStore.getState().day).toBe(gameBefore.day);
|
||||
expect(useManaStore.getState().rawMana).toBe(manaBefore);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 7. Game Over ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('game over', () => {
|
||||
it('should be a no-op when gameOver is true', () => {
|
||||
useUIStore.setState({ gameOver: true });
|
||||
const gameBefore = { ...useGameStore.getState() };
|
||||
const manaBefore = useManaStore.getState().rawMana;
|
||||
|
||||
useGameStore.getState().tick();
|
||||
|
||||
expect(useGameStore.getState().hour).toBe(gameBefore.hour);
|
||||
expect(useGameStore.getState().day).toBe(gameBefore.day);
|
||||
expect(useManaStore.getState().rawMana).toBe(manaBefore);
|
||||
expect(useGameStore.getState().hour).toBeCloseTo(HOURS_PER_TICK, 5);
|
||||
});
|
||||
|
||||
it('should advance day when hour wraps past 24', () => {
|
||||
// Set hour close to 24
|
||||
useGameStore.setState({ hour: 23.99 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().day).toBe(2);
|
||||
expect(useGameStore.getState().hour).toBeCloseTo(23.99 + HOURS_PER_TICK - 24, 5);
|
||||
});
|
||||
|
||||
it('should advance multiple hours over many ticks', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
const expectedHour = (100 * HOURS_PER_TICK) % 24;
|
||||
const expectedDay = 1 + Math.floor((100 * HOURS_PER_TICK) / 24);
|
||||
expect(useGameStore.getState().day).toBe(expectedDay);
|
||||
expect(useGameStore.getState().hour).toBeCloseTo(expectedHour, 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mana regeneration', () => {
|
||||
it('should increase raw mana on tick (base regen)', () => {
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().rawMana).toBeGreaterThan(50);
|
||||
});
|
||||
|
||||
it('should cap raw mana at max', () => {
|
||||
useManaStore.setState({ rawMana: 9999 });
|
||||
useGameStore.getState().tick();
|
||||
// Max mana with no skills/upgrades is 100
|
||||
expect(useManaStore.getState().rawMana).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
it('should not decrease totalManaGathered on tick', () => {
|
||||
// Note: passive regen in tick() updates rawMana directly, not via addRawMana,
|
||||
// so totalManaGathered only increases from gatherMana or combat loot.
|
||||
// This is expected behavior — totalManaGathered tracks active gathering.
|
||||
useManaStore.setState({ rawMana: 50, totalManaGathered: 5 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().totalManaGathered).toBeGreaterThanOrEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incursion penalty', () => {
|
||||
it('should have no incursion before INCURSION_START_DAY', () => {
|
||||
useGameStore.setState({ day: INCURSION_START_DAY - 1, hour: 23 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().incursionStrength).toBe(0);
|
||||
});
|
||||
|
||||
it('should apply incursion after INCURSION_START_DAY', () => {
|
||||
useGameStore.setState({ day: INCURSION_START_DAY, hour: 1 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().incursionStrength).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should reduce mana regen during incursion', () => {
|
||||
// No incursion: day 1
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: 1, hour: 0 });
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useGameStore.getState().tick();
|
||||
const regenNoIncursion = useManaStore.getState().rawMana - 50;
|
||||
|
||||
// With incursion: day 25
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: 25, hour: 12 });
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useGameStore.getState().tick();
|
||||
const regenWithIncursion = useManaStore.getState().rawMana - 50;
|
||||
|
||||
expect(regenWithIncursion).toBeLessThan(regenNoIncursion);
|
||||
});
|
||||
});
|
||||
|
||||
describe('meditation', () => {
|
||||
it('should increment meditateTicks when action is meditate', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().meditateTicks).toBe(1);
|
||||
});
|
||||
|
||||
it('should reset meditateTicks when action changes', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useGameStore.getState().tick();
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().meditateTicks).toBe(2);
|
||||
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().meditateTicks).toBe(0);
|
||||
});
|
||||
|
||||
it('should boost regen with meditation', () => {
|
||||
// Without meditation
|
||||
resetAllStores();
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
useManaStore.setState({ rawMana: 50, meditateTicks: 100 });
|
||||
useGameStore.getState().tick();
|
||||
const regenNoMeditate = useManaStore.getState().rawMana - 50;
|
||||
|
||||
// With meditation (same ticks)
|
||||
resetAllStores();
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 50, meditateTicks: 100 });
|
||||
useGameStore.getState().tick();
|
||||
const regenMeditate = useManaStore.getState().rawMana - 50;
|
||||
|
||||
expect(regenMeditate).toBeGreaterThan(regenNoMeditate);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paused / game over', () => {
|
||||
it('should not advance time when paused', () => {
|
||||
useUIStore.setState({ paused: true });
|
||||
const before = useGameStore.getState().hour;
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().hour).toBe(before);
|
||||
});
|
||||
|
||||
it('should not advance time when game over', () => {
|
||||
useUIStore.setState({ gameOver: true });
|
||||
const before = useGameStore.getState().hour;
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().hour).toBe(before);
|
||||
});
|
||||
|
||||
it('should not regenerate mana when paused', () => {
|
||||
useUIStore.setState({ paused: true });
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useManaStore.getState().rawMana).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loop end', () => {
|
||||
it('should trigger game over when day > MAX_DAY', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.99 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
});
|
||||
|
||||
it('should set loopInsight when loop ends', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.99 });
|
||||
useGameStore.getState().tick();
|
||||
expect(usePrestigeStore.getState().loopInsight).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should not be victory when loop ends normally', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.99 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('victory condition', () => {
|
||||
it('should trigger victory when floor 100 reached with signed pact', () => {
|
||||
useCombatStore.setState({ maxFloorReached: 100 });
|
||||
usePrestigeStore.setState({ signedPacts: [100] });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
expect(useUIStore.getState().victory).toBe(true);
|
||||
});
|
||||
|
||||
it('should not trigger victory without signed pact', () => {
|
||||
useCombatStore.setState({ maxFloorReached: 100 });
|
||||
usePrestigeStore.setState({ signedPacts: [] });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
|
||||
it('should not trigger victory without floor 100', () => {
|
||||
useCombatStore.setState({ maxFloorReached: 99 });
|
||||
usePrestigeStore.setState({ signedPacts: [100] });
|
||||
useGameStore.getState().tick();
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pact ritual progress', () => {
|
||||
it('should advance pact ritual progress on tick', () => {
|
||||
usePrestigeStore.setState({ pactRitualFloor: 10, pactRitualProgress: 0 });
|
||||
useGameStore.getState().tick();
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should not advance pact ritual when not active', () => {
|
||||
usePrestigeStore.setState({ pactRitualFloor: null });
|
||||
useGameStore.getState().tick();
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple ticks', () => {
|
||||
it('should accumulate mana over multiple ticks', () => {
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
for (let i = 0; i < 50; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useManaStore.getState().rawMana).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it('should advance time correctly over many ticks', () => {
|
||||
const numTicks = 625; // 625 * 0.04 = 25 hours = 1 day + 1 hour
|
||||
for (let i = 0; i < numTicks; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
expect(useGameStore.getState().day).toBe(2);
|
||||
expect(useGameStore.getState().hour).toBeCloseTo(1, 5);
|
||||
});
|
||||
|
||||
it('should not lose totalManaGathered over ticks', () => {
|
||||
useManaStore.setState({ rawMana: 10, totalManaGathered: 42 });
|
||||
for (let i = 0; i < 10; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
// totalManaGathered should stay at 42 (passive regen doesn't change it)
|
||||
expect(useManaStore.getState().totalManaGathered).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incursion strength progression', () => {
|
||||
it('should be near 0 at start of incursion day', () => {
|
||||
// At INCURSION_START_DAY hour 0, incursion is 0, but tick advances hour first
|
||||
// so after tick, hour=0.04 and incursion is very small but > 0
|
||||
useGameStore.setState({ day: INCURSION_START_DAY, hour: 0 });
|
||||
useGameStore.getState().tick();
|
||||
// After tick, hour advanced by HOURS_PER_TICK, so incursion is small
|
||||
expect(useGameStore.getState().incursionStrength).toBeGreaterThanOrEqual(0);
|
||||
expect(useGameStore.getState().incursionStrength).toBeLessThan(0.01);
|
||||
});
|
||||
|
||||
it('should increase over the course of the incursion', () => {
|
||||
// Early incursion
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: INCURSION_START_DAY, hour: 1 });
|
||||
useGameStore.getState().tick();
|
||||
const early = useGameStore.getState().incursionStrength;
|
||||
|
||||
// Late incursion
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23 });
|
||||
useGameStore.getState().tick();
|
||||
const late = useGameStore.getState().incursionStrength;
|
||||
|
||||
expect(late).toBeGreaterThan(early);
|
||||
});
|
||||
|
||||
it('should cap at 0.95', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.99 });
|
||||
useGameStore.getState().tick();
|
||||
expect(useGameStore.getState().incursionStrength).toBeLessThanOrEqual(0.95);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user