import { expect } from "@esm-bundle/chai"; import sinon from "sinon"; import { Persistence } from "../../src/core/Persistence.js"; describe("Core: Persistence", () => { let persistence; let mockDB; let mockStore; let mockTransaction; let mockRequest; let globalObj; beforeEach(() => { persistence = new Persistence(); // Mock IndexedDB mockStore = { put: sinon.stub(), get: sinon.stub(), delete: sinon.stub(), }; mockTransaction = { objectStore: sinon.stub().returns(mockStore), }; mockDB = { objectStoreNames: { contains: sinon.stub().returns(false), }, createObjectStore: sinon.stub(), transaction: sinon.stub().returns(mockTransaction), }; // Mock indexedDB.open mockRequest = { onerror: null, onsuccess: null, onupgradeneeded: null, result: mockDB, }; // Use window or self for browser environment globalObj = typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : globalThis); globalObj.indexedDB = { open: sinon.stub().returns(mockRequest), }; }); const triggerSuccess = () => { if (mockRequest.onsuccess) { mockRequest.onsuccess({ target: { result: mockDB } }); } }; const triggerUpgrade = () => { if (mockRequest.onupgradeneeded) { mockRequest.onupgradeneeded({ target: { result: mockDB } }); } }; it("CoA 1: init should create database and object stores", async () => { triggerUpgrade(); triggerSuccess(); await persistence.init(); expect(globalObj.indexedDB.open.calledWith("AetherShardsDB", 2)).to.be.true; expect(mockDB.createObjectStore.calledWith("Runs", { keyPath: "id" })).to.be.true; expect(mockDB.createObjectStore.calledWith("Roster", { keyPath: "id" })).to.be.true; expect(persistence.db).to.equal(mockDB); }); it("CoA 2: saveRun should store run data with active_run id", async () => { persistence.db = mockDB; const runData = { seed: 12345, depth: 5, squad: [] }; const mockPutRequest = { onsuccess: null, onerror: null, }; mockStore.put.returns(mockPutRequest); const savePromise = persistence.saveRun(runData); mockPutRequest.onsuccess(); await savePromise; expect(mockDB.transaction.calledWith(["Runs"], "readwrite")).to.be.true; expect(mockStore.put.calledOnce).to.be.true; const savedData = mockStore.put.firstCall.args[0]; expect(savedData.id).to.equal("active_run"); expect(savedData.seed).to.equal(12345); }); it("CoA 3: loadRun should retrieve active_run data", async () => { persistence.db = mockDB; const savedData = { id: "active_run", seed: 12345, depth: 5 }; const mockGetRequest = { onsuccess: null, onerror: null, result: savedData, }; mockStore.get.returns(mockGetRequest); const loadPromise = persistence.loadRun(); mockGetRequest.onsuccess(); const result = await loadPromise; expect(mockDB.transaction.calledWith(["Runs"], "readonly")).to.be.true; expect(mockStore.get.calledWith("active_run")).to.be.true; expect(result).to.deep.equal(savedData); }); it("CoA 4: clearRun should delete active_run", async () => { persistence.db = mockDB; const mockDeleteRequest = { onsuccess: null, onerror: null, }; mockStore.delete.returns(mockDeleteRequest); const deletePromise = persistence.clearRun(); mockDeleteRequest.onsuccess(); await deletePromise; expect(mockDB.transaction.calledWith(["Runs"], "readwrite")).to.be.true; expect(mockStore.delete.calledWith("active_run")).to.be.true; }); it("CoA 5: saveRoster should wrap roster data with id", async () => { persistence.db = mockDB; const rosterData = { roster: [], graveyard: [] }; const mockPutRequest = { onsuccess: null, onerror: null, }; mockStore.put.returns(mockPutRequest); const savePromise = persistence.saveRoster(rosterData); mockPutRequest.onsuccess(); await savePromise; expect(mockDB.transaction.calledWith(["Roster"], "readwrite")).to.be.true; expect(mockStore.put.calledOnce).to.be.true; const savedData = mockStore.put.firstCall.args[0]; expect(savedData.id).to.equal("player_roster"); expect(savedData.data).to.deep.equal(rosterData); }); it("CoA 6: loadRoster should extract data from stored object", async () => { persistence.db = mockDB; const storedData = { id: "player_roster", data: { roster: [], graveyard: [] } }; const mockGetRequest = { onsuccess: null, onerror: null, result: storedData, }; mockStore.get.returns(mockGetRequest); const loadPromise = persistence.loadRoster(); mockGetRequest.onsuccess(); const result = await loadPromise; expect(mockDB.transaction.calledWith(["Roster"], "readonly")).to.be.true; expect(mockStore.get.calledWith("player_roster")).to.be.true; expect(result).to.deep.equal(storedData.data); }); it("CoA 7: loadRoster 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.loadRoster(); mockGetRequest.onsuccess(); const result = await loadPromise; expect(result).to.be.null; }); it("CoA 8: saveRun should auto-init if db not initialized", async () => { triggerUpgrade(); triggerSuccess(); const runData = { seed: 12345 }; const mockPutRequest = { onsuccess: null, onerror: null, }; mockStore.put.returns(mockPutRequest); const savePromise = persistence.saveRun(runData); mockPutRequest.onsuccess(); await savePromise; expect(persistence.db).to.equal(mockDB); expect(mockStore.put.calledOnce).to.be.true; }); });