Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions extension/src/__test-mocks__/chrome-scripting-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) 2026 PixieBrix, Inc.
// Licensed under PolyForm Shield 1.0.0 — see LICENSE.

// Shared stateful-mock for `chrome.scripting.registerContentScripts` /
// `unregisterContentScripts` / `getRegisteredContentScripts`. The three
// main-world bundle registration modules (`webdriver-probe-registration`,
// `checkout-checkbox-defense-registration`, `shadow-root-probe-registration`)
// each ship a near-identical test that needs the same in-memory store
// semantics: `register` appends, `unregister` splices by id, `getRegistered`
// returns the current set (optionally filtered by id list).
//
// Sits under `__test-mocks__/` instead of `lib/` because it's
// test-environment plumbing — the production code never references it,
// and it depends on `chrome.scripting` having already been stubbed by
// `chrome-mv3-extras.ts` (wired through `setupFiles` in
// `jest.config.cjs`).
//
// Test usage:
//
// let registry: ScriptingRegistryHandle;
// beforeEach(() => {
// // Fresh handle per test — resets jest's call history on the three
// // shared chrome.scripting mocks and installs new implementations
// // backed by a per-test store.
// registry = installScriptingRegistry();
// });
//
// it("registers when the rule is enabled", async () => {
// await jest.isolateModulesAsync(async () => {
// registry.seed([{ id: "my-script", ... }]); // optional pre-state
// const module = await import("../my-registration-module");
// module.startMyRegistration();
// await flushScriptingPromises();
// expect(registry.registerMock).toHaveBeenCalledTimes(1);
// });
// });
//
// Customization escape hatches: each underlying mock is exposed on the
// returned handle (`registerMock`, `unregisterMock`, `getRegisteredMock`)
// so a one-off test can `mockImplementationOnce` a rejection or alter
// the return value without rewriting the helper.

export interface RegisteredScript {
id: string;
matches: string[];
js: string[];
runAt: string;
world: string;
allFrames: boolean;
}

export interface ScriptingRegistryHandle {
registerMock: jest.Mock;
unregisterMock: jest.Mock;
getRegisteredMock: jest.Mock;
// Snapshot of the current store. Returns a fresh array each call;
// mutating it does not affect the store.
registered: () => RegisteredScript[];
// Add entries to the store. Useful for "already registered before
// startup" scenarios that the registration module's `sync` should
// reconcile away (or leave alone).
seed: (scripts: RegisteredScript[]) => void;
}

export function installScriptingRegistry(): ScriptingRegistryHandle {
const registerMock = chrome.scripting
.registerContentScripts as unknown as jest.Mock;
const unregisterMock = chrome.scripting
.unregisterContentScripts as unknown as jest.Mock;
const getRegisteredMock = chrome.scripting
.getRegisteredContentScripts as unknown as jest.Mock;

// Reset before installing so prior-test call history and any leftover
// implementations are cleared. Jest's `clearMocks: true` clears call
// history between tests but does NOT reset implementations — the
// explicit reset here makes the helper safe to call regardless of
// surrounding test configuration.
registerMock.mockReset();
unregisterMock.mockReset();
getRegisteredMock.mockReset();

const store: RegisteredScript[] = [];

registerMock.mockImplementation(
(scripts: RegisteredScript[]): Promise<void> => {
store.push(...scripts);
return Promise.resolve();
},
);
unregisterMock.mockImplementation(
(filter: { ids: string[] }): Promise<void> => {
for (const id of filter.ids) {
const index = store.findIndex((script) => script.id === id);
if (index !== -1) {
store.splice(index, 1);
}
}
return Promise.resolve();
},
);
getRegisteredMock.mockImplementation(
(filter?: { ids?: string[] }): Promise<RegisteredScript[]> => {
if (!filter?.ids) {
return Promise.resolve([...store]);
}
return Promise.resolve(
store.filter((script) => filter.ids?.includes(script.id)),
);
},
);

return {
registerMock,
unregisterMock,
getRegisteredMock,
registered: () => [...store],
seed: (scripts) => {
store.push(...scripts);
},
};
}

// Microtask drain. Registration modules kick off `void sync()` chains
// from synchronous `subscribe` callbacks; the test awaits this so the
// resulting promises settle before assertions run.
export function flushScriptingPromises(): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
// `webdriver-probe-registration.test.ts` — same shape, different script
// id and filename. The module syncs chrome.scripting state against
// (rule-enabled AND enforcement-enabled).
//
// `chrome.scripting`'s in-memory store comes from the shared
// `installScriptingRegistry()` helper, see
// `__test-mocks__/chrome-scripting-registry.ts`.

import type {
RegisteredScript,
ScriptingRegistryHandle,
} from "../../__test-mocks__/chrome-scripting-registry";
import {
flushScriptingPromises,
installScriptingRegistry,
} from "../../__test-mocks__/chrome-scripting-registry";

