import { expect } from "@esm-bundle/chai"; import sinon from "sinon"; import { gameStateManager, GameStateManager, } from "../../../src/core/GameStateManager.js"; describe("Core: GameStateManager - Hub Integration", () => { let mockPersistence; let mockGameLoop; beforeEach(() => { gameStateManager.reset(); mockPersistence = { init: sinon.stub().resolves(), saveRun: sinon.stub().resolves(), loadRun: sinon.stub().resolves(null), loadRoster: sinon.stub().resolves(null), saveRoster: sinon.stub().resolves(), loadCampaign: sinon.stub().resolves(null), saveCampaign: sinon.stub().resolves(), loadMarketState: sinon.stub().resolves(null), saveMarketState: sinon.stub().resolves(), loadHubStash: sinon.stub().resolves(null), saveHubStash: sinon.stub().resolves(), loadUnlocks: sinon.stub().resolves([]), saveUnlocks: sinon.stub().resolves(), }; gameStateManager.persistence = mockPersistence; mockGameLoop = { init: sinon.spy(), startLevel: sinon.stub().resolves(), stop: sinon.spy(), }; gameStateManager.missionManager = { setupActiveMission: sinon.stub(), getActiveMission: sinon.stub().returns({ id: "MISSION_TUTORIAL_01", config: { title: "Test Mission" }, biome: { generator_config: { seed_type: "RANDOM", seed: 12345, }, }, objectives: [], }), playIntro: sinon.stub().resolves(), missionRegistry: new Map(), load: sinon.stub(), save: sinon.stub().returns({ completedMissions: [] }), completedMissions: new Set(), }; }); describe("continueGame - Hub vs Main Menu Logic", () => { it("should go to Hub when there's campaign progress but no active run", async () => { // Setup: roster exists (campaign progress) gameStateManager.rosterManager.roster = [ { id: "u1", name: "Test Unit", status: "READY" }, ]; mockPersistence.loadRun.resolves(null); // No active run await gameStateManager.init(); const transitionSpy = sinon.spy(gameStateManager, "transitionTo"); await gameStateManager.continueGame(); expect(mockPersistence.loadRun.called).to.be.true; expect(transitionSpy.calledWith(GameStateManager.STATES.MAIN_MENU)).to.be.true; // Hub should show because roster exists }); it("should resume active run when save exists", async () => { const savedRun = { id: "RUN_123", missionId: "MISSION_TUTORIAL_01", seed: 12345, depth: 1, squad: [], }; mockPersistence.loadRun.resolves(savedRun); gameStateManager.setGameLoop(mockGameLoop); await gameStateManager.init(); await gameStateManager.continueGame(); expect(mockPersistence.loadRun.called).to.be.true; expect(gameStateManager.activeRunData).to.deep.equal(savedRun); expect(mockGameLoop.startLevel.called).to.be.true; // Should transition to DEPLOYMENT, not MAIN_MENU }); it("should go to Hub when completed missions exist but no active run", async () => { gameStateManager.missionManager.completedMissions.add("MISSION_TUTORIAL_01"); mockPersistence.loadRun.resolves(null); await gameStateManager.init(); const transitionSpy = sinon.spy(gameStateManager, "transitionTo"); await gameStateManager.continueGame(); expect(transitionSpy.calledWith(GameStateManager.STATES.MAIN_MENU)).to.be.true; // Hub should show because completed missions exist }); it("should stay on main menu when no campaign progress and no active run", async () => { // No roster, no completed missions, no active run gameStateManager.rosterManager.roster = []; gameStateManager.missionManager.completedMissions.clear(); mockPersistence.loadRun.resolves(null); await gameStateManager.init(); const transitionSpy = sinon.spy(gameStateManager, "transitionTo"); await gameStateManager.continueGame(); // Should not transition (stays on current state) // Main menu should remain visible expect(transitionSpy.called).to.be.false; }); it("should prioritize active run over campaign progress", async () => { // Both active run and campaign progress exist const savedRun = { id: "RUN_123", missionId: "MISSION_TUTORIAL_01", seed: 12345, depth: 1, squad: [], }; mockPersistence.loadRun.resolves(savedRun); gameStateManager.rosterManager.roster = [ { id: "u1", name: "Test Unit", status: "READY" }, ]; gameStateManager.setGameLoop(mockGameLoop); await gameStateManager.init(); await gameStateManager.continueGame(); // Should resume run, not go to hub expect(mockGameLoop.startLevel.called).to.be.true; expect(gameStateManager.activeRunData).to.deep.equal(savedRun); }); }); describe("State Transitions - Hub Visibility", () => { it("should transition to MAIN_MENU after mission completion", async () => { // This simulates what happens after mission victory await gameStateManager.init(); gameStateManager.rosterManager.roster = [ { id: "u1", name: "Test Unit", status: "READY" }, ]; const transitionSpy = sinon.spy(gameStateManager, "transitionTo"); await gameStateManager.transitionTo(GameStateManager.STATES.MAIN_MENU); expect(transitionSpy.calledWith(GameStateManager.STATES.MAIN_MENU)).to.be.true; // Hub should be shown because roster exists }); }); });