clarkbw / jest-webextension-mock Goto Github PK
View Code? Open in Web Editor NEWA module to mock WebExtensions in Jest
License: BSD 3-Clause "New" or "Revised" License
A module to mock WebExtensions in Jest
License: BSD 3-Clause "New" or "Revised" License
Currently onChanged is only supported in chrome.storage. Need to add it for chrome.storage.sync as well. Should suffice to add onChanged: {
addListener: jest.fn(),
removeListener: jest.fn(),
hasListener: jest.fn(),
}, in storage.js
This is such a great library for testing, thanks for all the effort of putting this together!
Adding this as a feature request, I'll try to submit a PR if I have time 😄
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/management
https://developer.chrome.com/extensions/management
This is more for discussion than anything.
There are lots of callbacks (promises in FF) in the web-extension API. And I guess it would be fair to implement all functions with them so, that the callbacks/promises would be called immediately.
Now, one have to overwrite all mocked function, where callback call is needed, with new mock with implementation with callback call weather any other than call is needed or not.
What to pass as parameters which are often needed?
There's no support for the onCreated
event for tabs: https://github.com/clarkbw/jest-webextension-mock/blob/v3.5.0/src/tabs.js
Could you please add it?
I try to test an extension that uses separate codepaths for chrome and firefox depending on if 'browsers' is set. Currently, tests do only test the chrome paths.
So far, I wasn't able to make it work that: var browser = browser || chrome;
evaluates as browser with undefined chrome. Any ideas how I can achieve this with the jest-webextension-mock?
in generic.js during test execution, chrome
global.chrome
and global.browser
is defined but not browser
. If I set chrome to null, chromeand
global.chromeare set. I assume that this is due to the node context with globally scoped variables in
globalbut I do not get where
chrome` is set. Any hints?
I'm not sure if it's reasonable to mock such behavior.
Since webextension-polyfill 0.9.0 we get this in tests, i.e.
This script should only be loaded in a browser extension.
> 1 | import browser from "webextension-polyfill";
| ^
Is it supposed to work with the current version? Just wanted to ask first if anyone else has the same issue.
I'm getting the following error:
Test suite failed to run
This script should only be loaded in a browser extension.
Even after adding "setupFiles": ["jest-webextension-mock"]
to jest.config.json
With some of the recent changes, we need a push to npm. I need the chrome.runtime.getURL
changes specifically.
Thanks! 😄
Update: I would also be happy to help out with this repo however I can! I can look into setting up some CI/CD integration to deploy the extension and maybe fleshing out the documentation a bit? Would that be helpful?
I'm getting this error if the keys is undefined, but why no compare witch undefined values too?
on: jest-webextension-mock/dist/setup.js:264
Hi, do you have any plans to add support for the connectNative method (and what comes with it)?
Thanks
Edit: same would apply for chrome.webNavigation and chrome.webRequest or maybe it's not in the scope of this package?
official types for MV3 https://www.npmjs.com/package/chrome-types
I'm looking to switch to Vitest, do you think it would be possible to look for the vi
global as well as jest
? I'm not sure if that's enough.
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
setupFiles: [
"jest-webextension-mock"
]
},
})
FAIL utils.test.ts [ utils.test.ts ]
ReferenceError: jest is not defined
❯ Object.<anonymous> node_modules/jest-webextension-mock/dist/setup.js:5:25
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Hi! This seems to be a fantastic project.
Currently I'm trying to test chrome.storage.local.get
and chrome.storage.local.set
, but I'm struggling to have my typscript environment, more specifically ts-jest
understand that mockClear
actually exists as per:
beforeEach(() => {
chrome.storage.local.get.mockClear(); // Property 'mockClear' does not exist on type ... ...
});
I've managed to setup a similar project called jest-fetch-mock, and if I dig into their source, I can see that they have types such as:
export interface FetchMock
extends jest.MockInstance<Promise<Response>, [string | Request | undefined, RequestInit | undefined]> {
(input?: string | Request, init?: RequestInit): Promise<Response>;
// Response mocking
mockResponse(fn: MockResponseInitFunction): FetchMock;
mockResponse(response: string, responseInit?: MockParams): FetchMock;
...
}
defined in their project. And I can't seem to find something similar in this project.
Could it be that my project doesn't understand that the implementations has actually created functions such as mockClear
? Perhaps this project doesn't support typescript testing? If so, maybe I could declare
something for the functions I'm using. I just want to make sure I'm not misunderstanding something that you've documented somewhere.
Thanks in advance!
While making the changes for #129 I felt that there may be a need for this.
By the looks of the commit log, the format of the messages varies quite a lot. I'm also wondering about what the preferred merge strategy is, like would it be appreciated if one was to keep their branch up to date with master
through rebasing? I'm not sure if this is something that is valued here, it might just be me that is overly picky about such things.
Other than that, clarifying the goal with this mocking library might be beneficial. I mentioned in #129 that the changes only seem to relevant for Chrome extensions. Should such cases be supported by this project, or should anything outside of the actual WebExtensions API be omitted?
Awesome library! Calling out a potential source of confusion:
The storage API currently stores all types (local, managed, etc.) in the same object: https://github.com/clarkbw/jest-webextension-mock/blob/main/src/storage.js#L1
That leads to some confusing results when testing code that calls multiple types of storage
I have a function
export const send = async msg =>
new Promise(resolve =>
chrome.runtime.sendMessage(msg, response => resolve(response))
);
and corresponding test
describe('send', () => {
const message = 'some message'
beforeAll(async () => {
await send(message);
})
it('called chrome.runtime.sendMessage with the message', () => {
expect(chrome.runtime.sendMessage).toHaveBeenCalledWith(message, expect.any(Function));
})
})
but I get the error
FAIL tests/messaging/messageManager.test.js
● send › called chrome.runtime.sendMessage with the message
TypeError: listener is not a function
14 | export const send = async msg =>
15 | new Promise(resolve =>
> 16 | chrome.runtime.sendMessage(msg, response => resolve(response))
| ^
17 | );
18 |
at node_modules/jest-webextension-mock/dist/setup.js:123:14
at Array.forEach (<anonymous>)
at Object.<anonymous> (node_modules/jest-webextension-mock/dist/setup.js:122:24)
at sendMessage (src/messaging/messageManager.js:16:20)
What am I doing wrong?
Missing support for this: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/getManifest
Otherwise getting:
TypeError: _webextensionPolyfill.default.runtime.getManifest is not a function
Hey!
First let me thanks you for this great package 😄
I created this issue to let you know that I am actually working on an implementation of notifications API.
I will make a PR soon!
There's no mock for browser.runtime.onMessageExternal
References:
Suggestion for extending API. tx for ext.
I'm attempting to test my onMessage listeners, but I keep getting the error TypeError: sendResponse is not a function. It appears that the implementation of runtime.sendMessage accepts a callback as the second argument, but doesn't actually pass it to the listener.
I've also tried the MDN-recommended method of returning a promise from the onMessage listener and using .then() from runtime.sendMessage, but this doesn't work either.
My workaround thus far has been to export the listener function and hit it directly. This works but is less than ideal.
Hi!
I'm working with the code located in the tabs.js
module of this project as per:
sendMessage: jest.fn((tabId, message, cb) => {
onMessageListeners.forEach((listener) => listener(tabId, message));
if (cb !== undefined) {
return cb();
}
return Promise.resolve();
}),
From what I can see in the official docs for onMessage, the structure is as follows:
(message: any, sender: [MessageSender](https://developer.chrome.com/docs/extensions/reference/runtime/#type-MessageSender), sendResponse: function) => boolean | undefined
In my test if I run chrome.tabs.sendMessage(tabId, Message)
, the tabId
is the first parameter of the listener, which is inconsistent with the way chrome.runtime.sendMessage
calls its listener.
For more info, the listener I'm testing is
private async onMessage(
message: Message,
_: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void
) {
console.log(message);
}
There's also the issue with the sendResponse
, which I don't believe is covered here?
Any guidance would be greatly appreciated :)
Hi I am using Zest + Enzyme for testing my browser plugin. My plugin consists of background.js, contentscript.js and dev tools.
chrome.extension.onConnect.addListener(function (incoming_port) {
let port = incoming_port;
if (port.name === '3pm-panel') {
port.onMessage.addListener(function (message) {
switch (message.type) {
case 'reloadWithDebug':
// This is called even for iframe is loaded
chrome.tabs.query({
active: true,
...
I am trying to test if the onConnect.addListener is called once, reloadWithDebug is called once . I am not sure how to proceed with the testing. Can you elaborate with the below example. Thank you in Advance for your help
describe('your function to test', () => {
it('should have called a webextension API', () => {
yourFunctionToTest();
expect(chrome.tabs.update).toHaveBeenCalled();
});
HI @clarkbw firstly, thanks for this awesome repo, it help me a lots 👍
However, I encounter a bug maybe, following is my source code:
browser.storage.local.get({ apiErrReport: [] }, (result) => {
console.log(result);
const { apiErrReport } = result;
// ...
});
I pass an object to get
function, this is work well as usual, but when I started to use jest-webextension-mock
and wrote the test, I found the callback argument reuslt
become as:
There's a hardcode id
always in the root object
{ id: { apiErrReport: [] } }
But I think it is suppose to be a plain object without id
{ apiErrReport: [] } }
Any idea? BTW, I saw the source code and it always call the callback with { id }
, but I think:
if object given, the argument should be
const args = { ...id }
cb(args);
if string given, the argument should be
const args = {};
args[id] = ...;
cb(args);
I'm not sure, please let me know your idea. thanks
Regards,
Allen
There is no implementation at all for the browser.webRequest API.
Also since this API will be/is going through changes with MV3 (see here), it'll be awesome to be able to configure the mocking to be MV2/MV3 so we can write unit tests for these different scenarios.
Presently it's possible for test behavior and real code behavior to differ because changes to objects returned by chrome.storage.local.get can be "persistently" modified in functions that do not save the result to chrome.storage.
interface Thing {
array: string[]
}
function saveThing(thing: Thing) {
return new Promise((resolve) => {
chrome.storage.local.set({ thing: thing }, () => {
resolve(true)
})
})
}
function getThing(): Promise<Thing> {
return new Promise((resolve) => {
chrome.storage.local.get('thing', (data: { thing: Thing }) => {
resolve(data.thing)
})
})
}
function doSomethingWithThing(thing: Thing) {
thing.array = thing.array.map((string) => {
return 'bar'
})
}
describe('Thing should work', () => {
it('thing works', async () => {
await saveThing({ array: ['foo'] })
let thing = await getThing()
expect(thing.array[0]).toEqual('foo')
doSomethingWithThing(thing)
thing = await getThing()
expect(thing.array[0]).toEqual('foo')
})
})
Expected: "foo"
Received: "bar"
First of all thanks for making this library, it seems really promising!
I was trying to use this library in my extension and followed the instructions in the README but I keep getting this error when I run certain tests:
I think the issue is just that there are no mocks for this part of the API yet (correct me if I am wrong). Let me know if this is actually an issue or I am just misusing the library.
If it is just a case of missing support, I think we can add it. If you want I can make a PR to add these mocks, I looked over the existing source code and I think I understand how this library works.
Thanks!
Hi, I got a question about chrome.storage.*
.
So I have a class StorageManager
which is a very simple wrapper of chrome.storage.sync
's get
and set
methods.
This class works fine in a web extension context, but it doesn't in tests context.
Each time I try to get a value, it returns an empty string (https://github.com/clarkbw/jest-webextension-mock/blob/master/src/storage.js#L4).
So my question is, is this behaviour correct for you, or you didn't have the desire and/or time to write a better implementation?
If it's about desire/time, I can take on my own time to implement a more correct version of chrome.storage.sync
👍
Thanks
As in title, the API is not being mocked and it's not mentioned anywhre:
Documentation:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.