'use strict'; var EventEmitter = require('events').EventEmitter; var util = require('util'); var Promise = require('bluebird'); var optionChain = require('option-chain'); var matcher = require('matcher'); var TestCollection = require('./test-collection'); function noop() {} var chainableMethods = { defaults: { type: 'test', serial: false, exclusive: false, skipped: false, todo: false, failing: false, callback: false, always: false }, chainableMethods: { test: {}, serial: {serial: true}, before: {type: 'before'}, after: {type: 'after'}, skip: {skipped: true}, todo: {todo: true}, failing: {failing: true}, only: {exclusive: true}, beforeEach: {type: 'beforeEach'}, afterEach: {type: 'afterEach'}, cb: {callback: true}, always: {always: true} } }; function Runner(options) { if (!(this instanceof Runner)) { throw new TypeError('Class constructor Runner cannot be invoked without \'new\''); } EventEmitter.call(this); options = options || {}; this.results = []; this.tests = new TestCollection(); this._bail = options.bail; this._serial = options.serial; this._match = options.match || []; this._addTestResult = this._addTestResult.bind(this); this._buildStats = this._buildStats.bind(this); } util.inherits(Runner, EventEmitter); module.exports = Runner; optionChain(chainableMethods, function (opts, args) { var title; var fn; var macroArgIndex; if (typeof args[0] === 'string') { title = args[0]; fn = args[1]; macroArgIndex = 2; } else { fn = args[0]; title = null; macroArgIndex = 1; } if (this._serial) { opts.serial = true; } if (args.length > macroArgIndex) { args = args.slice(macroArgIndex); } else { args = null; } if (Array.isArray(fn)) { fn.forEach(function (fn) { this._addTest(title, opts, fn, args); }, this); } else { this._addTest(title, opts, fn, args); } }, Runner.prototype); function wrapFunction(fn, args) { return function (t) { return fn.apply(this, [t].concat(args)); }; } Runner.prototype._addTest = function (title, opts, fn, args) { if (args) { if (fn.title) { title = fn.title.apply(fn, [title || ''].concat(args)); } fn = wrapFunction(fn, args); } if (opts.type === 'test' && this._match.length > 0) { opts.exclusive = title !== null && matcher([title], this._match).length === 1; } if (opts.todo) { if (typeof fn === 'function') { throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.'); } fn = noop; if (typeof title !== 'string') { throw new TypeError('`todo` tests require a title'); } } else if (typeof fn !== 'function') { throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.'); } this.tests.add({ metadata: opts, fn: fn, title: title }); }; Runner.prototype._addTestResult = function (result) { var test = result.result; var props = { duration: test.duration, title: test.title, error: result.reason, type: test.metadata.type, skip: test.metadata.skipped, todo: test.metadata.todo, failing: test.metadata.failing }; this.results.push(result); this.emit('test', props); }; Runner.prototype._buildStats = function () { var stats = { testCount: 0, skipCount: 0, todoCount: 0 }; this.results .map(function (result) { return result.result; }) .filter(function (test) { return test.metadata.type === 'test'; }) .forEach(function (test) { stats.testCount++; if (test.metadata.skipped) { stats.skipCount++; } if (test.metadata.todo) { stats.todoCount++; } }); stats.failCount = this.results .filter(function (result) { return result.passed === false; }) .length; stats.knownFailureCount = this.results .filter(function (result) { return result.passed === true && result.result.metadata.failing; }) .length; stats.passCount = stats.testCount - stats.failCount - stats.skipCount - stats.todoCount; return stats; }; Runner.prototype.run = function (options) { if (options.runOnlyExclusive && !this.tests.hasExclusive) { return Promise.resolve(null); } this.tests.on('test', this._addTestResult); return Promise.resolve(this.tests.build(this._bail).run()).then(this._buildStats); };