test: introduced a test suite for unit testing.
This commit is contained in:
parent
c882612855
commit
c08b8af982
|
@ -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#amo-bots
|
||||
on_success: change
|
||||
on_failure: always
|
|
@ -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"],
|
||||
tests: ["tests/**/*.js"],
|
||||
},
|
||||
|
||||
replace: {
|
||||
|
@ -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"]);
|
||||
|
|
19
package.json
19
package.json
|
@ -13,9 +13,26 @@
|
|||
},
|
||||
"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": "*",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../.eslintrc",
|
||||
"env": {
|
||||
"mocha": true,
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"webextensions": true
|
||||
},
|
||||
"globals": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
"use strict";
|
||||
|
||||
const {createInstrumenter} = require("istanbul-lib-instrument");
|
||||
const fs = require("fs");
|
||||
const {jsdom, createVirtualConsole} = require("jsdom");
|
||||
|
||||
var virtualConsole = createVirtualConsole().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";
|
||||
|
||||
function setupTestDOMWindow(chromeObject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let window;
|
||||
|
||||
// If a jsdom window has already been created, reuse it so that
|
||||
// we can retrieve the final code coverage data, which has been
|
||||
// collected in the jsdom window (where the instrumented browser-polyfill
|
||||
// is running).
|
||||
if (global.window) {
|
||||
window = global.window;
|
||||
window.browser = undefined;
|
||||
} else {
|
||||
window = jsdom({virtualConsole}).defaultView;
|
||||
global.window = window;
|
||||
}
|
||||
|
||||
// Inject the fake chrome object used as a fixture for the particular
|
||||
// browser-polyfill test scenario.
|
||||
window.chrome = chromeObject;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Prepare to listen for script loading errors (which results in a rejection),
|
||||
// and to detect when the browser-polyfill has been executed (which resolves
|
||||
// to the jsdom window where the loading has been completed).
|
||||
window.__browserPolyfillLoaded__ = {resolve, reject};
|
||||
window.addEventListener("error", (evt) => reject(evt));
|
||||
const loadedScriptEl = window.document.createElement("script");
|
||||
loadedScriptEl.textContent = `
|
||||
window.removeEventListener("error", window.__browserPolyfillLoaded__.reject);
|
||||
window.__browserPolyfillLoaded__.resolve(window);
|
||||
`;
|
||||
|
||||
window.document.body.appendChild(scriptEl);
|
||||
window.document.body.appendChild(loadedScriptEl);
|
||||
});
|
||||
}
|
||||
|
||||
// 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 (global.window && process.env.COVERAGE == "y") {
|
||||
global.__coverage__ = global.window.__coverage__;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
BROWSER_POLYFILL_PATH,
|
||||
setupTestDOMWindow,
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
"use strict";
|
||||
|
||||
const {assert} = require("chai");
|
||||
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: undefined},
|
||||
};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
fakeChrome.alarms.clear
|
||||
.onFirstCall().callsArgWith(1, "res1")
|
||||
.onSecondCall().callsArgWith(1, "res1", "res2", "res3");
|
||||
|
||||
return Promise.all([
|
||||
window.browser.alarms.clear("test1"),
|
||||
window.browser.alarms.clear("test2"),
|
||||
]);
|
||||
}).then(results => {
|
||||
assert.equal(results[0], "res1", "The first call resolved to a single value");
|
||||
assert.deepEqual(results[1], ["res1", "res2", "res3"],
|
||||
"The second call resolved to an array of the expected values");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
|
||||
const {assert} = require("chai");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
it("automatically wrapps chrome into a browser object", () => {
|
||||
const fakeChrome = {};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
assert.equal(typeof window.browser, "object", "Got the window.browser object");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
"use strict";
|
||||
|
||||
const {assert} = require("chai");
|
||||
const sinon = require("sinon");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
describe("proxies non-wrapped functions", () => {
|
||||
it("should proxy getters and setters", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {myprop: "previous-value"},
|
||||
nowrapns: {nowrapkey: "previous-value"},
|
||||
};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
const setResult = window.browser.runtime.myprop = "new-value";
|
||||
const setResult2 = window.browser.nowrapns.nowrapkey = "new-value";
|
||||
|
||||
assert.equal(setResult, "new-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
assert.equal(setResult2, "new-value",
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
});
|
||||
});
|
||||
|
||||
it("delete proxy getter/setter that are not wrapped", () => {
|
||||
const fakeChrome = {};
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
window.browser.newns = {newkey: "test-value"};
|
||||
assert.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"};
|
||||
assert.equal(window.browser.newns.newkey2, "new-value",
|
||||
"The new non-wrapped getter is cached");
|
||||
assert.deepEqual(setRes, {newkey2: "new-value"},
|
||||
"Got the expected result from setting a new wrapped property name");
|
||||
assert.deepEqual(window.browser.newns, window.chrome.newns,
|
||||
"chrome.newns and browser.newns are the same");
|
||||
|
||||
|
||||
delete window.browser.newns.newkey2;
|
||||
assert.equal(window.browser.newns.newkey2, undefined,
|
||||
"Got the expected result from setting a wrapped property name");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
"use strict";
|
||||
|
||||
const {assert} = require("chai");
|
||||
const sinon = require("sinon");
|
||||
|
||||
const {setupTestDOMWindow} = require("./setup");
|
||||
|
||||
describe("browser-polyfill", () => {
|
||||
describe("wrapped runtime.onMessage listener", () => {
|
||||
it("keep track of the listeners added", () => {
|
||||
const messageListener = sinon.spy();
|
||||
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: undefined,
|
||||
onMessage: {
|
||||
addListener: sinon.spy(),
|
||||
hasListener: sinon.stub(),
|
||||
removeListener: sinon.spy(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
fakeChrome.runtime.onMessage.hasListener
|
||||
.onFirstCall().returns(false)
|
||||
.onSecondCall().returns(true);
|
||||
|
||||
assert.equal(window.browser.runtime.onMessage.hasListener(messageListener),
|
||||
false, "Got hasListener==false before the listener has been added");
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
assert.equal(window.browser.runtime.onMessage.hasListener(messageListener),
|
||||
true, "Got hasListener=true once the listener has been added");
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
assert.ok(fakeChrome.runtime.onMessage.addListener.calledTwice,
|
||||
"addListener has been called twice");
|
||||
assert.equal(fakeChrome.runtime.onMessage.addListener.secondCall.args[0],
|
||||
fakeChrome.runtime.onMessage.addListener.firstCall.args[0],
|
||||
"both the addListener calls received the same wrapped listener");
|
||||
|
||||
const wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
|
||||
wrappedListener("msg", {name: "sender"}, function sendResponse() {});
|
||||
assert.ok(messageListener.calledOnce, "The listener has been called once");
|
||||
|
||||
window.browser.runtime.onMessage.removeListener(messageListener);
|
||||
assert.ok(fakeChrome.runtime.onMessage.removeListener.calledOnce,
|
||||
"removeListener has been called once");
|
||||
assert.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");
|
||||
});
|
||||
});
|
||||
|
||||
it("sends the returned value as a message response", () => {
|
||||
const fakeChrome = {
|
||||
runtime: {
|
||||
lastError: undefined,
|
||||
onMessage: {
|
||||
addListener: sinon.spy(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const messageListener = sinon.stub();
|
||||
const firstResponse = "fake reply";
|
||||
const secondResponse = Promise.resolve("fake reply2");
|
||||
const sendResponseSpy = sinon.spy();
|
||||
|
||||
messageListener.onFirstCall().returns(firstResponse)
|
||||
.onSecondCall().returns(secondResponse);
|
||||
|
||||
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||
window.browser.runtime.onMessage.addListener(messageListener);
|
||||
|
||||
assert.ok(fakeChrome.runtime.onMessage.addListener.calledOnce);
|
||||
|
||||
const wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
|
||||
|
||||
wrappedListener("fake message", {name: "fake sender"}, sendResponseSpy);
|
||||
|
||||
assert.ok(messageListener.calledOnce, "The unwrapped message listener has been called");
|
||||
assert.deepEqual(messageListener.firstCall.args,
|
||||
["fake message", {name: "fake sender"}],
|
||||
"The unwrapped message listener has received the expected parameters");
|
||||
|
||||
assert.ok(sendResponseSpy.calledOnce, "The sendResponse function has been called");
|
||||
assert.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(() => {
|
||||
assert.ok(messageListener.calledTwice,
|
||||
"The unwrapped message listener has been called");
|
||||
assert.deepEqual(messageListener.secondCall.args,
|
||||
["fake message2", {name: "fake sender2"}],
|
||||
"The unwrapped message listener has received the expected parameters");
|
||||
|
||||
assert.ok(sendResponseSpy.calledTwice, "The sendResponse function has been called");
|
||||
assert.equal(sendResponseSpy.secondCall.args[0], "fake reply2",
|
||||
"sendResponse callback has been called with the expected parameters");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue