├── .babelrc ├── .eslintrc ├── .github ├── stale.yml └── workflows │ ├── publish.yml │ └── push.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __setups__ └── chrome.js ├── __tests__ ├── browserAction.test.js ├── commands.test.js ├── downloads.test.js ├── extension.test.js ├── geckoProfiler.test.js ├── i18n.test.js ├── notifications.test.js ├── omnibox.test.js ├── permissions.test.js ├── runtime.test.js ├── setup.test.js ├── storage.test.js ├── tabs.test.js └── webNavigation.test.js ├── dist └── setup.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── rollup.config.js └── src ├── browserAction.js ├── commands.js ├── downloads.js ├── extension.js ├── geckoProfiler.js ├── i18n.js ├── index.js ├── notifications.js ├── omnibox.js ├── permissions.js ├── runtime.js ├── setup.js ├── storage.js ├── tabs.js └── webNavigation.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["prettier"], 3 | "plugins": ["prettier"], 4 | "parserOptions": { 5 | "ecmaVersion": 2016, 6 | "sourceType": "module" 7 | }, 8 | "env": { 9 | "es6": true, 10 | "node": true 11 | }, 12 | "rules": { 13 | "prettier/prettier": ["error"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: true 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Publish 7 | 8 | jobs: 9 | all: 10 | name: Merge-Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: npm ci 15 | - run: npm test 16 | - run: npm run build --if-present 17 | - run: git status --porcelain dist/setup.js 18 | - uses: mikeal/merge-release@master 19 | if: github.ref == 'refs/heads/main' 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 2 21 | - name: node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm ci 26 | - run: npm test 27 | - run: npm run build --if-present 28 | - run: git status --porcelain dist/setup.js 29 | - name: check dist build 30 | run: | 31 | if ! git diff --quiet --exit-code dist/setup.js; then 32 | echo "::error run 'npm run build' and commit the dist/setup.js file" 33 | exit 1 34 | fi 35 | - uses: codecov/codecov-action@v3 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # IDE 40 | .idea 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.requireConfig": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Bryan Clark 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/jest-webextension-mock.svg)](https://www.npmjs.com/package/jest-webextension-mock) [![npm](https://img.shields.io/npm/l/jest-webextension-mock.svg)](https://github.com/clarkbw/jest-webextension-mock/blob/master/LICENSE) [![Codecov](https://img.shields.io/codecov/c/github/clarkbw/jest-webextension-mock.svg)](https://codecov.io/gh/clarkbw/jest-webextension-mock) [![Greenkeeper badge](https://badges.greenkeeper.io/clarkbw/jest-webextension-mock.svg)](https://greenkeeper.io/) [![Twitter](https://img.shields.io/twitter/url/https/github.com/clarkbw/jest-webextension-mock.svg?style=social)](https://twitter.com/intent/tweet?text=Wow:&url=%5Bobject%20Object%5D) 2 | 3 | 💪 @RickyMarou is an official maintainer. This change was made on `2024-04-12` as @clarkbw has not been able to devote sufficient time necessary for this project. 4 | 5 | ## Install 6 | 7 | For npm: 8 | 9 | ```bash 10 | npm i --save-dev jest-webextension-mock 11 | ``` 12 | 13 | For yarn: 14 | 15 | ```bash 16 | yarn add --dev jest-webextension-mock 17 | ``` 18 | 19 | ## Setup 20 | 21 | ### Require module directly 22 | 23 | In your `package.json` under the `jest` section add the `setupFiles` attribute with this module name. 24 | 25 | ```json 26 | "jest": { 27 | "setupFiles": [ 28 | "jest-webextension-mock" 29 | ] 30 | } 31 | ``` 32 | 33 | ### Use setup file 34 | 35 | Alternatively you can create a new setup file and require this module. 36 | 37 | `__setups__/chrome.js` 38 | ```js 39 | require('jest-webextension-mock'); 40 | ``` 41 | 42 | And add that file to your `setupFiles`: 43 | 44 | ```json 45 | "jest": { 46 | "setupFiles": [ 47 | "./__setups__/chrome.js" 48 | ] 49 | } 50 | ``` 51 | 52 | ## Usage 53 | 54 | Use this module to check that API calls were made when expected. 55 | 56 | ```js 57 | describe('your function to test', () => { 58 | it('should have called a webextension API', () => { 59 | yourFunctionToTest(); 60 | expect(chrome.tabs.update).toHaveBeenCalled(); 61 | }); 62 | }); 63 | ``` 64 | 65 | Check the API was called with certain parameters. 66 | 67 | ```js 68 | describe('your function to test', () => { 69 | it('should have called a webextension API', () => { 70 | yourFunctionToTest(); 71 | expect(chrome.tabs.update).toHaveBeenCalledWith({ 72 | url: 'https://example.com/' 73 | }); 74 | }); 75 | }); 76 | ``` 77 | 78 | And you can reset the API mocks to ensure APIs are only called when needed. 79 | 80 | ```js 81 | beforeEach(() => { 82 | browser.geckoProfiler.start.mockClear(); 83 | browser.geckoProfiler.stop.mockClear(); 84 | }); 85 | 86 | it('should toggle the profiler on from stopped', () => { 87 | const store = mockStore(reducer(undefined, {})); 88 | const expectedActions = [ 89 | { type: 'PROFILER_START', status: 'start' }, 90 | { type: 'PROFILER_START', status: 'done' }, 91 | ]; 92 | return store.dispatch(actions.toggle()).then(() => { 93 | expect(browser.geckoProfiler.start).toHaveBeenCalledTimes(1); 94 | expect(store.getActions()).toEqual(expectedActions); 95 | }); 96 | }); 97 | ``` 98 | 99 | ## Development 100 | 101 | ``` 102 | npm install 103 | npm test 104 | ``` 105 | 106 | ## Publish 107 | 108 | Publishing new releases is automated via the GitHub Action https://github.com/mikeal/merge-release tool. 109 | 110 | To ensure your feature is properly released prefix your commit message with `feat` for any new feature. For example: `feat: new API` and this will bump the minor release number. All other changes will be assumed as patch releases unless you include the string `BREAKING CHANGE` in your commit message or description which will trigger a new major release. (do not do this unless absolutely required) 111 | 112 | -------------------------------------------------------------------------------- /__setups__/chrome.js: -------------------------------------------------------------------------------- 1 | require('../src/setup'); 2 | -------------------------------------------------------------------------------- /__tests__/browserAction.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.browserAction', () => { 2 | test('setTitle', () => { 3 | expect(jest.isMockFunction(browser.browserAction.setTitle)).toBe(true); 4 | const title = 'TITLE'; 5 | browser.browserAction.setTitle(title); 6 | expect(browser.browserAction.setTitle).toHaveBeenCalledWith(title); 7 | }); 8 | test('getTitle', (done) => { 9 | const callback = jest.fn(() => done()); 10 | expect(jest.isMockFunction(browser.browserAction.getTitle)).toBe(true); 11 | browser.browserAction.getTitle({}, callback); 12 | expect(browser.browserAction.getTitle).toHaveBeenCalledTimes(1); 13 | expect(callback).toBeCalled(); 14 | }); 15 | test('getTitle promise', () => { 16 | return expect(browser.browserAction.getTitle({})).resolves.toBeUndefined(); 17 | }); 18 | 19 | test('setIcon', () => { 20 | expect(jest.isMockFunction(browser.browserAction.setIcon)).toBe(true); 21 | const details = { path: 'icon' }; 22 | browser.browserAction.setIcon(details); 23 | expect(browser.browserAction.setIcon).toHaveBeenCalledWith(details); 24 | }); 25 | 26 | test('setPopup', () => { 27 | expect(jest.isMockFunction(browser.browserAction.setPopup)).toBe(true); 28 | const details = { path: 'icon' }; 29 | browser.browserAction.setPopup(details); 30 | expect(browser.browserAction.setPopup).toHaveBeenCalledWith(details); 31 | }); 32 | test('getPopup', (done) => { 33 | const callback = jest.fn(() => done()); 34 | expect(jest.isMockFunction(browser.browserAction.getPopup)).toBe(true); 35 | browser.browserAction.getPopup({}, callback); 36 | expect(browser.browserAction.getPopup).toHaveBeenCalledTimes(1); 37 | expect(callback).toBeCalled(); 38 | }); 39 | test('getPopup promise', () => { 40 | return expect(browser.browserAction.getPopup({})).resolves.toBeUndefined(); 41 | }); 42 | 43 | test('setBadgeText', () => { 44 | expect(jest.isMockFunction(browser.browserAction.setBadgeText)).toBe(true); 45 | const text = 'BADGE TEXT'; 46 | browser.browserAction.setBadgeText(text); 47 | expect(browser.browserAction.setBadgeText).toHaveBeenCalledWith(text); 48 | }); 49 | test('getBadgeText', (done) => { 50 | const callback = jest.fn(() => done()); 51 | expect(jest.isMockFunction(browser.browserAction.getBadgeText)).toBe(true); 52 | browser.browserAction.getBadgeText({}, callback); 53 | expect(browser.browserAction.getBadgeText).toHaveBeenCalledTimes(1); 54 | expect(callback).toBeCalled(); 55 | }); 56 | test('getBadgeText promise', () => { 57 | return expect( 58 | browser.browserAction.getBadgeText({}) 59 | ).resolves.toBeUndefined(); 60 | }); 61 | 62 | test('setBadgeBackgroundColor', () => { 63 | expect( 64 | jest.isMockFunction(browser.browserAction.setBadgeBackgroundColor) 65 | ).toBe(true); 66 | const details = { color: 'red' }; 67 | browser.browserAction.setBadgeBackgroundColor(details); 68 | expect(browser.browserAction.setBadgeBackgroundColor).toHaveBeenCalledWith( 69 | details 70 | ); 71 | }); 72 | test('getBadgeBackgroundColor', (done) => { 73 | const callback = jest.fn(() => done()); 74 | expect( 75 | jest.isMockFunction(browser.browserAction.getBadgeBackgroundColor) 76 | ).toBe(true); 77 | browser.browserAction.getBadgeBackgroundColor({}, callback); 78 | expect(browser.browserAction.getBadgeBackgroundColor).toHaveBeenCalledTimes( 79 | 1 80 | ); 81 | expect(callback).toBeCalled(); 82 | }); 83 | test('getBadgeBackgroundColor promise', () => { 84 | return expect( 85 | browser.browserAction.getBadgeBackgroundColor({}) 86 | ).resolves.toBeUndefined(); 87 | }); 88 | 89 | test('enable', () => { 90 | expect(jest.isMockFunction(browser.browserAction.enable)).toBe(true); 91 | browser.browserAction.enable(); 92 | expect(browser.browserAction.enable).toHaveBeenCalled(); 93 | }); 94 | test('disable', () => { 95 | expect(jest.isMockFunction(browser.browserAction.disable)).toBe(true); 96 | browser.browserAction.disable(); 97 | expect(browser.browserAction.disable).toHaveBeenCalled(); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /__tests__/commands.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.commands', () => { 2 | test('getAll', (done) => { 3 | const callback = jest.fn(() => done()); 4 | expect(jest.isMockFunction(browser.commands.getAll)).toBe(true); 5 | browser.commands.getAll(callback); 6 | expect(browser.commands.getAll).toHaveBeenCalledTimes(1); 7 | expect(callback).toBeCalled(); 8 | }); 9 | test('getAll promise', () => { 10 | return expect(browser.commands.getAll()).resolves.toBeUndefined(); 11 | }); 12 | test('onCommand.addListener', () => { 13 | expect(jest.isMockFunction(browser.commands.onCommand.addListener)).toBe( 14 | true 15 | ); 16 | browser.commands.onCommand.addListener(() => {}); 17 | expect(browser.commands.onCommand.addListener).toHaveBeenCalledTimes(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /__tests__/downloads.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.downloads', () => { 2 | test('acceptDanger', (done) => { 3 | const downloadId = 1; 4 | const callback = jest.fn(() => done()); 5 | 6 | expect(jest.isMockFunction(chrome.downloads.acceptDanger)).toBe(true); 7 | chrome.downloads.acceptDanger(downloadId, callback); 8 | expect(chrome.downloads.acceptDanger).toHaveBeenCalledTimes(1); 9 | expect(chrome.downloads.acceptDanger).toHaveBeenLastCalledWith(downloadId, callback); 10 | expect(callback).toHaveBeenCalled(); 11 | }); 12 | test('acceptDanger Promise', () => { 13 | const downloadId = 1; 14 | 15 | expect(chrome.downloads.acceptDanger(downloadId)).resolves.toBeUndefined(); 16 | }); 17 | test('cancel', (done) => { 18 | const downloadId = 1; 19 | const callback = jest.fn(() => done()); 20 | 21 | expect(jest.isMockFunction(chrome.downloads.cancel)).toBe(true); 22 | chrome.downloads.cancel(downloadId, callback); 23 | expect(chrome.downloads.cancel).toHaveBeenCalledTimes(1); 24 | expect(chrome.downloads.cancel).toHaveBeenLastCalledWith(downloadId, callback); 25 | expect(callback).toHaveBeenCalled(); 26 | }); 27 | test('cancel Promise', () => { 28 | const downloadId = 1; 29 | 30 | expect(chrome.downloads.cancel(downloadId)).resolves.toBeUndefined(); 31 | }); 32 | test('download', (done) => { 33 | const options = {}; 34 | const callback = jest.fn(() => done()); 35 | 36 | expect(jest.isMockFunction(chrome.downloads.download)).toBe(true); 37 | chrome.downloads.download(options, callback); 38 | expect(chrome.downloads.download).toHaveBeenCalledTimes(1); 39 | expect(chrome.downloads.download).toHaveBeenLastCalledWith(options, callback); 40 | expect(callback).toHaveBeenCalled(); 41 | }); 42 | test('download Promise', () => { 43 | const options = {}; 44 | 45 | expect(chrome.downloads.download(options)).resolves.toBeUndefined(); 46 | }); 47 | test('erase', (done) => { 48 | const query = {}; 49 | const callback = jest.fn(() => done()); 50 | 51 | expect(jest.isMockFunction(chrome.downloads.erase)).toBe(true); 52 | chrome.downloads.erase(query, callback); 53 | expect(chrome.downloads.erase).toHaveBeenCalledTimes(1); 54 | expect(chrome.downloads.erase).toHaveBeenLastCalledWith(query, callback); 55 | expect(callback).toHaveBeenCalled(); 56 | }); 57 | test('erase Promise', () => { 58 | const query = {}; 59 | 60 | expect(chrome.downloads.erase(query)).resolves.toBeUndefined(); 61 | }); 62 | test('getFileIcon', (done) => { 63 | const downloadId = 1; 64 | const callback = jest.fn(() => done()); 65 | 66 | expect(jest.isMockFunction(chrome.downloads.getFileIcon)).toBe(true); 67 | chrome.downloads.getFileIcon(downloadId, callback); 68 | expect(chrome.downloads.getFileIcon).toHaveBeenCalledTimes(1); 69 | expect(chrome.downloads.getFileIcon).toHaveBeenLastCalledWith(downloadId, callback); 70 | expect(callback).toHaveBeenCalled(); 71 | }); 72 | test('getFileIcon Promise', () => { 73 | const downloadId = 1; 74 | 75 | expect(chrome.downloads.getFileIcon(downloadId)).resolves.toBeUndefined(); 76 | }); 77 | test('open', () => { 78 | const downloadId = 1; 79 | 80 | expect(jest.isMockFunction(chrome.downloads.open)).toBe(true); 81 | chrome.downloads.open(downloadId); 82 | expect(chrome.downloads.open).toHaveBeenCalledTimes(1); 83 | expect(chrome.downloads.open).toHaveBeenLastCalledWith(downloadId); 84 | }); 85 | test('pause', (done) => { 86 | const downloadId = 1; 87 | const callback = jest.fn(() => done()); 88 | 89 | expect(jest.isMockFunction(chrome.downloads.pause)).toBe(true); 90 | chrome.downloads.pause(downloadId, callback); 91 | expect(chrome.downloads.pause).toHaveBeenCalledTimes(1); 92 | expect(chrome.downloads.pause).toHaveBeenLastCalledWith(downloadId, callback); 93 | expect(callback).toHaveBeenCalled(); 94 | }); 95 | test('pause Promise', () => { 96 | const downloadId = 1; 97 | 98 | expect(chrome.downloads.pause(downloadId)).resolves.toBeUndefined(); 99 | }); 100 | test('removeFile', (done) => { 101 | const downloadId = 1; 102 | const callback = jest.fn(() => done()); 103 | 104 | expect(jest.isMockFunction(chrome.downloads.removeFile)).toBe(true); 105 | chrome.downloads.removeFile(downloadId, callback); 106 | expect(chrome.downloads.removeFile).toHaveBeenCalledTimes(1); 107 | expect(chrome.downloads.removeFile).toHaveBeenLastCalledWith(downloadId, callback); 108 | expect(callback).toHaveBeenCalled(); 109 | }); 110 | test('removeFile Promise', () => { 111 | const downloadId = 1; 112 | 113 | expect(chrome.downloads.removeFile(downloadId)).resolves.toBeUndefined(); 114 | }); 115 | test('resume', (done) => { 116 | const downloadId = 1; 117 | const callback = jest.fn(() => done()); 118 | 119 | expect(jest.isMockFunction(chrome.downloads.resume)).toBe(true); 120 | chrome.downloads.resume(downloadId, callback); 121 | expect(chrome.downloads.resume).toHaveBeenCalledTimes(1); 122 | expect(chrome.downloads.resume).toHaveBeenLastCalledWith(downloadId, callback); 123 | expect(callback).toHaveBeenCalled(); 124 | }); 125 | test('resume Promise', () => { 126 | const downloadId = 1; 127 | 128 | expect(chrome.downloads.resume(downloadId)).resolves.toBeUndefined(); 129 | }); 130 | test('search', (done) => { 131 | const query = {}; 132 | const callback = jest.fn(() => done()); 133 | 134 | expect(jest.isMockFunction(chrome.downloads.search)).toBe(true); 135 | chrome.downloads.search(query, callback); 136 | expect(chrome.downloads.search).toHaveBeenCalledTimes(1); 137 | expect(chrome.downloads.search).toHaveBeenLastCalledWith(query, callback); 138 | expect(callback).toHaveBeenCalled(); 139 | }); 140 | test('search Promise', () => { 141 | const query = {}; 142 | 143 | expect(chrome.downloads.search(query)).resolves.toBeUndefined(); 144 | }); 145 | test('setShelfEnabled', () => { 146 | expect(jest.isMockFunction(chrome.downloads.setShelfEnabled)).toBe(true); 147 | chrome.downloads.setShelfEnabled(true); 148 | expect(chrome.downloads.setShelfEnabled).toHaveBeenCalledTimes(1); 149 | expect(chrome.downloads.setShelfEnabled).toHaveBeenLastCalledWith(true); 150 | }); 151 | test('show', () => { 152 | const downloadId = 1; 153 | 154 | expect(jest.isMockFunction(chrome.downloads.show)).toBe(true); 155 | chrome.downloads.show(downloadId); 156 | expect(chrome.downloads.show).toHaveBeenCalledTimes(1); 157 | expect(chrome.downloads.show).toHaveBeenLastCalledWith(downloadId); 158 | }); 159 | test('showDefaultFolder', () => { 160 | expect(jest.isMockFunction(chrome.downloads.showDefaultFolder)).toBe(true); 161 | chrome.downloads.showDefaultFolder(); 162 | expect(chrome.downloads.showDefaultFolder).toHaveBeenCalledTimes(1); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /__tests__/extension.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.extension', () => { 2 | test('getURL', () => { 3 | expect(jest.isMockFunction(chrome.extension.getURL)).toBe(true); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /__tests__/geckoProfiler.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.geckoProfiler', () => { 2 | test('stop', () => { 3 | expect(jest.isMockFunction(browser.geckoProfiler.stop)).toBe(true); 4 | browser.geckoProfiler.stop(); 5 | expect(browser.geckoProfiler.stop).toHaveBeenCalledTimes(1); 6 | }); 7 | test('stop promise', () => { 8 | return expect(browser.geckoProfiler.stop()).resolves.toBeUndefined(); 9 | }); 10 | test('start', () => { 11 | expect(jest.isMockFunction(browser.geckoProfiler.start)).toBe(true); 12 | browser.geckoProfiler.start(); 13 | expect(browser.geckoProfiler.start).toHaveBeenCalledTimes(1); 14 | }); 15 | test('start promise', () => { 16 | return expect(browser.geckoProfiler.start()).resolves.toBeUndefined(); 17 | }); 18 | test('pause', () => { 19 | expect(jest.isMockFunction(browser.geckoProfiler.pause)).toBe(true); 20 | browser.geckoProfiler.pause(); 21 | expect(browser.geckoProfiler.pause).toHaveBeenCalledTimes(1); 22 | }); 23 | test('pause promise', () => { 24 | return expect(browser.geckoProfiler.pause()).resolves.toBeUndefined(); 25 | }); 26 | test('resume', () => { 27 | expect(jest.isMockFunction(browser.geckoProfiler.resume)).toBe(true); 28 | browser.geckoProfiler.resume(); 29 | expect(browser.geckoProfiler.resume).toHaveBeenCalledTimes(1); 30 | }); 31 | test('resume promise', () => { 32 | return expect(browser.geckoProfiler.resume()).resolves.toBeUndefined(); 33 | }); 34 | test('getProfile', () => { 35 | expect(jest.isMockFunction(browser.geckoProfiler.getProfile)).toBe(true); 36 | browser.geckoProfiler.getProfile(); 37 | expect(browser.geckoProfiler.getProfile).toHaveBeenCalledTimes(1); 38 | }); 39 | test('getProfile promise', () => { 40 | return expect(browser.geckoProfiler.getProfile()).resolves.toBeUndefined(); 41 | }); 42 | test('getProfileAsArrayBuffer', () => { 43 | expect( 44 | jest.isMockFunction(browser.geckoProfiler.getProfileAsArrayBuffer) 45 | ).toBe(true); 46 | browser.geckoProfiler.getProfileAsArrayBuffer(); 47 | expect(browser.geckoProfiler.getProfileAsArrayBuffer).toHaveBeenCalledTimes( 48 | 1 49 | ); 50 | }); 51 | test('getProfileAsArrayBuffer promise', () => { 52 | return expect( 53 | browser.geckoProfiler.getProfileAsArrayBuffer() 54 | ).resolves.toBeUndefined(); 55 | }); 56 | test('getSymbols', () => { 57 | expect(jest.isMockFunction(browser.geckoProfiler.getSymbols)).toBe(true); 58 | browser.geckoProfiler.getSymbols('NAME', 'ID'); 59 | expect(browser.geckoProfiler.getSymbols).toHaveBeenCalledTimes(1); 60 | }); 61 | test('getSymbols promise', () => { 62 | return expect(browser.geckoProfiler.getSymbols()).resolves.toBeUndefined(); 63 | }); 64 | test('onRunning.addListener', () => { 65 | expect( 66 | jest.isMockFunction(browser.geckoProfiler.onRunning.addListener) 67 | ).toBe(true); 68 | browser.geckoProfiler.onRunning.addListener(() => {}); 69 | expect(browser.geckoProfiler.onRunning.addListener).toHaveBeenCalledTimes( 70 | 1 71 | ); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /__tests__/i18n.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.i18n', () => { 2 | test('getMessage', () => { 3 | return expect(browser.i18n.getMessage('keyToTranslate')).toBe( 4 | 'Translated' 5 | ); 6 | }); 7 | 8 | test('getUILanguage', () => { 9 | return expect(browser.i18n.getUILanguage()).toBe('en'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /__tests__/notifications.test.js: -------------------------------------------------------------------------------- 1 | describe('chrome.notifications', () => { 2 | test('create', (done) => { 3 | const options = { type: 'basic' }; 4 | const callback = jest.fn(() => done()); 5 | 6 | expect(jest.isMockFunction(chrome.notifications.create)).toBe(true); 7 | 8 | chrome.notifications.create(options, callback); 9 | expect(chrome.notifications.create).toHaveBeenCalledTimes(1); 10 | expect(chrome.notifications.create).toHaveBeenLastCalledWith( 11 | options, 12 | callback 13 | ); 14 | expect(callback).toHaveBeenLastCalledWith('generated-id'); 15 | 16 | chrome.notifications.create('id', options, callback); 17 | expect(chrome.notifications.create).toHaveBeenCalledTimes(2); 18 | expect(chrome.notifications.create).toHaveBeenLastCalledWith( 19 | 'id', 20 | options, 21 | callback 22 | ); 23 | expect(callback).toHaveBeenLastCalledWith('id'); 24 | }); 25 | 26 | test('create promise', () => { 27 | const options = { type: 'basic' }; 28 | 29 | return expect(chrome.notifications.create(options)).resolves.toBe( 30 | 'generated-id' 31 | ); 32 | }); 33 | 34 | test('update', (done) => { 35 | const options = { type: 'basic' }; 36 | const callback = jest.fn(() => done()); 37 | 38 | expect(jest.isMockFunction(chrome.notifications.update)).toBe(true); 39 | chrome.notifications.update('id', options, callback); 40 | expect(chrome.notifications.update).toHaveBeenCalledTimes(1); 41 | expect(chrome.notifications.update).toHaveBeenLastCalledWith( 42 | 'id', 43 | options, 44 | callback 45 | ); 46 | expect(callback).toHaveBeenLastCalledWith(true); 47 | }); 48 | 49 | test('update promise', () => { 50 | const options = { type: 'basic' }; 51 | 52 | return expect(chrome.notifications.update(options)).resolves.toBe(true); 53 | }); 54 | 55 | test('clear', (done) => { 56 | const callback = jest.fn(() => done()); 57 | 58 | expect(jest.isMockFunction(chrome.notifications.clear)).toBe(true); 59 | chrome.notifications.clear('id', callback); 60 | expect(chrome.notifications.clear).toHaveBeenCalledTimes(1); 61 | expect(chrome.notifications.clear).toHaveBeenLastCalledWith('id', callback); 62 | expect(callback).toHaveBeenLastCalledWith(true); 63 | }); 64 | 65 | test('clear - promise', () => { 66 | return expect(chrome.notifications.clear('id')).resolves.toBe(true); 67 | }); 68 | 69 | test('getAll', (done) => { 70 | const callback = jest.fn(() => done()); 71 | 72 | expect(jest.isMockFunction(chrome.notifications.getAll)).toBe(true); 73 | chrome.notifications.getAll(callback); 74 | expect(chrome.notifications.getAll).toHaveBeenCalledTimes(1); 75 | expect(chrome.notifications.getAll).toHaveBeenLastCalledWith(callback); 76 | expect(callback).toHaveBeenLastCalledWith([]); 77 | }); 78 | 79 | test('getAll - promise', () => { 80 | return expect(chrome.notifications.getAll()).resolves.toEqual([]); 81 | }); 82 | 83 | test('getPermissionLevel', (done) => { 84 | const callback = jest.fn(() => done()); 85 | 86 | expect(jest.isMockFunction(chrome.notifications.getPermissionLevel)).toBe( 87 | true 88 | ); 89 | chrome.notifications.getPermissionLevel(callback); 90 | expect(chrome.notifications.getPermissionLevel).toHaveBeenCalledTimes(1); 91 | expect(chrome.notifications.getPermissionLevel).toHaveBeenLastCalledWith( 92 | callback 93 | ); 94 | expect(callback).toHaveBeenLastCalledWith('granted'); 95 | }); 96 | 97 | test('getPermissionLevel - promise', () => { 98 | return expect(chrome.notifications.getPermissionLevel()).resolves.toEqual( 99 | 'granted' 100 | ); 101 | }); 102 | 103 | test('onClosed.addListener', () => { 104 | expect(jest.isMockFunction(chrome.notifications.onClosed.addListener)).toBe( 105 | true 106 | ); 107 | 108 | chrome.notifications.onClosed.addListener(() => {}); 109 | expect(chrome.notifications.onClosed.addListener).toHaveBeenCalledTimes(1); 110 | }); 111 | 112 | test('onClicked.addListener', () => { 113 | expect( 114 | jest.isMockFunction(chrome.notifications.onClicked.addListener) 115 | ).toBe(true); 116 | 117 | chrome.notifications.onClicked.addListener(() => {}); 118 | expect(chrome.notifications.onClicked.addListener).toHaveBeenCalledTimes(1); 119 | }); 120 | 121 | test('onButtonClicked.addListener', () => { 122 | expect( 123 | jest.isMockFunction(chrome.notifications.onButtonClicked.addListener) 124 | ).toBe(true); 125 | 126 | chrome.notifications.onButtonClicked.addListener(() => {}); 127 | expect( 128 | chrome.notifications.onButtonClicked.addListener 129 | ).toHaveBeenCalledTimes(1); 130 | }); 131 | 132 | test('onPermissionLevelChanged.addListener', () => { 133 | expect( 134 | jest.isMockFunction( 135 | chrome.notifications.onPermissionLevelChanged.addListener 136 | ) 137 | ).toBe(true); 138 | 139 | chrome.notifications.onPermissionLevelChanged.addListener(() => {}); 140 | expect( 141 | chrome.notifications.onPermissionLevelChanged.addListener 142 | ).toHaveBeenCalledTimes(1); 143 | }); 144 | 145 | test('onShowSettings.addListener', () => { 146 | expect( 147 | jest.isMockFunction(chrome.notifications.onShowSettings.addListener) 148 | ).toBe(true); 149 | 150 | chrome.notifications.onShowSettings.addListener(() => {}); 151 | expect( 152 | chrome.notifications.onShowSettings.addListener 153 | ).toHaveBeenCalledTimes(1); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /__tests__/omnibox.test.js: -------------------------------------------------------------------------------- 1 | test('chrome.omnibox.setDefaultSuggestion', () => { 2 | expect(jest.isMockFunction(chrome.omnibox.setDefaultSuggestion)).toBe(true); 3 | chrome.omnibox.setDefaultSuggestion({ description: 'mocking things' }); 4 | expect(chrome.omnibox.setDefaultSuggestion).toBeCalled(); 5 | }); 6 | 7 | test('chrome.omnibox.onInputStarted.addListener', () => { 8 | expect(jest.isMockFunction(chrome.omnibox.onInputStarted.addListener)).toBe( 9 | true 10 | ); 11 | chrome.omnibox.onInputStarted.addListener(() => {}); 12 | expect(chrome.omnibox.onInputStarted.addListener).toBeCalled(); 13 | }); 14 | 15 | test('chrome.omnibox.onInputChanged.addListener', () => { 16 | expect(jest.isMockFunction(chrome.omnibox.onInputChanged.addListener)).toBe( 17 | true 18 | ); 19 | chrome.omnibox.onInputChanged.addListener(() => {}); 20 | expect(chrome.omnibox.onInputStarted.addListener).toBeCalled(); 21 | }); 22 | 23 | test('chrome.omnibox.onInputEntered.addListener', () => { 24 | expect(jest.isMockFunction(chrome.omnibox.onInputEntered.addListener)).toBe( 25 | true 26 | ); 27 | chrome.omnibox.onInputEntered.addListener(() => {}); 28 | expect(chrome.omnibox.onInputEntered.addListener).toBeCalled(); 29 | }); 30 | -------------------------------------------------------------------------------- /__tests__/permissions.test.js: -------------------------------------------------------------------------------- 1 | test('chrome.permissions.contains', () => { 2 | expect(jest.isMockFunction(chrome.permissions.contains)).toBe(true); 3 | chrome.permissions.contains({ permissions: ['tabs'] }, () => {}); 4 | expect(chrome.permissions.contains).toBeCalled(); 5 | }); 6 | 7 | test('chrome.permissions.getAll', () => { 8 | expect(jest.isMockFunction(chrome.permissions.getAll)).toBe(true); 9 | chrome.permissions.getAll(() => {}); 10 | expect(chrome.permissions.getAll).toBeCalled(); 11 | }); 12 | 13 | test('chrome.permissions.remove', () => { 14 | expect(jest.isMockFunction(chrome.permissions.remove)).toBe(true); 15 | chrome.permissions.remove({ permissions: ['tabs'] }, () => {}); 16 | expect(chrome.permissions.remove).toBeCalled(); 17 | }); 18 | 19 | test('chrome.permissions.request', () => { 20 | expect(jest.isMockFunction(chrome.permissions.request)).toBe(true); 21 | chrome.permissions.request({ permissions: ['tabs'] }, () => {}); 22 | expect(chrome.permissions.request).toBeCalled(); 23 | }); 24 | 25 | test('chrome.permissions.onAdded.addListener', () => { 26 | expect(jest.isMockFunction(chrome.permissions.onAdded.addListener)).toBe( 27 | true 28 | ); 29 | chrome.permissions.onAdded.addListener(() => {}); 30 | expect(chrome.permissions.onAdded.addListener).toBeCalled(); 31 | }); 32 | 33 | test('chrome.permissions.onRemoved.addListener', () => { 34 | expect(jest.isMockFunction(chrome.permissions.onRemoved.addListener)).toBe( 35 | true 36 | ); 37 | chrome.permissions.onRemoved.addListener(() => {}); 38 | expect(chrome.permissions.onRemoved.addListener).toBeCalled(); 39 | }); 40 | -------------------------------------------------------------------------------- /__tests__/runtime.test.js: -------------------------------------------------------------------------------- 1 | beforeEach(() => { 2 | browser.runtime.sendMessage.mockClear(); 3 | browser.runtime.onMessage.addListener.mockClear(); 4 | browser.runtime.onMessage.removeListener.mockClear(); 5 | browser.runtime.onMessage.hasListener.mockClear(); 6 | browser.runtime.onMessageExternal.addListener.mockClear(); 7 | browser.runtime.onMessageExternal.removeListener.mockClear(); 8 | browser.runtime.onMessageExternal.hasListener.mockClear(); 9 | }); 10 | 11 | describe('browser.runtime', () => { 12 | test('connect', () => { 13 | const name = 'CONNECT_NAME'; 14 | expect(jest.isMockFunction(browser.runtime.connect)).toBe(true); 15 | const connection = browser.runtime.connect({ name }); 16 | expect(connection.name).toEqual(name); 17 | expect(jest.isMockFunction(connection.postMessage)).toBe(true); 18 | expect(jest.isMockFunction(connection.onDisconnect.addListener)).toBe(true); 19 | expect(jest.isMockFunction(connection.onMessage.addListener)).toBe(true); 20 | expect(jest.isMockFunction(connection.disconnect)).toBe(true); 21 | expect(browser.runtime.connect).toHaveBeenCalledTimes(1); 22 | }); 23 | test('connect.onMessage listener', (done) => { 24 | const name = 'CONNECT_NAME'; 25 | const listener = jest.fn(); 26 | browser.runtime.connect(name).onMessage.addListener(listener); 27 | browser.runtime.sendMessage({ test: 'message' }, done); 28 | expect(listener).toHaveBeenCalledWith({ test: 'message' }); 29 | }); 30 | test('getURL', () => { 31 | const path = 'TEST_PATH'; 32 | const extensionOriginURL = globalThis[Symbol.for('jest-webextension-mock')].extensionPath; 33 | expect(jest.isMockFunction(browser.runtime.getURL)).toBe(true); 34 | const respPath = browser.runtime.getURL(path); 35 | expect(respPath).toEqual(extensionOriginURL + path); 36 | expect(browser.runtime.getURL).toHaveBeenCalledTimes(1); 37 | }); 38 | test('sendMessage', (done) => { 39 | const callback = jest.fn(() => done()); 40 | expect(jest.isMockFunction(browser.runtime.sendMessage)).toBe(true); 41 | browser.runtime.sendMessage({ test: 'message' }, callback); 42 | expect(browser.runtime.sendMessage).toHaveBeenCalledTimes(1); 43 | expect(callback).toHaveBeenCalledTimes(1); 44 | browser.runtime.sendMessage({ test: 'message' }); 45 | expect(browser.runtime.sendMessage).toHaveBeenCalledTimes(2); 46 | }); 47 | test('sendMessage listener', (done) => { 48 | const listener = jest.fn(); 49 | browser.runtime.onMessage.addListener(listener); 50 | browser.runtime.sendMessage({ test: 'message' }, done); 51 | expect(listener).toHaveBeenCalledWith({ test: 'message' }); 52 | }); 53 | test('sendMessage promise', () => { 54 | return expect(browser.runtime.sendMessage({})).resolves.toBeUndefined(); 55 | }); 56 | test(`onMessage.addListener`, () => { 57 | const callback = jest.fn(); 58 | expect(jest.isMockFunction(browser.runtime.onMessage.addListener)).toBe( 59 | true 60 | ); 61 | browser.runtime.onMessage.addListener(callback); 62 | expect(browser.runtime.onMessage.addListener).toHaveBeenCalledTimes(1); 63 | expect(callback).toHaveBeenCalledTimes(0); 64 | }); 65 | test(`onMessage.removeListener`, () => { 66 | const callback = jest.fn(); 67 | expect(jest.isMockFunction(browser.runtime.onMessage.removeListener)).toBe( 68 | true 69 | ); 70 | browser.runtime.onMessage.removeListener(callback); 71 | expect(browser.runtime.onMessage.hasListener(callback)).toBe(false); 72 | expect(browser.runtime.onMessage.removeListener).toHaveBeenCalledTimes(1); 73 | expect(callback).toHaveBeenCalledTimes(0); 74 | }); 75 | test(`onMessage.hasListener`, () => { 76 | const callback = jest.fn(); 77 | expect(jest.isMockFunction(browser.runtime.onMessage.hasListener)).toBe( 78 | true 79 | ); 80 | browser.runtime.onMessage.addListener(callback); 81 | const returnVal = browser.runtime.onMessage.hasListener(callback); 82 | expect(returnVal).toBe(true); 83 | expect(browser.runtime.onMessage.hasListener).toHaveBeenCalledTimes(1); 84 | expect(callback).toHaveBeenCalledTimes(0); 85 | }); 86 | test(`onMessageExternal.addListener`, () => { 87 | const callback = jest.fn(); 88 | expect(jest.isMockFunction(browser.runtime.onMessageExternal.addListener)).toBe( 89 | true 90 | ); 91 | browser.runtime.onMessageExternal.addListener(callback); 92 | expect(browser.runtime.onMessageExternal.addListener).toHaveBeenCalledTimes(1); 93 | expect(callback).toHaveBeenCalledTimes(0); 94 | }); 95 | test(`onMessageExternal.removeListener`, () => { 96 | const callback = jest.fn(); 97 | expect(jest.isMockFunction(browser.runtime.onMessageExternal.removeListener)).toBe( 98 | true 99 | ); 100 | browser.runtime.onMessageExternal.removeListener(callback); 101 | expect(browser.runtime.onMessageExternal.hasListener(callback)).toBe(false); 102 | expect(browser.runtime.onMessageExternal.removeListener).toHaveBeenCalledTimes(1); 103 | expect(callback).toHaveBeenCalledTimes(0); 104 | }); 105 | test(`onMessageExternal.hasListener`, () => { 106 | const callback = jest.fn(); 107 | expect(jest.isMockFunction(browser.runtime.onMessageExternal.hasListener)).toBe( 108 | true 109 | ); 110 | browser.runtime.onMessageExternal.addListener(callback); 111 | const returnVal = browser.runtime.onMessageExternal.hasListener(callback); 112 | expect(returnVal).toBe(true); 113 | expect(browser.runtime.onMessageExternal.hasListener).toHaveBeenCalledTimes(1); 114 | expect(callback).toHaveBeenCalledTimes(0); 115 | }); 116 | ['addListener', 'removeListener', 'hasListener'].forEach((method) => { 117 | test(`onConnect.${method}`, () => { 118 | const callback = jest.fn(); 119 | expect(jest.isMockFunction(browser.runtime.onConnect[method])).toBe(true); 120 | browser.runtime.onConnect[method](callback); 121 | expect(browser.runtime.onConnect[method]).toHaveBeenCalledTimes(1); 122 | expect(callback).toHaveBeenCalledTimes(0); 123 | }); 124 | }); 125 | ['addListener', 'removeListener', 'hasListener'].forEach((method) => { 126 | test(`onInstalled.${method}`, () => { 127 | const callback = jest.fn(); 128 | expect(jest.isMockFunction(browser.runtime.onInstalled[method])).toBe( 129 | true 130 | ); 131 | browser.runtime.onInstalled[method](callback); 132 | expect(browser.runtime.onInstalled[method]).toHaveBeenCalledTimes(1); 133 | expect(callback).toHaveBeenCalledTimes(0); 134 | }); 135 | }); 136 | test('openOptionsPage', () => { 137 | expect(jest.isMockFunction(browser.runtime.openOptionsPage)).toBe(true); 138 | browser.runtime.openOptionsPage(); 139 | expect(browser.runtime.openOptionsPage).toHaveBeenCalledTimes(1); 140 | }); 141 | test('getManifest', () => { 142 | expect(jest.isMockFunction(browser.runtime.getManifest)).toBe(true); 143 | expect(browser.runtime.getManifest()).toEqual({ manifest_version: 3 }); 144 | expect(browser.runtime.getManifest).toHaveBeenCalledTimes(1); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /__tests__/setup.test.js: -------------------------------------------------------------------------------- 1 | describe('globals', () => { 2 | test('browser', () => { 3 | expect(browser).toBe(chrome); 4 | }); 5 | test('chrome', () => { 6 | expect(chrome).toBe(browser); 7 | }); 8 | }); 9 | 10 | describe('globals for firefox', () => { 11 | test('exportFunction', () => { 12 | const func = () => {}; 13 | expect(jest.isMockFunction(exportFunction)).toBe(true); 14 | expect(exportFunction(func)).toBe(func); 15 | }); 16 | test('cloneInto', () => { 17 | const obj = {}; 18 | expect(jest.isMockFunction(cloneInto)).toBe(true); 19 | expect(cloneInto(obj)).toBe(obj); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/storage.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.storage', () => { 2 | ['addListener', 'removeListener', 'hasListener'].forEach((method) => { 3 | test(`onChanged.${method}`, () => { 4 | const callback = jest.fn(); 5 | expect(jest.isMockFunction(browser.storage.onChanged[method])).toBe(true); 6 | browser.storage.onChanged[method](callback); 7 | expect(browser.storage.onChanged[method]).toHaveBeenCalledTimes(1); 8 | expect(callback).toHaveBeenCalledTimes(0); 9 | }); 10 | }); 11 | }); 12 | 13 | describe('browser.storage', () => { 14 | ['sync', 'local', 'managed'].forEach((type) => { 15 | describe(type, () => { 16 | const storage = browser.storage[type]; 17 | describe('get', () => { 18 | expect(jest.isMockFunction(storage.get)).toBe(true); 19 | test('a string key', (done) => { 20 | const key = 'test'; 21 | storage.get(key, (result) => { 22 | expect(result).toBeDefined(); 23 | expect(typeof result === 'object').toBeTruthy(); 24 | expect(result).toHaveProperty(key, undefined); 25 | done(); 26 | }); 27 | }); 28 | test('an array key', (done) => { 29 | const keys = ['test1', 'test2']; 30 | storage.get(keys, (result) => { 31 | expect(result).toBeDefined(); 32 | expect(typeof result === 'object').toBeTruthy(); 33 | keys.forEach((k) => { 34 | expect(result).toHaveProperty(k, undefined); 35 | }); 36 | done(); 37 | }); 38 | }); 39 | test('an object key', (done) => { 40 | const key = { test: [] }; 41 | storage.get(key, (result) => { 42 | expect(result).toBeDefined(); 43 | expect(typeof result === 'object').toBeTruthy(); 44 | Object.keys(key).forEach((k) => { 45 | expect(result).toHaveProperty(k); 46 | expect(result[k]).toEqual(key[k]); 47 | }); 48 | done(); 49 | }); 50 | }); 51 | test('a invalid key', () => { 52 | try { 53 | storage.get(1, jest.fn()); 54 | } catch (e) { 55 | expect(e.message).toBe('Wrong key given'); 56 | } 57 | }); 58 | afterEach(() => { 59 | expect(storage.get).toHaveBeenCalledTimes(1); 60 | storage.clear(); 61 | storage.get.mockClear(); 62 | storage.set.mockClear(); 63 | storage.remove.mockClear(); 64 | storage.clear.mockClear(); 65 | }); 66 | }); 67 | test('get promise', () => { 68 | const key = 'key'; 69 | return expect(storage.get(key)).resolves.toEqual({ key: undefined }); 70 | }); 71 | test('getBytesInUse', (done) => { 72 | const callback = jest.fn(() => done()); 73 | expect(jest.isMockFunction(storage.getBytesInUse)).toBe(true); 74 | storage.getBytesInUse('key', callback); 75 | expect(storage.getBytesInUse).toHaveBeenCalledTimes(1); 76 | expect(callback).toBeCalled(); 77 | }); 78 | test('getBytesInUse promise', () => { 79 | return expect(storage.getBytesInUse('key')).resolves.toBe(0); 80 | }); 81 | test('set', (done) => { 82 | const callback = jest.fn(() => done()); 83 | expect(jest.isMockFunction(storage.set)).toBe(true); 84 | storage.set({ key: 'foo' }, callback); 85 | expect(storage.set).toHaveBeenCalledTimes(1); 86 | expect(callback).toBeCalled(); 87 | }); 88 | test('set promise', () => { 89 | return expect(storage.set(1)).resolves.toBeUndefined(); 90 | }); 91 | test('remove', (done) => { 92 | const callback = jest.fn(() => done()); 93 | expect(jest.isMockFunction(storage.remove)).toBe(true); 94 | storage.remove('key', callback); 95 | expect(storage.remove).toHaveBeenCalledTimes(1); 96 | expect(callback).toBeCalled(); 97 | }); 98 | test('remove promise', () => { 99 | return expect(storage.remove(['foo', 'bar'])).resolves.toBeUndefined(); 100 | }); 101 | test('clear', (done) => { 102 | const callback = jest.fn(() => done()); 103 | expect(jest.isMockFunction(browser.storage.sync.clear)).toBe(true); 104 | storage.clear(callback); 105 | expect(storage.clear).toHaveBeenCalledTimes(1); 106 | expect(callback).toBeCalled(); 107 | }); 108 | test('clear promise', () => { 109 | return expect(storage.clear()).resolves.toBeUndefined(); 110 | }); 111 | test('real scenario', (done) => { 112 | expect(jest.isMockFunction(storage.get)).toBe(true); 113 | expect(jest.isMockFunction(storage.set)).toBe(true); 114 | expect(jest.isMockFunction(storage.remove)).toBe(true); 115 | expect(jest.isMockFunction(storage.clear)).toBe(true); 116 | // set keys 117 | storage.set({ key: 'value', foo: 'bar', foo2: 'bar2' }, () => { 118 | // get 'key' 119 | storage.get(['key'], (result) => { 120 | expect(result).toBeDefined(); 121 | expect(typeof result === 'object').toBeTruthy(); 122 | expect(result).toHaveProperty('key', 'value'); 123 | expect(result).not.toHaveProperty('foo'); 124 | expect(result).not.toHaveProperty('foo2'); 125 | // remove 'key' 126 | storage.remove('key', () => { 127 | // get all values 128 | storage.get(null, (result) => { 129 | expect(result).toHaveProperty('key', undefined); 130 | expect(result).toHaveProperty('foo', 'bar'); 131 | expect(result).toHaveProperty('foo2', 'bar2'); 132 | // clear values 133 | storage.clear(() => { 134 | storage.get(['key', 'foo', 'foo2'], (result) => { 135 | expect(result).toHaveProperty('key', undefined); 136 | expect(result).toHaveProperty('foo', undefined); 137 | expect(result).toHaveProperty('foo2', undefined); 138 | done(); 139 | }); 140 | }); 141 | }); 142 | }); 143 | }); 144 | }); 145 | }); 146 | }); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /__tests__/tabs.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.tabs', () => { 2 | test('get', (done) => { 3 | const callback = jest.fn(() => done()); 4 | expect(jest.isMockFunction(chrome.tabs.get)).toBe(true); 5 | chrome.tabs.get(1, callback); 6 | expect(chrome.tabs.get).toHaveBeenCalledTimes(1); 7 | expect(callback).toHaveBeenCalledTimes(1); 8 | }); 9 | test('getCurrent', (done) => { 10 | const callback = jest.fn(() => done()); 11 | expect(jest.isMockFunction(chrome.tabs.getCurrent)).toBe(true); 12 | chrome.tabs.getCurrent(callback); 13 | expect(chrome.tabs.getCurrent).toHaveBeenCalledTimes(1); 14 | expect(callback).toHaveBeenCalledTimes(1); 15 | }); 16 | test('connect', () => { 17 | const name = 'CONNECT_NAME'; 18 | expect(jest.isMockFunction(chrome.tabs.connect)).toBe(true); 19 | const connection = chrome.tabs.connect(1, { name }); 20 | expect(connection.name).toEqual(name); 21 | expect(jest.isMockFunction(connection.disconnect)).toBe(true); 22 | expect(jest.isMockFunction(connection.postMessage)).toBe(true); 23 | expect(jest.isMockFunction(connection.onDisconnect.addListener)).toBe(true); 24 | expect(jest.isMockFunction(connection.onMessage.addListener)).toBe(true); 25 | expect(chrome.tabs.connect).toHaveBeenCalledTimes(1); 26 | }); 27 | test('create', (done) => { 28 | const callback = jest.fn(() => done()); 29 | const props = { pinned: true }; 30 | expect(jest.isMockFunction(chrome.tabs.create)).toBe(true); 31 | chrome.tabs.create(props, callback); 32 | expect(chrome.tabs.create).toHaveBeenCalledTimes(1); 33 | expect(callback).toHaveBeenCalledTimes(1); 34 | expect(callback).toHaveBeenCalledWith(props); 35 | }); 36 | test('create promise', () => { 37 | const props = { pinned: true }; 38 | return expect(chrome.tabs.create(props)).resolves.toBe(props); 39 | }); 40 | test('duplicate', (done) => { 41 | const callback = jest.fn(() => done()); 42 | expect(jest.isMockFunction(chrome.tabs.duplicate)).toBe(true); 43 | chrome.tabs.duplicate(1, callback); 44 | expect(chrome.tabs.duplicate).toHaveBeenCalledTimes(1); 45 | expect(callback).toHaveBeenCalledTimes(1); 46 | expect(callback).toHaveBeenCalledWith({ id: 1 }); 47 | }); 48 | test('remove', (done) => { 49 | const callback = jest.fn(() => done()); 50 | expect(jest.isMockFunction(chrome.tabs.remove)).toBe(true); 51 | chrome.tabs.remove([1], callback); 52 | expect(chrome.tabs.remove).toHaveBeenCalledTimes(1); 53 | expect(callback).toHaveBeenCalledTimes(1); 54 | }); 55 | test('remove promise', () => { 56 | expect(chrome.tabs.remove([1])).resolves.toBeUndefined(); 57 | }); 58 | test('query', (done) => { 59 | const callback = jest.fn(() => done()); 60 | expect(jest.isMockFunction(chrome.tabs.query)).toBe(true); 61 | chrome.tabs.query({ pinned: true }, callback); 62 | expect(chrome.tabs.query).toHaveBeenCalledTimes(1); 63 | expect(callback).toHaveBeenCalledTimes(1); 64 | expect(callback).toHaveBeenCalledWith([{}]); 65 | }); 66 | test('highlight', (done) => { 67 | const callback = jest.fn(() => done()); 68 | expect(jest.isMockFunction(chrome.tabs.highlight)).toBe(true); 69 | chrome.tabs.highlight({}, callback); 70 | expect(chrome.tabs.highlight).toHaveBeenCalledTimes(1); 71 | expect(callback).toHaveBeenCalledTimes(1); 72 | }); 73 | test('update', (done) => { 74 | const callback = jest.fn(() => done()); 75 | const props = { pinned: true }; 76 | expect(jest.isMockFunction(chrome.tabs.update)).toBe(true); 77 | chrome.tabs.update(1, props, callback); 78 | expect(chrome.tabs.update).toHaveBeenCalledTimes(1); 79 | expect(callback).toHaveBeenCalledTimes(1); 80 | expect(callback).toHaveBeenCalledWith({ id: 1, pinned: true }); 81 | chrome.tabs.update(props); 82 | expect(chrome.tabs.update).toHaveBeenCalledTimes(2); 83 | }); 84 | test('move', (done) => { 85 | const callback = jest.fn(() => done()); 86 | const props = { pinned: true }; 87 | expect(jest.isMockFunction(chrome.tabs.move)).toBe(true); 88 | chrome.tabs.move([1, 2, 3], props, callback); 89 | expect(chrome.tabs.move).toHaveBeenCalledTimes(1); 90 | expect(callback).toHaveBeenCalledTimes(1); 91 | expect(callback).toHaveBeenCalledWith([ 92 | { id: 1, pinned: true }, 93 | { id: 2, pinned: true }, 94 | { id: 3, pinned: true }, 95 | ]); 96 | }); 97 | ['addListener', 'removeListener', 'hasListener'].forEach((method) => { 98 | test(`onUpdated.${method}`, () => { 99 | const callback = jest.fn(); 100 | expect(jest.isMockFunction(browser.tabs.onUpdated[method])).toBe(true); 101 | browser.tabs.onUpdated[method](callback); 102 | expect(browser.tabs.onUpdated[method]).toHaveBeenCalledTimes(1); 103 | expect(callback).toHaveBeenCalledTimes(0); 104 | }); 105 | }); 106 | test('reload', (done) => { 107 | const callback = jest.fn(() => done()); 108 | expect(jest.isMockFunction(chrome.tabs.reload)).toBe(true); 109 | 110 | chrome.tabs.reload(1, {}, callback); 111 | 112 | expect(chrome.tabs.reload).toHaveBeenCalledTimes(1); 113 | expect(callback).toHaveBeenCalledTimes(1); 114 | }); 115 | test('sendMessage', (done) => { 116 | const callback = jest.fn(() => done()); 117 | expect(jest.isMockFunction(chrome.tabs.sendMessage)).toBe(true); 118 | chrome.tabs.sendMessage(1, { test: 'message' }, callback); 119 | expect(chrome.tabs.sendMessage).toHaveBeenCalledTimes(1); 120 | expect(callback).toHaveBeenCalledTimes(1); 121 | chrome.tabs.sendMessage(1, { test: 'message' }); 122 | expect(chrome.tabs.sendMessage).toHaveBeenCalledTimes(2); 123 | }); 124 | test('sendMessage listener', (done) => { 125 | const listener = jest.fn(); 126 | browser.runtime.onMessage.addListener(listener); 127 | chrome.tabs.sendMessage(1, { test: 'message' }, done); 128 | expect(listener).toHaveBeenCalledWith(1, { test: 'message' }); 129 | }); 130 | test('sendMessage promise', () => { 131 | return expect(chrome.tabs.sendMessage({})).resolves.toBeUndefined(); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /__tests__/webNavigation.test.js: -------------------------------------------------------------------------------- 1 | describe('browser.webNavigation', () => { 2 | test('onCompleted listener', () => { 3 | expect( 4 | jest.isMockFunction(chrome.webNavigation.onCompleted.addListener) 5 | ).toBe(true); 6 | 7 | const listener = () => {}; 8 | chrome.webNavigation.onCompleted.addListener(listener); 9 | 10 | expect(chrome.webNavigation.onCompleted.addListener).toHaveBeenCalledWith( 11 | listener 12 | ); 13 | }); 14 | 15 | test('onHistoryStateUpdated listener', () => { 16 | expect( 17 | jest.isMockFunction( 18 | chrome.webNavigation.onHistoryStateUpdated.addListener 19 | ) 20 | ).toBe(true); 21 | 22 | const listener = () => {}; 23 | chrome.webNavigation.onHistoryStateUpdated.addListener(listener); 24 | 25 | expect( 26 | chrome.webNavigation.onHistoryStateUpdated.addListener 27 | ).toHaveBeenCalledWith(listener); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /dist/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function ownKeys(object, enumerableOnly) { 4 | var keys = Object.keys(object); 5 | 6 | if (Object.getOwnPropertySymbols) { 7 | var symbols = Object.getOwnPropertySymbols(object); 8 | enumerableOnly && (symbols = symbols.filter(function (sym) { 9 | return Object.getOwnPropertyDescriptor(object, sym).enumerable; 10 | })), keys.push.apply(keys, symbols); 11 | } 12 | 13 | return keys; 14 | } 15 | 16 | function _objectSpread2(target) { 17 | for (var i = 1; i < arguments.length; i++) { 18 | var source = null != arguments[i] ? arguments[i] : {}; 19 | i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { 20 | _defineProperty(target, key, source[key]); 21 | }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { 22 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 23 | }); 24 | } 25 | 26 | return target; 27 | } 28 | 29 | function _typeof(obj) { 30 | "@babel/helpers - typeof"; 31 | 32 | return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { 33 | return typeof obj; 34 | } : function (obj) { 35 | return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 36 | }, _typeof(obj); 37 | } 38 | 39 | function _defineProperty(obj, key, value) { 40 | if (key in obj) { 41 | Object.defineProperty(obj, key, { 42 | value: value, 43 | enumerable: true, 44 | configurable: true, 45 | writable: true 46 | }); 47 | } else { 48 | obj[key] = value; 49 | } 50 | 51 | return obj; 52 | } 53 | 54 | // https://developer.chrome.com/extensions/omnibox 55 | var omnibox = { 56 | setDefaultSuggestion: jest.fn(), 57 | onInputStarted: { 58 | addListener: jest.fn() 59 | }, 60 | onInputChanged: { 61 | addListener: jest.fn() 62 | }, 63 | onInputEntered: { 64 | addListener: jest.fn() 65 | }, 66 | onInputCancelled: { 67 | addListener: jest.fn() 68 | } 69 | }; 70 | 71 | var onMessageListeners = []; 72 | var onMessageExternalListeners = []; 73 | var runtime = { 74 | connect: jest.fn(function (_ref) { 75 | var name = _ref.name; 76 | return { 77 | name: name, 78 | postMessage: jest.fn(), 79 | onDisconnect: { 80 | addListener: jest.fn() 81 | }, 82 | onMessage: { 83 | addListener: jest.fn(function (listener) { 84 | onMessageListeners.push(listener); 85 | }) 86 | }, 87 | disconnect: jest.fn() 88 | }; 89 | }), 90 | sendMessage: jest.fn(function (message, cb) { 91 | onMessageListeners.forEach(function (listener) { 92 | return listener(message); 93 | }); 94 | 95 | if (cb !== undefined) { 96 | return cb(); 97 | } 98 | 99 | return Promise.resolve(); 100 | }), 101 | onMessage: { 102 | addListener: jest.fn(function (listener) { 103 | onMessageListeners.push(listener); 104 | }), 105 | removeListener: jest.fn(function (listener) { 106 | onMessageListeners = onMessageListeners.filter(function (lstn) { 107 | return lstn !== listener; 108 | }); 109 | }), 110 | hasListener: jest.fn(function (listener) { 111 | return onMessageListeners.includes(listener); 112 | }) 113 | }, 114 | onMessageExternal: { 115 | addListener: jest.fn(function (listener) { 116 | onMessageExternalListeners.push(listener); 117 | }), 118 | removeListener: jest.fn(function (listener) { 119 | onMessageExternalListeners = onMessageExternalListeners.filter(function (lstn) { 120 | return lstn !== listener; 121 | }); 122 | }), 123 | hasListener: jest.fn(function (listener) { 124 | return onMessageExternalListeners.includes(listener); 125 | }) 126 | }, 127 | onConnect: { 128 | addListener: jest.fn(), 129 | removeListener: jest.fn(), 130 | hasListener: jest.fn() 131 | }, 132 | onInstalled: { 133 | addListener: jest.fn(), 134 | removeListener: jest.fn(), 135 | hasListener: jest.fn() 136 | }, 137 | getURL: jest.fn(function (path) { 138 | var origin = globalThis[Symbol["for"]('jest-webextension-mock')].extensionPath; 139 | return String(new URL(path, origin)); 140 | }), 141 | openOptionsPage: jest.fn(), 142 | getManifest: jest.fn(function () { 143 | return { 144 | manifest_version: 3 145 | }; 146 | }) 147 | }; 148 | 149 | // https://developer.chrome.com/extensions/tabs 150 | var tabs = { 151 | get: jest.fn(function () { 152 | var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 153 | return cb({}); 154 | }), 155 | getCurrent: jest.fn(function (cb) { 156 | return cb({}); 157 | }), 158 | connect: jest.fn(function () { 159 | var info = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 160 | // returns a Port 161 | return { 162 | name: info.name, 163 | disconnect: jest.fn(), 164 | onDisconnect: { 165 | addListener: jest.fn() 166 | }, 167 | onMessage: { 168 | addListener: jest.fn() 169 | }, 170 | postMessage: jest.fn() // TODO: add sender 171 | 172 | }; 173 | }), 174 | create: jest.fn(function () { 175 | var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 176 | var cb = arguments.length > 1 ? arguments[1] : undefined; 177 | 178 | if (cb !== undefined) { 179 | return cb(props); 180 | } 181 | 182 | return Promise.resolve(props); 183 | }), 184 | remove: jest.fn(function (tabIds, cb) { 185 | if (cb !== undefined) { 186 | return cb(); 187 | } 188 | 189 | return Promise.resolve(); 190 | }), 191 | duplicate: jest.fn(function () { 192 | var id = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 193 | var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 194 | return cb(Object.assign({}, { 195 | id: id 196 | })); 197 | }), 198 | query: jest.fn(function () { 199 | var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 200 | return cb([{}]); 201 | }), 202 | highlight: jest.fn(function () { 203 | var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 204 | return cb(); 205 | }), 206 | update: jest.fn(function () { 207 | var id = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 208 | var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 209 | var cb = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; 210 | return cb(Object.assign({}, props, { 211 | id: id 212 | })); 213 | }), 214 | move: jest.fn(function () { 215 | var ids = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 216 | var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 217 | var cb = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; 218 | return cb(ids.map(function (id) { 219 | return Object.assign({}, props, { 220 | id: id 221 | }); 222 | })); 223 | }), 224 | onUpdated: { 225 | addListener: jest.fn(), 226 | removeListener: jest.fn(), 227 | hasListener: jest.fn() 228 | }, 229 | sendMessage: jest.fn(function (tabId, message, cb) { 230 | onMessageListeners.forEach(function (listener) { 231 | return listener(tabId, message); 232 | }); 233 | 234 | if (cb !== undefined) { 235 | return cb(); 236 | } 237 | 238 | return Promise.resolve(); 239 | }), 240 | reload: jest.fn(function (tabId, reloadProperties, cb) { 241 | return cb(); 242 | }) 243 | }; 244 | 245 | var store = {}; 246 | 247 | function resolveKey(key) { 248 | if (typeof key === 'string') { 249 | var result = {}; 250 | result[key] = store[key]; 251 | return result; 252 | } else if (Array.isArray(key)) { 253 | return key.reduce(function (acc, curr) { 254 | acc[curr] = store[curr]; 255 | return acc; 256 | }, {}); 257 | } else if (_typeof(key) === 'object') { 258 | return Object.keys(key).reduce(function (acc, curr) { 259 | acc[curr] = store[curr] || key[curr]; 260 | return acc; 261 | }, {}); 262 | } 263 | 264 | throw new Error('Wrong key given'); 265 | } 266 | 267 | var storage = { 268 | sync: { 269 | get: jest.fn(function (id, cb) { 270 | var result = id === null ? store : resolveKey(id); 271 | 272 | if (cb !== undefined) { 273 | return cb(result); 274 | } 275 | 276 | return Promise.resolve(result); 277 | }), 278 | getBytesInUse: jest.fn(function (id, cb) { 279 | if (cb !== undefined) { 280 | return cb(0); 281 | } 282 | 283 | return Promise.resolve(0); 284 | }), 285 | set: jest.fn(function (payload, cb) { 286 | Object.keys(payload).forEach(function (key) { 287 | return store[key] = payload[key]; 288 | }); 289 | 290 | if (cb !== undefined) { 291 | return cb(); 292 | } 293 | 294 | return Promise.resolve(); 295 | }), 296 | remove: jest.fn(function (id, cb) { 297 | var keys = typeof id === 'string' ? [id] : id; 298 | keys.forEach(function (key) { 299 | return delete store[key]; 300 | }); 301 | 302 | if (cb !== undefined) { 303 | return cb(); 304 | } 305 | 306 | return Promise.resolve(); 307 | }), 308 | clear: jest.fn(function (cb) { 309 | store = {}; 310 | 311 | if (cb !== undefined) { 312 | return cb(); 313 | } 314 | 315 | return Promise.resolve(); 316 | }) 317 | }, 318 | local: { 319 | get: jest.fn(function (id, cb) { 320 | var result = id === null ? store : resolveKey(id); 321 | 322 | if (cb !== undefined) { 323 | return cb(result); 324 | } 325 | 326 | return Promise.resolve(result); 327 | }), 328 | getBytesInUse: jest.fn(function (id, cb) { 329 | if (cb !== undefined) { 330 | return cb(0); 331 | } 332 | 333 | return Promise.resolve(0); 334 | }), 335 | set: jest.fn(function (payload, cb) { 336 | Object.keys(payload).forEach(function (key) { 337 | return store[key] = payload[key]; 338 | }); 339 | 340 | if (cb !== undefined) { 341 | return cb(); 342 | } 343 | 344 | return Promise.resolve(); 345 | }), 346 | remove: jest.fn(function (id, cb) { 347 | var keys = typeof id === 'string' ? [id] : id; 348 | keys.forEach(function (key) { 349 | return delete store[key]; 350 | }); 351 | 352 | if (cb !== undefined) { 353 | return cb(); 354 | } 355 | 356 | return Promise.resolve(); 357 | }), 358 | clear: jest.fn(function (cb) { 359 | store = {}; 360 | 361 | if (cb !== undefined) { 362 | return cb(); 363 | } 364 | 365 | return Promise.resolve(); 366 | }) 367 | }, 368 | managed: { 369 | get: jest.fn(function (id, cb) { 370 | var result = id === null ? store : resolveKey(id); 371 | 372 | if (cb !== undefined) { 373 | return cb(result); 374 | } 375 | 376 | return Promise.resolve(result); 377 | }), 378 | getBytesInUse: jest.fn(function (id, cb) { 379 | if (cb !== undefined) { 380 | return cb(0); 381 | } 382 | 383 | return Promise.resolve(0); 384 | }), 385 | set: jest.fn(function (payload, cb) { 386 | Object.keys(payload).forEach(function (key) { 387 | return store[key] = payload[key]; 388 | }); 389 | 390 | if (cb !== undefined) { 391 | return cb(); 392 | } 393 | 394 | return Promise.resolve(); 395 | }), 396 | remove: jest.fn(function (id, cb) { 397 | var keys = typeof id === 'string' ? [id] : id; 398 | keys.forEach(function (key) { 399 | return delete store[key]; 400 | }); 401 | 402 | if (cb !== undefined) { 403 | return cb(); 404 | } 405 | 406 | return Promise.resolve(); 407 | }), 408 | clear: jest.fn(function (cb) { 409 | store = {}; 410 | 411 | if (cb !== undefined) { 412 | return cb(); 413 | } 414 | 415 | return Promise.resolve(); 416 | }) 417 | }, 418 | onChanged: { 419 | addListener: jest.fn(), 420 | removeListener: jest.fn(), 421 | hasListener: jest.fn() 422 | } 423 | }; 424 | 425 | var getDetails = function getDetails(details, cb) { 426 | if (cb !== undefined) { 427 | return cb(); 428 | } 429 | 430 | return Promise.resolve(); 431 | }; 432 | 433 | var browserAction = { 434 | setTitle: jest.fn(), 435 | getTitle: jest.fn(getDetails), 436 | setIcon: jest.fn(getDetails), 437 | setPopup: jest.fn(), 438 | getPopup: jest.fn(getDetails), 439 | setBadgeText: jest.fn(), 440 | getBadgeText: jest.fn(getDetails), 441 | setBadgeBackgroundColor: jest.fn(), 442 | getBadgeBackgroundColor: jest.fn(getDetails), 443 | enable: jest.fn(), 444 | disable: jest.fn(), 445 | onClicked: { 446 | addListener: jest.fn() 447 | } 448 | }; 449 | 450 | // https://developer.chrome.com/extensions/commands 451 | var commands = { 452 | getAll: jest.fn(function (cb) { 453 | if (cb !== undefined) { 454 | return cb(); 455 | } 456 | 457 | return Promise.resolve(); 458 | }), 459 | onCommand: { 460 | addListener: jest.fn() 461 | } 462 | }; 463 | 464 | var cbOrPromise$1 = function cbOrPromise(cb, value) { 465 | if (cb !== undefined) { 466 | return cb(value); 467 | } 468 | 469 | return Promise.resolve(value); 470 | }; 471 | 472 | var create = function create(notificationId, options, cb) { 473 | if (typeof notificationId !== 'string') { 474 | notificationId = 'generated-id'; 475 | } 476 | 477 | if (typeof options === 'function') { 478 | cb = options; 479 | } 480 | 481 | return cbOrPromise$1(cb, notificationId); 482 | }; 483 | 484 | var notifications = { 485 | create: jest.fn(create), 486 | update: jest.fn(function (notificationId, options, cb) { 487 | return cbOrPromise$1(cb, true); 488 | }), 489 | clear: jest.fn(function (notificationId, cb) { 490 | return cbOrPromise$1(cb, true); 491 | }), 492 | getAll: jest.fn(function (cb) { 493 | return cbOrPromise$1(cb, []); 494 | }), 495 | getPermissionLevel: jest.fn(function (cb) { 496 | return cbOrPromise$1(cb, 'granted'); 497 | }), 498 | onClosed: { 499 | addListener: jest.fn() 500 | }, 501 | onClicked: { 502 | addListener: jest.fn() 503 | }, 504 | onButtonClicked: { 505 | addListener: jest.fn() 506 | }, 507 | onPermissionLevelChanged: { 508 | addListener: jest.fn() 509 | }, 510 | onShowSettings: { 511 | addListener: jest.fn() 512 | } 513 | }; 514 | 515 | var i18n = { 516 | getAcceptLanguages: jest.fn(), 517 | getMessage: jest.fn(function (key) { 518 | return "Translated<".concat(key, ">"); 519 | }), 520 | getUILanguage: jest.fn(function () { 521 | return 'en'; 522 | }), 523 | detectLanguage: jest.fn() 524 | }; 525 | 526 | var webNavigation = { 527 | onCompleted: { 528 | addListener: jest.fn() 529 | }, 530 | onHistoryStateUpdated: { 531 | addListener: jest.fn() 532 | } 533 | }; 534 | 535 | var extension = { 536 | getURL: jest.fn() 537 | }; 538 | 539 | var cbOrPromise = function cbOrPromise(cb, value) { 540 | if (cb !== undefined) { 541 | return cb(value); 542 | } 543 | 544 | return Promise.resolve(value); 545 | }; 546 | 547 | var downloads = { 548 | acceptDanger: jest.fn(function (downloadId, cb) { 549 | return cbOrPromise(cb); 550 | }), 551 | cancel: jest.fn(function (downloadId, cb) { 552 | return cbOrPromise(cb); 553 | }), 554 | download: jest.fn(function (options, cb) { 555 | return cbOrPromise(cb); 556 | }), 557 | erase: jest.fn(function (query, cb) { 558 | return cbOrPromise(cb); 559 | }), 560 | getFileIcon: jest.fn(function (downloadId, cb) { 561 | return cbOrPromise(cb); 562 | }), 563 | open: jest.fn(), 564 | pause: jest.fn(function (downloadId, cb) { 565 | return cbOrPromise(cb); 566 | }), 567 | removeFile: jest.fn(function (downloadId, cb) { 568 | return cbOrPromise(cb); 569 | }), 570 | resume: jest.fn(function (downloadId, cb) { 571 | return cbOrPromise(cb); 572 | }), 573 | search: jest.fn(function (query, cb) { 574 | return cbOrPromise(cb); 575 | }), 576 | setShelfEnabled: jest.fn(), 577 | show: jest.fn(), 578 | showDefaultFolder: jest.fn() 579 | }; 580 | 581 | // https://developer.chrome.com/extensions/permissions 582 | var permissions = { 583 | contains: jest.fn(), 584 | getAll: jest.fn(), 585 | remove: jest.fn(), 586 | request: jest.fn(), 587 | onAdded: { 588 | addListener: jest.fn() 589 | }, 590 | onRemoved: { 591 | addListener: jest.fn() 592 | } 593 | }; 594 | 595 | var geckoProfiler = { 596 | stop: jest.fn(function () { 597 | return Promise.resolve(); 598 | }), 599 | start: jest.fn(function () { 600 | return Promise.resolve(); 601 | }), 602 | pause: jest.fn(function () { 603 | return Promise.resolve(); 604 | }), 605 | resume: jest.fn(function () { 606 | return Promise.resolve(); 607 | }), 608 | getProfile: jest.fn(function () { 609 | return Promise.resolve(); 610 | }), 611 | getProfileAsArrayBuffer: jest.fn(function () { 612 | return Promise.resolve(); 613 | }), 614 | getSymbols: jest.fn(function (debugName, breakpadId) { 615 | return Promise.resolve(); 616 | }), 617 | onRunning: { 618 | addListener: jest.fn() 619 | } 620 | }; 621 | 622 | globalThis[Symbol["for"]('jest-webextension-mock')] = _objectSpread2({ 623 | extensionPath: 'moz-extension://8b413e68-1e0d-4cad-b98e-1eb000799783/' 624 | }, globalThis[Symbol["for"]('jest-webextension-mock')]); 625 | var chrome = { 626 | omnibox: omnibox, 627 | tabs: tabs, 628 | runtime: runtime, 629 | storage: storage, 630 | browserAction: browserAction, 631 | commands: commands, 632 | geckoProfiler: geckoProfiler, 633 | notifications: notifications, 634 | i18n: i18n, 635 | webNavigation: webNavigation, 636 | extension: extension, 637 | downloads: downloads, 638 | permissions: permissions 639 | }; 640 | // Firefox uses 'browser' but aliases it to chrome 641 | 642 | /** 643 | * This is a setup file we specify as our 'main' entry point 644 | * from the package.json file. This allows developers to 645 | * directly call the module in their `setupFiles` property. 646 | */ 647 | global.chrome = chrome; 648 | global.browser = chrome; // Firefox specific globals 649 | // if (navigator.userAgent.indexOf('Firefox') !== -1) { 650 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#exportFunction 651 | 652 | global.exportFunction = jest.fn(function (func) { 653 | return func; 654 | }); // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#cloneInto 655 | 656 | global.cloneInto = jest.fn(function (obj) { 657 | return obj; 658 | }); // } 659 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-webextension-mock", 3 | "version": "3.5.0", 4 | "description": "Mock the components of a WebExtension", 5 | "main": "dist/setup.js", 6 | "module": "src/setup.js", 7 | "author": "Bryan Clark (https://twitter.com/clarkbw)", 8 | "license": "ISC", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/clarkbw/jest-webextension-mock.git" 12 | }, 13 | "scripts": { 14 | "test": "jest", 15 | "build": "rollup -c", 16 | "prettier": "prettier --write \"{config,src,__{tests,setups}__}/**/*.js\" rollup.config.js", 17 | "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.11.6", 21 | "@babel/preset-env": "^7.11.5", 22 | "babel-core": "^7.0.0-bridge.0", 23 | "babel-jest": "^26.5.2", 24 | "eslint": "^7.11.0", 25 | "eslint-config-prettier": "^6.12.0", 26 | "eslint-plugin-prettier": "^3.1.4", 27 | "jest": "^26.5.3", 28 | "prettier": "^2.1.2", 29 | "rollup": "^2.30.0", 30 | "rollup-plugin-babel": "^4.4.0", 31 | "rollup-plugin-node-resolve": "^5.2.0", 32 | "rollup-watch": "^4.0.0" 33 | }, 34 | "jest": { 35 | "bail": true, 36 | "coverageDirectory": "./coverage/", 37 | "coverageReporters": [ 38 | "json", 39 | "lcov", 40 | "text", 41 | "html" 42 | ], 43 | "collectCoverage": true, 44 | "setupFiles": [ 45 | "./__setups__/chrome.js" 46 | ] 47 | }, 48 | "dependencies": {} 49 | } 50 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // prettier.config.js or .prettierrc.js 2 | module.exports = { 3 | trailingComma: 'es5', 4 | semi: true, 5 | singleQuote: true, 6 | } 7 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | export default { 5 | input: 'src/setup.js', 6 | plugins: [ 7 | resolve(), 8 | babel({ 9 | exclude: 'node_modules/**', 10 | babelrc: false, 11 | presets: [['@babel/preset-env', { modules: false }]], 12 | }), 13 | ], 14 | output: { 15 | format: 'cjs', 16 | file: 'dist/setup.js', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/browserAction.js: -------------------------------------------------------------------------------- 1 | const getDetails = (details, cb) => { 2 | if (cb !== undefined) { 3 | return cb(); 4 | } 5 | return Promise.resolve(); 6 | }; 7 | export const browserAction = { 8 | setTitle: jest.fn(), 9 | getTitle: jest.fn(getDetails), 10 | setIcon: jest.fn(getDetails), 11 | setPopup: jest.fn(), 12 | getPopup: jest.fn(getDetails), 13 | setBadgeText: jest.fn(), 14 | getBadgeText: jest.fn(getDetails), 15 | setBadgeBackgroundColor: jest.fn(), 16 | getBadgeBackgroundColor: jest.fn(getDetails), 17 | enable: jest.fn(), 18 | disable: jest.fn(), 19 | onClicked: { 20 | addListener: jest.fn(), 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/commands.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/extensions/commands 2 | 3 | export const commands = { 4 | getAll: jest.fn((cb) => { 5 | if (cb !== undefined) { 6 | return cb(); 7 | } 8 | return Promise.resolve(); 9 | }), 10 | onCommand: { 11 | addListener: jest.fn(), 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/downloads.js: -------------------------------------------------------------------------------- 1 | const cbOrPromise = (cb, value) => { 2 | if (cb !== undefined) { 3 | return cb(value); 4 | } 5 | 6 | return Promise.resolve(value); 7 | }; 8 | 9 | export const downloads = { 10 | acceptDanger: jest.fn((downloadId, cb) => cbOrPromise(cb)), 11 | cancel: jest.fn((downloadId, cb) => cbOrPromise(cb)), 12 | download: jest.fn((options, cb) => cbOrPromise(cb)), 13 | erase: jest.fn((query, cb) => cbOrPromise(cb)), 14 | getFileIcon: jest.fn((downloadId, cb) => cbOrPromise(cb)), 15 | open: jest.fn(), 16 | pause: jest.fn((downloadId, cb) => cbOrPromise(cb)), 17 | removeFile: jest.fn((downloadId, cb) => cbOrPromise(cb)), 18 | resume: jest.fn((downloadId, cb) => cbOrPromise(cb)), 19 | search: jest.fn((query, cb) => cbOrPromise(cb)), 20 | setShelfEnabled: jest.fn(), 21 | show: jest.fn(), 22 | showDefaultFolder: jest.fn(), 23 | }; -------------------------------------------------------------------------------- /src/extension.js: -------------------------------------------------------------------------------- 1 | export const extension = { 2 | getURL: jest.fn(), 3 | }; 4 | -------------------------------------------------------------------------------- /src/geckoProfiler.js: -------------------------------------------------------------------------------- 1 | export const geckoProfiler = { 2 | stop: jest.fn(() => Promise.resolve()), 3 | start: jest.fn(() => Promise.resolve()), 4 | pause: jest.fn(() => Promise.resolve()), 5 | resume: jest.fn(() => Promise.resolve()), 6 | getProfile: jest.fn(() => Promise.resolve()), 7 | getProfileAsArrayBuffer: jest.fn(() => Promise.resolve()), 8 | getSymbols: jest.fn((debugName, breakpadId) => Promise.resolve()), 9 | onRunning: { 10 | addListener: jest.fn(), 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | export const i18n = { 2 | getAcceptLanguages: jest.fn(), 3 | getMessage: jest.fn((key) => `Translated<${key}>`), 4 | getUILanguage: jest.fn(() => 'en'), 5 | detectLanguage: jest.fn(), 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { omnibox } from './omnibox'; 2 | import { tabs } from './tabs'; 3 | import { runtime } from './runtime'; 4 | import { storage } from './storage'; 5 | import { browserAction } from './browserAction'; 6 | import { commands } from './commands'; 7 | import { notifications } from './notifications'; 8 | import { i18n } from './i18n'; 9 | import { webNavigation } from './webNavigation'; 10 | import { extension } from './extension'; 11 | import { downloads } from './downloads'; 12 | import { permissions } from './permissions'; 13 | 14 | // Firefox specific API 15 | import { geckoProfiler } from './geckoProfiler'; 16 | 17 | globalThis[Symbol.for('jest-webextension-mock')] = { 18 | extensionPath: 'moz-extension://8b413e68-1e0d-4cad-b98e-1eb000799783/', 19 | ...globalThis[Symbol.for('jest-webextension-mock')], 20 | }; 21 | 22 | const chrome = { 23 | omnibox, 24 | tabs, 25 | runtime, 26 | storage, 27 | browserAction, 28 | commands, 29 | geckoProfiler, 30 | notifications, 31 | i18n, 32 | webNavigation, 33 | extension, 34 | downloads, 35 | permissions, 36 | }; 37 | 38 | export { chrome }; 39 | export { chrome as browser }; // Firefox uses 'browser' but aliases it to chrome 40 | -------------------------------------------------------------------------------- /src/notifications.js: -------------------------------------------------------------------------------- 1 | const cbOrPromise = (cb, value) => { 2 | if (cb !== undefined) { 3 | return cb(value); 4 | } 5 | 6 | return Promise.resolve(value); 7 | }; 8 | 9 | const create = (notificationId, options, cb) => { 10 | if (typeof notificationId !== 'string') { 11 | notificationId = 'generated-id'; 12 | } 13 | 14 | if (typeof options === 'function') { 15 | cb = options; 16 | } 17 | 18 | return cbOrPromise(cb, notificationId); 19 | }; 20 | 21 | export const notifications = { 22 | create: jest.fn(create), 23 | update: jest.fn((notificationId, options, cb) => cbOrPromise(cb, true)), 24 | clear: jest.fn((notificationId, cb) => cbOrPromise(cb, true)), 25 | getAll: jest.fn((cb) => cbOrPromise(cb, [])), 26 | getPermissionLevel: jest.fn((cb) => cbOrPromise(cb, 'granted')), 27 | onClosed: { 28 | addListener: jest.fn(), 29 | }, 30 | onClicked: { 31 | addListener: jest.fn(), 32 | }, 33 | onButtonClicked: { 34 | addListener: jest.fn(), 35 | }, 36 | onPermissionLevelChanged: { 37 | addListener: jest.fn(), 38 | }, 39 | onShowSettings: { 40 | addListener: jest.fn(), 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/omnibox.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/extensions/omnibox 2 | 3 | export const omnibox = { 4 | setDefaultSuggestion: jest.fn(), 5 | onInputStarted: { 6 | addListener: jest.fn(), 7 | }, 8 | onInputChanged: { 9 | addListener: jest.fn(), 10 | }, 11 | onInputEntered: { 12 | addListener: jest.fn(), 13 | }, 14 | onInputCancelled: { 15 | addListener: jest.fn(), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/permissions.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/extensions/permissions 2 | 3 | export let onMessageListeners = []; 4 | export let onMessageExternalListeners = []; 5 | 6 | export const permissions = { 7 | contains: jest.fn(), 8 | getAll: jest.fn(), 9 | remove: jest.fn(), 10 | request: jest.fn(), 11 | onAdded: { 12 | addListener: jest.fn(), 13 | }, 14 | onRemoved: { 15 | addListener: jest.fn(), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/runtime.js: -------------------------------------------------------------------------------- 1 | export let onMessageListeners = []; 2 | export let onMessageExternalListeners = []; 3 | export const runtime = { 4 | connect: jest.fn(function ({ name }) { 5 | return { 6 | name, 7 | postMessage: jest.fn(), 8 | onDisconnect: { 9 | addListener: jest.fn(), 10 | }, 11 | onMessage: { 12 | addListener: jest.fn((listener) => { 13 | onMessageListeners.push(listener); 14 | }), 15 | }, 16 | disconnect: jest.fn(), 17 | }; 18 | }), 19 | sendMessage: jest.fn((message, cb) => { 20 | onMessageListeners.forEach((listener) => listener(message)); 21 | if (cb !== undefined) { 22 | return cb(); 23 | } 24 | return Promise.resolve(); 25 | }), 26 | onMessage: { 27 | addListener: jest.fn((listener) => { 28 | onMessageListeners.push(listener); 29 | }), 30 | removeListener: jest.fn((listener) => { 31 | onMessageListeners = onMessageListeners.filter( 32 | (lstn) => lstn !== listener 33 | ); 34 | }), 35 | hasListener: jest.fn((listener) => onMessageListeners.includes(listener)), 36 | }, 37 | onMessageExternal: { 38 | addListener: jest.fn((listener) => { 39 | onMessageExternalListeners.push(listener); 40 | }), 41 | removeListener: jest.fn((listener) => { 42 | onMessageExternalListeners = onMessageExternalListeners.filter( 43 | (lstn) => lstn !== listener 44 | ); 45 | }), 46 | hasListener: jest.fn((listener) => onMessageExternalListeners.includes(listener)), 47 | }, 48 | onConnect: { 49 | addListener: jest.fn(), 50 | removeListener: jest.fn(), 51 | hasListener: jest.fn(), 52 | }, 53 | onInstalled: { 54 | addListener: jest.fn(), 55 | removeListener: jest.fn(), 56 | hasListener: jest.fn(), 57 | }, 58 | getURL: jest.fn(function (path) { 59 | const origin = globalThis[Symbol.for('jest-webextension-mock')].extensionPath; 60 | return String(new URL(path, origin)); 61 | }), 62 | openOptionsPage: jest.fn(), 63 | getManifest: jest.fn(() => ({ manifest_version: 3 })), 64 | }; 65 | -------------------------------------------------------------------------------- /src/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a setup file we specify as our 'main' entry point 3 | * from the package.json file. This allows developers to 4 | * directly call the module in their `setupFiles` property. 5 | */ 6 | import { chrome } from './'; 7 | import { browser } from './'; 8 | 9 | global.chrome = chrome; 10 | global.browser = browser; 11 | 12 | // Firefox specific globals 13 | // if (navigator.userAgent.indexOf('Firefox') !== -1) { 14 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#exportFunction 15 | global.exportFunction = jest.fn((func) => func); 16 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#cloneInto 17 | global.cloneInto = jest.fn((obj) => obj); 18 | // } 19 | -------------------------------------------------------------------------------- /src/storage.js: -------------------------------------------------------------------------------- 1 | let store = {}; 2 | 3 | function resolveKey(key) { 4 | if (typeof key === 'string') { 5 | const result = {}; 6 | result[key] = store[key]; 7 | return result; 8 | } else if (Array.isArray(key)) { 9 | return key.reduce((acc, curr) => { 10 | acc[curr] = store[curr]; 11 | return acc; 12 | }, {}); 13 | } else if (typeof key === 'object') { 14 | return Object.keys(key).reduce((acc, curr) => { 15 | acc[curr] = store[curr] || key[curr]; 16 | return acc; 17 | }, {}); 18 | } 19 | throw new Error('Wrong key given'); 20 | } 21 | 22 | export const storage = { 23 | sync: { 24 | get: jest.fn((id, cb) => { 25 | const result = id === null ? store : resolveKey(id); 26 | if (cb !== undefined) { 27 | return cb(result); 28 | } 29 | return Promise.resolve(result); 30 | }), 31 | getBytesInUse: jest.fn((id, cb) => { 32 | if (cb !== undefined) { 33 | return cb(0); 34 | } 35 | return Promise.resolve(0); 36 | }), 37 | set: jest.fn((payload, cb) => { 38 | Object.keys(payload).forEach((key) => (store[key] = payload[key])); 39 | if (cb !== undefined) { 40 | return cb(); 41 | } 42 | return Promise.resolve(); 43 | }), 44 | remove: jest.fn((id, cb) => { 45 | const keys = typeof id === 'string' ? [id] : id; 46 | keys.forEach((key) => delete store[key]); 47 | if (cb !== undefined) { 48 | return cb(); 49 | } 50 | return Promise.resolve(); 51 | }), 52 | clear: jest.fn((cb) => { 53 | store = {}; 54 | if (cb !== undefined) { 55 | return cb(); 56 | } 57 | return Promise.resolve(); 58 | }), 59 | }, 60 | local: { 61 | get: jest.fn((id, cb) => { 62 | const result = id === null ? store : resolveKey(id); 63 | if (cb !== undefined) { 64 | return cb(result); 65 | } 66 | return Promise.resolve(result); 67 | }), 68 | getBytesInUse: jest.fn((id, cb) => { 69 | if (cb !== undefined) { 70 | return cb(0); 71 | } 72 | return Promise.resolve(0); 73 | }), 74 | set: jest.fn((payload, cb) => { 75 | Object.keys(payload).forEach((key) => (store[key] = payload[key])); 76 | if (cb !== undefined) { 77 | return cb(); 78 | } 79 | return Promise.resolve(); 80 | }), 81 | remove: jest.fn((id, cb) => { 82 | const keys = typeof id === 'string' ? [id] : id; 83 | keys.forEach((key) => delete store[key]); 84 | if (cb !== undefined) { 85 | return cb(); 86 | } 87 | return Promise.resolve(); 88 | }), 89 | clear: jest.fn((cb) => { 90 | store = {}; 91 | if (cb !== undefined) { 92 | return cb(); 93 | } 94 | return Promise.resolve(); 95 | }), 96 | }, 97 | managed: { 98 | get: jest.fn((id, cb) => { 99 | const result = id === null ? store : resolveKey(id); 100 | if (cb !== undefined) { 101 | return cb(result); 102 | } 103 | return Promise.resolve(result); 104 | }), 105 | getBytesInUse: jest.fn((id, cb) => { 106 | if (cb !== undefined) { 107 | return cb(0); 108 | } 109 | return Promise.resolve(0); 110 | }), 111 | set: jest.fn((payload, cb) => { 112 | Object.keys(payload).forEach((key) => (store[key] = payload[key])); 113 | if (cb !== undefined) { 114 | return cb(); 115 | } 116 | return Promise.resolve(); 117 | }), 118 | remove: jest.fn((id, cb) => { 119 | const keys = typeof id === 'string' ? [id] : id; 120 | keys.forEach((key) => delete store[key]); 121 | if (cb !== undefined) { 122 | return cb(); 123 | } 124 | return Promise.resolve(); 125 | }), 126 | clear: jest.fn((cb) => { 127 | store = {}; 128 | if (cb !== undefined) { 129 | return cb(); 130 | } 131 | return Promise.resolve(); 132 | }), 133 | }, 134 | onChanged: { 135 | addListener: jest.fn(), 136 | removeListener: jest.fn(), 137 | hasListener: jest.fn(), 138 | }, 139 | }; 140 | -------------------------------------------------------------------------------- /src/tabs.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/extensions/tabs 2 | import { onMessageListeners } from './runtime'; 3 | 4 | export const tabs = { 5 | get: jest.fn((id = '', cb = () => {}) => cb({})), 6 | getCurrent: jest.fn((cb) => cb({})), 7 | connect: jest.fn((id = '', info = {}) => { 8 | // returns a Port 9 | return { 10 | name: info.name, 11 | disconnect: jest.fn(), 12 | onDisconnect: { 13 | addListener: jest.fn(), 14 | }, 15 | onMessage: { 16 | addListener: jest.fn(), 17 | }, 18 | postMessage: jest.fn(), 19 | // TODO: add sender 20 | }; 21 | }), 22 | create: jest.fn((props = {}, cb) => { 23 | if (cb !== undefined) { 24 | return cb(props); 25 | } 26 | return Promise.resolve(props); 27 | }), 28 | remove: jest.fn((tabIds, cb) => { 29 | if (cb !== undefined) { 30 | return cb(); 31 | } 32 | return Promise.resolve(); 33 | }), 34 | duplicate: jest.fn((id = '', cb = () => {}) => cb(Object.assign({}, { id }))), 35 | query: jest.fn((query = '', cb = () => {}) => cb([{}])), 36 | highlight: jest.fn((info = {}, cb = () => {}) => cb()), 37 | update: jest.fn((id = '', props = {}, cb = () => {}) => 38 | cb(Object.assign({}, props, { id })) 39 | ), 40 | move: jest.fn((ids = [], props = {}, cb = () => {}) => 41 | cb(ids.map((id) => Object.assign({}, props, { id }))) 42 | ), 43 | onUpdated: { 44 | addListener: jest.fn(), 45 | removeListener: jest.fn(), 46 | hasListener: jest.fn(), 47 | }, 48 | sendMessage: jest.fn((tabId, message, cb) => { 49 | onMessageListeners.forEach((listener) => listener(tabId, message)); 50 | if (cb !== undefined) { 51 | return cb(); 52 | } 53 | return Promise.resolve(); 54 | }), 55 | reload: jest.fn((tabId, reloadProperties, cb) => cb()), 56 | }; 57 | -------------------------------------------------------------------------------- /src/webNavigation.js: -------------------------------------------------------------------------------- 1 | export const webNavigation = { 2 | onCompleted: { 3 | addListener: jest.fn(), 4 | }, 5 | onHistoryStateUpdated: { 6 | addListener: jest.fn(), 7 | }, 8 | }; 9 | --------------------------------------------------------------------------------