import { describe, it, expect } from 'vitest'; import { fmt, fmtDec, getFloorMaxHP, getFloorElement, canAffordSpellCost, deductSpellCost, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, } from '../computed-stats'; import { MAX_DAY, INCURSION_START_DAY, HOURS_PER_TICK } from '../constants'; describe('fmt', () => { it('should format numbers < 1000 as integers', () => { expect(fmt(500)).toBe('500'); expect(fmt(0)).toBe('0'); expect(fmt(999)).toBe('999'); }); it('should format thousands with K suffix', () => { expect(fmt(1500)).toBe('1.5K'); expect(fmt(1000)).toBe('1.0K'); expect(fmt(9999)).toBe('10.0K'); }); it('should format millions with M suffix', () => { expect(fmt(1500000)).toBe('1.50M'); expect(fmt(1000000)).toBe('1.00M'); }); it('should format billions with B suffix', () => { expect(fmt(1500000000)).toBe('1.50B'); expect(fmt(1000000000)).toBe('1.00B'); }); it('should handle edge cases', () => { expect(fmt(NaN)).toBe('0'); expect(fmt(Infinity)).toBe('0'); expect(fmt(-100)).toBe('-100'); }); }); describe('fmtDec', () => { it('should format decimal numbers', () => { expect(fmtDec(1.5, 1)).toBe('1.5'); expect(fmtDec(1.234, 2)).toBe('1.23'); expect(fmtDec(1000.5, 1)).toBe('1000.5'); }); }); describe('getFloorMaxHP', () => { it('should return base HP for floor 1', () => { const hp = getFloorMaxHP(1); expect(hp).toBeGreaterThan(0); }); it('should scale HP with floor number', () => { const hp1 = getFloorMaxHP(1); const hp5 = getFloorMaxHP(5); const hp10 = getFloorMaxHP(10); expect(hp5).toBeGreaterThan(hp1); expect(hp10).toBeGreaterThan(hp5); }); it('should return higher HP for guardian floors', () => { // Floor 10 is Ignis Prime guardian const hp10 = getFloorMaxHP(10); const hp9 = getFloorMaxHP(9); expect(hp10).toBeGreaterThan(hp9 * 2); // Guardians have much more HP }); }); describe('getFloorElement', () => { it('should return an element string for valid floors', () => { const elem = getFloorElement(1); expect(typeof elem).toBe('string'); expect(['fire', 'water', 'earth', 'air', 'raw']).toContain(elem); }); it('should cycle through elements based on floor number', () => { // Check that floors have different elements const elem1 = getFloorElement(1); const elem7 = getFloorElement(7); // Since it cycles every 5 floors, floor 1 and 7 might have same element const elem2 = getFloorElement(2); // Floor 1 and 2 should have different elements (if cycle allows) }); }); describe('canAffordSpellCost', () => { it('should return true for raw mana cost when enough mana', () => { const result = canAffordSpellCost({ type: 'raw', amount: 10 }, 50, {}); expect(result).toBe(true); }); it('should return false for raw mana cost when not enough mana', () => { const result = canAffordSpellCost({ type: 'raw', amount: 100 }, 50, {}); expect(result).toBe(false); }); it('should handle zero cost', () => { const result = canAffordSpellCost({ type: 'raw', amount: 0 }, 0, {}); expect(result).toBe(true); }); it('should handle elemental costs', () => { const elements = { fire: { current: 10, max: 50, unlocked: true }, }; const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements); expect(result).toBe(true); }); it('should return false for elemental cost when not enough', () => { const elements = { fire: { current: 3, max: 50, unlocked: true }, }; const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements); expect(result).toBe(false); }); it('should return false for locked element', () => { const elements = { fire: { current: 10, max: 50, unlocked: false }, }; const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements); expect(result).toBe(false); }); }); describe('deductSpellCost', () => { it('should deduct raw mana correctly', () => { const result = deductSpellCost({ type: 'raw', amount: 10 }, 50, {}); expect(result.rawMana).toBe(40); }); it('should not go below zero', () => { const result = deductSpellCost({ type: 'raw', amount: 100 }, 50, {}); expect(result.rawMana).toBe(0); }); it('should deduct elemental mana correctly', () => { const elements = { fire: { current: 10, max: 50, unlocked: true }, }; const result = deductSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements); expect(result.elements.fire.current).toBe(5); }); it('should return same values when cost is zero', () => { const elements = { fire: { current: 10, max: 50, unlocked: true }, }; const result = deductSpellCost({ type: 'raw', amount: 0 }, 50, elements); expect(result.rawMana).toBe(50); expect(result.elements.fire.current).toBe(10); }); }); describe('computeMaxMana', () => { it('should return base 100 with no skills or upgrades', () => { const state = { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {}, }; const effects = { maxManaBonus: 0, maxManaMultiplier: 1 }; const result = computeMaxMana(state, effects); expect(result).toBe(100); }); it('should include manaWell prestige upgrade', () => { const state = { skills: {}, prestigeUpgrades: { manaWell: 5 }, skillUpgrades: {}, skillTiers: {}, }; const effects = { maxManaBonus: 0, maxManaMultiplier: 1 }; const result = computeMaxMana(state, effects); expect(result).toBe(100 + 5 * 500); // Base + 500 per level }); it('should apply multiplier from effects', () => { const state = { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {}, }; const effects = { maxManaBonus: 0, maxManaMultiplier: 1.5 }; const result = computeMaxMana(state, effects); expect(result).toBe(150); // 100 * 1.5 }); it('should apply bonus from effects', () => { const state = { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {}, }; const effects = { maxManaBonus: 50, maxManaMultiplier: 1 }; const result = computeMaxMana(state, effects); expect(result).toBe(150); // 100 + 50 }); }); describe('computeRegen', () => { it('should return base regen with no skills', () => { const state = { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {}, }; const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 }; const result = computeRegen(state, effects); // Base regen is 2 (this test provides effects, so no attunement bonus) expect(result).toBe(2); }); }); describe('computeClickMana', () => { it('should return base click mana with no skills', () => { const state = { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {}, }; const effects = { clickManaBonus: 0, clickManaMultiplier: 1 }; const result = computeClickMana(state, effects); expect(result).toBeGreaterThanOrEqual(1); }); }); describe('getMeditationBonus', () => { it('should return 1.0 with zero ticks', () => { const result = getMeditationBonus(0, {}); expect(result).toBe(1.0); }); it('should increase with more ticks', () => { const result1 = getMeditationBonus(10, {}); const result2 = getMeditationBonus(100, {}); expect(result2).toBeGreaterThan(result1); }); }); describe('getIncursionStrength', () => { it('should return 0 before incursion start day', () => { const result = getIncursionStrength(1, 12); expect(result).toBe(0); }); it('should return positive value during incursion', () => { // After incursion start day const result = getIncursionStrength(INCURSION_START_DAY, 12); expect(result).toBeGreaterThan(0); }); it('should increase with later days', () => { const result1 = getIncursionStrength(INCURSION_START_DAY, 12); const result2 = getIncursionStrength(MAX_DAY, 12); expect(result2).toBeGreaterThan(result1); }); });