| /*! |
| * 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; |
| }; |