Skip to content

Commit f680f49

Browse files
committed
Switch from mockzilla to jest-webextension-mock
1 parent 452c1dc commit f680f49

11 files changed

Lines changed: 383 additions & 246 deletions

File tree

.github/dependabot.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ updates:
3030
- "@testing-library/*"
3131
- "@types/jest"
3232
- "*jest*"
33-
- "mockzilla*"
3433
exclude-patterns:
3534
- "*eslint*"
3635
typescript:

configs/jest.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const textFilesModuleNameMapper = {
1616
const config: Config.InitialOptions = {
1717
rootDir,
1818
testEnvironment: 'jsdom',
19+
setupFiles: [
20+
'jest-webextension-mock',
21+
],
1922
setupFilesAfterEnv: [
2023
path.resolve(__dirname, 'jest.setup.ts'),
2124
'jest-canvas-mock', // only for 'react-game-snake'

configs/jest.setup.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { TextEncoder, TextDecoder } from 'util'
22
import crypto from 'crypto';
33

4-
import 'mockzilla-webextension';
4+
import 'jest-webextension-mock';
55
import '@testing-library/jest-dom';
66
import TimeAgo from 'javascript-time-ago';
77
import en from 'javascript-time-ago/locale/en.json';
@@ -13,3 +13,49 @@ globalThis.TextEncoder = TextEncoder;
1313
globalThis.TextDecoder = TextDecoder;
1414
// @ts-expect-error
1515
globalThis.crypto = crypto;
16+
17+
// Ensure jest mock functions are present on the webextension mock where possible
18+
const mb = (globalThis as any).mockBrowser || (globalThis as any).browser || {};
19+
const ensure = (obj: any, key: string, def?: any) => {
20+
obj[key] = obj[key] && obj[key].mock ? obj[key] : (def !== undefined ? jest.fn().mockReturnValue(def) : jest.fn());
21+
};
22+
23+
mb.runtime = mb.runtime || {};
24+
ensure(mb.runtime, 'getURL', '');
25+
ensure(mb.runtime, 'openOptionsPage');
26+
27+
mb.storage = mb.storage || {};
28+
mb.storage.local = mb.storage.local || {};
29+
ensure(mb.storage.local, 'get', {});
30+
ensure(mb.storage.local, 'set');
31+
ensure(mb.storage.local, 'remove');
32+
33+
mb.notifications = mb.notifications || {};
34+
ensure(mb.notifications, 'create', '');
35+
ensure(mb.notifications, 'clear', false);
36+
mb.notifications.onClicked = mb.notifications.onClicked || { addListener: jest.fn() };
37+
38+
mb.contextMenus = mb.contextMenus || {};
39+
ensure(mb.contextMenus, 'create');
40+
ensure(mb.contextMenus, 'remove');
41+
42+
mb.browserAction = mb.browserAction || {};
43+
ensure(mb.browserAction, 'openPopup');
44+
45+
mb.contentScripts = mb.contentScripts || {};
46+
ensure(mb.contentScripts, 'register', { unregister: jest.fn() });
47+
48+
mb.tabs = mb.tabs || {};
49+
ensure(mb.tabs, 'create');
50+
ensure(mb.tabs, 'query', []);
51+
ensure(mb.tabs, 'executeScript', []);
52+
53+
mb.alarms = mb.alarms || {};
54+
ensure(mb.alarms, 'create');
55+
mb.alarms.onAlarm = mb.alarms.onAlarm || { addListener: jest.fn() };
56+
57+
mb.pageAction = mb.pageAction || {};
58+
ensure(mb.pageAction, 'setTitle');
59+
ensure(mb.pageAction, 'setIcon');
60+
ensure(mb.pageAction, 'show');
61+
ensure(mb.pageAction, 'hide');

package-lock.json

Lines changed: 14 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,9 @@
8585
"jest-canvas-mock": "^2.5.2",
8686
"jest-environment-jsdom": "^29.7.0",
8787
"jest-transform-stub": "^2.0.0",
88+
"jest-webextension-mock": "^4.0.0",
8889
"markdown-loader": "^8.0.0",
8990
"mini-css-extract-plugin": "^2.10.0",
90-
"mockzilla": "^0.14.0",
91-
"mockzilla-webextension": "^0.15.0",
9291
"npm-run-all": "^4.1.5",
9392
"package-json-type": "^1.1.2",
9493
"postcss-styled-syntax": "^0.7.1",
@@ -105,11 +104,6 @@
105104
"webpack": "^5.104.1",
106105
"webpack-cli": "^6.0.1"
107106
},
108-
"overrides": {
109-
"mockzilla-webextension": {
110-
"webextension-polyfill": "^0.12.0"
111-
}
112-
},
113107
"engines": {
114108
"node": "^22",
115109
"npm": ">= 10",

src/commons/backgroundMessaging/spec.ts

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/* eslint-disable jest/expect-expect */
2-
import type { MockzillaDeep } from 'mockzilla';
3-
import 'mockzilla-webextension';
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
3+
import 'jest-webextension-mock';
4+
5+
const mockBrowser =
6+
(globalThis as any).mockBrowser || (globalThis as any).browser || {};
47

58
import { getOneOption } from '../options';
69

@@ -38,14 +41,6 @@ afterEach(() => {
3841
jest.clearAllMocks();
3942
});
4043

41-
function mockBrowserSendMessage<T extends keyof BackgroundMessage>() {
42-
return mockBrowser.runtime.sendMessage as unknown as MockzillaDeep<{
43-
(
44-
message: BackgroundMessage[T]['message'],
45-
): Promise<BackgroundMessage[T]['response']>;
46-
}>;
47-
}
48-
4944
describe('messagingClient', () => {
5045
it('getNotificationsUrl', async () => {
5146
const url = await getNotificationsUrl();
@@ -77,22 +72,26 @@ describe('messagingClient', () => {
7772
const mockTeamsList: BackgroundMessage['UPDATE_TEAMS_LIST']['response'] = {
7873
cs: 'Czech',
7974
};
80-
mockBrowserSendMessage<'UPDATE_TEAMS_LIST'>()
81-
.expect({ type: 'update-teams-list' })
82-
.andResolve(mockTeamsList);
75+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
76+
mockTeamsList,
77+
);
8378

8479
const teams = await updateTeamsList();
8580

81+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
82+
type: 'update-teams-list',
83+
});
8684
expect(teams).toStrictEqual(mockTeamsList);
8785
});
8886

8987
it('getUsersTeamFromPontoon', async () => {
90-
mockBrowserSendMessage<'GET_TEAM_FROM_PONTOON'>()
91-
.expect({ type: 'get-team-from-pontoon' })
92-
.andResolve('cs');
88+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce('cs');
9389

9490
const team = await getUsersTeamFromPontoon();
9591

92+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
93+
type: 'get-team-from-pontoon',
94+
});
9695
expect(team).toBe('cs');
9796
});
9897

@@ -103,68 +102,76 @@ describe('messagingClient', () => {
103102
name: 'Firefox',
104103
domains: [],
105104
};
106-
mockBrowserSendMessage<'GET_CURRENT_TAB_PROJECT'>()
107-
.expect({ type: 'get-current-tab-project' })
108-
.andResolve(mockProject);
105+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
106+
mockProject,
107+
);
109108

110109
const project = await getPontoonProjectForTheCurrentTab();
111110

111+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
112+
type: 'get-current-tab-project',
113+
});
112114
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113115
expect(project).toStrictEqual(mockProject);
114116
});
115117

