aether-shards/test/core/GameStateManager/hub-integration.test.js

174 lines
5.9 KiB
JavaScript
Raw Normal View History

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
const transitionSpy = sinon.spy(gameStateManager, "transitionTo");
await gameStateManager.init();
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
transitionSpy.restore();
});
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);
const transitionSpy = sinon.spy(gameStateManager, "transitionTo");
await gameStateManager.init();
await gameStateManager.continueGame();
expect(transitionSpy.calledWith(GameStateManager.STATES.MAIN_MENU)).to.be.true;
// Hub should show because completed missions exist
transitionSpy.restore();
});
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);
const transitionSpy = sinon.spy(gameStateManager, "transitionTo");
await gameStateManager.init();
// init() calls transitionTo once, so reset the call count
const callCountBeforeContinue = transitionSpy.callCount;
await gameStateManager.continueGame();
// Should not transition again (stays on current state)
// Main menu should remain visible
expect(transitionSpy.callCount).to.equal(callCountBeforeContinue);
transitionSpy.restore();
});
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
const transitionSpy = sinon.spy(gameStateManager, "transitionTo");
await gameStateManager.init();
gameStateManager.rosterManager.roster = [
{ id: "u1", name: "Test Unit", status: "READY" },
];
await gameStateManager.transitionTo(GameStateManager.STATES.MAIN_MENU);
// Check that transitionTo was called with MAIN_MENU (could be from init or our call)
expect(transitionSpy.calledWith(GameStateManager.STATES.MAIN_MENU)).to.be.true;
// Hub should be shown because roster exists
transitionSpy.restore();
});
});
});