test: introduced a test suite for unit testing.

This commit is contained in:
Luca Greco 2016-10-10 02:48:35 +02:00
parent c882612855
commit c08b8af982
10 changed files with 335 additions and 1 deletions

4
.gitignore vendored
View File

@ -1,2 +1,6 @@
node_modules/*
dist/*
## code coverage
coverage/
.nyc_output/

18
.travis.yml Normal file
View File

@ -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

View File

@ -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"]);

View File

@ -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"
}
}

11
test/.eslintrc Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "../.eslintrc",
"env": {
"mocha": true,
"node": true,
"browser": true,
"webextensions": true
},
"globals": {
}
}

75
test/setup.js Normal file
View File

@ -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,
};

View File

@ -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");
});
});
});
});

View File

@ -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");
});
});
});

View File

@ -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");
});
});
});
});

View File

@ -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");
});
});
});
});