jest.mock("nanoid", () => ({ nanoid: () => "test-ref" }));
jest.mock("abort-utils", () => ({
Expand All @@ -22,31 +35,11 @@ jest.mock("abort-utils", () => ({
},
}));

const defenseRegister = chrome.scripting
.registerContentScripts as unknown as jest.Mock;
const defenseUnregister = chrome.scripting
.unregisterContentScripts as unknown as jest.Mock;
const defenseGetRegistered = chrome.scripting
.getRegisteredContentScripts as unknown as jest.Mock;

interface DefenseModule {
startCheckoutCheckboxDefenseRegistration: () => void;
}

interface DefenseScript {
id: string;
matches: string[];
js: string[];
runAt: string;
world: string;
allFrames: boolean;
}

function flushDefense(): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
let registry: ScriptingRegistryHandle;

async function loadDefenseModule(
overrides: Record<string, boolean>,
Expand All @@ -58,46 +51,18 @@ async function loadDefenseModule(
await jest.isolateModulesAsync(async () => {
process.env.EXTENSION_DEFAULT_OVERRIDES = JSON.stringify(overrides);

const registered: DefenseScript[] = initiallyRegistered
? [
{
id: "checkout-checkbox-sanitize-main-world",
matches: ["<all_urls>"],
js: ["checkout-checkbox-defense.js"],
runAt: "document_start",
world: "MAIN",
allFrames: true,
},
]
: [];

defenseRegister.mockImplementation(
(scripts: DefenseScript[]): Promise<void> => {
registered.push(...scripts);
return Promise.resolve();
},
);
defenseUnregister.mockImplementation(
(filter: { ids: string[] }): Promise<void> => {
for (const id of filter.ids) {
const index = registered.findIndex((script) => script.id === id);
if (index !== -1) {
registered.splice(index, 1);
}
}
return Promise.resolve();
},
);
defenseGetRegistered.mockImplementation(
(filter?: { ids?: string[] }): Promise<DefenseScript[]> => {
if (!filter?.ids) {
return Promise.resolve([...registered]);
}
return Promise.resolve(
registered.filter((script) => filter.ids?.includes(script.id)),
);
},
);
if (initiallyRegistered) {
registry.seed([
{
id: "checkout-checkbox-sanitize-main-world",
matches: ["<all_urls>"],
js: ["checkout-checkbox-defense.js"],
runAt: "document_start",
world: "MAIN",
allFrames: true,
},
]);
}

const enforcement = await import("../enforcement");
await enforcement.enforcementStorage.set(enforcementEnabled);
Expand All @@ -109,9 +74,7 @@ async function loadDefenseModule(
}

beforeEach(() => {
defenseRegister.mockReset();
defenseUnregister.mockReset();
defenseGetRegistered.mockReset();
registry = installScriptingRegistry();
});

afterEach(() => {
Expand All @@ -125,10 +88,12 @@ describe("startCheckoutCheckboxDefenseRegistration", () => {
});

module.startCheckoutCheckboxDefenseRegistration();
await flushDefense();
await flushScriptingPromises();

expect(defenseRegister).toHaveBeenCalledTimes(1);
const [scripts] = defenseRegister.mock.calls[0] as [DefenseScript[]];
expect(registry.registerMock).toHaveBeenCalledTimes(1);
const [scripts] = registry.registerMock.mock.calls[0] as [
RegisteredScript[],
];
expect(scripts[0]).toMatchObject({
id: "checkout-checkbox-sanitize-main-world",
matches: ["<all_urls>"],
Expand All @@ -148,10 +113,10 @@ describe("startCheckoutCheckboxDefenseRegistration", () => {
});

module.startCheckoutCheckboxDefenseRegistration();
await flushDefense();
await flushScriptingPromises();

expect(defenseRegister).not.toHaveBeenCalled();
expect(defenseUnregister).not.toHaveBeenCalled();
expect(registry.registerMock).not.toHaveBeenCalled();
expect(registry.unregisterMock).not.toHaveBeenCalled();
});

it("unregisters when an already-registered script becomes ineligible", async () => {
Expand All @@ -162,9 +127,9 @@ describe("startCheckoutCheckboxDefenseRegistration", () => {
);

module.startCheckoutCheckboxDefenseRegistration();
await flushDefense();
await flushScriptingPromises();

expect(defenseUnregister).toHaveBeenCalledWith({
expect(registry.unregisterMock).toHaveBeenCalledWith({
ids: ["checkout-checkbox-sanitize-main-world"],
});
});
Expand All @@ -177,10 +142,10 @@ describe("startCheckoutCheckboxDefenseRegistration", () => {
);

module.startCheckoutCheckboxDefenseRegistration();
await flushDefense();
await flushScriptingPromises();

expect(defenseRegister).not.toHaveBeenCalled();
expect(defenseUnregister).not.toHaveBeenCalled();
expect(registry.registerMock).not.toHaveBeenCalled();
expect(registry.unregisterMock).not.toHaveBeenCalled();
});

it("treats enforcement-off as if the rule were disabled", async () => {
Expand All @@ -190,8 +155,8 @@ describe("startCheckoutCheckboxDefenseRegistration", () => {
);

module.startCheckoutCheckboxDefenseRegistration();
await flushDefense();
await flushScriptingPromises();

expect(defenseRegister).not.toHaveBeenCalled();
expect(registry.registerMock).not.toHaveBeenCalled();
});
});
Loading
Loading