test: introduced a test suite for unit testing.
Merge pull request #2 from rpl/proposal/unit-testing
This commit is contained in:
commit
df71efea17
|
@ -69,7 +69,11 @@
|
|||
"generator-star-spacing": [2, {"before": false, "after": true}],
|
||||
|
||||
// Two space indent
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"indent": [2, 2, {
|
||||
"SwitchCase": 1,
|
||||
"CallExpression": {"arguments": "first"}
|
||||
}
|
||||
],
|
||||
|
||||
// Space after colon not before in property declarations
|
||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
node_modules/*
|
||||
dist/*
|
||||
|
||||
## code coverage
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
## Some of the ES6 syntax used in the browser-polyfill sources is only supported on nodejs >= 6
|
||||
- '6'
|
||||
|
||||
script:
|
||||
- npm run build
|
||||
- COVERAGE=y npm run test-coverage
|
||||
|
||||
after_script: npm run publish-coverage
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- irc.mozilla.org#webextensions
|
||||
on_success: change
|
||||
on_failure: always
|
12
Gruntfile.js
12
Gruntfile.js
|
@ -12,8 +12,15 @@ module.exports = function(grunt) {
|
|||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
|
||||
coveralls: {
|
||||
all: {
|
||||
src: "coverage/lcov.info",
|
||||
},
|
||||
},
|
||||
|
||||
eslint: {
|
||||
src: ["browser-polyfill.in.js", "Gruntfile.js"],
|
||||
src: ["src/browser-polyfill.js", "Gruntfile.js"],
|
||||
test: ["test/**/*.js"],
|
||||
},
|
||||
|
||||
replace: {
|
||||
|
@ -41,7 +48,7 @@ module.exports = function(grunt) {
|
|||
{
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: ["browser-polyfill.in.js"],
|
||||
src: ["src/browser-polyfill.js"],
|
||||
dest: "dist/",
|
||||
},
|
||||
],
|
||||
|
@ -81,6 +88,7 @@ module.exports = function(grunt) {
|
|||
|
||||
grunt.loadNpmTasks("gruntify-eslint");
|
||||
grunt.loadNpmTasks("grunt-replace");
|
||||
grunt.loadNpmTasks("grunt-coveralls");
|
||||
require("google-closure-compiler").grunt(grunt);
|
||||
|
||||
grunt.registerTask("default", ["eslint", "replace", "closure-compiler"]);
|
||||
|
|
20
package.json
20
package.json
|
@ -13,9 +13,27 @@
|
|||
},
|
||||
"homepage": "https://github.com/mozilla/webextension-polyfill",
|
||||
"devDependencies": {
|
||||
"chai": "^3.5.0",
|
||||
"google-closure-compiler": "^20160911.0.0",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-coveralls": "^1.0.1",
|
||||
"grunt-replace": "*",
|
||||
"gruntify-eslint": "*"
|
||||
"gruntify-eslint": "*",
|
||||
"eslint": "3.9.1",
|
||||
"istanbul-lib-instrument": "^1.1.3",
|
||||
"jsdom": "^9.6.0",
|
||||
"mocha": "^3.1.0",
|
||||
"nyc": "^8.3.1",
|
||||
"sinon": "^1.17.6"
|
||||
},
|
||||
"nyc": {
|
||||
"reporter": ["lcov", "text", "html"],
|
||||
"instrument": false
|
||||
},
|
||||
"scripts": {
|
||||
"build": "grunt",
|
||||
"test": "mocha",
|
||||
"test-coverage": "COVERAGE=y nyc mocha",
|
||||
"publish-coverage": "grunt coveralls"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,13 +105,15 @@ if (typeof browser === "undefined") {
|
|||
* The generated wrapper function.
|
||||
*/
|
||||
const wrapAsyncFunction = (name, metadata) => {
|
||||
const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments";
|
||||
|
||||
return function asyncFunctionWrapper(target, ...args) {
|
||||
if (args.length < metadata.minArgs) {
|
||||
throw new Error(`Expected at least ${metadata.minArgs} arguments for ${name}(), got ${args.length}`);
|
||||
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
||||
}
|
||||
|
||||
if (args.length > metadata.maxArgs) {
|
||||
throw new Error(`Expected at most ${metadata.maxArgs} arguments for ${name}(), got ${args.length}`);
|
||||
throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"env": {
|
||||
"mocha": true,
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"webextensions": true
|
||||
},
|
||||
"globals": {},
|
||||
"rules": {
|
||||
"max-nested-callbacks": ["warn", 6]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const {createInstrumenter} = require("istanbul-lib-instrument");
|
||||
const {jsdom, createVirtualConsole} = require("jsdom");
|
||||
|
||||
var virtualConsole = createVirtualConsole();
|
||||
|
||||
// Optionally print console logs from the jsdom window.
|
||||
if (process.env.ENABLE_JSDOM_CONSOLE == "y") {
|
||||
virtualConsole.sendTo(console);
|
||||
}
|
||||
|
||||
// Path to the browser-polyfill script, relative to the current work dir
|
||||
// where mocha is executed.
|
||||
const BROWSER_POLYFILL_PATH = "./dist/browser-polyfill.js";
|
||||
|
||||
// Create the jsdom window used to run the tests
|
||||
const testDOMWindow = jsdom("", {virtualConsole}).defaultView;
|
||||
|
||||
// Copy the code coverage of the browser-polyfill script from the jsdom window
|
||||
// to the nodejs global, where nyc expects to find the code coverage data to
|
||||
// render in the reports.
|
||||
after(() => {
|
||||
if (testDOMWindow && process.env.COVERAGE == "y") {
|
||||
global.__coverage__ = testDOMWindow.__coverage__;
|
||||
}
|
||||
});
|
||||
|
||||
function setupTestDOMWindow(chromeObject, browserObject = undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const window = testDOMWindow;
|
||||
|
||||
// Inject the fake chrome object used as a fixture for the particular
|
||||
// browser-polyfill test scenario.
|
||||
window.chrome = chromeObject;
|
||||
|
||||
// Set (or reset) the browser property.
|
||||
if (browserObject) {
|
||||
window.browser = browserObject;
|
||||
} else {
|
||||
// TODO: change into `delete window.browser` once tmpvar/jsdom#1622 has been fixed.
|
||||
window.browser = undefined;
|
||||
}
|
||||
|
||||
const scriptEl = window.document.createElement("script");
|
||||
|
||||
if (process.env.COVERAGE == "y") {
|
||||
// If the code coverage is enabled, instrument the code on the fly
|
||||
// before executing it in the jsdom window.
|
||||
const inst = createInstrumenter({
|
||||
compact: false, esModules: false, produceSourceMap: false,
|
||||
});
|
||||
const scriptContent = fs.readFileSync(BROWSER_POLYFILL_PATH, "utf-8");
|
||||
scriptEl.textContent = inst.instrumentSync(scriptContent, BROWSER_POLYFILL_PATH);
|
||||
} else {
|
||||
scriptEl.src = BROWSER_POLYFILL_PATH;
|
||||
}
|
||||
|
||||
let onLoad;
|
||||
let onLoadError;
|
||||
let onError;
|
||||
|
||||
let cleanLoadListeners = () => {
|
||||
scriptEl.removeEventListener("load", onLoad);
|
||||
scriptEl.removeEventListener("error", onLoadError);
|
||||
|
||||
window.removeEventListener("error", onError);
|
||||
};
|
||||
|
||||
onLoad = () => { cleanLoadListeners(); resolve(window); };
|
||||
onLoadError = () => {
|
||||
cleanLoadListeners();
|
||||
reject(new Error(`Error loading script: ${BROWSER_POLYFILL_PATH}`));
|
||||
};
|
||||
onError = (err) => { cleanLoadListeners(); reject(err); };
|
||||
|
||||
// Listen to any uncaught errors.
|
||||
window.addEventListener("error", onError);
|
||||
scriptEl.addEventListener("error", onLoadError);
|
||||
|
||||
scriptEl.addEventListener("load", onLoad);
|
||||
|
||||
window.document.body.appendChild(scriptEl);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BROWSER_POLYFILL_PATH,
|
||||
setupTestDOMWindow,
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
"use strict";
|
||||
|
||||
const {deepEqual, equal, fail, throws} = require("chai").assert;
|
||||
const sinon = require("sinon");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
describe("wrapped async functions", () => {
|
||||
it("returns a promise which resolves to the callback parameters", () => {
|
||||
const fakeChrome = {
|
||||
alarms: {clear: sinon.stub()},
|
||||
runtime: {
|
||||
lastError: null,
|
||||
requestUpdateCheck: sinon.stub(),
|
||||
},
|
||||
tabs: {
|
||||
query: sinon.stub(),
|
||||
},
|
||||
};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
// Test for single callback argument.
|
||||
fakeChrome.alarms.clear
|
||||
.onFirstCall().callsArgWith(1, "res1");
|
||||
|
||||
// Test for single array callback argument.
|
||||
fakeChrome.tabs.query
|
||||
.onFirstCall().callsArgWith(1, ["res1", "res2"]);
|
||||
|
||||
// Test for multiple callback arguments.
|
||||
fakeChrome.runtime.requestUpdateCheck
|
||||
.onFirstCall().callsArgWith(0, "res1", "res2");
|
||||
|
||||
return Promise.all([
|
||||
window.browser.alarms.clear("test1"),
|
||||
window.browser.tabs.query({active: true}),
|
||||
window.browser.runtime.requestUpdateCheck(),
|
||||
]);
|
||||
}).then(results => {
|
||||
equal(results[0], "res1", "Fake alarms.clear call resolved to a single value");
|
||||
deepEqual(results[1], ["res1", "res2"],
|
||||
"Fake tabs.query resolved to an array of values");
|
||||
deepEqual(results[2], ["res1", "res2"],
|
||||
"Fake runtime.requestUpdateCheck resolved to an array of values");
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects the returned promise if chrome.runtime.lastError is not null", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: new Error("fake lastError"),
|
||||
},
|
||||
tabs: {
|
||||
query: sinon.stub(),
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
// Test for single array callback argument.
|
||||
fakeChrome.tabs.query
|
||||
.onFirstCall().callsArgWith(1, ["res1", "res2"]);
|
||||
|
||||
return window.browser.tabs.query({active: true}).then(
|
||||
() => fail("Expected a rejected promise"),
|
||||
(err) => equal(err, fakeChrome.runtime.lastError,
|
||||
"Got the expected error in the rejected promise")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("throws if the number of arguments are not in the range defined in the metadata", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: null,
|
||||
sendMessage: sinon.spy(),
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
throws(() => {
|
||||
window.browser.runtime.sendMessage();
|
||||
}, "Expected at least 1 argument for sendMessage(), got 0");
|
||||
|
||||
throws(() => {
|
||||
window.browser.runtime.sendMessage("0", "1", "2", "3");
|
||||
}, "Expected at most 3 arguments for sendMessage(), got 4");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
"use strict";
|
||||
|
||||
const {deepEqual, equal, ok} = require("chai").assert;
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
it("wraps the global chrome namespace with a global browser namespace", () => {
|
||||
const fakeChrome = {};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
equal(typeof window.browser, "object", "Got the window.browser object");
|
||||
});
|
||||
});
|
||||
|
||||
it("does not override the global browser namespace if it already exists", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {lastError: null},
|
||||
};
|
||||
const fakeBrowser = {
|
||||
mycustomns: {mykey: true},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome, fakeBrowser).then(window => {
|
||||
deepEqual(window.browser, fakeBrowser,
|
||||
"The existing browser has not been wrapped");
|
||||
});
|
||||
});
|
||||
|
||||
describe("browser wrapper", () => {
|
||||
it("supports custom properties defined using Object.defineProperty", () => {
|
||||
const fakeChrome = {};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
Object.defineProperty(window.browser, "myns", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: {mykey: true},
|
||||
});
|
||||
|
||||
ok("myns" in window.browser, "The custom property exists");
|
||||
ok("mykey" in window.browser.myns,
|
||||
"The content of the custom property exists");
|
||||
|
||||
deepEqual(window.browser.myns, {mykey: true},
|
||||
"The custom property has the expected content");
|
||||
|
||||
delete window.browser.myns;
|
||||
|
||||
ok(!("myns" in window.browser),
|
||||
"The deleted custom defined property has been removed");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined for property undefined in the target", () => {
|
||||
const fakeChrome = {myns: {mykey: true}};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
equal(window.browser.myns.mykey, true,
|
||||
"Got the expected result from a wrapped property");
|
||||
equal(window.browser.myns.nonexistent, undefined,
|
||||
"Got undefined for non existent property");
|
||||
equal(window.browser.nonexistent, undefined,
|
||||
"Got undefined for non existent namespaces");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
"use strict";
|
||||
|
||||
const {deepEqual, equal, ok} = require("chai").assert;
|
||||
const sinon = require("sinon");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
describe("proxies non-wrapped functions", () => {
|
||||
it("should proxy non-wrapped methods", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
nonwrappedmethod: sinon.spy(),
|
||||
},
|
||||
};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
ok(window.browser.runtime.nonwrappedmethod);
|
||||
|
||||
const fakeCallback = () => {};
|
||||
window.browser.runtime.nonwrappedmethod(fakeCallback);
|
||||
|
||||
const receivedCallback = fakeChrome.runtime.nonwrappedmethod.firstCall.args[0];
|
||||
|
||||
equal(fakeCallback, receivedCallback,
|
||||
"The callback has not been wrapped for the nonwrappedmethod");
|
||||
});
|
||||
});
|
||||
|
||||
it("should proxy getters and setters", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {myprop: "previous-value"},
|
||||
nowrapns: {
|
||||
nowrapkey: "previous-value",
|
||||
nowrapkey2: "previous-value",
|
||||
},
|
||||
};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
// Check that the property values on the generated wrapper.
|
||||
equal(window.browser.runtime.myprop, "previous-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
equal(window.browser.nowrapns.nowrapkey, "previous-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
|
||||
// Update the properties on the generated wrapper.
|
||||
const setResult = window.browser.runtime.myprop = "new-value";
|
||||
const setResult2 = window.browser.nowrapns.nowrapkey = "new-value";
|
||||
|
||||
// Check the results of setting the new value of the wrapped properties.
|
||||
equal(setResult, "new-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
equal(setResult2, "new-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
|
||||
// Verify that the wrapped properties has been updated.
|
||||
equal(window.browser.runtime.myprop, "new-value",
|
||||
"Got the expected updated value from the browser property");
|
||||
equal(window.browser.nowrapns.nowrapkey, "new-value",
|
||||
"Got the expected updated value from the browser property");
|
||||
|
||||
// Verify that the target properties has been updated.
|
||||
equal(window.chrome.runtime.myprop, "new-value",
|
||||
"Got the expected updated value on the related chrome property");
|
||||
equal(window.chrome.nowrapns.nowrapkey, "new-value",
|
||||
"Got the expected updated value on the related chrome property");
|
||||
|
||||
// Set a property multiple times before read.
|
||||
window.browser.nowrapns.nowrapkey2 = "new-value2";
|
||||
window.browser.nowrapns.nowrapkey2 = "new-value3";
|
||||
|
||||
equal(window.chrome.nowrapns.nowrapkey2, "new-value3",
|
||||
"Got the expected updated value on the related chrome property");
|
||||
equal(window.browser.nowrapns.nowrapkey2, "new-value3",
|
||||
"Got the expected updated value on the wrapped property");
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes proxy getter/setter that are not wrapped", () => {
|
||||
const fakeChrome = {};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
window.browser.newns = {newkey: "test-value"};
|
||||
|
||||
ok("newns" in window.browser, "The custom namespace is in the wrapper");
|
||||
ok("newns" in window.chrome, "The custom namespace is in the target");
|
||||
|
||||
equal(window.browser.newns.newkey, "test-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
|
||||
const setRes = window.browser.newns = {newkey2: "new-value"};
|
||||
equal(window.browser.newns.newkey2, "new-value",
|
||||
"The new non-wrapped getter is cached");
|
||||
deepEqual(setRes, {newkey2: "new-value"},
|
||||
"Got the expected result from setting a new wrapped property name");
|
||||
deepEqual(window.browser.newns, window.chrome.newns,
|
||||
"chrome.newns and browser.newns are the same");
|
||||
|
||||
delete window.browser.newns.newkey2;
|
||||
equal(window.browser.newns.newkey2, undefined,
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
ok(!("newkey2" in window.browser.newns),
|
||||
"The deleted property is not listed anymore");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,199 @@
|
|||
"use strict";
|
||||
|
||||
const {deepEqual, equal, ok} = require("chai").assert;
|
||||
const sinon = require("sinon");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
describe("wrapped runtime.onMessage listener", () => {
|
||||
it("does not wrap the listener if it is not a function", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: null,
|
||||
onMessage: {
|
||||
addListener: sinon.spy(),
|
||||
hasListener: sinon.stub(),
|
||||
removeListener: sinon.spy(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
const fakeNonFunctionListener = {fake: "non function listener"};
|
||||
|
||||
window.browser.runtime.onMessage.addListener(fakeNonFunctionListener);
|
||||
|
||||
deepEqual(fakeChrome.runtime.onMessage.addListener.firstCall.args[0],
|
||||
fakeNonFunctionListener,
|
||||
"The non-function listener has not been wrapped");
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps track of the listeners added", () => {
|
||||
const messageListener = sinon.spy();
|
||||
const fakeChromeListeners = new Set();
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: null,
|
||||
onMessage: {
|
||||
addListener: sinon.spy((listener, ...args) => {
|
||||
fakeChromeListeners.add(listener);
|
||||
}),
|
||||
hasListener: sinon.spy(listener => fakeChromeListeners.has(listener)),
|
||||
removeListener: sinon.spy(listener => {
|
||||
fakeChromeListeners.delete(listener);
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
equal(window.browser.runtime.onMessage.hasListener(messageListener),
|
||||
false, "Got hasListener==false before the listener has been added");
|
||||
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
equal(window.browser.runtime.onMessage.hasListener(messageListener),
|
||||
true, "Got hasListener==true once the listener has been added");
|
||||
|
||||
// Add the same listener again to test that it will be called with the
|
||||
// same wrapped listener.
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
ok(fakeChrome.runtime.onMessage.addListener.calledTwice,
|
||||
"addListener has been called twice");
|
||||
equal(fakeChrome.runtime.onMessage.addListener.secondCall.args[0],
|
||||
fakeChrome.runtime.onMessage.addListener.firstCall.args[0],
|
||||
"both the addListener calls received the same wrapped listener");
|
||||
|
||||
// Retrieve the wrapped listener and execute it to fake a received message.
|
||||
const wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
|
||||
wrappedListener("msg", {name: "sender"}, function sendResponse() {});
|
||||
ok(messageListener.calledOnce, "The listener has been called once");
|
||||
|
||||
// Remove the listener.
|
||||
window.browser.runtime.onMessage.removeListener(messageListener);
|
||||
ok(fakeChrome.runtime.onMessage.removeListener.calledOnce,
|
||||
"removeListener has been called once");
|
||||
equal(fakeChrome.runtime.onMessage.addListener.secondCall.args[0],
|
||||
fakeChrome.runtime.onMessage.removeListener.firstCall.args[0],
|
||||
"both the addListener and removeListenercalls received the same wrapped listener");
|
||||
equal(fakeChrome.runtime.onMessage.hasListener(messageListener), false,
|
||||
"Got hasListener==false once the listener has been removed");
|
||||
});
|
||||
});
|
||||
|
||||
it("generates different wrappers for different listeners", () => {
|
||||
const fakeChromeListeners = [];
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: null,
|
||||
onMessage: {
|
||||
addListener: sinon.spy((listener, ...args) => {
|
||||
fakeChromeListeners.push(listener);
|
||||
}),
|
||||
hasListener: sinon.spy(),
|
||||
removeListener: sinon.spy(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
const firstMessageListener = sinon.spy();
|
||||
const secondMessageListener = sinon.spy();
|
||||
|
||||
window.browser.runtime.onMessage.addListener(firstMessageListener);
|
||||
window.browser.runtime.onMessage.addListener(secondMessageListener);
|
||||
|
||||
equal(fakeChromeListeners.length, 2, "Got two wrapped listeners");
|
||||
|
||||
fakeChromeListeners[0]("call first wrapper");
|
||||
ok(firstMessageListener.calledOnce);
|
||||
equal(firstMessageListener.firstCall.args[0], "call first wrapper");
|
||||
|
||||
fakeChromeListeners[1]("call second wrapper");
|
||||
ok(secondMessageListener.calledOnce);
|
||||
equal(secondMessageListener.firstCall.args[0], "call second wrapper");
|
||||
});
|
||||
});
|
||||
|
||||
it("sends the returned value as a message response", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: null,
|
||||
onMessage: {
|
||||
addListener: sinon.spy(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Plain value returned.
|
||||
const messageListener = sinon.stub();
|
||||
const firstResponse = "fake reply";
|
||||
// Resolved Promise returned.
|
||||
const secondResponse = Promise.resolve("fake reply 2");
|
||||
// Rejected Promise returned.
|
||||
const thirdResponse = Promise.reject("fake error 3");
|
||||
|
||||
const sendResponseSpy = sinon.spy();
|
||||
|
||||
messageListener
|
||||
.onFirstCall().returns(firstResponse)
|
||||
.onSecondCall().returns(secondResponse)
|
||||
.onThirdCall().returns(thirdResponse);
|
||||
|
||||
let wrappedListener;
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
ok(fakeChrome.runtime.onMessage.addListener.calledOnce);
|
||||
|
||||
wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
|
||||
|
||||
wrappedListener("fake message", {name: "fake sender"}, sendResponseSpy);
|
||||
|
||||
ok(messageListener.calledOnce, "The unwrapped message listener has been called");
|
||||
deepEqual(messageListener.firstCall.args,
|
||||
["fake message", {name: "fake sender"}],
|
||||
"The unwrapped message listener has received the expected parameters");
|
||||
|
||||
ok(sendResponseSpy.calledOnce, "The sendResponse function has been called");
|
||||
equal(sendResponseSpy.firstCall.args[0], "fake reply",
|
||||
"sendResponse callback has been called with the expected parameters");
|
||||
|
||||
wrappedListener("fake message2", {name: "fake sender2"}, sendResponseSpy);
|
||||
|
||||
// Wait the second response promise to be resolved.
|
||||
return secondResponse;
|
||||
}).then(() => {
|
||||
ok(messageListener.calledTwice,
|
||||
"The unwrapped message listener has been called");
|
||||
deepEqual(messageListener.secondCall.args,
|
||||
["fake message2", {name: "fake sender2"}],
|
||||
"The unwrapped listener has received the expected parameters");
|
||||
|
||||
ok(sendResponseSpy.calledTwice, "The sendResponse function has been called");
|
||||
equal(sendResponseSpy.secondCall.args[0], "fake reply 2",
|
||||
"sendResponse callback has been called with the expected parameters");
|
||||
}).then(() => {
|
||||
wrappedListener("fake message3", {name: "fake sender3"}, sendResponseSpy);
|
||||
|
||||
// Wait the third response promise to be rejected.
|
||||
return thirdResponse.catch(err => {
|
||||
equal(messageListener.callCount, 3,
|
||||
"The unwrapped message listener has been called");
|
||||
deepEqual(messageListener.thirdCall.args,
|
||||
["fake message3", {name: "fake sender3"}],
|
||||
"The unwrapped listener has received the expected parameters");
|
||||
|
||||
equal(sendResponseSpy.callCount, 3,
|
||||
"The sendResponse function has been called");
|
||||
equal(sendResponseSpy.thirdCall.args[0], err,
|
||||
"sendResponse callback has been called with the expected parameters");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue