e0e7beb495
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
- Remove debugSetFloor and resetFloorHP actions from combatStore.ts - Remove their type definitions from combat-state.types.ts - Remove Skip to Floor 100 and Reset Floor HP buttons from GameStateDebugSection.tsx - Remove same buttons from legacy GameStateDebug.tsx - Remove floor quick-jump and Reset Floor HP from SpireDebugSection.tsx - Remove associated tests from DebugTab.test.ts, store-actions.test.ts, store-actions-combat-prestige.test.ts - Add missing src/test/setup.ts required by vitest config These debug buttons created inconsistent game states by teleporting players to floors without proper initialization (no spireMode, no room state, no clearedFloors, no guardian encounters). resetFloorHP could be spammed to infinitely retry any floor for free.
327 lines
12 KiB
TypeScript
327 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// ─── Test: DebugTab barrel export ─────────────────────────────────────────────
|
|
// Verifies that the DebugTab component is properly exported from the barrel
|
|
// and that all section components are importable.
|
|
|
|
describe('DebugTab module structure', () => {
|
|
it('exports DebugTab from barrel index', async () => {
|
|
const mod = await import('./DebugTab');
|
|
expect(mod.DebugTab).toBeDefined();
|
|
expect(typeof mod.DebugTab).toBe('function');
|
|
});
|
|
|
|
it('exports GameStateDebugSection', async () => {
|
|
const mod = await import('./DebugTab/GameStateDebugSection');
|
|
expect(mod.GameStateDebugSection).toBeDefined();
|
|
expect(typeof mod.GameStateDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports DisciplineDebugSection', async () => {
|
|
const mod = await import('./DebugTab/DisciplineDebugSection');
|
|
expect(mod.DisciplineDebugSection).toBeDefined();
|
|
expect(typeof mod.DisciplineDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports AttunementDebugSection', async () => {
|
|
const mod = await import('./DebugTab/AttunementDebugSection');
|
|
expect(mod.AttunementDebugSection).toBeDefined();
|
|
expect(typeof mod.AttunementDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports ElementDebugSection', async () => {
|
|
const mod = await import('./DebugTab/ElementDebugSection');
|
|
expect(mod.ElementDebugSection).toBeDefined();
|
|
expect(typeof mod.ElementDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports GolemDebugSection', async () => {
|
|
const mod = await import('./DebugTab/GolemDebugSection');
|
|
expect(mod.GolemDebugSection).toBeDefined();
|
|
expect(typeof mod.GolemDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports PactDebugSection', async () => {
|
|
const mod = await import('./DebugTab/PactDebugSection');
|
|
expect(mod.PactDebugSection).toBeDefined();
|
|
expect(typeof mod.PactDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports SpireDebugSection', async () => {
|
|
const mod = await import('./DebugTab/SpireDebugSection');
|
|
expect(mod.SpireDebugSection).toBeDefined();
|
|
expect(typeof mod.SpireDebugSection).toBe('function');
|
|
});
|
|
|
|
it('exports AchievementDebugSection', async () => {
|
|
const mod = await import('./DebugTab/AchievementDebugSection');
|
|
expect(mod.AchievementDebugSection).toBeDefined();
|
|
expect(typeof mod.AchievementDebugSection).toBe('function');
|
|
});
|
|
});
|
|
|
|
// ─── Test: Barrel export includes DebugTab ────────────────────────────────────
|
|
|
|
describe('Tab barrel export', () => {
|
|
it('includes DebugTab in the tabs index', async () => {
|
|
const mod = await import('@/components/game/tabs');
|
|
expect(mod.DebugTab).toBeDefined();
|
|
expect(typeof mod.DebugTab).toBe('function');
|
|
});
|
|
});
|
|
|
|
// ─── Test: Store interactions used by DebugTab sections ───────────────────────
|
|
|
|
describe('GameStateDebugSection store interactions', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('resetGame action is callable', () => {
|
|
const mockReset = vi.fn();
|
|
// Simulate what GameStateDebugSection does on reset
|
|
mockReset();
|
|
expect(mockReset).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('gatherMana action is callable N times for bulk add', () => {
|
|
const mockGather = vi.fn();
|
|
const amount = 100;
|
|
for (let i = 0; i < amount; i++) {
|
|
mockGather();
|
|
}
|
|
expect(mockGather).toHaveBeenCalledTimes(amount);
|
|
});
|
|
|
|
it('togglePause action is callable', () => {
|
|
const mockToggle = vi.fn();
|
|
mockToggle();
|
|
expect(mockToggle).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
});
|
|
|
|
describe('DisciplineDebugSection store interactions', () => {
|
|
it('activate action is callable', () => {
|
|
const mockActivate = vi.fn();
|
|
mockActivate('meditation');
|
|
expect(mockActivate).toHaveBeenCalledWith('meditation');
|
|
});
|
|
|
|
it('deactivate action is callable', () => {
|
|
const mockDeactivate = vi.fn();
|
|
mockDeactivate('meditation');
|
|
expect(mockDeactivate).toHaveBeenCalledWith('meditation');
|
|
});
|
|
|
|
it('XP can be added to discipline via setState', () => {
|
|
const disciplines: Record<string, { xp: number; paused: boolean }> = {
|
|
meditation: { xp: 0, paused: false },
|
|
};
|
|
const id = 'meditation';
|
|
const amount = 100;
|
|
disciplines[id] = { ...disciplines[id], xp: disciplines[id].xp + amount };
|
|
expect(disciplines[id].xp).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe('AttunementDebugSection store interactions', () => {
|
|
it('debugUnlockAttunement is callable', () => {
|
|
const mockUnlock = vi.fn();
|
|
mockUnlock('invoker');
|
|
expect(mockUnlock).toHaveBeenCalledWith('invoker');
|
|
});
|
|
|
|
it('addAttunementXP is callable', () => {
|
|
const mockAddXP = vi.fn();
|
|
mockAddXP('enchanter', 100);
|
|
expect(mockAddXP).toHaveBeenCalledWith('enchanter', 100);
|
|
});
|
|
});
|
|
|
|
describe('ElementDebugSection store interactions', () => {
|
|
it('unlockElement is callable with zero cost', () => {
|
|
const mockUnlock = vi.fn();
|
|
mockUnlock('fire', 0);
|
|
expect(mockUnlock).toHaveBeenCalledWith('fire', 0);
|
|
});
|
|
|
|
it('addElementMana is callable', () => {
|
|
const mockAdd = vi.fn();
|
|
mockAdd('fire', 10, 50);
|
|
expect(mockAdd).toHaveBeenCalledWith('fire', 10, 50);
|
|
});
|
|
});
|
|
|
|
describe('GolemDebugSection store interactions', () => {
|
|
it('setEnabledGolems is callable with all golem IDs', () => {
|
|
const mockSet = vi.fn();
|
|
const allIds = ['stoneGolem', 'fireGolem'];
|
|
mockSet(allIds);
|
|
expect(mockSet).toHaveBeenCalledWith(allIds);
|
|
});
|
|
|
|
it('setEnabledGolems is callable with empty array to disable all', () => {
|
|
const mockSet = vi.fn();
|
|
mockSet([]);
|
|
expect(mockSet).toHaveBeenCalledWith([]);
|
|
});
|
|
});
|
|
|
|
describe('PactDebugSection store interactions', () => {
|
|
it('addSignedPact is callable', () => {
|
|
const mockAdd = vi.fn();
|
|
mockAdd(10);
|
|
expect(mockAdd).toHaveBeenCalledWith(10);
|
|
});
|
|
|
|
it('removePact is callable', () => {
|
|
const mockRemove = vi.fn();
|
|
mockRemove(10);
|
|
expect(mockRemove).toHaveBeenCalledWith(10);
|
|
});
|
|
|
|
it('debugSetSignedPacts is callable', () => {
|
|
const mockSet = vi.fn();
|
|
mockSet([10, 20, 30]);
|
|
expect(mockSet).toHaveBeenCalledWith([10, 20, 30]);
|
|
});
|
|
});
|
|
|
|
describe('SpireDebugSection store interactions', () => {
|
|
it('enterSpireMode is callable', () => {
|
|
const mockEnter = vi.fn();
|
|
mockEnter();
|
|
expect(mockEnter).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('exitSpireMode is callable', () => {
|
|
const mockExit = vi.fn();
|
|
mockExit();
|
|
expect(mockExit).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('setMaxFloorReached is callable', () => {
|
|
const mockSet = vi.fn();
|
|
mockSet(50);
|
|
expect(mockSet).toHaveBeenCalledWith(50);
|
|
});
|
|
});
|
|
|
|
describe('AchievementDebugSection store interactions', () => {
|
|
it('can set all achievements as unlocked via setState', () => {
|
|
const allIds = ['firstBlood', 'floorClimber'];
|
|
const newState = {
|
|
achievements: {
|
|
unlocked: allIds,
|
|
progress: Object.fromEntries(allIds.map(id => [id, 100])),
|
|
},
|
|
};
|
|
expect(newState.achievements.unlocked).toEqual(allIds);
|
|
expect(Object.keys(newState.achievements.progress)).toEqual(allIds);
|
|
});
|
|
|
|
it('can reset all achievements via setState', () => {
|
|
const newState = {
|
|
achievements: {
|
|
unlocked: [],
|
|
progress: {},
|
|
},
|
|
};
|
|
expect(newState.achievements.unlocked).toEqual([]);
|
|
expect(newState.achievements.progress).toEqual({});
|
|
});
|
|
});
|
|
|
|
// ─── Test: DebugTab component displayName ─────────────────────────────────────
|
|
|
|
describe('DebugTab component metadata', () => {
|
|
it('DebugTab has correct displayName', async () => {
|
|
const { DebugTab } = await import('./DebugTab');
|
|
expect(DebugTab.displayName).toBe('DebugTab');
|
|
});
|
|
|
|
it('GameStateDebugSection has correct displayName', async () => {
|
|
const { GameStateDebugSection } = await import('./DebugTab/GameStateDebugSection');
|
|
expect(GameStateDebugSection.displayName).toBe('GameStateDebugSection');
|
|
});
|
|
|
|
it('DisciplineDebugSection has correct displayName', async () => {
|
|
const { DisciplineDebugSection } = await import('./DebugTab/DisciplineDebugSection');
|
|
expect(DisciplineDebugSection.displayName).toBe('DisciplineDebugSection');
|
|
});
|
|
|
|
it('AttunementDebugSection has correct displayName', async () => {
|
|
const { AttunementDebugSection } = await import('./DebugTab/AttunementDebugSection');
|
|
expect(AttunementDebugSection.displayName).toBe('AttunementDebugSection');
|
|
});
|
|
|
|
it('ElementDebugSection has correct displayName', async () => {
|
|
const { ElementDebugSection } = await import('./DebugTab/ElementDebugSection');
|
|
expect(ElementDebugSection.displayName).toBe('ElementDebugSection');
|
|
});
|
|
|
|
it('GolemDebugSection has correct displayName', async () => {
|
|
const { GolemDebugSection } = await import('./DebugTab/GolemDebugSection');
|
|
expect(GolemDebugSection.displayName).toBe('GolemDebugSection');
|
|
});
|
|
|
|
it('PactDebugSection has correct displayName', async () => {
|
|
const { PactDebugSection } = await import('./DebugTab/PactDebugSection');
|
|
expect(PactDebugSection.displayName).toBe('PactDebugSection');
|
|
});
|
|
|
|
it('SpireDebugSection has correct displayName', async () => {
|
|
const { SpireDebugSection } = await import('./DebugTab/SpireDebugSection');
|
|
expect(SpireDebugSection.displayName).toBe('SpireDebugSection');
|
|
});
|
|
|
|
it('AchievementDebugSection has correct displayName', async () => {
|
|
const { AchievementDebugSection } = await import('./DebugTab/AchievementDebugSection');
|
|
expect(AchievementDebugSection.displayName).toBe('AchievementDebugSection');
|
|
});
|
|
});
|
|
|
|
// ─── Test: File size limits ───────────────────────────────────────────────────
|
|
// Note: 400-line limit is enforced by pre-commit hook (check-file-size.js).
|
|
// These tests verify the source files are importable; line count enforcement
|
|
// is handled by the hook, not by runtime tests.
|
|
|
|
describe('File size limits (400 lines max)', () => {
|
|
it('DebugTab.tsx is importable and under 400 lines (enforced by pre-commit hook)', async () => {
|
|
const fs = await import('fs');
|
|
const path = await import('path');
|
|
const filePath = path.join(__dirname, 'DebugTab.tsx');
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n').length;
|
|
expect(lines).toBeLessThan(400);
|
|
});
|
|
|
|
it('GameStateDebugSection.tsx is under 400 lines', async () => {
|
|
const fs = await import('fs');
|
|
const path = await import('path');
|
|
const filePath = path.join(__dirname, 'DebugTab', 'GameStateDebugSection.tsx');
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n').length;
|
|
expect(lines).toBeLessThan(400);
|
|
});
|
|
|
|
it('DisciplineDebugSection.tsx is under 400 lines', async () => {
|
|
const fs = await import('fs');
|
|
const path = await import('path');
|
|
const filePath = path.join(__dirname, 'DebugTab', 'DisciplineDebugSection.tsx');
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n').length;
|
|
expect(lines).toBeLessThan(400);
|
|
});
|
|
|
|
it('PactDebugSection.tsx is under 400 lines', async () => {
|
|
const fs = await import('fs');
|
|
const path = await import('path');
|
|
const filePath = path.join(__dirname, 'DebugTab', 'PactDebugSection.tsx');
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n').length;
|
|
expect(lines).toBeLessThan(400);
|
|
});
|
|
});
|