diff --git a/package.json b/package.json index 3bfcfba..bf05207 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "eslint-plugin-no-unsanitized": "^2.0.0", "eslint-plugin-promise": "^3.4.0", "htmllint-cli": "0.0.7", - "jsdom": "^11.6.2", "json": "^9.0.6", "mocha": "^6.2.2", "npm-run-all": "^4.0.0", @@ -25,7 +24,8 @@ "stylelint": "^7.9.0", "stylelint-config-standard": "^16.0.0", "stylelint-order": "^0.3.0", - "web-ext": "^2.9.3" + "web-ext": "^2.9.3", + "webextensions-jsdom": "^1.1.0" }, "homepage": "https://github.com/mozilla/multi-account-containers#readme", "license": "MPL-2.0", diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 0b29fac..d32e265 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -3,10 +3,14 @@ module.exports = { "node": true, "mocha": true }, + "parserOptions": { + "ecmaVersion": 2018 + }, globals: { "sinon": false, "expect": false, "nextTick": false, + "buildDom": false, "buildBackgroundDom": false, "background": false, "buildPopupDom": false, diff --git a/test/browser.mock.js b/test/browser.mock.js deleted file mode 100644 index ad52c3d..0000000 --- a/test/browser.mock.js +++ /dev/null @@ -1,149 +0,0 @@ -module.exports = () => { - const _storage = {}; - - // could maybe be replaced by https://github.com/acvetkov/sinon-chrome - const browserMock = { - _storage, - runtime: { - onMessage: { - addListener: sinon.stub(), - }, - onMessageExternal: { - addListener: sinon.stub(), - }, - sendMessage: sinon.stub().resolves(), - }, - webRequest: { - onBeforeRequest: { - addListener: sinon.stub() - }, - onCompleted: { - addListener: sinon.stub() - }, - onErrorOccurred: { - addListener: sinon.stub() - } - }, - windows: { - getCurrent: sinon.stub().resolves({}), - onFocusChanged: { - addListener: sinon.stub(), - } - }, - tabs: { - onActivated: { - addListener: sinon.stub() - }, - onCreated: { - addListener: sinon.stub() - }, - onUpdated: { - addListener: sinon.stub() - }, - sendMessage: sinon.stub(), - query: sinon.stub().resolves([{}]), - get: sinon.stub(), - create: sinon.stub().resolves({}), - remove: sinon.stub().resolves() - }, - history: { - deleteUrl: sinon.stub() - }, - storage: { - local: { - get: sinon.stub(), - set: sinon.stub() - } - }, - contextualIdentities: { - create: sinon.stub(), - get: sinon.stub(), - query: sinon.stub().resolves([]), - onCreated: { - addListener: sinon.stub() - }, - onUpdated: { - addListener: sinon.stub() - }, - onRemoved: { - addListener: sinon.stub() - } - }, - contextMenus: { - create: sinon.stub(), - remove: sinon.stub(), - onClicked: { - addListener: sinon.stub() - } - }, - browserAction: { - setBadgeBackgroundColor: sinon.stub(), - setBadgeText: sinon.stub() - }, - management: { - get: sinon.stub(), - onInstalled: { - addListener: sinon.stub() - }, - onUninstalled: { - addListener: sinon.stub() - } - }, - extension: { - getURL: sinon.stub().returns("moz-extension://multi-account-containers/confirm-page.html") - }, - permissions: { - contains: sinon.stub().returns(true) - } - }; - - // inmemory local storage - browserMock.storage.local = { - get: sinon.spy(async key => { - if (!key) { - return _storage; - } - let result = {}; - if (Array.isArray(key)) { - key.map(akey => { - if (typeof _storage[akey] !== "undefined") { - result[akey] = _storage[akey]; - } - }); - } else if (typeof key === "object") { - // TODO support nested objects - Object.keys(key).map(oKey => { - if (typeof _storage[oKey] !== "undefined") { - result[oKey] = _storage[oKey]; - } else { - result[oKey] = key[oKey]; - } - }); - } else { - result = _storage[key]; - } - return result; - }), - set: sinon.spy(async (key, value) => { - if (typeof key === "object") { - // TODO support nested objects - Object.keys(key).map(oKey => { - _storage[oKey] = key[oKey]; - }); - } else { - _storage[key] = value; - } - }), - remove: sinon.spy(async (key) => { - if (Array.isArray(key)) { - key.map(aKey => { - delete _storage[aKey]; - }); - } else { - delete _storage[key]; - } - }), - }; - - return browserMock; -}; diff --git a/test/helper.js b/test/helper.js index 555ee7a..2704bac 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,47 +1,44 @@ module.exports = { browser: { - async initializeWithTab(tab) { - await buildBackgroundDom({ - beforeParse(window) { - window.browser.tabs.get.resolves(tab); - window.browser.tabs.query.resolves([tab]); - window.browser.contextualIdentities.get.resolves({ - cookieStoreId: tab.cookieStoreId - }); - } - }); - await buildPopupDom({ - beforeParse(window) { - window.browser.tabs.get.resolves(tab); - window.browser.tabs.query.resolves([tab]); + async initializeWithTab(details = { + cookieStoreId: "firefox-default" + }) { + let tab; + await buildDom({ + background: { + async afterBuild(background) { + tab = await background.browser.tabs._create(details); + } + }, + popup: { + jsdom: { + beforeParse(window) { + window.browser.storage.local.set({ + "browserActionBadgesClicked": [], + "onboarding-stage": 5, + "achievements": [] + }); + window.browser.storage.local.set.resetHistory(); + } + } } }); + + return tab; }, async openNewTab(tab, options = {}) { - if (options.resetHistory) { - background.browser.tabs.create.resetHistory(); - background.browser.tabs.remove.resetHistory(); - } - background.browser.tabs.get.resolves(tab); - background.browser.tabs.onCreated.addListener.yield(tab); - const [promise] = background.browser.webRequest.onBeforeRequest.addListener.yield({ - frameId: 0, - tabId: tab.id, - url: tab.url, - requestId: options.requestId - }); - - return promise; + return background.browser.tabs._create(tab, options); } }, popup: { async clickElementById(id) { - const clickEvent = popup.document.createEvent("HTMLEvents"); - clickEvent.initEvent("click"); - popup.document.getElementById(id).dispatchEvent(clickEvent); - await nextTick(); + await popup.helper.clickElementById(id); + }, + + async clickLastMatchingElementByQuerySelector(querySelector) { + await popup.helper.clickElementByQuerySelectorAll(querySelector, "last"); } - }, + } }; diff --git a/test/setup.js b/test/setup.js index b6d563d..0cc45ee 100644 --- a/test/setup.js +++ b/test/setup.js @@ -2,7 +2,6 @@ if (!process.listenerCount("unhandledRejection")) { // eslint-disable-next-line no-console process.on("unhandledRejection", r => console.log(r)); } -const jsdom = require("jsdom"); const path = require("path"); const chai = require("chai"); const sinonChai = require("sinon-chai"); @@ -19,83 +18,64 @@ global.nextTick = () => { }; global.helper = require("./helper"); -const browserMock = require("./browser.mock"); -const srcBasePath = path.resolve(path.join(__dirname, "..", "src")); -const srcJsBackgroundPath = path.join(srcBasePath, "js", "background"); -global.buildBackgroundDom = async (options = {}) => { - const dom = await jsdom.JSDOM.fromFile(path.join(srcJsBackgroundPath, "index.html"), { - runScripts: "dangerously", - resources: "usable", - virtualConsole: (new jsdom.VirtualConsole).sendTo(console), - beforeParse(window) { - window.browser = browserMock(); - window.fetch = sinon.stub().resolves({ - json: sinon.stub().resolves({}) - }); - if (options.beforeParse) { - options.beforeParse(window); +const webExtensionsJSDOM = require("webextensions-jsdom"); +const manifestPath = path.resolve(path.join(__dirname, "../src/manifest.json")); + +global.buildDom = async ({background = {}, popup = {}}) => { + background = { + ...background, + jsdom: { + ...background.jsom, + beforeParse(window) { + window.browser.permissions.getAll.resolves({permissions: ["bookmarks"]}); } } - }); - await new Promise(resolve => { - dom.window.document.addEventListener("DOMContentLoaded", resolve); - }); - await nextTick(); - - global.background = { - dom, - browser: dom.window.browser }; -}; -global.buildPopupDom = async (options = {}) => { - const dom = await jsdom.JSDOM.fromFile(path.join(srcBasePath, "popup.html"), { - runScripts: "dangerously", - resources: "usable", - virtualConsole: (new jsdom.VirtualConsole).sendTo(console), - beforeParse(window) { - window.browser = browserMock(); - window.browser.storage.local.set("browserActionBadgesClicked", []); - window.browser.storage.local.set("onboarding-stage", 5); - window.browser.storage.local.set("achievements", []); - window.browser.storage.local.set.resetHistory(); - window.fetch = sinon.stub().resolves({ - json: sinon.stub().resolves({}) - }); - - if (options.beforeParse) { - options.beforeParse(window); - } + popup = { + ...popup, + jsdom: { + ...popup.jsdom, + pretendToBeVisual: true } - }); - await new Promise(resolve => { - dom.window.document.addEventListener("DOMContentLoaded", resolve); - }); - await nextTick(); - dom.window.browser.runtime.sendMessage.resetHistory(); - - if (global.background) { - dom.window.browser.runtime.sendMessage = sinon.spy(function() { - global.background.browser.runtime.onMessage.addListener.yield(...arguments); - }); - } - - global.popup = { - dom, - document: dom.window.document, - browser: dom.window.browser }; + + const webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, { + apiFake: true, + wiring: true, + sinon: global.sinon, + background, + popup + }); + + // eslint-disable-next-line require-atomic-updates + global.background = webExtension.background; + // eslint-disable-next-line require-atomic-updates + global.popup = webExtension.popup; }; +global.buildBackgroundDom = async background => { + await global.buildDom({ + background, + popup: false + }); +}; + +global.buildPopupDom = async popup => { + await global.buildDom({ + popup, + background: false + }); +}; + + global.afterEach(() => { if (global.background) { - global.background.dom.window.close(); - delete global.background; + global.background.destroy(); } if (global.popup) { - global.popup.dom.window.close(); - delete global.popup; + global.popup.destroy(); } });