2025-12-28 00:54:03 +00:00
|
|
|
import { expect } from "@esm-bundle/chai";
|
|
|
|
|
import sinon from "sinon";
|
|
|
|
|
import {
|
|
|
|
|
gameStateManager,
|
|
|
|
|
GameStateManager,
|
|
|
|
|
} from "../../../src/core/GameStateManager.js";
|
|
|
|
|
|
|
|
|
|
describe("Core: GameStateManager - Inventory Integration", () => {
|
|
|
|
|
let mockPersistence;
|
|
|
|
|
let mockGameLoop;
|
|
|
|
|
let mockInventoryManager;
|
|
|
|
|
let mockRunStash;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
// Reset Singleton State
|
|
|
|
|
gameStateManager.reset();
|
|
|
|
|
|
|
|
|
|
// Mock InventoryManager
|
|
|
|
|
mockRunStash = {
|
|
|
|
|
id: "RUN_LOOT",
|
|
|
|
|
getAllItems: sinon.stub().returns([
|
|
|
|
|
{
|
|
|
|
|
uid: "ITEM_001",
|
|
|
|
|
defId: "ITEM_RUSTY_BLADE",
|
|
|
|
|
isNew: false,
|
|
|
|
|
quantity: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
uid: "ITEM_002",
|
|
|
|
|
defId: "ITEM_SCRAP_PLATE",
|
|
|
|
|
isNew: true,
|
|
|
|
|
quantity: 1,
|
|
|
|
|
},
|
|
|
|
|
]),
|
|
|
|
|
currency: {
|
|
|
|
|
aetherShards: 150,
|
|
|
|
|
ancientCores: 2,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mockInventoryManager = {
|
|
|
|
|
runStash: mockRunStash,
|
|
|
|
|
hubStash: {
|
|
|
|
|
id: "HUB_VAULT",
|
|
|
|
|
getAllItems: sinon.stub().returns([]),
|
|
|
|
|
currency: {
|
|
|
|
|
aetherShards: 0,
|
|
|
|
|
ancientCores: 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Mock Persistence
|
|
|
|
|
mockPersistence = {
|
|
|
|
|
init: sinon.stub().resolves(),
|
|
|
|
|
saveRun: sinon.stub().resolves(),
|
|
|
|
|
loadRun: sinon.stub().resolves(null),
|
|
|
|
|
loadRoster: sinon.stub().resolves(null),
|
|
|
|
|
saveRoster: sinon.stub().resolves(),
|
2026-01-01 04:11:00 +00:00
|
|
|
loadHubStash: sinon.stub().resolves(null),
|
|
|
|
|
saveHubStash: sinon.stub().resolves(),
|
|
|
|
|
loadUnlocks: sinon.stub().resolves([]),
|
|
|
|
|
saveUnlocks: sinon.stub().resolves(),
|
2026-01-02 00:08:54 +00:00
|
|
|
loadCampaign: sinon.stub().resolves(null),
|
|
|
|
|
loadMarketState: sinon.stub().resolves(null),
|
|
|
|
|
saveMarketState: sinon.stub().resolves(),
|
|
|
|
|
loadResearchState: sinon.stub().resolves(null),
|
2025-12-28 00:54:03 +00:00
|
|
|
};
|
|
|
|
|
gameStateManager.persistence = mockPersistence;
|
|
|
|
|
|
|
|
|
|
// Mock GameLoop with inventoryManager
|
|
|
|
|
mockGameLoop = {
|
|
|
|
|
init: sinon.spy(),
|
|
|
|
|
startLevel: sinon.stub().resolves(),
|
|
|
|
|
stop: sinon.spy(),
|
|
|
|
|
inventoryManager: mockInventoryManager,
|
|
|
|
|
};
|
|
|
|
|
gameStateManager.gameLoop = mockGameLoop;
|
|
|
|
|
|
|
|
|
|
// Mock MissionManager
|
|
|
|
|
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(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set gameLoop so gameLoopInitialized promise can resolve
|
|
|
|
|
gameStateManager.gameLoop = mockGameLoop;
|
|
|
|
|
// Call the internal method that resolves the promise (simulating init completion)
|
|
|
|
|
// We need to trigger the resolution - check if there's a method or we need to call init
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 1: _initializeRun should include inventory data in runData", async () => {
|
|
|
|
|
// Initialize gameStateManager first
|
|
|
|
|
await gameStateManager.init();
|
|
|
|
|
// Use setGameLoop to properly resolve the promise
|
|
|
|
|
gameStateManager.setGameLoop(mockGameLoop);
|
|
|
|
|
|
|
|
|
|
const squadManifest = [
|
|
|
|
|
{
|
|
|
|
|
id: "UNIT_001",
|
|
|
|
|
classId: "CLASS_VANGUARD",
|
|
|
|
|
name: "Test Vanguard",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
await gameStateManager._initializeRun(squadManifest);
|
|
|
|
|
|
|
|
|
|
// Verify saveRun was called
|
|
|
|
|
expect(mockPersistence.saveRun.calledOnce).to.be.true;
|
|
|
|
|
|
|
|
|
|
// Get the saved run data
|
|
|
|
|
const savedData = mockPersistence.saveRun.firstCall.args[0];
|
|
|
|
|
|
|
|
|
|
// Verify inventory data is included
|
|
|
|
|
expect(savedData.inventory).to.exist;
|
|
|
|
|
expect(savedData.inventory.runStash).to.exist;
|
|
|
|
|
expect(savedData.inventory.runStash.id).to.equal("RUN_LOOT");
|
|
|
|
|
expect(savedData.inventory.runStash.items).to.be.an("array");
|
|
|
|
|
expect(savedData.inventory.runStash.items.length).to.equal(2);
|
|
|
|
|
expect(savedData.inventory.runStash.currency).to.exist;
|
|
|
|
|
expect(savedData.inventory.runStash.currency.aetherShards).to.equal(150);
|
|
|
|
|
expect(savedData.inventory.runStash.currency.ancientCores).to.equal(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 2: _initializeRun should handle missing inventoryManager gracefully", async () => {
|
|
|
|
|
await gameStateManager.init();
|
|
|
|
|
// Remove inventoryManager
|
|
|
|
|
mockGameLoop.inventoryManager = null;
|
|
|
|
|
gameStateManager.setGameLoop(mockGameLoop);
|
|
|
|
|
|
|
|
|
|
const squadManifest = [
|
|
|
|
|
{
|
|
|
|
|
id: "UNIT_001",
|
|
|
|
|
classId: "CLASS_VANGUARD",
|
|
|
|
|
name: "Test Vanguard",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
await gameStateManager._initializeRun(squadManifest);
|
|
|
|
|
|
|
|
|
|
// Should still save without error
|
|
|
|
|
expect(mockPersistence.saveRun.calledOnce).to.be.true;
|
|
|
|
|
|
|
|
|
|
const savedData = mockPersistence.saveRun.firstCall.args[0];
|
|
|
|
|
// Inventory should be undefined if manager doesn't exist
|
|
|
|
|
expect(savedData.inventory).to.be.undefined;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 3: saved inventory should include item instances with uid", async () => {
|
|
|
|
|
await gameStateManager.init();
|
|
|
|
|
gameStateManager.setGameLoop(mockGameLoop);
|
|
|
|
|
|
|
|
|
|
const squadManifest = [
|
|
|
|
|
{
|
|
|
|
|
id: "UNIT_001",
|
|
|
|
|
classId: "CLASS_VANGUARD",
|
|
|
|
|
name: "Test Vanguard",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
await gameStateManager._initializeRun(squadManifest);
|
|
|
|
|
|
|
|
|
|
const savedData = mockPersistence.saveRun.firstCall.args[0];
|
|
|
|
|
|
|
|
|
|
// Verify items have uid (not just defId)
|
|
|
|
|
expect(savedData.inventory.runStash.items[0].uid).to.equal("ITEM_001");
|
|
|
|
|
expect(savedData.inventory.runStash.items[0].defId).to.equal("ITEM_RUSTY_BLADE");
|
|
|
|
|
expect(savedData.inventory.runStash.items[0].quantity).to.equal(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 4: saved inventory should preserve currency values", async () => {
|
|
|
|
|
await gameStateManager.init();
|
|
|
|
|
gameStateManager.setGameLoop(mockGameLoop);
|
|
|
|
|
|
|
|
|
|
const squadManifest = [
|
|
|
|
|
{
|
|
|
|
|
id: "UNIT_001",
|
|
|
|
|
classId: "CLASS_VANGUARD",
|
|
|
|
|
name: "Test Vanguard",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
await gameStateManager._initializeRun(squadManifest);
|
|
|
|
|
|
|
|
|
|
const savedData = mockPersistence.saveRun.firstCall.args[0];
|
|
|
|
|
|
|
|
|
|
// Verify currency is saved correctly
|
|
|
|
|
expect(savedData.inventory.runStash.currency.aetherShards).to.equal(150);
|
|
|
|
|
expect(savedData.inventory.runStash.currency.ancientCores).to.equal(2);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|