Introduce the HubScreen as the main interface for managing resources, units, and mission selection, integrating with the GameStateManager for dynamic data binding. Implement the MissionBoard component to display and select available missions, enhancing user interaction with mission details and selection logic. Update the GameStateManager to handle transitions between game states, ensuring a seamless experience for players. Add tests for HubScreen and MissionBoard to validate functionality and integration with the overall game architecture.
156 lines
5.1 KiB
JavaScript
156 lines
5.1 KiB
JavaScript
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(),
|
|
};
|
|
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(),
|
|
completedMissions: new Set(),
|
|
missionRegistry: new Map(),
|
|
};
|
|
});
|
|
|
|
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
|
|
});
|
|
});
|
|
});
|
|
|