aether-shards/test/core/Persistence.test.js

246 lines
6.6 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),
};
// Use window or self for browser environment
globalObj =
typeof window !== "undefined"
? window
: typeof self !== "undefined"
? self
: globalThis;
});
const createMockRequest = () => {
// Mock indexedDB.open - create a new request each time
mockRequest = {
onerror: null,
onsuccess: null,
onupgradeneeded: null,
result: mockDB,
};
// Mock indexedDB using defineProperty since it's read-only
Object.defineProperty(globalObj, "indexedDB", {
value: {
open: sinon.stub().returns(mockRequest),
},
writable: true,
configurable: true,
});
return mockRequest;
};
it("CoA 1: init should create database and object stores", async () => {
const request = createMockRequest();
// Start init, which will call indexedDB.open
const initPromise = persistence.init();
// Trigger upgrade first (happens synchronously during open)
if (request.onupgradeneeded) {
request.onupgradeneeded({ target: { result: mockDB } });
}
// Then trigger success
if (request.onsuccess) {
request.onsuccess({ target: { result: mockDB } });
}
await initPromise;
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 () => {
const request = createMockRequest();
const runData = { seed: 12345 };
const mockPutRequest = {
onsuccess: sinon.stub(),
onerror: null,
};
mockStore.put.returns(mockPutRequest);
// Start saveRun, which will trigger init
const savePromise = persistence.saveRun(runData);
// Trigger upgrade and success for init
if (request.onupgradeneeded) {
request.onupgradeneeded({ target: { result: mockDB } });
}
if (request.onsuccess) {
request.onsuccess({ target: { result: mockDB } });
}
// Wait a bit for init to complete, then trigger put success
await new Promise((resolve) => setTimeout(resolve, 10));
mockPutRequest.onsuccess();
await savePromise;
expect(persistence.db).to.equal(mockDB);
expect(mockStore.put.calledOnce).to.be.true;
});
});