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
3 changes: 3 additions & 0 deletions extension/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions extension/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,20 @@ module.exports = {
moduleNameMapper: {
"^webext-storage$": "<rootDir>/src/__test-mocks__/webext-storage.ts",
},
// Polyfills/stubs for browser APIs jsdom omits (Element.checkVisibility) or
// returns degenerate values for (offsetWidth/offsetHeight). Centralized so
// tests don't redo the same Object.defineProperty dance.
setupFiles: ["<rootDir>/src/__test-mocks__/jsdom-extras.ts"],
// - jest-webextension-mock: installs globalThis.chrome + browser with
// jest.fn() stubs for MV2/MV3 APIs the extension uses (runtime, storage,
// tabs, action, browserAction). Tests reference `chrome.*` directly.
// - chrome-mv3-extras: adds chrome.scripting (MV3 content-script
// registration), which jest-webextension-mock 4.1 does not include.
// - jsdom-extras: polyfills/stubs for browser APIs jsdom omits
// (Element.checkVisibility) or returns degenerate values for
// (offsetWidth/offsetHeight). Centralized so tests don't redo the same
// Object.defineProperty dance.
setupFiles: [
"jest-webextension-mock",
"<rootDir>/src/__test-mocks__/chrome-mv3-extras.ts",
"<rootDir>/src/__test-mocks__/jsdom-extras.ts",
],
clearMocks: true,
collectCoverageFrom: [
"src/lib/**/*.ts",
Expand Down
1 change: 1 addition & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"globals": "17.6.0",
"jest": "^30",
"jest-environment-jsdom": "^30",
"jest-webextension-mock": "^4.1.1",
"js-yaml": "^4.1.0",
"knip": "^6.15.0",
"ts-jest": "^29",
Expand Down
26 changes: 26 additions & 0 deletions extension/src/__test-mocks__/chrome-mv3-extras.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// <reference types="jest" />
// Copyright (c) 2026 PixieBrix, Inc.
// Licensed under PolyForm Shield 1.0.0 — see LICENSE.

// jest-webextension-mock 4.1 installs `globalThis.chrome` with most MV2/MV3
// APIs (runtime, storage, tabs, action, browserAction, etc.) but does NOT
// include chrome.scripting (MV3 content-script registration). Patch the
// missing namespace here so tests can `chrome.scripting.registerContentScripts
// .mockImplementation(...)` against the global. Wired via `setupFiles` AFTER
// jest-webextension-mock so the global already exists.

interface ChromeScriptingMock {
executeScript: jest.Mock;
registerContentScripts: jest.Mock;
unregisterContentScripts: jest.Mock;
getRegisteredContentScripts: jest.Mock;
}

(
globalThis as unknown as { chrome: { scripting: ChromeScriptingMock } }
).chrome.scripting = {
executeScript: jest.fn(),
registerContentScripts: jest.fn(),
unregisterContentScripts: jest.fn(),
getRegisteredContentScripts: jest.fn(),
};
1 change: 0 additions & 1 deletion extension/src/lib/__tests__/rule-engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ jest.mock("../availability", () => ({
// the real implementation walking the DOM for placeholder elements that
// don't exist in these unit tests.
jest.mock("../placeholder", () => {
// biome-ignore lint/suspicious/noExplicitAny: jest.requireActual returns any
const actual = jest.requireActual<Record<string, unknown>>("../placeholder");
return {
...actual,
Expand Down
128 changes: 60 additions & 68 deletions extension/src/lib/__tests__/webdriver-probe-registration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
// so the test covers the four corners plus the no-op short-circuits when
// the desired state already matches the current one.
//
// chrome.scripting and chrome.storage aren't available in jsdom; the
// `chrome` global is replaced with a hand-rolled stub for the duration
// of the test.
// chrome.* APIs come from jest-webextension-mock + chrome-mv3-extras
// (wired via `setupFiles` in jest.config.cjs). Each loadModule call
// replays a stateful registration store on top of those stubs.

// See storage.test.ts for the rationale on these mocks — the storage
// module pulls in the full rule catalog transitively.
Expand All @@ -28,6 +28,16 @@ jest.mock("abort-utils", () => ({
},
}));

// chrome.scripting comes from chrome-mv3-extras.ts; @types/chrome types the
// methods as plain functions, so cast each one to a jest.Mock for the
// mock-control surface (mockImplementation, mockReset, .mock.calls).
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;

interface RegistrationModule {
startWebdriverProbeRegistration: () => void;
}
Expand All @@ -41,14 +51,6 @@ interface RegisteredScript {
allFrames: boolean;
}

interface ChromeStub {
scripting: {
registerContentScripts: jest.Mock;
unregisterContentScripts: jest.Mock;
getRegisteredContentScripts: jest.Mock;
};
}

function flushPromises(): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, 0);
Expand All @@ -59,8 +61,7 @@ async function loadModule(
overrides: Record<string, boolean>,
enforcementEnabled = true,
initiallyRegistered = false,
): Promise<{ chromeStub: ChromeStub; module: RegistrationModule }> {
let chromeStub!: ChromeStub;
): Promise<{ module: RegistrationModule }> {
let module!: RegistrationModule;

await jest.isolateModulesAsync(async () => {
Expand All @@ -79,69 +80,64 @@ async function loadModule(
]
: [];

chromeStub = {
scripting: {
registerContentScripts: jest.fn(
(scripts: RegisteredScript[]): Promise<void> => {
for (const script of scripts) {
registered.push(script);
}
return Promise.resolve();
},
),
unregisterContentScripts: jest.fn(
(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();
},
),
getRegisteredContentScripts: jest.fn(
(filter?: { ids?: string[] }): Promise<RegisteredScript[]> => {
if (!filter?.ids) {
return Promise.resolve([...registered]);
}
return Promise.resolve(
registered.filter((script) => filter.ids?.includes(script.id)),
);
},
),
registerMock.mockImplementation(
(scripts: RegisteredScript[]): Promise<void> => {
registered.push(...scripts);
return Promise.resolve();
},
);
unregisterMock.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();
},
);
getRegisteredMock.mockImplementation(
(filter?: { ids?: string[] }): Promise<RegisteredScript[]> => {
if (!filter?.ids) {
return Promise.resolve([...registered]);
}
return Promise.resolve(
registered.filter((script) => filter.ids?.includes(script.id)),
);
},
};
(globalThis as unknown as { chrome: ChromeStub }).chrome = chromeStub;
);

const enforcement = await import("../enforcement");
await enforcement.enforcementStorage.set(enforcementEnabled);

module = await import("../webdriver-probe-registration");
});

return { chromeStub, module };
return { module };
}

beforeEach(() => {
registerMock.mockReset();
unregisterMock.mockReset();
getRegisteredMock.mockReset();
});

afterEach(() => {
delete (globalThis as unknown as { chrome?: unknown }).chrome;
delete process.env.EXTENSION_DEFAULT_OVERRIDES;
});

describe("startWebdriverProbeRegistration", () => {
it("registers the main-world script on startup when the rule is enabled", async () => {
const { chromeStub, module } = await loadModule({
const { module } = await loadModule({
"webdriver-probe-annotate": true,
});

module.startWebdriverProbeRegistration();
await flushPromises();

expect(chromeStub.scripting.registerContentScripts).toHaveBeenCalledTimes(
1,
);
const [scripts] = chromeStub.scripting.registerContentScripts.mock
.calls[0] as [RegisteredScript[]];
expect(registerMock).toHaveBeenCalledTimes(1);
const [scripts] = registerMock.mock.calls[0] as [RegisteredScript[]];
expect(scripts[0]).toMatchObject({
id: "webdriver-probe-annotate-main-world",
matches: ["<all_urls>"],
Expand All @@ -153,21 +149,19 @@ describe("startWebdriverProbeRegistration", () => {
});

it("does not register when the rule is disabled", async () => {
const { chromeStub, module } = await loadModule({
const { module } = await loadModule({
"webdriver-probe-annotate": false,
});

module.startWebdriverProbeRegistration();
await flushPromises();

expect(chromeStub.scripting.registerContentScripts).not.toHaveBeenCalled();
expect(
chromeStub.scripting.unregisterContentScripts,
).not.toHaveBeenCalled();
expect(registerMock).not.toHaveBeenCalled();
expect(unregisterMock).not.toHaveBeenCalled();
});

it("unregisters when an already-registered script becomes ineligible", async () => {
const { chromeStub, module } = await loadModule(
const { module } = await loadModule(
{ "webdriver-probe-annotate": false },
true,
/* initiallyRegistered */ true,
Expand All @@ -176,13 +170,13 @@ describe("startWebdriverProbeRegistration", () => {
module.startWebdriverProbeRegistration();
await flushPromises();

expect(chromeStub.scripting.unregisterContentScripts).toHaveBeenCalledWith({
expect(unregisterMock).toHaveBeenCalledWith({
ids: ["webdriver-probe-annotate-main-world"],
});
});

it("does not re-register if the desired state already matches", async () => {
const { chromeStub, module } = await loadModule(
const { module } = await loadModule(
{ "webdriver-probe-annotate": true },
true,
/* initiallyRegistered */ true,
Expand All @@ -191,21 +185,19 @@ describe("startWebdriverProbeRegistration", () => {
module.startWebdriverProbeRegistration();
await flushPromises();

expect(chromeStub.scripting.registerContentScripts).not.toHaveBeenCalled();
expect(
chromeStub.scripting.unregisterContentScripts,
).not.toHaveBeenCalled();
expect(registerMock).not.toHaveBeenCalled();
expect(unregisterMock).not.toHaveBeenCalled();
});

it("treats enforcement-off as if the rule were disabled", async () => {
const { chromeStub, module } = await loadModule(
const { module } = await loadModule(
{ "webdriver-probe-annotate": true },
/* enforcementEnabled */ false,
);

module.startWebdriverProbeRegistration();
await flushPromises();

expect(chromeStub.scripting.registerContentScripts).not.toHaveBeenCalled();
expect(registerMock).not.toHaveBeenCalled();
});
});
15 changes: 7 additions & 8 deletions extension/src/rules/__tests__/roach-motel-annotate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,21 @@ import { findWarning, roachMotelAnnotateRule } from "../roach-motel-annotate";
const LANDMARK_SELECTOR = 'section[data-abs-rule="roach-motel-annotate"]';

// Rule's `apply` sends a `rule-detection` runtime message after landing
// the landmark. `chrome.runtime.sendMessage` isn't present in jsdom, so
// stub it here. Tests that assert call shape read from this mock.
const sendMessageMock = jest.fn().mockResolvedValue(undefined);
// the landmark. jest-webextension-mock provides chrome.runtime.sendMessage
// as a jest.fn() on globalThis.chrome; cast to jest.Mock to expose the
// mock-control surface (assertion against typeof's overloaded signature
// resolves to `never`-arg, which blocks .mockResolvedValue).
const sendMessageMock = chrome.runtime.sendMessage as unknown as jest.Mock;

beforeEach(() => {
document.body.innerHTML = "";
sendMessageMock.mockClear();
(globalThis as unknown as { chrome: unknown }).chrome = {
runtime: { sendMessage: sendMessageMock },
};
sendMessageMock.mockReset();
sendMessageMock.mockResolvedValue(undefined);
});

afterEach(() => {
roachMotelAnnotateRule.teardown();
hiddenTextStripRule.teardown();
delete (globalThis as unknown as { chrome?: unknown }).chrome;
});

describe("findWarning", () => {
Expand Down
11 changes: 5 additions & 6 deletions extension/src/rules/__tests__/webdriver-probe-annotate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ function dispatchProbe(): void {
document.dispatchEvent(new CustomEvent(EVENT_NAME));
}

let sendMessageMock: jest.Mock;
// chrome.runtime.sendMessage is installed as jest.fn() on globalThis by
// jest-webextension-mock. Cast to jest.Mock for the control surface.
const sendMessageMock = chrome.runtime.sendMessage as unknown as jest.Mock;

// The rule fires two distinct sendMessage flows: `inject-webdriver-probe`
// on every apply (background-side executeScript fallback) and
Expand All @@ -38,15 +40,12 @@ function detectionCalls(): unknown[] {

beforeEach(() => {
document.body.innerHTML = "";
sendMessageMock = jest.fn().mockResolvedValue(undefined);
(globalThis as unknown as { chrome: unknown }).chrome = {
runtime: { sendMessage: sendMessageMock },
};
sendMessageMock.mockReset();
sendMessageMock.mockResolvedValue(undefined);
});

afterEach(() => {
webdriverProbeAnnotateRule.teardown();
delete (globalThis as unknown as { chrome?: unknown }).chrome;
});

describe("webdriverProbeAnnotateRule.apply", () => {
Expand Down
Loading