116118
it('pageLoaded', async () => {
117-
mockBrowserSendMessage<'PAGE_LOADED'>()
118-
.expect({
119-
type: 'pontoon-page-loaded',
120-
documentHTML: '<html></html>',
121-
})
122-
.andResolve();
119+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
120+
undefined,
121+
);
123122

124123
await pageLoaded('<html></html>');
124+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
125+
type: 'pontoon-page-loaded',
126+
documentHTML: '<html></html>',
127+
});
125128
});
126129

127130
it('markAllNotificationsAsRead', async () => {
128-
mockBrowserSendMessage<'NOTIFICATIONS_READ'>()
129-
.expect({ type: 'notifications-read' })
130-
.andResolve();
131+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
132+
undefined,
133+
);
131134

132135
await markAllNotificationsAsRead();
136+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
137+
type: 'notifications-read',
138+
});
133139
});
134140

135141
it('searchTextInPontoon', async () => {
136-
mockBrowserSendMessage<'SEARCH_TEXT_IN_PONTOON'>()
137-
.expect({
138-
type: 'search-text-in-pontoon',
139-
text: 'foo bar',
140-
})
141-
.andResolve();
142+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
143+
undefined,
144+
);
142145

143146
await searchTextInPontoon('foo bar');
147+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
148+
type: 'search-text-in-pontoon',
149+
text: 'foo bar',
150+
});
144151
});
145152

146153
it('reportTranslatedTextToBugzilla', async () => {
147-
mockBrowserSendMessage<'REPORT_TRANSLATED_TEXT_TO_BUGZILLA'>()
148-
.expect({
149-
type: 'report-translated-text-to-bugzilla',
150-
text: 'foo bar',
151-
})
152-
.andResolve();
154+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce(
155+
undefined,
156+
);
153157

154158
await reportTranslatedTextToBugzilla('foo bar');
159+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
160+
type: 'report-translated-text-to-bugzilla',
161+
text: 'foo bar',
162+
});
155163
});
156164

157165
it('notificationBellIconScriptLoaded', async () => {
158-
mockBrowserSendMessage<'NOTIFICATIONS_BELL_SCRIPT_LOADED'>()
159-
.expect({
160-
type: 'notifications-bell-script-loaded',
161-
})
162-
.andResolve({
163-
type: 'enable-notifications-bell-script',
164-
});
166+
(mockBrowser.runtime.sendMessage as jest.Mock).mockResolvedValueOnce({
167+
type: 'enable-notifications-bell-script',
168+
});
165169

166170
const response = await notificationBellIconScriptLoaded();
167171

172+
expect(mockBrowser.runtime.sendMessage).toHaveBeenCalledWith({
173+
type: 'notifications-bell-script-loaded',
174+
});
168175
expect(response).toStrictEqual({
169176
type: 'enable-notifications-bell-script',
170177
});

0 commit comments

Comments
 (0)