import { expect } from "@esm-bundle/chai"; import sinon from "sinon"; import { GameLoop } from "../../../src/core/GameLoop.js"; import { Explorer } from "../../../src/units/Explorer.js"; import { UnitManager } from "../../../src/managers/UnitManager.js"; import { createGameLoopSetup, cleanupGameLoop, } from "./helpers.js"; // Mock Class Definition const CLASS_VANGUARD = { id: "CLASS_VANGUARD", base_stats: { health: 100, attack: 10, speed: 5 }, growth_rates: { health: 10, attack: 1 }, }; describe("Core: GameLoop - Explorer Progression", function () { this.timeout(30000); let gameLoop; let container; beforeEach(async () => { const setup = createGameLoopSetup(); gameLoop = setup.gameLoop; container = setup.container; await gameLoop.init(container); // Ensure unitManager is initialized (it's created during init) // If it's not, create a minimal one for testing if (!gameLoop.unitManager) { gameLoop.unitManager = new UnitManager({}); } }); afterEach(() => { cleanupGameLoop(gameLoop, container); }); it("CoA 1: _saveExplorerProgression should save classMastery to roster", () => { // Setup: Create a mock gameStateManager with roster const mockRosterUnit = { id: "UNIT_1", classId: "CLASS_VANGUARD", name: "Test Explorer", status: "READY", }; const mockRosterManager = { roster: [mockRosterUnit], }; const mockGameStateManager = { rosterManager: mockRosterManager, _saveRoster: sinon.spy(), }; gameLoop.gameStateManager = mockGameStateManager; // Create an Explorer unit with progression const explorer = new Explorer("UNIT_1", "Test Explorer", "CLASS_VANGUARD", CLASS_VANGUARD); explorer.rosterId = "UNIT_1"; explorer.classMastery.CLASS_VANGUARD.level = 5; explorer.classMastery.CLASS_VANGUARD.xp = 250; explorer.classMastery.CLASS_VANGUARD.skillPoints = 3; explorer.classMastery.CLASS_VANGUARD.unlockedNodes = ["ROOT", "NODE_1"]; explorer.activeClassId = "CLASS_VANGUARD"; // Add to unit manager gameLoop.unitManager.activeUnits.set(explorer.id, explorer); explorer.team = "PLAYER"; // Call the save method gameLoop._saveExplorerProgression(); // Verify progression was saved to roster expect(mockRosterUnit.classMastery).to.exist; expect(mockRosterUnit.classMastery.CLASS_VANGUARD.level).to.equal(5); expect(mockRosterUnit.classMastery.CLASS_VANGUARD.xp).to.equal(250); expect(mockRosterUnit.classMastery.CLASS_VANGUARD.skillPoints).to.equal(3); expect(mockRosterUnit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("ROOT"); expect(mockRosterUnit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("NODE_1"); expect(mockRosterUnit.activeClassId).to.equal("CLASS_VANGUARD"); // Verify roster was saved expect(mockGameStateManager._saveRoster.called).to.be.true; }); it("CoA 2: _saveExplorerProgression should handle multiple Explorers", () => { const mockRosterUnit1 = { id: "UNIT_1", classId: "CLASS_VANGUARD", name: "Explorer 1", status: "READY", }; const mockRosterUnit2 = { id: "UNIT_2", classId: "CLASS_TINKER", name: "Explorer 2", status: "READY", }; const mockRosterManager = { roster: [mockRosterUnit1, mockRosterUnit2], }; const mockGameStateManager = { rosterManager: mockRosterManager, _saveRoster: sinon.spy(), }; gameLoop.gameStateManager = mockGameStateManager; // Create two Explorers with different progression const explorer1 = new Explorer("UNIT_1", "Explorer 1", "CLASS_VANGUARD", CLASS_VANGUARD); explorer1.rosterId = "UNIT_1"; explorer1.classMastery.CLASS_VANGUARD.level = 3; explorer1.team = "PLAYER"; gameLoop.unitManager.activeUnits.set(explorer1.id, explorer1); const explorer2 = new Explorer("UNIT_2", "Explorer 2", "CLASS_TINKER", { id: "CLASS_TINKER", base_stats: { health: 80, attack: 8 }, growth_rates: { health: 5, attack: 2 }, }); explorer2.rosterId = "UNIT_2"; explorer2.classMastery.CLASS_TINKER.level = 7; explorer2.team = "PLAYER"; gameLoop.unitManager.activeUnits.set(explorer2.id, explorer2); gameLoop._saveExplorerProgression(); // Verify both were saved expect(mockRosterUnit1.classMastery.CLASS_VANGUARD.level).to.equal(3); expect(mockRosterUnit2.classMastery.CLASS_TINKER.level).to.equal(7); }); it("CoA 3: _saveExplorerProgression should not save if unit not in roster", () => { const mockRosterManager = { roster: [], }; const mockGameStateManager = { rosterManager: mockRosterManager, _saveRoster: sinon.spy(), }; gameLoop.gameStateManager = mockGameStateManager; // Create an Explorer that's not in roster const explorer = new Explorer("UNIT_UNKNOWN", "Unknown", "CLASS_VANGUARD", CLASS_VANGUARD); explorer.rosterId = "UNIT_UNKNOWN"; explorer.classMastery.CLASS_VANGUARD.level = 5; explorer.team = "PLAYER"; gameLoop.unitManager.activeUnits.set(explorer.id, explorer); gameLoop._saveExplorerProgression(); // Should not crash, but roster should remain empty expect(mockRosterManager.roster).to.be.empty; // Should still call save (in case other units were updated) expect(mockGameStateManager._saveRoster.called).to.be.true; }); it("CoA 4: _handleMissionVictory should save progression", () => { const mockRosterUnit = { id: "UNIT_1", classId: "CLASS_VANGUARD", name: "Test Explorer", status: "READY", }; const mockRosterManager = { roster: [mockRosterUnit], }; const mockGameStateManager = { rosterManager: mockRosterManager, _saveRoster: sinon.spy(), transitionTo: sinon.spy(), clearActiveRun: sinon.spy(), }; gameLoop.gameStateManager = mockGameStateManager; gameLoop.isPaused = false; gameLoop.stop = sinon.spy(); // Create Explorer with progression const explorer = new Explorer("UNIT_1", "Test Explorer", "CLASS_VANGUARD", CLASS_VANGUARD); explorer.rosterId = "UNIT_1"; explorer.classMastery.CLASS_VANGUARD.level = 4; explorer.team = "PLAYER"; gameLoop.unitManager.activeUnits.set(explorer.id, explorer); // Call victory handler gameLoop._handleMissionVictory({ missionId: "TEST_MISSION" }); // Verify progression was saved expect(mockRosterUnit.classMastery.CLASS_VANGUARD.level).to.equal(4); expect(mockGameStateManager._saveRoster.called).to.be.true; }); });