import { expect } from "@esm-bundle/chai"; import sinon from "sinon"; import { MissionManager } from "../../src/managers/MissionManager.js"; import { narrativeManager } from "../../src/managers/NarrativeManager.js"; describe("Manager: MissionManager", () => { let manager; let mockNarrativeManager; beforeEach(() => { manager = new MissionManager(); // Mock narrativeManager mockNarrativeManager = { startSequence: sinon.stub(), addEventListener: sinon.stub(), removeEventListener: sinon.stub(), }; // Replace the singleton reference in the manager if possible // Since it's imported, we'll need to stub the methods we use sinon.stub(narrativeManager, "startSequence"); sinon.stub(narrativeManager, "addEventListener"); sinon.stub(narrativeManager, "removeEventListener"); }); afterEach(() => { sinon.restore(); }); it("CoA 1: Should initialize with tutorial mission registered", () => { expect(manager.missionRegistry.has("MISSION_TUTORIAL_01")).to.be.true; expect(manager.activeMissionId).to.be.null; expect(manager.completedMissions).to.be.instanceof(Set); }); it("CoA 2: registerMission should add mission to registry", () => { const newMission = { id: "MISSION_TEST_01", config: { title: "Test Mission" }, objectives: { primary: [] }, }; manager.registerMission(newMission); expect(manager.missionRegistry.has("MISSION_TEST_01")).to.be.true; expect(manager.missionRegistry.get("MISSION_TEST_01")).to.equal(newMission); }); it("CoA 3: getActiveMission should return tutorial if no active mission", () => { const mission = manager.getActiveMission(); expect(mission).to.exist; expect(mission.id).to.equal("MISSION_TUTORIAL_01"); }); it("CoA 4: getActiveMission should return active mission if set", () => { const testMission = { id: "MISSION_TEST_01", config: { title: "Test" }, objectives: { primary: [] }, }; manager.registerMission(testMission); manager.activeMissionId = "MISSION_TEST_01"; const mission = manager.getActiveMission(); expect(mission.id).to.equal("MISSION_TEST_01"); }); it("CoA 5: setupActiveMission should initialize objectives", () => { const mission = manager.getActiveMission(); mission.objectives = { primary: [ { type: "ELIMINATE_ALL", target_count: 5 }, { type: "ELIMINATE_UNIT", target_def_id: "ENEMY_GOBLIN", target_count: 3 }, ], }; manager.setupActiveMission(); expect(manager.currentObjectives).to.have.length(2); expect(manager.currentObjectives[0].current).to.equal(0); expect(manager.currentObjectives[0].complete).to.be.false; expect(manager.currentObjectives[1].target_count).to.equal(3); }); it("CoA 6: onGameEvent should update ELIMINATE_ALL objectives", () => { manager.setupActiveMission(); manager.currentObjectives = [ { type: "ELIMINATE_ALL", target_count: 3, current: 0, complete: false }, ]; manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_1" }); manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_2" }); manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_3" }); expect(manager.currentObjectives[0].current).to.equal(3); expect(manager.currentObjectives[0].complete).to.be.true; }); it("CoA 7: onGameEvent should update ELIMINATE_UNIT objectives for specific unit", () => { manager.setupActiveMission(); manager.currentObjectives = [ { type: "ELIMINATE_UNIT", target_def_id: "ENEMY_GOBLIN", target_count: 2, current: 0, complete: false, }, ]; manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_GOBLIN" }); manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_OTHER" }); // Should not count manager.onGameEvent("ENEMY_DEATH", { unitId: "ENEMY_GOBLIN" }); expect(manager.currentObjectives[0].current).to.equal(2); expect(manager.currentObjectives[0].complete).to.be.true; }); it("CoA 8: checkVictory should dispatch mission-victory event when all objectives complete", () => { const victorySpy = sinon.spy(); window.addEventListener("mission-victory", victorySpy); manager.setupActiveMission(); manager.currentObjectives = [ { type: "ELIMINATE_ALL", target_count: 2, current: 2, complete: true }, ]; manager.activeMissionId = "MISSION_TUTORIAL_01"; manager.checkVictory(); expect(victorySpy.called).to.be.true; expect(manager.completedMissions.has("MISSION_TUTORIAL_01")).to.be.true; window.removeEventListener("mission-victory", victorySpy); }); it("CoA 9: completeActiveMission should add mission to completed set", () => { manager.activeMissionId = "MISSION_TUTORIAL_01"; manager.completeActiveMission(); expect(manager.completedMissions.has("MISSION_TUTORIAL_01")).to.be.true; }); it("CoA 10: load should restore completed missions", () => { const saveData = { completedMissions: ["MISSION_TUTORIAL_01", "MISSION_TEST_01"], }; manager.load(saveData); expect(manager.completedMissions.has("MISSION_TUTORIAL_01")).to.be.true; expect(manager.completedMissions.has("MISSION_TEST_01")).to.be.true; }); it("CoA 11: save should serialize completed missions", () => { manager.completedMissions.add("MISSION_TUTORIAL_01"); manager.completedMissions.add("MISSION_TEST_01"); const saved = manager.save(); expect(saved.completedMissions).to.be.an("array"); expect(saved.completedMissions).to.include("MISSION_TUTORIAL_01"); expect(saved.completedMissions).to.include("MISSION_TEST_01"); }); it("CoA 12: _mapNarrativeIdToFileName should convert narrative IDs to filenames", () => { expect(manager._mapNarrativeIdToFileName("NARRATIVE_TUTORIAL_INTRO")).to.equal( "tutorial_intro" ); expect(manager._mapNarrativeIdToFileName("NARRATIVE_TUTORIAL_SUCCESS")).to.equal( "tutorial_success" ); // The implementation converts NARRATIVE_UNKNOWN to narrative_unknown (lowercase with NARRATIVE_ prefix removed) expect(manager._mapNarrativeIdToFileName("NARRATIVE_UNKNOWN")).to.equal("narrative_unknown"); }); });