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}],
|
"generator-star-spacing": [2, {"before": false, "after": true}],
|
||||||
|
|
||||||
// Two space indent
|
// Two space indent
|
||||||
"indent": [2, 2, {"SwitchCase": 1}],
|
"indent": [2, 2, {
|
||||||
|
"SwitchCase": 1,
|
||||||
|
"CallExpression": {"arguments": "first"}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
// Space after colon not before in property declarations
|
// Space after colon not before in property declarations
|
||||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
|
"key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
node_modules/*
|
node_modules/*
|
||||||
dist/*
|
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({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON("package.json"),
|
pkg: grunt.file.readJSON("package.json"),
|
||||||
|
|
||||||
|
coveralls: {
|
||||||
|
all: {
|
||||||
|
src: "coverage/lcov.info",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
eslint: {
|
eslint: {
|
||||||
src: ["browser-polyfill.in.js", "Gruntfile.js"],
|
src: ["src/browser-polyfill.js", "Gruntfile.js"],
|
||||||
|
test: ["test/**/*.js"],
|
||||||
},
|
},
|
||||||
|
|
||||||
replace: {
|
replace: {
|
||||||
|
@ -41,7 +48,7 @@ module.exports = function(grunt) {
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
flatten: true,
|
flatten: true,
|
||||||
src: ["browser-polyfill.in.js"],
|
src: ["src/browser-polyfill.js"],
|
||||||
dest: "dist/",
|
dest: "dist/",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -81,6 +88,7 @@ module.exports = function(grunt) {
|
||||||
|
|
||||||
grunt.loadNpmTasks("gruntify-eslint");
|
grunt.loadNpmTasks("gruntify-eslint");
|
||||||
grunt.loadNpmTasks("grunt-replace");
|
grunt.loadNpmTasks("grunt-replace");
|
||||||
|
grunt.loadNpmTasks("grunt-coveralls");
|
||||||
require("google-closure-compiler").grunt(grunt);
|
require("google-closure-compiler").grunt(grunt);
|
||||||
|
|
||||||
grunt.registerTask("default", ["eslint", "replace", "closure-compiler"]);
|
grunt.registerTask("default", ["eslint", "replace", "closure-compiler"]);
|
||||||
|
|
20
package.json
20
package.json
|
@ -13,9 +13,27 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/mozilla/webextension-polyfill",
|
"homepage": "https://github.com/mozilla/webextension-polyfill",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"chai": "^3.5.0",
|
||||||
"google-closure-compiler": "^20160911.0.0",
|
"google-closure-compiler": "^20160911.0.0",
|
||||||
"grunt": "^1.0.1",
|
"grunt": "^1.0.1",
|
||||||
|
"grunt-coveralls": "^1.0.1",
|
||||||
"grunt-replace": "*",
|
"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.
|
* The generated wrapper function.
|
||||||
*/
|
*/
|
||||||
const wrapAsyncFunction = (name, metadata) => {
|
const wrapAsyncFunction = (name, metadata) => {
|
||||||
|
const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments";
|
||||||
|
|
||||||
return function asyncFunctionWrapper(target, ...args) {
|
return function asyncFunctionWrapper(target, ...args) {
|
||||||
if (args.length < metadata.minArgs) {
|
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) {
|
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) => {
|
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