Five defects, one of which destroyed every wallet, came from src/background reading and writing the module-level state singleton the MV3 worker never populates, which silently served DEFAULT_STATE. Each point fix created the next defect. The background now has its own per-call getState() and a queued read-modify-write updateState(); the singleton is unreachable from it, and an unpopulated read throws instead of serving defaults. The prohibition is enforced by the build, not by review: build.js asserts over esbuild's own metafile that no forbidden module is an input of a background bundle, so every specifier syntax esbuild resolves is covered, and both halves of the table are checked for rot -- a stale key, a stale module, an empty list, or an unlisted entry point under src/background/ all fail the build. The ESLint rule remains as fast local feedback and reads the same shared table. Known bounds are documented where the table lives. Also closes #320: getProvider() now requires a validated network id, so a cold worker no longer prepares a non-mainnet dApp transaction for mainnet and gets refused by the wallet's own verifier. backgroundRefresh() no longer mutates address objects across a network round trip, the broadcast path takes its endpoint and chain id from one snapshot, and eight test storage stubs now structured-clone on get as the real chrome.storage.local does. closes #320
118 lines
4.4 KiB
JavaScript
118 lines
4.4 KiB
JavaScript
// Tests for the UTC Timestamps setting.
|
|
//
|
|
// The checkbox was moved out of the Token Spam Protection well and into the
|
|
// Display well next to the theme selector. It is wired by id through the $()
|
|
// helper, so the move cannot break the handler — but nothing in the suite said
|
|
// so. These tests pin both halves down: the markup lives in Display and
|
|
// nowhere else, and the value still round-trips through storage.
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const { makeStorageStub } = require("./support/storageStub");
|
|
|
|
const POPUP_HTML = fs.readFileSync(
|
|
path.join(__dirname, "..", "src", "popup", "index.html"),
|
|
"utf8",
|
|
);
|
|
|
|
// The body of one `<div class="bg-well ...">` well, selected by its heading.
|
|
function wellWithHeading(html, heading) {
|
|
const headingIndex = html.indexOf(
|
|
'<h3 class="font-bold mb-1">' + heading + "</h3>",
|
|
);
|
|
expect(headingIndex).toBeGreaterThan(-1);
|
|
const start = html.lastIndexOf('<div class="bg-well', headingIndex);
|
|
const end = html.indexOf('<div class="bg-well', headingIndex);
|
|
return html.slice(start, end === -1 ? html.length : end);
|
|
}
|
|
|
|
describe("the UTC Timestamps checkbox placement", () => {
|
|
test("the checkbox appears exactly once in the popup markup", () => {
|
|
const matches = POPUP_HTML.match(/id="settings-utc-timestamps"/g);
|
|
expect(matches).toHaveLength(1);
|
|
});
|
|
|
|
test("it renders in the Display well, alongside the theme selector", () => {
|
|
const display = wellWithHeading(POPUP_HTML, "Display");
|
|
|
|
expect(display).toContain('id="settings-utc-timestamps"');
|
|
expect(display).toContain('id="settings-theme"');
|
|
});
|
|
|
|
test("it does not render in the Token Spam Protection well", () => {
|
|
const spam = wellWithHeading(POPUP_HTML, "Token Spam Protection");
|
|
|
|
expect(spam).not.toContain('id="settings-utc-timestamps"');
|
|
// The filters that do belong there are untouched.
|
|
expect(spam).toContain('id="settings-hide-low-holders"');
|
|
expect(spam).toContain('id="settings-hide-fraud-contracts"');
|
|
expect(spam).toContain('id="settings-hide-dust"');
|
|
expect(spam).toContain('id="settings-dust-threshold"');
|
|
});
|
|
});
|
|
|
|
describe("the UTC Timestamps setting round-trips through storage", () => {
|
|
let storage;
|
|
|
|
// The stub clones in both directions, as the real chrome.storage.local
|
|
// does. It used to alias, which is fatal to a round-trip test in
|
|
// particular: the object the module holds and the object "storage" holds
|
|
// are then the same object, so the setting appears to have been persisted
|
|
// and read back on a build where neither happened. See
|
|
// tests/support/storageStub.js.
|
|
function loadStateModule() {
|
|
storage = makeStorageStub();
|
|
global.chrome = { storage };
|
|
jest.resetModules();
|
|
return require("../src/shared/state");
|
|
}
|
|
|
|
afterEach(() => {
|
|
delete global.chrome;
|
|
});
|
|
|
|
test("defaults to off with nothing persisted", async () => {
|
|
const { state, loadState } = loadStateModule();
|
|
|
|
await loadState();
|
|
|
|
expect(state.utcTimestamps).toBe(false);
|
|
});
|
|
|
|
test("an enabled checkbox is persisted and read back", async () => {
|
|
const first = loadStateModule();
|
|
|
|
// What the change handler in views/settings.js does.
|
|
first.state.utcTimestamps = true;
|
|
await first.saveState();
|
|
expect(storage.read("autistmask").utcTimestamps).toBe(true);
|
|
|
|
// A fresh popup load sees it — and, before that load, refuses to
|
|
// answer at all rather than reporting the default. That refusal is
|
|
// what makes the assertion below evidence of a read from storage
|
|
// instead of a value that was already sitting in memory
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/324).
|
|
jest.resetModules();
|
|
const second = require("../src/shared/state");
|
|
expect(() => second.state.utcTimestamps).toThrow(
|
|
second.StateNotLoadedError,
|
|
);
|
|
await second.loadState();
|
|
expect(second.state.utcTimestamps).toBe(true);
|
|
});
|
|
|
|
test("turning it back off is persisted too", async () => {
|
|
const { state, saveState, loadState } = loadStateModule();
|
|
|
|
state.utcTimestamps = true;
|
|
await saveState();
|
|
state.utcTimestamps = false;
|
|
await saveState();
|
|
|
|
state.utcTimestamps = true;
|
|
await loadState();
|
|
expect(state.utcTimestamps).toBe(false);
|
|
});
|
|
});
|