test: add cross-module integration tests for tick pipeline
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
Add 38 integration tests split across 4 files (all under 400 lines): - cross-module-helpers.ts: shared resetAllStores() and tickN() utilities - cross-module-combat-meditation.test.ts (12 tests): combat floor clearing, meditation regen flow, incursion effects, convert action - cross-module-prestige-discipline.test.ts (15 tests): prestige loop reset, discipline mana drain/XP, pact ritual completion - cross-module-lifecycle-consistency.test.ts (11 tests): full loop lifecycle, store consistency invariants, pause/gameOver blocking All 38 new + 112 existing tests pass.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Circular Dependencies
|
||||
Generated: 2026-05-25T13:20:07.523Z
|
||||
Generated: 2026-05-25T15:37:17.998Z
|
||||
Found: 6 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||
|
||||
1. Processed 135 files (1.5s) (2 warnings)
|
||||
1. Processed 135 files (1.7s) (2 warnings)
|
||||
2. 1) utils/floor-utils.ts > utils/room-utils.ts > utils/enemy-utils.ts
|
||||
3. 2) utils/floor-utils.ts > utils/room-utils.ts
|
||||
4. 3) stores/gameStore.ts > stores/gameActions.ts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_meta": {
|
||||
"generated": "2026-05-25T13:20:05.764Z",
|
||||
"generated": "2026-05-25T15:37:16.139Z",
|
||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||
},
|
||||
|
||||
@@ -199,6 +199,10 @@ Mana-Loop/
|
||||
│ │ │ ├── crafting-utils-equipment.test.ts
|
||||
│ │ │ ├── crafting-utils-recipe.test.ts
|
||||
│ │ │ ├── crafting-utils-time.test.ts
|
||||
│ │ │ ├── cross-module-combat-meditation.test.ts
|
||||
│ │ │ ├── cross-module-helpers.ts
|
||||
│ │ │ ├── cross-module-lifecycle-consistency.test.ts
|
||||
│ │ │ ├── cross-module-prestige-discipline.test.ts
|
||||
│ │ │ ├── discipline-math.test.ts
|
||||
│ │ │ ├── discipline-prerequisites.test.ts
|
||||
│ │ │ ├── enemy-barrier-utils.test.ts
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useGameStore } from '../stores/gameStore';
|
||||
import { useManaStore } from '../stores/manaStore';
|
||||
import { useCombatStore } from '../stores/combatStore';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
import { MAX_DAY } from '../constants';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
import { resetAllStores, tickN } from './cross-module-helpers';
|
||||
|
||||
describe('Cross-Module: Combat & Meditation', () => {
|
||||
beforeEach(resetAllStores);
|
||||
|
||||
describe('combat floor clearing via tick', () => {
|
||||
it('should advance floor when climb action deals enough damage', () => {
|
||||
useManaStore.setState({ rawMana: 9999 });
|
||||
useCombatStore.setState({
|
||||
currentAction: 'climb',
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
floorMaxHP: getFloorMaxHP(1),
|
||||
activeSpell: 'manaBolt',
|
||||
});
|
||||
|
||||
tickN(500);
|
||||
|
||||
expect(useCombatStore.getState().currentFloor).toBeGreaterThan(1);
|
||||
const { floorHP, floorMaxHP } = useCombatStore.getState();
|
||||
expect(floorHP).toBeGreaterThanOrEqual(0);
|
||||
expect(floorHP).toBeLessThanOrEqual(floorMaxHP);
|
||||
});
|
||||
|
||||
it('should not advance floor when action is meditate', () => {
|
||||
useCombatStore.setState({
|
||||
currentAction: 'meditate',
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
activeSpell: 'manaBolt',
|
||||
});
|
||||
|
||||
tickN(50);
|
||||
|
||||
expect(useCombatStore.getState().currentFloor).toBe(1);
|
||||
expect(useCombatStore.getState().maxFloorReached).toBe(1);
|
||||
});
|
||||
|
||||
it('should track maxFloorReached across multiple floors', () => {
|
||||
useCombatStore.setState({
|
||||
currentAction: 'climb',
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
floorMaxHP: getFloorMaxHP(1),
|
||||
activeSpell: 'manaBolt',
|
||||
});
|
||||
|
||||
tickN(500);
|
||||
|
||||
const combat = useCombatStore.getState();
|
||||
expect(combat.maxFloorReached).toBeGreaterThanOrEqual(combat.currentFloor);
|
||||
expect(combat.maxFloorReached).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should cap maxFloorReached at 100', () => {
|
||||
useCombatStore.setState({
|
||||
currentAction: 'climb',
|
||||
currentFloor: 100,
|
||||
floorHP: getFloorMaxHP(100),
|
||||
floorMaxHP: getFloorMaxHP(100),
|
||||
maxFloorReached: 100,
|
||||
activeSpell: 'manaBolt',
|
||||
});
|
||||
|
||||
tickN(100);
|
||||
|
||||
expect(useCombatStore.getState().currentFloor).toBe(100);
|
||||
expect(useCombatStore.getState().maxFloorReached).toBe(100);
|
||||
});
|
||||
|
||||
it('should update maxFloorReached and reduce mana after climbing', () => {
|
||||
useManaStore.setState({ rawMana: 9999 });
|
||||
useCombatStore.setState({
|
||||
currentAction: 'climb',
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
floorMaxHP: getFloorMaxHP(1),
|
||||
activeSpell: 'manaBolt',
|
||||
});
|
||||
|
||||
tickN(500);
|
||||
|
||||
expect(useCombatStore.getState().maxFloorReached).toBeGreaterThan(1);
|
||||
expect(useManaStore.getState().rawMana).toBeLessThan(9999);
|
||||
expect(useGameStore.getState().day).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('meditation mana regen flow', () => {
|
||||
it('should increase raw mana over time while meditating', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
|
||||
tickN(20);
|
||||
|
||||
expect(useManaStore.getState().rawMana).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it('should track meditateTicks in mana store during meditation', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
|
||||
tickN(5);
|
||||
|
||||
expect(useManaStore.getState().meditateTicks).toBe(5);
|
||||
});
|
||||
|
||||
it('should reset meditateTicks when action changes from meditate', () => {
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
tickN(5);
|
||||
expect(useManaStore.getState().meditateTicks).toBe(5);
|
||||
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
tickN(1);
|
||||
expect(useManaStore.getState().meditateTicks).toBe(0);
|
||||
});
|
||||
|
||||
it('should boost mana regen with higher meditateTicks', () => {
|
||||
resetAllStores();
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
tickN(5);
|
||||
const after5 = useManaStore.getState().rawMana;
|
||||
|
||||
resetAllStores();
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
tickN(50);
|
||||
const after50 = useManaStore.getState().rawMana;
|
||||
|
||||
expect(after50 - 10).toBeGreaterThan(after5 - 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incursion strength affecting mana regen', () => {
|
||||
it('should reduce mana regen during high incursion', () => {
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: 1, hour: 0 });
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
tickN(10);
|
||||
const lowIncursionMana = useManaStore.getState().rawMana;
|
||||
|
||||
resetAllStores();
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23 });
|
||||
useCombatStore.setState({ currentAction: 'meditate' });
|
||||
useManaStore.setState({ rawMana: 10 });
|
||||
tickN(10);
|
||||
const highIncursionMana = useManaStore.getState().rawMana;
|
||||
|
||||
expect(highIncursionMana).toBeLessThan(lowIncursionMana);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert action via tick', () => {
|
||||
it('should convert raw mana to elements when action is convert', () => {
|
||||
useManaStore.getState().unlockElement('fire', 0);
|
||||
useManaStore.setState({ rawMana: 500 });
|
||||
useCombatStore.setState({ currentAction: 'convert' });
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useManaStore.getState().elements.fire.current).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should increase element mana when converting', () => {
|
||||
useManaStore.getState().unlockElement('fire', 0);
|
||||
useManaStore.setState({ rawMana: 500 });
|
||||
useCombatStore.setState({ currentAction: 'convert' });
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useManaStore.getState().elements.fire.current).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useGameStore } from '../stores/gameStore';
|
||||
import { useManaStore, makeInitialElements } from '../stores/manaStore';
|
||||
import { useCombatStore, makeInitialSpells } from '../stores/combatStore';
|
||||
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
import { useDisciplineStore } from '../stores/discipline-slice';
|
||||
import { useAttunementStore } from '../stores/attunementStore';
|
||||
import { useCraftingStore } from '../stores/craftingStore';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
|
||||
export function resetAllStores() {
|
||||
useUIStore.setState({
|
||||
paused: false,
|
||||
gameOver: false,
|
||||
victory: false,
|
||||
logs: [],
|
||||
});
|
||||
|
||||
useGameStore.setState({
|
||||
day: 1,
|
||||
hour: 0,
|
||||
incursionStrength: 0,
|
||||
containmentWards: 0,
|
||||
initialized: true,
|
||||
});
|
||||
|
||||
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: [] },
|
||||
clearedFloors: {},
|
||||
climbDirection: null,
|
||||
isDescending: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
spells: makeInitialSpells(),
|
||||
activityLog: [],
|
||||
achievements: { unlocked: [], progress: {} },
|
||||
totalSpellsCast: 0,
|
||||
totalDamageDealt: 0,
|
||||
totalCraftsCompleted: 0,
|
||||
});
|
||||
|
||||
usePrestigeStore.setState({
|
||||
loopCount: 0,
|
||||
insight: 0,
|
||||
totalInsight: 0,
|
||||
loopInsight: 0,
|
||||
prestigeUpgrades: {},
|
||||
pactSlots: 1,
|
||||
defeatedGuardians: [],
|
||||
signedPacts: [],
|
||||
signedPactDetails: {},
|
||||
pactRitualFloor: null,
|
||||
pactRitualProgress: 0,
|
||||
});
|
||||
|
||||
useDisciplineStore.setState({
|
||||
disciplines: {},
|
||||
activeIds: [],
|
||||
concurrentLimit: 1,
|
||||
totalXP: 0,
|
||||
processedPerks: [],
|
||||
});
|
||||
|
||||
useAttunementStore.setState({
|
||||
attunements: {},
|
||||
});
|
||||
|
||||
useCraftingStore.setState({
|
||||
designProgress: null,
|
||||
designProgress2: null,
|
||||
preparationProgress: null,
|
||||
applicationProgress: null,
|
||||
equipmentCraftingProgress: null,
|
||||
enchantmentDesigns: [],
|
||||
unlockedEffects: [],
|
||||
equippedInstances: {},
|
||||
equipmentInstances: {},
|
||||
lootInventory: {
|
||||
materials: {},
|
||||
blueprints: [],
|
||||
},
|
||||
enchantmentSelection: {
|
||||
selectedEquipmentType: null,
|
||||
selectedEffects: [],
|
||||
designName: '',
|
||||
selectedDesign: null,
|
||||
selectedEquipmentInstance: null,
|
||||
},
|
||||
lastError: null,
|
||||
});
|
||||
}
|
||||
|
||||
export function tickN(n: number) {
|
||||
for (let i = 0; i < n; i++) {
|
||||
useGameStore.getState().tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useGameStore } from '../stores/gameStore';
|
||||
import { useManaStore } from '../stores/manaStore';
|
||||
import { useCombatStore } from '../stores/combatStore';
|
||||
import { usePrestigeStore } from '../stores/prestigeStore';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
import { MAX_DAY } from '../constants';
|
||||
import { resetAllStores, tickN } from './cross-module-helpers';
|
||||
|
||||
describe('Cross-Module: Lifecycle & Consistency', () => {
|
||||
beforeEach(resetAllStores);
|
||||
|
||||
describe('full loop lifecycle', () => {
|
||||
it('should progress through multiple days without errors', () => {
|
||||
expect(() => {
|
||||
tickN(1000);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should end the loop when day exceeds MAX_DAY', () => {
|
||||
useGameStore.setState({ day: MAX_DAY, hour: 23.9 });
|
||||
|
||||
tickN(5);
|
||||
|
||||
expect(useUIStore.getState().gameOver).toBe(true);
|
||||
expect(usePrestigeStore.getState().loopInsight).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should generate log messages during the loop', () => {
|
||||
usePrestigeStore.setState({ defeatedGuardians: [10], insight: 1000 });
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
|
||||
tickN(100);
|
||||
|
||||
const logs = useUIStore.getState().logs;
|
||||
expect(Array.isArray(logs)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('store consistency after many ticks', () => {
|
||||
it('should keep rawMana within [0, maxMana] after many ticks', () => {
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
|
||||
tickN(500);
|
||||
|
||||
const mana = useManaStore.getState().rawMana;
|
||||
expect(mana).toBeGreaterThanOrEqual(0);
|
||||
expect(mana).toBeLessThanOrEqual(200);
|
||||
});
|
||||
|
||||
it('should keep floorHP within [0, floorMaxHP] after many ticks', () => {
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
|
||||
tickN(200);
|
||||
|
||||
const { floorHP, floorMaxHP } = useCombatStore.getState();
|
||||
expect(floorHP).toBeGreaterThanOrEqual(0);
|
||||
expect(floorHP).toBeLessThanOrEqual(floorMaxHP);
|
||||
});
|
||||
|
||||
it('should keep currentFloor within [1, 100] after many ticks', () => {
|
||||
useCombatStore.setState({ currentAction: 'climb' });
|
||||
|
||||
tickN(1000);
|
||||
|
||||
const { currentFloor } = useCombatStore.getState();
|
||||
expect(currentFloor).toBeGreaterThanOrEqual(1);
|
||||
expect(currentFloor).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
it('should keep day within [1, MAX_DAY + 1] during normal play', () => {
|
||||
tickN(100);
|
||||
|
||||
const { day } = useGameStore.getState();
|
||||
expect(day).toBeGreaterThanOrEqual(1);
|
||||
expect(day).toBeLessThanOrEqual(MAX_DAY + 1);
|
||||
});
|
||||
|
||||
it('should keep hour within [0, 24) after any number of ticks', () => {
|
||||
tickN(999);
|
||||
|
||||
const { hour } = useGameStore.getState();
|
||||
expect(hour).toBeGreaterThanOrEqual(0);
|
||||
expect(hour).toBeLessThan(24);
|
||||
});
|
||||
|
||||
it('should keep incursionStrength within [0, 0.95]', () => {
|
||||
tickN(2000);
|
||||
|
||||
const { incursionStrength } = useGameStore.getState();
|
||||
expect(incursionStrength).toBeGreaterThanOrEqual(0);
|
||||
expect(incursionStrength).toBeLessThanOrEqual(0.95);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pause and gameOver consistency', () => {
|
||||
it('should not change any store state when paused', () => {
|
||||
useUIStore.setState({ paused: true });
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
useCombatStore.setState({ currentFloor: 5, currentAction: 'climb' });
|
||||
|
||||
const manaBefore = useManaStore.getState().rawMana;
|
||||
const floorBefore = useCombatStore.getState().currentFloor;
|
||||
const dayBefore = useGameStore.getState().day;
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useManaStore.getState().rawMana).toBe(manaBefore);
|
||||
expect(useCombatStore.getState().currentFloor).toBe(floorBefore);
|
||||
expect(useGameStore.getState().day).toBe(dayBefore);
|
||||
});
|
||||
|
||||
it('should not change any store state when gameOver', () => {
|
||||
useUIStore.setState({ gameOver: true });
|
||||
useManaStore.setState({ rawMana: 50 });
|
||||
|
||||
const manaBefore = useManaStore.getState().rawMana;
|
||||
const dayBefore = useGameStore.getState().day;
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useManaStore.getState().rawMana).toBe(manaBefore);
|
||||
expect(useGameStore.getState().day).toBe(dayBefore);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useGameStore } from '../stores/gameStore';
|
||||
import { useManaStore } 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 { resetAllStores, tickN } from './cross-module-helpers';
|
||||
|
||||
describe('Cross-Module: Prestige & Discipline', () => {
|
||||
beforeEach(resetAllStores);
|
||||
|
||||
describe('prestige loop reset', () => {
|
||||
it('should reset game day/hour on startNewLoop', () => {
|
||||
useGameStore.setState({ day: 30, hour: 12, incursionStrength: 0.5 });
|
||||
usePrestigeStore.setState({ loopInsight: 100, insight: 50 });
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(useGameStore.getState().day).toBe(1);
|
||||
expect(useGameStore.getState().hour).toBe(0);
|
||||
expect(useGameStore.getState().incursionStrength).toBe(0);
|
||||
});
|
||||
|
||||
it('should preserve insight across loop reset', () => {
|
||||
usePrestigeStore.setState({ insight: 200, loopInsight: 50 });
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(usePrestigeStore.getState().insight).toBeGreaterThanOrEqual(250);
|
||||
});
|
||||
|
||||
it('should increment loop count on startNewLoop', () => {
|
||||
usePrestigeStore.setState({ loopCount: 2, insight: 500 });
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(usePrestigeStore.getState().loopCount).toBe(3);
|
||||
});
|
||||
|
||||
it('should clear defeatedGuardians and signedPacts on new loop', () => {
|
||||
usePrestigeStore.setState({
|
||||
defeatedGuardians: [10, 20, 30],
|
||||
signedPacts: [10],
|
||||
insight: 1000,
|
||||
});
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]);
|
||||
expect(usePrestigeStore.getState().signedPacts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should clear pact ritual state on new loop', () => {
|
||||
usePrestigeStore.setState({
|
||||
pactRitualFloor: 50,
|
||||
pactRitualProgress: 10,
|
||||
insight: 1000,
|
||||
});
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBeNull();
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBe(0);
|
||||
});
|
||||
|
||||
it('should reset combat floor on new loop', () => {
|
||||
usePrestigeStore.setState({ insight: 1000 });
|
||||
useCombatStore.setState({ currentFloor: 50, maxFloorReached: 50 });
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(useCombatStore.getState().currentFloor).toBe(1);
|
||||
expect(useCombatStore.getState().maxFloorReached).toBe(1);
|
||||
});
|
||||
|
||||
it('should clear gameOver and victory flags on new loop', () => {
|
||||
useUIStore.setState({ gameOver: true, victory: false });
|
||||
usePrestigeStore.setState({ insight: 1000 });
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(useUIStore.getState().gameOver).toBe(false);
|
||||
expect(useUIStore.getState().victory).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset mana on new loop', () => {
|
||||
usePrestigeStore.setState({ insight: 1000 });
|
||||
useManaStore.setState({
|
||||
rawMana: 5,
|
||||
totalManaGathered: 9999,
|
||||
});
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(useManaStore.getState().rawMana).toBeGreaterThan(5);
|
||||
expect(useManaStore.getState().totalManaGathered).toBe(0);
|
||||
});
|
||||
|
||||
it('should preserve prestige upgrades across loop reset', () => {
|
||||
usePrestigeStore.setState({
|
||||
insight: 1000,
|
||||
prestigeUpgrades: { manaWell: 3, spireKey: 1 },
|
||||
});
|
||||
|
||||
useGameStore.getState().startNewLoop();
|
||||
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades.manaWell).toBe(3);
|
||||
expect(usePrestigeStore.getState().prestigeUpgrades.spireKey).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('discipline mana drain and XP via tick', () => {
|
||||
it('should accrue discipline XP when enough mana', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useManaStore.setState({ rawMana: 99999 });
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useDisciplineStore.getState().totalXP).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should drain raw mana for raw-type disciplines', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useManaStore.setState({ rawMana: 99999 });
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useDisciplineStore.getState().totalXP).toBeGreaterThan(0);
|
||||
const disc = useDisciplineStore.getState().disciplines['raw-mastery'];
|
||||
expect(disc).toBeDefined();
|
||||
expect(disc!.xp).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should pause discipline when insufficient mana', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useManaStore.setState({ rawMana: 0 });
|
||||
|
||||
tickN(5);
|
||||
|
||||
const disc = useDisciplineStore.getState().disciplines['raw-mastery'];
|
||||
if (disc) {
|
||||
expect(disc.paused).toBe(true);
|
||||
} else {
|
||||
expect(useDisciplineStore.getState().totalXP).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should respect concurrent limit', () => {
|
||||
useDisciplineStore.getState().activate('raw-mastery');
|
||||
useManaStore.setState({ rawMana: 99999 });
|
||||
|
||||
tickN(10);
|
||||
|
||||
expect(useDisciplineStore.getState().totalXP).toBeGreaterThan(0);
|
||||
const activeIds = useDisciplineStore.getState().activeIds;
|
||||
expect(activeIds.length).toBeLessThanOrEqual(
|
||||
useDisciplineStore.getState().concurrentLimit,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pact ritual completion via tick', () => {
|
||||
it('should complete pact ritual after enough ticks', () => {
|
||||
usePrestigeStore.setState({
|
||||
defeatedGuardians: [10],
|
||||
pactRitualFloor: 10,
|
||||
pactRitualProgress: 0,
|
||||
signedPacts: [],
|
||||
pactSlots: 2,
|
||||
});
|
||||
|
||||
tickN(500);
|
||||
|
||||
expect(usePrestigeStore.getState().signedPacts).toContain(10);
|
||||
expect(usePrestigeStore.getState().pactRitualFloor).toBeNull();
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBe(0);
|
||||
});
|
||||
|
||||
it('should not advance pact ritual when not active', () => {
|
||||
usePrestigeStore.setState({ pactRitualFloor: null });
|
||||
|
||||
tickN(50);
|
||||
|
||||
expect(usePrestigeStore.getState().pactRitualProgress).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user