217 lines
5.8 KiB
JavaScript
217 lines
5.8 KiB
JavaScript
|
|
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;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|