320 lines
7.9 KiB
JavaScript
320 lines
7.9 KiB
JavaScript
/*!
|
|
* Nodeunit
|
|
* Copyright (c) 2010 Caolan McMahon
|
|
* MIT Licensed
|
|
*
|
|
* THIS FILE SHOULD BE BROWSER-COMPATIBLE JS!
|
|
* You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build.
|
|
* Only code on that line will be removed, it's mostly to avoid requiring code
|
|
* that is node specific
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies
|
|
*/
|
|
|
|
var async = require('../deps/async'), //@REMOVE_LINE_FOR_BROWSER
|
|
nodeunit = require('./nodeunit'), //@REMOVE_LINE_FOR_BROWSER
|
|
types = require('./types'); //@REMOVE_LINE_FOR_BROWSER
|
|
|
|
|
|
/**
|
|
* Added for browser compatibility
|
|
*/
|
|
|
|
var _keys = function (obj) {
|
|
if (Object.keys) {
|
|
return Object.keys(obj);
|
|
}
|
|
var keys = [];
|
|
for (var k in obj) {
|
|
if (obj.hasOwnProperty(k)) {
|
|
keys.push(k);
|
|
}
|
|
}
|
|
return keys;
|
|
};
|
|
|
|
|
|
var _copy = function (obj) {
|
|
var nobj = {};
|
|
var keys = _keys(obj);
|
|
for (var i = 0; i < keys.length; i += 1) {
|
|
nobj[keys[i]] = obj[keys[i]];
|
|
}
|
|
return nobj;
|
|
};
|
|
|
|
|
|
/**
|
|
* Runs a test function (fn) from a loaded module. After the test function
|
|
* calls test.done(), the callback is executed with an assertionList as its
|
|
* second argument.
|
|
*
|
|
* @param {String} name
|
|
* @param {Function} fn
|
|
* @param {Object} opt
|
|
* @param {Function} callback
|
|
* @api public
|
|
*/
|
|
|
|
exports.runTest = function (name, fn, opt, callback) {
|
|
var options = types.options(opt);
|
|
|
|
options.testStart(name);
|
|
var start = new Date().getTime();
|
|
var test = types.test(name, start, options, callback);
|
|
|
|
options.testReady(test);
|
|
try {
|
|
fn(test);
|
|
}
|
|
catch (e) {
|
|
test.done(e);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Takes an object containing test functions or other test suites as properties
|
|
* and runs each in series. After all tests have completed, the callback is
|
|
* called with a list of all assertions as the second argument.
|
|
*
|
|
* If a name is passed to this function it is prepended to all test and suite
|
|
* names that run within it.
|
|
*
|
|
* @param {String} name
|
|
* @param {Object} suite
|
|
* @param {Object} opt
|
|
* @param {Function} callback
|
|
* @api public
|
|
*/
|
|
|
|
exports.runSuite = function (name, suite, opt, callback) {
|
|
suite = wrapGroup(suite);
|
|
var keys = _keys(suite);
|
|
|
|
async.concatSeries(keys, function (k, cb) {
|
|
var prop = suite[k], _name;
|
|
|
|
_name = name ? [].concat(name, k) : [k];
|
|
_name.toString = function () {
|
|
// fallback for old one
|
|
return this.join(' - ');
|
|
};
|
|
|
|
if (typeof prop === 'function') {
|
|
var in_name = false,
|
|
in_specific_test = (_name.toString() === opt.testFullSpec) ? true : false;
|
|
for (var i = 0; i < _name.length; i += 1) {
|
|
if (_name[i] === opt.testspec) {
|
|
in_name = true;
|
|
}
|
|
}
|
|
|
|
if ((!opt.testFullSpec || in_specific_test) && (!opt.testspec || in_name)) {
|
|
if (opt.moduleStart) {
|
|
opt.moduleStart();
|
|
}
|
|
exports.runTest(_name, suite[k], opt, cb);
|
|
}
|
|
else {
|
|
return cb();
|
|
}
|
|
}
|
|
else {
|
|
exports.runSuite(_name, suite[k], opt, cb);
|
|
}
|
|
}, callback);
|
|
};
|
|
|
|
/**
|
|
* Run each exported test function or test suite from a loaded module.
|
|
*
|
|
* @param {String} name
|
|
* @param {Object} mod
|
|
* @param {Object} opt
|
|
* @param {Function} callback
|
|
* @api public
|
|
*/
|
|
|
|
exports.runModule = function (name, mod, opt, callback) {
|
|
var options = _copy(types.options(opt));
|
|
|
|
var _run = false;
|
|
var _moduleStart = options.moduleStart;
|
|
|
|
mod = wrapGroup(mod);
|
|
|
|
function run_once() {
|
|
if (!_run) {
|
|
_run = true;
|
|
_moduleStart(name);
|
|
}
|
|
}
|
|
options.moduleStart = run_once;
|
|
|
|
var start = new Date().getTime();
|
|
|
|
exports.runSuite(null, mod, options, function (err, a_list) {
|
|
var end = new Date().getTime();
|
|
var assertion_list = types.assertionList(a_list, end - start);
|
|
options.moduleDone(name, assertion_list);
|
|
if (nodeunit.complete) {
|
|
nodeunit.complete(name, assertion_list);
|
|
}
|
|
callback(null, a_list);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Treats an object literal as a list of modules keyed by name. Runs each
|
|
* module and finished with calling 'done'. You can think of this as a browser
|
|
* safe alternative to runFiles in the nodeunit module.
|
|
*
|
|
* @param {Object} modules
|
|
* @param {Object} opt
|
|
* @api public
|
|
*/
|
|
|
|
// TODO: add proper unit tests for this function
|
|
exports.runModules = function (modules, opt) {
|
|
var all_assertions = [];
|
|
var options = types.options(opt);
|
|
var start = new Date().getTime();
|
|
|
|
async.concatSeries(_keys(modules), function (k, cb) {
|
|
exports.runModule(k, modules[k], options, cb);
|
|
},
|
|
function (err, all_assertions) {
|
|
var end = new Date().getTime();
|
|
options.done(types.assertionList(all_assertions, end - start));
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Wraps a test function with setUp and tearDown functions.
|
|
* Used by testCase.
|
|
*
|
|
* @param {Function} setUp
|
|
* @param {Function} tearDown
|
|
* @param {Function} fn
|
|
* @api private
|
|
*/
|
|
|
|
var wrapTest = function (setUp, tearDown, fn) {
|
|
return function (test) {
|
|
var context = {};
|
|
if (tearDown) {
|
|
var done = test.done;
|
|
test.done = function (err) {
|
|
try {
|
|
tearDown.call(context, function (err2) {
|
|
if (err && err2) {
|
|
test._assertion_list.push(
|
|
types.assertion({error: err})
|
|
);
|
|
return done(err2);
|
|
}
|
|
done(err || err2);
|
|
});
|
|
}
|
|
catch (e) {
|
|
done(e);
|
|
}
|
|
};
|
|
}
|
|
if (setUp) {
|
|
setUp.call(context, function (err) {
|
|
if (err) {
|
|
return test.done(err);
|
|
}
|
|
fn.call(context, test);
|
|
});
|
|
}
|
|
else {
|
|
fn.call(context, test);
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns a serial callback from two functions.
|
|
*
|
|
* @param {Function} funcFirst
|
|
* @param {Function} funcSecond
|
|
* @api private
|
|
*/
|
|
|
|
var getSerialCallback = function (fns) {
|
|
if (!fns.length) {
|
|
return null;
|
|
}
|
|
return function (callback) {
|
|
var that = this;
|
|
var bound_fns = [];
|
|
for (var i = 0, len = fns.length; i < len; i++) {
|
|
(function (j) {
|
|
bound_fns.push(function () {
|
|
return fns[j].apply(that, arguments);
|
|
});
|
|
})(i);
|
|
}
|
|
return async.series(bound_fns, callback);
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
* Wraps a group of tests with setUp and tearDown functions.
|
|
* Used by testCase.
|
|
*
|
|
* @param {Object} group
|
|
* @param {Array} setUps - parent setUp functions
|
|
* @param {Array} tearDowns - parent tearDown functions
|
|
* @api private
|
|
*/
|
|
|
|
var wrapGroup = function (group, setUps, tearDowns) {
|
|
var tests = {};
|
|
|
|
var setUps = setUps ? setUps.slice(): [];
|
|
var tearDowns = tearDowns ? tearDowns.slice(): [];
|
|
|
|
if (group.setUp) {
|
|
setUps.push(group.setUp);
|
|
delete group.setUp;
|
|
}
|
|
if (group.tearDown) {
|
|
tearDowns.unshift(group.tearDown);
|
|
delete group.tearDown;
|
|
}
|
|
|
|
var keys = _keys(group);
|
|
|
|
for (var i = 0; i < keys.length; i += 1) {
|
|
var k = keys[i];
|
|
if (typeof group[k] === 'function') {
|
|
tests[k] = wrapTest(
|
|
getSerialCallback(setUps),
|
|
getSerialCallback(tearDowns),
|
|
group[k]
|
|
);
|
|
}
|
|
else if (typeof group[k] === 'object') {
|
|
tests[k] = wrapGroup(group[k], setUps, tearDowns);
|
|
}
|
|
}
|
|
return tests;
|
|
};
|
|
|
|
|
|
/**
|
|
* Backwards compatibility for test suites using old testCase API
|
|
*/
|
|
|
|
exports.testCase = function (suite) {
|
|
return suite;
|
|
};
|