// A chrome.storage.local stub that behaves like the real one. // // The real extension storage API is a serialization boundary: `set` writes a // structured clone of what it is given, and `get` hands back a structured // clone of what is stored. Nothing an extension page holds is ever the object // storage holds. // // A stub that skips the clone aliases them together, and that hides an entire // class of defect rather than merely being imprecise. loadState() assigns // nested references straight out of the get result, so over an aliasing stub a // test can assert "the endpoint was persisted" and pass on a build that never // called saveState() at all: the in-memory mutation IS the stored record. // Measured, not theorised — with an aliasing `get` restored over the handler // fixed in https://git.eeqj.de/sneak/AutistMask/pulls/319, the whole suite // passed 794/794 (https://git.eeqj.de/sneak/AutistMask/issues/324). // // So every test that drives real persistence uses this, and nothing rebuilds // a storage stub by hand. // `initial` is the starting contents, keyed as storage is: { autistmask: {...} }. // `onOp` runs before each operation, for a test that needs to advance a clock // or count round trips. function makeStorageStub(initial, onOp) { const store = initial ? structuredClone(initial) : {}; const tick = onOp || (() => {}); const get = jest.fn(async (key) => { tick(); if (key === undefined || key === null) return structuredClone(store); const keys = Array.isArray(key) ? key : [key]; const out = {}; for (const k of keys) { if (Object.prototype.hasOwnProperty.call(store, k)) { out[k] = structuredClone(store[k]); } } return out; }); const set = jest.fn(async (items) => { tick(); for (const k of Object.keys(items)) { store[k] = structuredClone(items[k]); } }); const remove = jest.fn(async (key) => { tick(); for (const k of Array.isArray(key) ? key : [key]) delete store[k]; }); return { // Drop this straight in as chrome.storage. local: { get, set, remove }, get, set, remove, // What is stored, cloned on the way out: a test can neither observe a // later write through an object it read nor reach into the store by // mutating one. read: (key) => key === undefined ? structuredClone(store) : structuredClone(store[key]), // Seed or replace a record without going through the module under // test — for standing in as "another page wrote this". write: (key, value) => { store[key] = structuredClone(value); }, }; } module.exports = { makeStorageStub };