feat(browser-extension): add background script tests
This commit is contained in:
parent
edf5eda7dc
commit
49ea170ec5
@ -9,15 +9,16 @@ const tsconfig = require('./tsconfig.json');
|
||||
*/
|
||||
const config = {
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
moduleNameMapper: pathsToModuleNameMapper(tsconfig.compilerOptions.paths, {
|
||||
prefix: '<rootDir>/',
|
||||
}),
|
||||
moduleNameMapper: {
|
||||
...pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '<rootDir>/' }),
|
||||
'^url:~assets/(.+).png$': '<rootDir>/mocks/assets/$1.mock.ts',
|
||||
},
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
setupFiles: ['jest-webextension-mock'],
|
||||
setupFiles: ['./jest.setup.ts'],
|
||||
testEnvironment: 'jsdom',
|
||||
testRegex: ['^.+\\.test.tsx?$'],
|
||||
transform: {
|
||||
'^.+.tsx?$': ['ts-jest', { isolatedModules: true }],
|
||||
'^.+.tsx?$': ['ts-jest', { isolatedModules: true, useESM: true }],
|
||||
},
|
||||
};
|
||||
|
||||
|
4
packages/browser-extension/jest.setup.ts
Normal file
4
packages/browser-extension/jest.setup.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import 'jest-webextension-mock';
|
||||
import './mocks/chrome.mock';
|
||||
import './mocks/utils/domain.mock';
|
||||
import './mocks/utils/storage.mock';
|
1
packages/browser-extension/mocks/assets/off.mock.ts
Normal file
1
packages/browser-extension/mocks/assets/off.mock.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'off-icon';
|
1
packages/browser-extension/mocks/assets/on.mock.ts
Normal file
1
packages/browser-extension/mocks/assets/on.mock.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'on-icon';
|
1
packages/browser-extension/mocks/assets/warn.mock.ts
Normal file
1
packages/browser-extension/mocks/assets/warn.mock.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'warn-icon';
|
32
packages/browser-extension/mocks/chrome.mock.ts
Normal file
32
packages/browser-extension/mocks/chrome.mock.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
chrome.action = {
|
||||
...chrome.action,
|
||||
openPopup: jest.fn(),
|
||||
setBadgeBackgroundColor: jest.fn(),
|
||||
setBadgeText: jest.fn(),
|
||||
setIcon: jest.fn(),
|
||||
} as typeof chrome.action;
|
||||
|
||||
chrome.contextMenus = {
|
||||
...chrome.contextMenus,
|
||||
create: jest.fn(),
|
||||
removeAll: jest.fn(),
|
||||
} as typeof chrome.contextMenus;
|
||||
|
||||
chrome.declarativeNetRequest = {
|
||||
...chrome.declarativeNetRequest,
|
||||
updateSessionRules: jest.fn(),
|
||||
} as typeof chrome.declarativeNetRequest;
|
||||
|
||||
chrome.runtime = {
|
||||
...chrome.runtime,
|
||||
getManifest: jest.fn(
|
||||
() =>
|
||||
({
|
||||
content_scripts: [{ matches: ['https://example.com/*'] }],
|
||||
version: '1.0.0',
|
||||
}) as chrome.runtime.ManifestV3
|
||||
),
|
||||
openOptionsPage: jest.fn(),
|
||||
} as typeof chrome.runtime;
|
6
packages/browser-extension/mocks/utils/domain.mock.ts
Normal file
6
packages/browser-extension/mocks/utils/domain.mock.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.mock('~utils/domain', () => ({
|
||||
formatDomainFromURL: jest.fn(() => 'example.com'),
|
||||
validateSupport: jest.fn(),
|
||||
}));
|
9
packages/browser-extension/mocks/utils/storage.mock.ts
Normal file
9
packages/browser-extension/mocks/utils/storage.mock.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.mock('~utils/storage', () => ({
|
||||
storage: {
|
||||
get: async (key: string) => (await chrome.storage.local.get(key))?.[key],
|
||||
remove: (key: string) => chrome.storage.local.remove(key),
|
||||
set: (key: string, value: any) => chrome.storage.local.set({ [key]: value }),
|
||||
},
|
||||
}));
|
@ -1,129 +1,11 @@
|
||||
import { sendToContentScript } from '@plasmohq/messaging';
|
||||
import onClicked from './utils/contextMenus/onClicked';
|
||||
import onInstalled from './utils/runtime/onInstalled';
|
||||
import onStartup from './utils/runtime/onStartup';
|
||||
import onBeforeRequest from './utils/webRequest/onBeforeRequest';
|
||||
import onErrorOccurred from './utils/webRequest/onErrorOccurred';
|
||||
|
||||
import {
|
||||
DEFAULT_DOMAIN_CONFIG,
|
||||
DEFAULT_EXTENSION_DATA,
|
||||
EXTENSION_MENU_ITEM_ID,
|
||||
REPORT_MENU_ITEM_ID,
|
||||
SETTINGS_MENU_ITEM_ID,
|
||||
} from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import { noop, suppressLastError } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
import databaseRefreshHandler from './messages/database/refresh';
|
||||
import extensionUpdateAvailableHandler from './messages/extension/updateAvailable';
|
||||
|
||||
chrome.contextMenus.onClicked.addListener((info) => {
|
||||
switch (info.menuItemId) {
|
||||
case REPORT_MENU_ITEM_ID:
|
||||
chrome.action.openPopup();
|
||||
break;
|
||||
case SETTINGS_MENU_ITEM_ID:
|
||||
chrome.runtime.openOptionsPage();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onInstalled.addListener(async () => {
|
||||
chrome.contextMenus.removeAll(() => {
|
||||
const documentUrlPatterns = chrome.runtime.getManifest().content_scripts?.[0].matches;
|
||||
|
||||
chrome.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: EXTENSION_MENU_ITEM_ID,
|
||||
title: 'Cookie Dialog Monster',
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
chrome.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: SETTINGS_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_settingsOption'),
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
chrome.contextMenus.create(
|
||||
{
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: REPORT_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_reportOption'),
|
||||
},
|
||||
suppressLastError
|
||||
);
|
||||
});
|
||||
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
});
|
||||
|
||||
chrome.runtime.onStartup.addListener(async () => {
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
await extensionUpdateAvailableHandler({ name: 'extension/updateAvailable' }, { send: noop });
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Listen to the moment before a request is made to apply the rules
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
chrome.webRequest.onBeforeRequest.addListener(
|
||||
(details) => {
|
||||
const { tabId, type, url } = details;
|
||||
const location = new URL(url);
|
||||
const domain = formatDomainFromURL(location);
|
||||
|
||||
if (tabId > -1 && type === 'main_frame') {
|
||||
storage.get<ExtensionData>('data').then(({ exclusions, rules } = DEFAULT_EXTENSION_DATA) => {
|
||||
if (!validateSupport(location.hostname, exclusions.domains)) {
|
||||
return;
|
||||
}
|
||||
|
||||
storage.get<DomainConfig>(domain).then((config = DEFAULT_DOMAIN_CONFIG) => {
|
||||
if (rules.length) {
|
||||
const rulesWithTabId = rules.map((rule) => ({
|
||||
...rule,
|
||||
condition: { ...rule.condition, tabIds: [tabId] },
|
||||
}));
|
||||
|
||||
chrome.declarativeNetRequest.updateSessionRules({
|
||||
addRules: config.on ? rulesWithTabId : undefined,
|
||||
removeRuleIds: rules.map((rule) => rule.id),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] }
|
||||
);
|
||||
|
||||
/**
|
||||
* @description Listen for errors on network requests
|
||||
*/
|
||||
chrome.webRequest.onErrorOccurred.addListener(
|
||||
async (details) => {
|
||||
const { error, tabId } = details;
|
||||
|
||||
if (error === 'net::ERR_BLOCKED_BY_CLIENT' && tabId > -1) {
|
||||
await sendToContentScript({
|
||||
body: {
|
||||
value: error,
|
||||
},
|
||||
name: 'INCREASE_LOG_COUNT',
|
||||
tabId,
|
||||
});
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] }
|
||||
);
|
||||
chrome.contextMenus.onClicked.addListener(onClicked);
|
||||
chrome.runtime.onInstalled.addListener(onInstalled);
|
||||
chrome.runtime.onStartup.addListener(onStartup);
|
||||
chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest as any, { urls: ['<all_urls>'] });
|
||||
chrome.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'] });
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { REPORT_MENU_ITEM_ID, SETTINGS_MENU_ITEM_ID } from '~utils/constants';
|
||||
import { suppressLastError } from '~utils/error';
|
||||
|
||||
export default function onClicked(data: chrome.contextMenus.OnClickData) {
|
||||
switch (data.menuItemId) {
|
||||
case REPORT_MENU_ITEM_ID:
|
||||
chrome.action.openPopup(suppressLastError);
|
||||
break;
|
||||
case SETTINGS_MENU_ITEM_ID:
|
||||
chrome.runtime.openOptionsPage(suppressLastError);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import {
|
||||
EXTENSION_MENU_ITEM_ID,
|
||||
REPORT_MENU_ITEM_ID,
|
||||
SETTINGS_MENU_ITEM_ID,
|
||||
} from '~utils/constants';
|
||||
import { noop } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
export default async function onInstalled() {
|
||||
await chrome.contextMenus.removeAll();
|
||||
|
||||
const documentUrlPatterns = chrome.runtime.getManifest().content_scripts?.[0].matches;
|
||||
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: EXTENSION_MENU_ITEM_ID,
|
||||
title: 'Cookie Dialog Monster',
|
||||
});
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: SETTINGS_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_settingsOption'),
|
||||
});
|
||||
await chrome.contextMenus.create({
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: REPORT_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_reportOption'),
|
||||
});
|
||||
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import extensionUpdateAvailableHandler from '~background/messages/extension/updateAvailable';
|
||||
import { noop } from '~utils/error';
|
||||
import { storage } from '~utils/storage';
|
||||
|
||||
export default async function onStartup() {
|
||||
await storage.remove('updateAvailable');
|
||||
await databaseRefreshHandler({ name: 'database/refresh' }, { send: noop });
|
||||
await extensionUpdateAvailableHandler({ name: 'extension/updateAvailable' }, { send: noop });
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import { DEFAULT_DOMAIN_CONFIG, DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import { storage } from '~utils/storage';
|
||||
import type { DomainConfig, ExtensionData } from '~utils/types';
|
||||
|
||||
export default async function onBeforeRequest(details: chrome.webRequest.WebRequestBodyDetails) {
|
||||
const { tabId, type, url } = details;
|
||||
const location = new URL(url);
|
||||
const domain = formatDomainFromURL(location);
|
||||
|
||||
if (tabId > -1 && type === 'main_frame') {
|
||||
const data = (await storage.get<ExtensionData>('data')) || DEFAULT_EXTENSION_DATA;
|
||||
|
||||
if (!validateSupport(location.hostname, data.exclusions.domains)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = (await storage.get<DomainConfig>(domain)) || DEFAULT_DOMAIN_CONFIG;
|
||||
|
||||
if (data.rules.length) {
|
||||
const rulesWithTabId = data.rules.map((rule) => ({
|
||||
...rule,
|
||||
condition: { ...rule.condition, tabIds: [tabId] },
|
||||
}));
|
||||
|
||||
chrome.declarativeNetRequest.updateSessionRules({
|
||||
addRules: config.on ? rulesWithTabId : undefined,
|
||||
removeRuleIds: data.rules.map((rule) => rule.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { sendToContentScript } from '@plasmohq/messaging';
|
||||
|
||||
export default async function onErrorOccurred(details: chrome.webRequest.WebResponseErrorDetails) {
|
||||
const { error, tabId } = details;
|
||||
|
||||
if (error === 'net::ERR_BLOCKED_BY_CLIENT' && tabId > -1) {
|
||||
await sendToContentScript({
|
||||
body: {
|
||||
value: error,
|
||||
},
|
||||
name: 'INCREASE_LOG_COUNT',
|
||||
tabId,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/database/get';
|
||||
import { DEFAULT_EXTENSION_DATA } from '~utils/constants';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
describe('background/messages/database/get.ts', () => {
|
||||
const req = { name: 'database/get' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return default data if no data is in storage', async () => {
|
||||
await handler(req, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('data');
|
||||
expect(res.send).toHaveBeenCalledWith({ data: DEFAULT_EXTENSION_DATA, success: true });
|
||||
});
|
||||
|
||||
it('should return stored data if it exists in storage', async () => {
|
||||
const data: ExtensionData = {
|
||||
actions: [
|
||||
{
|
||||
domain: 'jestjs.io',
|
||||
name: 'click',
|
||||
selector: '#jest',
|
||||
},
|
||||
],
|
||||
exclusions: {
|
||||
domains: ['*.jestjs.io'],
|
||||
overflows: ['*.jestjs.io'],
|
||||
tags: ['JEST'],
|
||||
},
|
||||
keywords: ['jest'],
|
||||
rules: [
|
||||
{
|
||||
action: {
|
||||
type: 'block' as chrome.declarativeNetRequest.RuleActionType,
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: [
|
||||
'font' as chrome.declarativeNetRequest.ResourceType,
|
||||
'image' as chrome.declarativeNetRequest.ResourceType,
|
||||
'media' as chrome.declarativeNetRequest.ResourceType,
|
||||
'object' as chrome.declarativeNetRequest.ResourceType,
|
||||
'script' as chrome.declarativeNetRequest.ResourceType,
|
||||
'stylesheet' as chrome.declarativeNetRequest.ResourceType,
|
||||
'xmlhttprequest' as chrome.declarativeNetRequest.ResourceType,
|
||||
],
|
||||
urlFilter: '||jestjs.io^',
|
||||
},
|
||||
id: 1,
|
||||
priority: 1,
|
||||
},
|
||||
],
|
||||
tokens: {
|
||||
backdrops: ['#backdrop'],
|
||||
classes: ['jest'],
|
||||
containers: ['#container'],
|
||||
selectors: ['#element'],
|
||||
},
|
||||
version: '0.0.0',
|
||||
};
|
||||
|
||||
await chrome.storage.local.set({ data });
|
||||
await handler(req, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('data');
|
||||
expect(res.send).toHaveBeenCalledWith({ data, success: true });
|
||||
});
|
||||
});
|
@ -0,0 +1,91 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/database/refresh';
|
||||
import { API_URL } from '~utils/constants';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
describe('background/messages/database/refresh.ts', () => {
|
||||
const req = { name: 'database/refresh' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch data from the API, store it, and return the data when successful', async () => {
|
||||
const data: ExtensionData = {
|
||||
actions: [
|
||||
{
|
||||
domain: 'jestjs.io',
|
||||
name: 'click',
|
||||
selector: '#jest',
|
||||
},
|
||||
],
|
||||
exclusions: {
|
||||
domains: ['*.jestjs.io'],
|
||||
overflows: ['*.jestjs.io'],
|
||||
tags: ['JEST'],
|
||||
},
|
||||
keywords: ['jest'],
|
||||
rules: [
|
||||
{
|
||||
action: {
|
||||
type: 'block' as chrome.declarativeNetRequest.RuleActionType,
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: [
|
||||
'font' as chrome.declarativeNetRequest.ResourceType,
|
||||
'image' as chrome.declarativeNetRequest.ResourceType,
|
||||
'media' as chrome.declarativeNetRequest.ResourceType,
|
||||
'object' as chrome.declarativeNetRequest.ResourceType,
|
||||
'script' as chrome.declarativeNetRequest.ResourceType,
|
||||
'stylesheet' as chrome.declarativeNetRequest.ResourceType,
|
||||
'xmlhttprequest' as chrome.declarativeNetRequest.ResourceType,
|
||||
],
|
||||
urlFilter: '||jestjs.io^',
|
||||
},
|
||||
id: 1,
|
||||
priority: 1,
|
||||
},
|
||||
],
|
||||
tokens: {
|
||||
backdrops: ['#backdrop'],
|
||||
classes: ['jest'],
|
||||
containers: ['#container'],
|
||||
selectors: ['#element'],
|
||||
},
|
||||
version: '0.0.0',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () => Promise.resolve({ data, success: true }),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/data/`);
|
||||
expect(chrome.storage.local.set).toHaveBeenCalledWith({ data });
|
||||
expect(res.send).toHaveBeenCalledWith({ data, success: true });
|
||||
});
|
||||
|
||||
it('should return success: false if the API call fails', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () => Promise.resolve({ success: false }),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/data/`);
|
||||
expect(chrome.storage.local.set).not.toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
});
|
@ -0,0 +1,176 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/domain/config';
|
||||
import { API_URL } from '~utils/constants';
|
||||
import type { DomainConfig } from '~utils/types';
|
||||
|
||||
describe('background/messages/domain/config.ts', () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const req = { name: 'domain/config' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return success: false when domain is missing in request', async () => {
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
|
||||
it('should return cached data when issue is still valid', async () => {
|
||||
const config: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() + 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
flags: config.issue?.flags,
|
||||
url: config.issue?.url,
|
||||
},
|
||||
success: true,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await chrome.storage.local.set({ 'example.com': config });
|
||||
await handler({ ...req, body }, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('example.com');
|
||||
expect(res.send).toHaveBeenCalledWith({ data: config, success: true });
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch new issue data if issue is expired', async () => {
|
||||
const previous: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() - 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
const next: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() + 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
flags: previous.issue?.flags,
|
||||
url: previous.issue?.url,
|
||||
},
|
||||
success: true,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await chrome.storage.local.set({ 'example.com': previous });
|
||||
await handler({ ...req, body }, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('example.com');
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/issues/example.com/`);
|
||||
expect(chrome.storage.local.set).toHaveBeenCalledWith({ 'example.com': next });
|
||||
expect(res.send).toHaveBeenCalledWith({ data: next, success: true });
|
||||
});
|
||||
|
||||
it('should handle rate-limiting gracefully (HTTP 429)', async () => {
|
||||
const previous: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() - 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
const next: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() + 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
errors: [],
|
||||
success: false,
|
||||
}),
|
||||
status: 429,
|
||||
})
|
||||
);
|
||||
|
||||
await chrome.storage.local.set({ 'example.com': previous });
|
||||
await handler({ ...req, body }, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('example.com');
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/issues/example.com/`);
|
||||
expect(chrome.storage.local.set).not.toHaveBeenCalledWith({ 'example.com': next });
|
||||
expect(res.send).toHaveBeenCalledWith({ data: previous, success: true });
|
||||
});
|
||||
|
||||
it('should fallback to a 24-hour expiration if API returns non-success response', async () => {
|
||||
const previous: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() - 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://jestjs.io/',
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
const next: DomainConfig = {
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
on: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
errors: [],
|
||||
success: false,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await chrome.storage.local.set({ 'example.com': previous });
|
||||
await handler({ ...req, body }, res);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenCalledWith('example.com');
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/issues/example.com/`);
|
||||
expect(chrome.storage.local.set).toHaveBeenCalledWith({ 'example.com': next });
|
||||
expect(res.send).toHaveBeenCalledWith({ data: next, success: true });
|
||||
});
|
||||
});
|
@ -0,0 +1,96 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/extension/updateAvailable';
|
||||
import { API_URL } from '~utils/constants';
|
||||
|
||||
describe('background/messages/extension/updateAvailable.ts', () => {
|
||||
const req = { name: 'extension/updateAvailable' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should set updateAvailable if next version differs from current version', async () => {
|
||||
// const current = '1.0.0';
|
||||
const next = '2.0.0';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
data: next,
|
||||
success: true,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/version/`);
|
||||
expect(chrome.storage.local.set).toHaveBeenCalledWith({ updateAvailable: next });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should remove updateAvailable if next version matches current version', async () => {
|
||||
// const current = '1.0.0';
|
||||
const next = '1.0.0';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
data: next,
|
||||
success: true,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/version/`);
|
||||
expect(chrome.storage.local.remove).toHaveBeenCalledWith('updateAvailable');
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
|
||||
it('should return success: false if server responds with HTTP 429', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
errors: [],
|
||||
success: false,
|
||||
}),
|
||||
status: 429,
|
||||
})
|
||||
);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/version/`);
|
||||
expect(chrome.storage.local.set).not.toHaveBeenCalled();
|
||||
expect(chrome.storage.local.remove).not.toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
|
||||
it('should return success: false if an error occurs during fetch', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Error()));
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`${API_URL}/version/`);
|
||||
expect(chrome.storage.local.set).not.toHaveBeenCalled();
|
||||
expect(chrome.storage.local.remove).not.toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/extension/updateBadge';
|
||||
|
||||
describe('background/messages/extension/updateBadge.ts', () => {
|
||||
const req = { name: 'extension/updateBadge' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should set badge text and color when frameId is 0 and tabId exists', async () => {
|
||||
const body = { value: 50 };
|
||||
const sender = { frameId: 0, tab: { id: 123 } } as chrome.runtime.MessageSender;
|
||||
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setBadgeBackgroundColor).toHaveBeenCalledWith({ color: '#6b7280' });
|
||||
expect(chrome.action.setBadgeText).toHaveBeenCalledWith({ tabId: 123, text: '50' });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should clear badge text if value is zero', async () => {
|
||||
const body = { value: 0 };
|
||||
const sender = { frameId: 0, tab: { id: 456 } } as chrome.runtime.MessageSender;
|
||||
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setBadgeBackgroundColor).toHaveBeenCalledWith({ color: '#6b7280' });
|
||||
expect(chrome.action.setBadgeText).toHaveBeenCalledWith({ tabId: 456, text: '' });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should return success: false if frameId is not 0', async () => {
|
||||
const body = { value: 50 };
|
||||
const sender = { frameId: 1, tab: { id: 123 } } as chrome.runtime.MessageSender;
|
||||
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setBadgeBackgroundColor).not.toHaveBeenCalledWith();
|
||||
expect(chrome.action.setBadgeText).not.toHaveBeenCalledWith();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
|
||||
it('should return success: false if tabId is undefined', async () => {
|
||||
const body = { value: 50 };
|
||||
const sender = { frameId: 1, tab: undefined } as chrome.runtime.MessageSender;
|
||||
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setBadgeBackgroundColor).not.toHaveBeenCalledWith();
|
||||
expect(chrome.action.setBadgeText).not.toHaveBeenCalledWith();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
});
|
@ -0,0 +1,156 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import handler from '~background/messages/extension/updateIcon';
|
||||
import { validateSupport } from '~utils/domain';
|
||||
|
||||
describe('background/messages/extension/updateIcon.ts', () => {
|
||||
const req = { name: 'extension/updateIcon' as const };
|
||||
const res = { send: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(validateSupport as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should set warnIcon when config is on, tab.url is valid, and issue URL exists', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = {
|
||||
frameId: 0,
|
||||
tab: {
|
||||
id: 123,
|
||||
url: 'https://example.com/page',
|
||||
},
|
||||
} as chrome.runtime.MessageSender;
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: true,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 8 * 60 * 60 * 1000,
|
||||
flags: ['jest'],
|
||||
url: 'https://example.com/issue',
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).toHaveBeenCalledWith({ path: 'warn-icon', tabId: 123 });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should set onIcon when config is on, tab.url is valid, and no issue URL exists', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = {
|
||||
frameId: 0,
|
||||
tab: {
|
||||
id: 123,
|
||||
url: 'https://example.com/page',
|
||||
},
|
||||
} as chrome.runtime.MessageSender;
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: true,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).toHaveBeenCalledWith({ path: 'on-icon', tabId: 123 });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should set offIcon when config is off or tab.url is not valid', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = {
|
||||
frameId: 0,
|
||||
tab: {
|
||||
id: 123,
|
||||
url: 'https://example.com/page',
|
||||
},
|
||||
} as chrome.runtime.MessageSender;
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: false,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).toHaveBeenCalledWith({ path: 'off-icon', tabId: 123 });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should set offIcon when config is on but validateSupport is false', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = {
|
||||
frameId: 0,
|
||||
tab: {
|
||||
id: 123,
|
||||
url: 'https://example.com/page',
|
||||
},
|
||||
} as chrome.runtime.MessageSender;
|
||||
|
||||
(validateSupport as jest.Mock).mockReturnValue(false);
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: true,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).toHaveBeenCalledWith({ path: 'off-icon', tabId: 123 });
|
||||
expect(res.send).toHaveBeenCalledWith({ success: true });
|
||||
});
|
||||
|
||||
it('should return success: false when frameId is not 0', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = {
|
||||
frameId: 1,
|
||||
tab: {
|
||||
id: 123,
|
||||
url: 'https://example.com/page',
|
||||
},
|
||||
} as chrome.runtime.MessageSender;
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: false,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).not.toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
|
||||
it('should return success: false when tab.id is undefined', async () => {
|
||||
const body = { domain: 'example.com' };
|
||||
const sender = { frameId: 0, tab: undefined } as chrome.runtime.MessageSender;
|
||||
|
||||
await chrome.storage.local.set({
|
||||
'example.com': {
|
||||
on: false,
|
||||
issue: {
|
||||
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
await handler({ ...req, body, sender }, res);
|
||||
|
||||
expect(chrome.action.setIcon).not.toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith({ success: false });
|
||||
});
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import onClicked from '~background/utils/contextMenus/onClicked';
|
||||
import { REPORT_MENU_ITEM_ID, SETTINGS_MENU_ITEM_ID } from '~utils/constants';
|
||||
|
||||
describe('background/utils/runtime/onClicked.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call chrome.action.openPopup when REPORT_MENU_ITEM_ID is clicked', () => {
|
||||
const data = { menuItemId: REPORT_MENU_ITEM_ID } as chrome.contextMenus.OnClickData;
|
||||
onClicked(data);
|
||||
expect(chrome.runtime.openOptionsPage).not.toHaveBeenCalled();
|
||||
expect(chrome.action.openPopup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call chrome.runtime.openOptionsPage when SETTINGS_MENU_ITEM_ID is clicked', () => {
|
||||
const data = { menuItemId: SETTINGS_MENU_ITEM_ID } as chrome.contextMenus.OnClickData;
|
||||
onClicked(data);
|
||||
expect(chrome.runtime.openOptionsPage).toHaveBeenCalled();
|
||||
expect(chrome.action.openPopup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing when an unrecognized menu item ID is clicked', () => {
|
||||
const data = { menuItemId: 'UNKNOWN_MENU_ITEM_ID' } as chrome.contextMenus.OnClickData;
|
||||
onClicked(data);
|
||||
expect(chrome.runtime.openOptionsPage).not.toHaveBeenCalled();
|
||||
expect(chrome.action.openPopup).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import onInstalled from '~background/utils/runtime/onInstalled';
|
||||
import {
|
||||
EXTENSION_MENU_ITEM_ID,
|
||||
REPORT_MENU_ITEM_ID,
|
||||
SETTINGS_MENU_ITEM_ID,
|
||||
} from '~utils/constants';
|
||||
|
||||
jest.mock('~background/messages/database/refresh');
|
||||
|
||||
describe('background/utils/runtime/onInstalled.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should remove existing context menus and create new ones', async () => {
|
||||
const documentUrlPatterns = ['https://example.com/*'];
|
||||
|
||||
await onInstalled();
|
||||
|
||||
expect(chrome.contextMenus.removeAll).toHaveBeenCalled();
|
||||
expect(chrome.contextMenus.create).toHaveBeenNthCalledWith(1, {
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: EXTENSION_MENU_ITEM_ID,
|
||||
title: 'Cookie Dialog Monster',
|
||||
});
|
||||
expect(chrome.contextMenus.create).toHaveBeenNthCalledWith(2, {
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: SETTINGS_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_settingsOption'),
|
||||
});
|
||||
expect(chrome.contextMenus.create).toHaveBeenNthCalledWith(3, {
|
||||
contexts: ['all'],
|
||||
documentUrlPatterns,
|
||||
id: REPORT_MENU_ITEM_ID,
|
||||
parentId: EXTENSION_MENU_ITEM_ID,
|
||||
title: chrome.i18n.getMessage('contextMenu_reportOption'),
|
||||
});
|
||||
expect(chrome.storage.local.remove).toHaveBeenCalledWith('updateAvailable');
|
||||
expect(databaseRefreshHandler).toHaveBeenCalledWith(
|
||||
{ name: 'database/refresh' },
|
||||
{ send: expect.any(Function) }
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import databaseRefreshHandler from '~background/messages/database/refresh';
|
||||
import updateAvailableHandler from '~background/messages/extension/updateAvailable';
|
||||
import onStartup from '~background/utils/runtime/onStartup';
|
||||
|
||||
jest.mock('~background/messages/database/refresh');
|
||||
jest.mock('~background/messages/extension/updateAvailable');
|
||||
|
||||
describe('background/utils/runtime/onStartup.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should execute calls in sequence', async () => {
|
||||
await onStartup();
|
||||
|
||||
expect(chrome.storage.local.remove).toHaveBeenCalledWith('updateAvailable');
|
||||
expect(databaseRefreshHandler).toHaveBeenCalledWith(
|
||||
{ name: 'database/refresh' },
|
||||
{ send: expect.any(Function) }
|
||||
);
|
||||
expect(updateAvailableHandler).toHaveBeenCalledWith(
|
||||
{ name: 'extension/updateAvailable' },
|
||||
{ send: expect.any(Function) }
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,163 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import onBeforeRequest from '~background/utils/webRequest/onBeforeRequest';
|
||||
import { DEFAULT_DOMAIN_CONFIG } from '~utils/constants';
|
||||
import { formatDomainFromURL, validateSupport } from '~utils/domain';
|
||||
import type { ExtensionData } from '~utils/types';
|
||||
|
||||
describe('background/utils/webRequest/onBeforeRequest.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(validateSupport as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should update session rules if conditions are met', async () => {
|
||||
const data: ExtensionData = {
|
||||
actions: [
|
||||
{
|
||||
domain: 'jestjs.io',
|
||||
name: 'click',
|
||||
selector: '#jest',
|
||||
},
|
||||
],
|
||||
exclusions: {
|
||||
domains: ['*.jestjs.io'],
|
||||
overflows: ['*.jestjs.io'],
|
||||
tags: ['JEST'],
|
||||
},
|
||||
keywords: ['jest'],
|
||||
rules: [
|
||||
{
|
||||
action: {
|
||||
type: 'block' as chrome.declarativeNetRequest.RuleActionType,
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: [
|
||||
'font' as chrome.declarativeNetRequest.ResourceType,
|
||||
'image' as chrome.declarativeNetRequest.ResourceType,
|
||||
'media' as chrome.declarativeNetRequest.ResourceType,
|
||||
'object' as chrome.declarativeNetRequest.ResourceType,
|
||||
'script' as chrome.declarativeNetRequest.ResourceType,
|
||||
'stylesheet' as chrome.declarativeNetRequest.ResourceType,
|
||||
'xmlhttprequest' as chrome.declarativeNetRequest.ResourceType,
|
||||
],
|
||||
urlFilter: '||jestjs.io^',
|
||||
tabIds: [123],
|
||||
},
|
||||
id: 1,
|
||||
priority: 1,
|
||||
},
|
||||
],
|
||||
tokens: {
|
||||
backdrops: ['#backdrop'],
|
||||
classes: ['jest'],
|
||||
containers: ['#container'],
|
||||
selectors: ['#element'],
|
||||
},
|
||||
version: '0.0.0',
|
||||
};
|
||||
const details: chrome.webRequest.WebRequestBodyDetails = {
|
||||
tabId: 123,
|
||||
type: 'main_frame',
|
||||
url: 'https://example.com/path',
|
||||
} as chrome.webRequest.WebRequestBodyDetails;
|
||||
|
||||
await chrome.storage.local.set({ data });
|
||||
await chrome.storage.local.set({ 'example.com': DEFAULT_DOMAIN_CONFIG });
|
||||
await onBeforeRequest(details);
|
||||
|
||||
expect(chrome.storage.local.get).toHaveBeenNthCalledWith(1, 'data');
|
||||
expect(chrome.storage.local.get).toHaveBeenNthCalledWith(2, 'example.com');
|
||||
expect(formatDomainFromURL).toHaveBeenCalledWith(new URL(details.url));
|
||||
expect(validateSupport).toHaveBeenCalledWith('example.com', ['*.jestjs.io']);
|
||||
expect(chrome.declarativeNetRequest.updateSessionRules).toHaveBeenCalledWith({
|
||||
addRules: [data.rules[0]],
|
||||
removeRuleIds: [data.rules[0].id],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update session rules if config.on is false', async () => {
|
||||
const details: chrome.webRequest.WebRequestBodyDetails = {
|
||||
tabId: 123,
|
||||
type: 'main_frame',
|
||||
url: 'https://example.com/path',
|
||||
} as chrome.webRequest.WebRequestBodyDetails;
|
||||
|
||||
(validateSupport as jest.Mock).mockReturnValue(false);
|
||||
|
||||
await onBeforeRequest(details);
|
||||
|
||||
expect(chrome.declarativeNetRequest.updateSessionRules).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not update session rules if validateSupport returns false', async () => {
|
||||
const data: ExtensionData = {
|
||||
actions: [
|
||||
{
|
||||
domain: 'jestjs.io',
|
||||
name: 'click',
|
||||
selector: '#jest',
|
||||
},
|
||||
],
|
||||
exclusions: {
|
||||
domains: ['*.jestjs.io'],
|
||||
overflows: ['*.jestjs.io'],
|
||||
tags: ['JEST'],
|
||||
},
|
||||
keywords: ['jest'],
|
||||
rules: [
|
||||
{
|
||||
action: {
|
||||
type: 'block' as chrome.declarativeNetRequest.RuleActionType,
|
||||
},
|
||||
condition: {
|
||||
resourceTypes: [
|
||||
'font' as chrome.declarativeNetRequest.ResourceType,
|
||||
'image' as chrome.declarativeNetRequest.ResourceType,
|
||||
'media' as chrome.declarativeNetRequest.ResourceType,
|
||||
'object' as chrome.declarativeNetRequest.ResourceType,
|
||||
'script' as chrome.declarativeNetRequest.ResourceType,
|
||||
'stylesheet' as chrome.declarativeNetRequest.ResourceType,
|
||||
'xmlhttprequest' as chrome.declarativeNetRequest.ResourceType,
|
||||
],
|
||||
urlFilter: '||jestjs.io^',
|
||||
tabIds: [123],
|
||||
},
|
||||
id: 1,
|
||||
priority: 1,
|
||||
},
|
||||
],
|
||||
tokens: {
|
||||
backdrops: ['#backdrop'],
|
||||
classes: ['jest'],
|
||||
containers: ['#container'],
|
||||
selectors: ['#element'],
|
||||
},
|
||||
version: '0.0.0',
|
||||
};
|
||||
const details: chrome.webRequest.WebRequestBodyDetails = {
|
||||
tabId: 123,
|
||||
type: 'main_frame',
|
||||
url: 'https://example.com/path',
|
||||
} as chrome.webRequest.WebRequestBodyDetails;
|
||||
|
||||
await chrome.storage.local.set({ 'example.com': { ...DEFAULT_DOMAIN_CONFIG, on: false } });
|
||||
await onBeforeRequest(details);
|
||||
|
||||
expect(chrome.declarativeNetRequest.updateSessionRules).toHaveBeenCalledWith({
|
||||
addRules: undefined,
|
||||
removeRuleIds: [data.rules[0].id],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not process if tabId is invalid or type is not "main_frame"', async () => {
|
||||
let details = {} as chrome.webRequest.WebRequestBodyDetails;
|
||||
|
||||
details = { ...details, tabId: -1, type: 'main_frame', url: 'https://example.com' };
|
||||
await onBeforeRequest(details);
|
||||
details = { ...details, tabId: 123, type: 'sub_frame', url: 'https://example.com' };
|
||||
await onBeforeRequest(details);
|
||||
|
||||
expect(chrome.declarativeNetRequest.updateSessionRules).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
import { sendToContentScript } from '@plasmohq/messaging';
|
||||
|
||||
import onErrorOccurred from '~background/utils/webRequest/onErrorOccurred';
|
||||
|
||||
jest.mock('@plasmohq/messaging', () => ({ sendToContentScript: jest.fn() }));
|
||||
|
||||
describe('background/utils/webRequest/onErrorOcurred.ts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks(); // Clear previous mocks before each test
|
||||
});
|
||||
|
||||
it('should call sendToContentScript when error is "net::ERR_BLOCKED_BY_CLIENT" and tabId is valid', async () => {
|
||||
const details = {
|
||||
error: 'net::ERR_BLOCKED_BY_CLIENT',
|
||||
tabId: 1,
|
||||
} as chrome.webRequest.WebResponseErrorDetails;
|
||||
|
||||
await onErrorOccurred(details);
|
||||
|
||||
expect(sendToContentScript).toHaveBeenCalledTimes(1);
|
||||
expect(sendToContentScript).toHaveBeenCalledWith({
|
||||
body: {
|
||||
value: 'net::ERR_BLOCKED_BY_CLIENT',
|
||||
},
|
||||
name: 'INCREASE_LOG_COUNT',
|
||||
tabId: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT call sendToContentScript when error is not "net::ERR_BLOCKED_BY_CLIENT"', async () => {
|
||||
const details = {
|
||||
error: 'net::ERR_FAILED',
|
||||
tabId: 1,
|
||||
} as chrome.webRequest.WebResponseErrorDetails;
|
||||
|
||||
await onErrorOccurred(details);
|
||||
|
||||
expect(sendToContentScript).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT call sendToContentScript when tabId is invalid (less than 0)', async () => {
|
||||
const details = {
|
||||
error: 'net::ERR_BLOCKED_BY_CLIENT',
|
||||
tabId: -1,
|
||||
} as chrome.webRequest.WebResponseErrorDetails;
|
||||
|
||||
await onErrorOccurred(details);
|
||||
|
||||
expect(sendToContentScript).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,3 +1,5 @@
|
||||
import { describe } from '@jest/globals';
|
||||
import { describe, it } from '@jest/globals';
|
||||
|
||||
describe('contents/index.ts', () => {});
|
||||
describe('contents/index.ts', () => {
|
||||
it.skip('temp', () => {});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user