From 9bc9509316d4032dbb701b312c7546ab8589dac2 Mon Sep 17 00:00:00 2001
From: stoically <29637501+stoically@users.noreply.github.com>
Date: Wed, 31 Jan 2018 05:12:46 +0100
Subject: [PATCH] Added assignment feature test Part of #1107
---
.travis.yml | 2 +-
package.json | 8 +-
src/popup.html | 2 +-
test/.eslintrc.js | 16 ++++
test/browser.mock.js | 122 +++++++++++++++++++++++++++++++
test/features/assignment.test.js | 74 +++++++++++++++++++
test/helper.js | 41 +++++++++++
test/setup.js | 101 +++++++++++++++++++++++++
8 files changed, 363 insertions(+), 3 deletions(-)
create mode 100644 test/.eslintrc.js
create mode 100644 test/browser.mock.js
create mode 100644 test/features/assignment.test.js
create mode 100644 test/helper.js
create mode 100644 test/setup.js
diff --git a/.travis.yml b/.travis.yml
index 4614306..2642444 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: node_js
node_js:
- - "6.1"
+ - "lts/*"
notifications:
irc:
diff --git a/package.json b/package.json
index ca8eb7d..39ecde3 100644
--- a/package.json
+++ b/package.json
@@ -10,13 +10,18 @@
"dependencies": {},
"devDependencies": {
"addons-linter": "^0.15.14",
+ "chai": "^4.1.2",
"deploy-txp": "^1.0.7",
"eslint": "^3.17.1",
"eslint-plugin-no-unsanitized": "^2.0.0",
"eslint-plugin-promise": "^3.4.0",
"htmllint-cli": "^0.0.5",
+ "jsdom": "^11.6.2",
"json": "^9.0.6",
+ "mocha": "^5.0.0",
"npm-run-all": "^4.0.0",
+ "sinon": "^4.2.2",
+ "sinon-chai": "^2.14.0",
"stylelint": "^7.9.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-order": "^0.3.0",
@@ -38,6 +43,7 @@
"lint:html": "htmllint *.html",
"lint:js": "eslint .",
"package": "rm -rf src/web-ext-artifacts && npm run build && mv src/web-ext-artifacts/firefox_multi-account_containers-*.zip addon.xpi",
- "test": "npm run lint"
+ "test": "npm run lint && mocha ./test/setup.js test/**/*.test.js",
+ "test-watch": "mocha ./test/setup.js test/**/*.test.js --watch"
}
}
diff --git a/src/popup.html b/src/popup.html
index 8cde498..a28dd32 100644
--- a/src/popup.html
+++ b/src/popup.html
@@ -2,7 +2,7 @@
Multi-Account Containers
-
+
diff --git a/test/.eslintrc.js b/test/.eslintrc.js
new file mode 100644
index 0000000..0b29fac
--- /dev/null
+++ b/test/.eslintrc.js
@@ -0,0 +1,16 @@
+module.exports = {
+ env: {
+ "node": true,
+ "mocha": true
+ },
+ globals: {
+ "sinon": false,
+ "expect": false,
+ "nextTick": false,
+ "buildBackgroundDom": false,
+ "background": false,
+ "buildPopupDom": false,
+ "popup": false,
+ "helper": false
+ }
+}
diff --git a/test/browser.mock.js b/test/browser.mock.js
new file mode 100644
index 0000000..1c71598
--- /dev/null
+++ b/test/browser.mock.js
@@ -0,0 +1,122 @@
+module.exports = () => {
+ const _storage = {};
+
+ // could maybe be replaced by https://github.com/acvetkov/sinon-chrome
+ const browserMock = {
+ _storage,
+ runtime: {
+ onMessage: {
+ addListener: sinon.stub(),
+ },
+ sendMessage: sinon.stub().resolves(),
+ },
+ webRequest: {
+ onBeforeRequest: {
+ addListener: sinon.stub()
+ },
+ onCompleted: {
+ 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([])
+ },
+ contextMenus: {
+ create: sinon.stub(),
+ remove: sinon.stub(),
+ onClicked: {
+ addListener: sinon.stub()
+ }
+ },
+ browserAction: {
+ setBadgeBackgroundColor: sinon.stub(),
+ setBadgeText: sinon.stub()
+ },
+ extension: {
+ getURL: sinon.stub().returns("moz-extension://multi-account-containers/confirm-page.html")
+ }
+ };
+
+ // 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/features/assignment.test.js b/test/features/assignment.test.js
new file mode 100644
index 0000000..f336962
--- /dev/null
+++ b/test/features/assignment.test.js
@@ -0,0 +1,74 @@
+describe("Assignment Feature", () => {
+ const activeTab = {
+ id: 1,
+ cookieStoreId: "firefox-container-1",
+ url: "http://example.com",
+ index: 0
+ };
+ beforeEach(async () => {
+ await helper.browser.initializeWithTab(activeTab);
+ });
+
+ describe("click the 'Always open in' checkbox in the popup", () => {
+ beforeEach(async () => {
+ // popup click to set assignment for activeTab.url
+ await helper.popup.clickElementById("container-page-assigned");
+ });
+
+ describe("open new Tab with the assigned URL in the default container", () => {
+ const newTab = {
+ id: 2,
+ cookieStoreId: "firefox-default",
+ url: activeTab.url,
+ index: 1,
+ active: true
+ };
+ beforeEach(async () => {
+ // new Tab opening activeTab.url in default container
+ await helper.browser.openNewTab(newTab);
+ });
+
+ it("should open the confirm page", async () => {
+ // should have created a new tab with the confirm page
+ background.browser.tabs.create.should.have.been.calledWith({
+ url: "moz-extension://multi-account-containers/confirm-page.html?" +
+ `url=${encodeURIComponent(activeTab.url)}` +
+ `&cookieStoreId=${activeTab.cookieStoreId}`,
+ cookieStoreId: undefined,
+ index: 2,
+ active: true
+ });
+ });
+
+ it("should remove the new Tab that got opened in the default container", () => {
+ background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
+ });
+ });
+
+ describe("click the 'Always open in' checkbox in the popup again", () => {
+ beforeEach(async () => {
+ // popup click to remove assignment for activeTab.url
+ await helper.popup.clickElementById("container-page-assigned");
+ });
+
+ describe("open new Tab with the no longer assigned URL in the default container", () => {
+ const newTab = {
+ id: 3,
+ cookieStoreId: "firefox-default",
+ url: activeTab.url,
+ index: 3,
+ active: true
+ };
+ beforeEach(async () => {
+ // new Tab opening activeTab.url in default container
+ await helper.browser.openNewTab(newTab);
+ });
+
+ it("should not open the confirm page", async () => {
+ // should not have created a new tab
+ background.browser.tabs.create.should.not.have.been.called;
+ });
+ });
+ });
+ });
+});
diff --git a/test/helper.js b/test/helper.js
new file mode 100644
index 0000000..a19750b
--- /dev/null
+++ b/test/helper.js
@@ -0,0 +1,41 @@
+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 openNewTab(tab) {
+ background.browser.tabs.get.resolves(tab);
+ background.browser.webRequest.onBeforeRequest.addListener.yield({
+ frameId: 0,
+ tabId: tab.id,
+ url: tab.url
+ });
+ background.browser.tabs.onCreated.addListener.yield(tab);
+ await nextTick();
+ }
+ },
+
+ popup: {
+ async clickElementById(id) {
+ const clickEvent = popup.document.createEvent("HTMLEvents");
+ clickEvent.initEvent("click");
+ popup.document.getElementById(id).dispatchEvent(clickEvent);
+ await nextTick();
+ }
+ },
+};
diff --git a/test/setup.js b/test/setup.js
new file mode 100644
index 0000000..9672bfc
--- /dev/null
+++ b/test/setup.js
@@ -0,0 +1,101 @@
+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");
+global.sinon = require("sinon");
+global.expect = chai.expect;
+chai.should();
+chai.use(sinonChai);
+global.nextTick = () => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ process.nextTick(resolve);
+ });
+ });
+};
+
+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);
+ }
+ }
+ });
+ 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);
+ }
+ }
+ });
+ 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
+ };
+};
+
+global.afterEach(() => {
+ if (global.background) {
+ global.background.dom.window.close();
+ delete global.background.dom;
+ }
+
+ if (global.popup) {
+ global.popup.dom.window.close();
+ delete global.popup.dom;
+ }
+});