You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

200 lines
4.2 KiB

'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);
};