diff --git a/test/core/GameStateManager.test.js b/test/core/GameStateManager.test.js index ddf2823..f8c02bf 100644 --- a/test/core/GameStateManager.test.js +++ b/test/core/GameStateManager.test.js @@ -21,6 +21,10 @@ describe("Core: GameStateManager (Singleton)", () => { 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(), }; // Inject Mock (replacing the real Persistence instance) gameStateManager.persistence = mockPersistence; @@ -47,6 +51,9 @@ describe("Core: GameStateManager (Singleton)", () => { objectives: [], }), playIntro: sinon.stub().resolves(), + load: sinon.stub(), + save: sinon.stub().returns({ completedMissions: [] }), + completedMissions: new Set(), }; }); @@ -146,4 +153,45 @@ describe("Core: GameStateManager (Singleton)", () => { expect(gameStateManager.activeRunData).to.deep.equal(savedData); expect(mockGameLoop.startLevel.calledWith(savedData)).to.be.true; }); + + it("CoA 5: init should load campaign data if available", async () => { + const savedCampaignData = { + completedMissions: ["MISSION_TUTORIAL_01"], + }; + mockPersistence.loadCampaign.resolves(savedCampaignData); + + await gameStateManager.init(); + + expect(mockPersistence.loadCampaign.called).to.be.true; + expect(gameStateManager.missionManager.load.calledWith(savedCampaignData)).to.be.true; + }); + + it("CoA 6: init should handle missing campaign data gracefully", async () => { + mockPersistence.loadCampaign.resolves(null); + + await gameStateManager.init(); + + expect(mockPersistence.loadCampaign.called).to.be.true; + expect(gameStateManager.missionManager.load.called).to.be.false; + }); + + it("CoA 7: campaign-data-changed event should trigger save", async () => { + await gameStateManager.init(); + + // Clear any previous calls + mockPersistence.saveCampaign.resetHistory(); + + // Dispatch campaign-data-changed event + window.dispatchEvent(new CustomEvent("campaign-data-changed", { + detail: { missionCompleted: "MISSION_TUTORIAL_01" } + })); + + // Wait for async save (event listener is synchronous but save is async) + await new Promise((resolve) => setTimeout(resolve, 50)); + + expect(mockPersistence.saveCampaign.called).to.be.true; + const savedData = mockPersistence.saveCampaign.firstCall.args[0]; + expect(savedData).to.exist; + expect(savedData.completedMissions).to.be.an("array"); + }); }); diff --git a/test/core/GameStateManager/hub-integration.test.js b/test/core/GameStateManager/hub-integration.test.js index dd6cf03..2e7db19 100644 --- a/test/core/GameStateManager/hub-integration.test.js +++ b/test/core/GameStateManager/hub-integration.test.js @@ -18,6 +18,10 @@ describe("Core: GameStateManager - Hub Integration", () => { 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(), }; gameStateManager.persistence = mockPersistence; @@ -41,8 +45,10 @@ describe("Core: GameStateManager - Hub Integration", () => { objectives: [], }), playIntro: sinon.stub().resolves(), - completedMissions: new Set(), missionRegistry: new Map(), + load: sinon.stub(), + save: sinon.stub().returns({ completedMissions: [] }), + completedMissions: new Set(), }; }); diff --git a/test/core/Persistence.test.js b/test/core/Persistence.test.js index 4301c68..61d8cd3 100644 --- a/test/core/Persistence.test.js +++ b/test/core/Persistence.test.js @@ -80,11 +80,13 @@ describe("Core: Persistence", () => { await initPromise; - expect(globalObj.indexedDB.open.calledWith("AetherShardsDB", 2)).to.be.true; + expect(globalObj.indexedDB.open.calledWith("AetherShardsDB", 4)).to.be.true; expect(mockDB.createObjectStore.calledWith("Runs", { keyPath: "id" })).to.be .true; expect(mockDB.createObjectStore.calledWith("Roster", { keyPath: "id" })).to .be.true; + expect(mockDB.createObjectStore.calledWith("Campaign", { keyPath: "id" })) + .to.be.true; expect(persistence.db).to.equal(mockDB); }); @@ -243,4 +245,72 @@ describe("Core: Persistence", () => { expect(persistence.db).to.equal(mockDB); expect(mockStore.put.calledOnce).to.be.true; }); + + it("CoA 9: saveCampaign should store campaign data with campaign_data id", async () => { + persistence.db = mockDB; + const campaignData = { + completedMissions: ["MISSION_TUTORIAL_01", "MISSION_TEST_01"], + }; + + const mockPutRequest = { + onsuccess: null, + onerror: null, + }; + mockStore.put.returns(mockPutRequest); + + const savePromise = persistence.saveCampaign(campaignData); + mockPutRequest.onsuccess(); + + await savePromise; + + expect(mockDB.transaction.calledWith(["Campaign"], "readwrite")).to.be.true; + expect(mockStore.put.calledOnce).to.be.true; + const savedData = mockStore.put.firstCall.args[0]; + expect(savedData.id).to.equal("campaign_data"); + expect(savedData.data).to.deep.equal(campaignData); + }); + + it("CoA 10: loadCampaign should extract data from stored object", async () => { + persistence.db = mockDB; + const storedData = { + id: "campaign_data", + data: { + completedMissions: ["MISSION_TUTORIAL_01", "MISSION_TEST_01"], + }, + }; + + const mockGetRequest = { + onsuccess: null, + onerror: null, + result: storedData, + }; + mockStore.get.returns(mockGetRequest); + + const loadPromise = persistence.loadCampaign(); + mockGetRequest.onsuccess(); + + const result = await loadPromise; + + expect(mockDB.transaction.calledWith(["Campaign"], "readonly")).to.be.true; + expect(mockStore.get.calledWith("campaign_data")).to.be.true; + expect(result).to.deep.equal(storedData.data); + }); + + it("CoA 11: loadCampaign should return null if no data exists", async () => { + persistence.db = mockDB; + + const mockGetRequest = { + onsuccess: null, + onerror: null, + result: undefined, + }; + mockStore.get.returns(mockGetRequest); + + const loadPromise = persistence.loadCampaign(); + mockGetRequest.onsuccess(); + + const result = await loadPromise; + + expect(result).to.be.null; + }); }); diff --git a/test/managers/MissionManager.test.js b/test/managers/MissionManager.test.js index b86f2cf..2629a71 100644 --- a/test/managers/MissionManager.test.js +++ b/test/managers/MissionManager.test.js @@ -156,9 +156,17 @@ describe("Manager: MissionManager", () => { rewards: { guaranteed: {} }, }; + // Spy on window.dispatchEvent to verify campaign-data-changed event + const eventSpy = sinon.spy(); + window.addEventListener("campaign-data-changed", eventSpy); + await manager.completeActiveMission(); expect(manager.completedMissions.has("MISSION_TUTORIAL_01")).to.be.true; + expect(eventSpy.called).to.be.true; + expect(eventSpy.firstCall.args[0].detail.missionCompleted).to.equal("MISSION_TUTORIAL_01"); + + window.removeEventListener("campaign-data-changed", eventSpy); }); it("CoA 10: load should restore completed missions", () => {