Enhance GameStateManager and Persistence for campaign data management
- Introduce methods for loading and saving campaign data in GameStateManager, improving state management for player progress. - Update Persistence layer to handle campaign data storage and retrieval, ensuring data integrity and availability. - Add comprehensive tests for campaign data handling, including scenarios for loading existing data and managing data changes through events. - Refactor related components to support new campaign features, enhancing overall game state management.
This commit is contained in:
parent
a9d4064dd8
commit
cc38ee2808
4 changed files with 134 additions and 2 deletions
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue