Commit 59d4f7ea authored by Robert Knight's avatar Robert Knight

Add a helper function for writing parameterized JS tests

Mocha lacks built-in support [1] for writing parameterized tests and the
suggested solution [2] involves a bunch of boilerplate* which has IMO
resulted in different styles of parameterized tests in our codebase and
not having parameterized tests when they would be useful to attain more
complete coverage.

This adds a helper inspired by [3] for writing parameterized tests and
switches several existing places in our code to use it.

* Though less with ES2015 syntax.

[1] https://github.com/mochajs/mocha/issues/1454
[2] https://mochajs.org/#dynamically-generating-tests
[3] https://github.com/lawrencec/Unroll
parent 89afb494
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
var angular = require('angular'); var angular = require('angular');
var util = require('./util'); var util = require('./util');
var unroll = require('../../test/util').unroll;
describe('searchStatusBar', function () { describe('searchStatusBar', function () {
before(function () { before(function () {
...@@ -25,21 +26,19 @@ describe('searchStatusBar', function () { ...@@ -25,21 +26,19 @@ describe('searchStatusBar', function () {
}); });
context('when there is a selection', function () { context('when there is a selection', function () {
var cases = [ var FIXTURES = [
{count: 0, message: 'Show all annotations'}, {count: 0, message: 'Show all annotations'},
{count: 1, message: 'Show all annotations'}, {count: 1, message: 'Show all annotations'},
{count: 10, message: 'Show all 10 annotations'}, {count: 10, message: 'Show all 10 annotations'},
]; ];
cases.forEach(function (testCase) { unroll('should display the "Show all annotations" message when there are #count annotations', function (testCase) {
it('should display the "Show all annotations" message', function () { var elem = util.createDirective(document, 'searchStatusBar', {
var elem = util.createDirective(document, 'searchStatusBar', { selectionCount: 1,
selectionCount: 1, totalCount: testCase.count
totalCount: testCase.count
});
var clearBtn = elem[0].querySelector('button');
assert.include(clearBtn.textContent, testCase.message);
}); });
}); var clearBtn = elem[0].querySelector('button');
assert.include(clearBtn.textContent, testCase.message);
}, FIXTURES);
}); });
}); });
'use strict'; 'use strict';
var commands = require('../markdown-commands'); var commands = require('../markdown-commands');
var unroll = require('./util').unroll;
/** /**
* Convert a string containing '<sel>' and '</sel>' markers * Convert a string containing '<sel>' and '</sel>' markers
...@@ -72,33 +73,30 @@ describe('markdown commands', function () { ...@@ -72,33 +73,30 @@ describe('markdown commands', function () {
}); });
describe('block formatting', function () { describe('block formatting', function () {
var CASES = { var FIXTURES = [{
'adds formatting to blocks': { tag: 'adds formatting to blocks',
input: 'one\n<sel>two\nthree</sel>\nfour', input: 'one\n<sel>two\nthree</sel>\nfour',
output: 'one\n> <sel>two\n> three</sel>\nfour', output: 'one\n> <sel>two\n> three</sel>\nfour',
}, },{
'removes formatting from blocks': { tag: 'removes formatting from blocks',
input: 'one \n<sel>> two\n> three</sel>\nfour', input: 'one \n<sel>> two\n> three</sel>\nfour',
output: 'one \n<sel>two\nthree</sel>\nfour', output: 'one \n<sel>two\nthree</sel>\nfour',
}, },{
'preserves the selection': { tag: 'preserves the selection',
input: 'one <sel>two\nthree </sel>four', input: 'one <sel>two\nthree </sel>four',
output: '> one <sel>two\n> three </sel>four', output: '> one <sel>two\n> three </sel>four',
}, },{
'inserts the block prefix before an empty selection': { tag: 'inserts the block prefix before an empty selection',
input: '<sel></sel>', input: '<sel></sel>',
output: '> <sel></sel>', output: '> <sel></sel>',
} }];
};
unroll('#tag', function (fixture) {
Object.keys(CASES).forEach(function (case_) { var output = commands.toggleBlockStyle(
it(case_, function () { parseState(fixture.input), '> '
var output = commands.toggleBlockStyle( );
parseState(CASES[case_].input), '> ' assert.equal(formatState(output), fixture.output);
); }, FIXTURES);
assert.equal(formatState(output), CASES[case_].output);
});
});
}); });
describe('link formatting', function () { describe('link formatting', function () {
......
...@@ -34,7 +34,7 @@ describe('media-embedder', function () { ...@@ -34,7 +34,7 @@ describe('media-embedder', function () {
var urls = [ var urls = [
'https://youtu.be/QCkm0lL-6lc', 'https://youtu.be/QCkm0lL-6lc',
'https://youtu.be/QCkm0lL-6lc/', 'https://youtu.be/QCkm0lL-6lc/',
] ];
urls.forEach(function (url) { urls.forEach(function (url) {
var element = domElement('<a href="' + url + '">' + url + '</a>'); var element = domElement('<a href="' + url + '">' + url + '</a>');
...@@ -55,7 +55,7 @@ describe('media-embedder', function () { ...@@ -55,7 +55,7 @@ describe('media-embedder', function () {
'https://vimeo.com/149000090/#fragment', 'https://vimeo.com/149000090/#fragment',
'https://vimeo.com/149000090?foo=bar&a=b', 'https://vimeo.com/149000090?foo=bar&a=b',
'https://vimeo.com/149000090/?foo=bar&a=b', 'https://vimeo.com/149000090/?foo=bar&a=b',
] ];
urls.forEach(function (url) { urls.forEach(function (url) {
var element = domElement('<a href="' + url + '">' + url + '</a>'); var element = domElement('<a href="' + url + '">' + url + '</a>');
......
...@@ -22,6 +22,61 @@ function noCallThru(stubs) { ...@@ -22,6 +22,61 @@ function noCallThru(stubs) {
return Object.assign(stubs, {'@noCallThru':true}); return Object.assign(stubs, {'@noCallThru':true});
} }
/**
* Helper for writing parameterized tests.
*
* This is a wrapper around the `it()` function for creating a Mocha test case
* which takes an array of fixture objects and calls it() once for each fixture,
* passing in the fixture object as an argument to the test function.
*
* Usage:
* unroll('should return #output with #input', function (fixture) {
* assert.equal(functionUnderTest(fixture.input), fixture.output);
* },[
* {input: 'foo', output: 'bar'}
* ]);
*
* Based on https://github.com/lawrencec/Unroll with the following changes:
*
* 1. Support for test functions that return promises
* 2. Mocha's `it()` is the only supported test function
* 3. Fixtures are objects rather than arrays
*
* @param {string} description - Description with optional '#key' placeholders
* which are replaced by the values of the corresponding key from each
* fixture object.
* @param {Function} testFn - Test function which can accept either `fixture`
* or `done, fixture` as arguments, where `done` is the callback for
* reporting completion of an async test and `fixture` is an object
* from the `fixtures` array.
* @param {Array<T>} fixtures - Array of fixture objects.
*/
function unroll(description, testFn, fixtures) {
fixtures.forEach(function (fixture) {
var caseDescription = Object.keys(fixture).reduce(function (desc, key) {
return desc.replace('#' + key, String(fixture[key]));
}, description);
it(caseDescription, function (done) {
if (testFn.length === 1) {
// Test case does not accept a 'done' callback argument, so we either
// call done() immediately if it returns a non-Promiselike object
// or when the Promise resolves otherwise
var result = testFn(fixture);
if (typeof result === 'object' && result.then) {
result.then(function () { done(); }, done);
} else {
done();
}
} else {
// Test case accepts a 'done' callback argument and takes responsibility
// for calling it when the test completes.
testFn(done, fixture);
}
});
});
}
module.exports = { module.exports = {
noCallThru: noCallThru, noCallThru: noCallThru,
unroll: unroll,
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment