MeshChatX/tests/frontend/networkVisualiserCache.test.js

129 lines
4.2 KiB
JavaScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
loadVisualiserCache,
saveVisualiserCache,
clearVisualiserCache,
resetVisualiserCacheDbHandle,
} from "@/js/networkVisualiserCache.js";
function mockIndexedDb() {
const stores = new Map();
const fakeDb = {
objectStoreNames: {
contains: (name) => name === "snapshots",
},
transaction(storeNames, mode) {
const storeName = Array.isArray(storeNames) ? storeNames[0] : storeNames;
if (!stores.has(storeName)) stores.set(storeName, new Map());
const data = stores.get(storeName);
const store = {
get(key) {
const req = {};
queueMicrotask(() => {
req.result = data.get(key);
req.onsuccess?.();
});
return req;
},
put(row) {
data.set(row.identityHash, row);
const req = {};
queueMicrotask(() => req.onsuccess?.());
return req;
},
delete(key) {
data.delete(key);
const req = {};
queueMicrotask(() => req.onsuccess?.());
return req;
},
};
const tx = {
objectStore: () => store,
oncomplete: null,
onerror: null,
};
queueMicrotask(() => tx.oncomplete?.());
return tx;
},
};
const idb = {
open() {
const req = {
result: fakeDb,
onupgradeneeded: null,
onsuccess: null,
onerror: null,
};
queueMicrotask(() => {
req.onupgradeneeded?.({ target: req });
req.onsuccess?.({ target: req });
});
return req;
},
};
return idb;
}
describe("networkVisualiserCache", () => {
beforeEach(() => {
resetVisualiserCacheDbHandle();
vi.stubGlobal("indexedDB", mockIndexedDb());
});
afterEach(() => {
resetVisualiserCacheDbHandle();
vi.unstubAllGlobals();
});
it("round-trips path table announces and positions for one identity", async () => {
const ok = await saveVisualiserCache({
identityHash: "abc123",
pathTable: [{ hash: "n1", hops: 1, interface: "eth0" }],
announces: { n1: { destination_hash: "n1", aspect: "lxmf.delivery" } },
positions: { n1: { x: 10, y: 20 } },
});
expect(ok).toBe(true);
const loaded = await loadVisualiserCache("abc123");
expect(loaded.pathTable).toHaveLength(1);
expect(loaded.announces.n1.aspect).toBe("lxmf.delivery");
expect(loaded.positions.n1).toEqual({ x: 10, y: 20 });
});
it("isolates cache by identity hash", async () => {
await saveVisualiserCache({
identityHash: "id-a",
pathTable: [{ hash: "a" }],
announces: {},
positions: {},
});
await saveVisualiserCache({
identityHash: "id-b",
pathTable: [{ hash: "b" }],
announces: {},
positions: {},
});
expect((await loadVisualiserCache("id-a")).pathTable[0].hash).toBe("a");
expect((await loadVisualiserCache("id-b")).pathTable[0].hash).toBe("b");
});
it("clearVisualiserCache removes a snapshot", async () => {
await saveVisualiserCache({
identityHash: "gone",
pathTable: [{ hash: "x" }],
announces: {},
positions: {},
});
await clearVisualiserCache("gone");
expect(await loadVisualiserCache("gone")).toBeNull();
});
it("returns null when IndexedDB is unavailable", async () => {
resetVisualiserCacheDbHandle();
vi.stubGlobal("indexedDB", undefined);
expect(await loadVisualiserCache("x")).toBeNull();
expect(await saveVisualiserCache({ identityHash: "x", pathTable: [], announces: {}, positions: {} })).toBe(
false
);
});
});