415 lines
8.8 KiB
JavaScript
415 lines
8.8 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var EventEmitter = require('events').EventEmitter;
|
||
|
var Hook = require('./hook');
|
||
|
var utils = require('./utils');
|
||
|
var inherits = utils.inherits;
|
||
|
var debug = require('debug')('mocha:suite');
|
||
|
var milliseconds = require('./ms');
|
||
|
|
||
|
/**
|
||
|
* Expose `Suite`.
|
||
|
*/
|
||
|
|
||
|
exports = module.exports = Suite;
|
||
|
|
||
|
/**
|
||
|
* Create a new `Suite` with the given `title` and parent `Suite`. When a suite
|
||
|
* with the same title is already present, that suite is returned to provide
|
||
|
* nicer reporter and more flexible meta-testing.
|
||
|
*
|
||
|
* @api public
|
||
|
* @param {Suite} parent
|
||
|
* @param {string} title
|
||
|
* @return {Suite}
|
||
|
*/
|
||
|
exports.create = function (parent, title) {
|
||
|
var suite = new Suite(title, parent.ctx);
|
||
|
suite.parent = parent;
|
||
|
title = suite.fullTitle();
|
||
|
parent.addSuite(suite);
|
||
|
return suite;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Initialize a new `Suite` with the given `title` and `ctx`.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {string} title
|
||
|
* @param {Context} parentContext
|
||
|
*/
|
||
|
function Suite (title, parentContext) {
|
||
|
if (!utils.isString(title)) {
|
||
|
throw new Error('Suite `title` should be a "string" but "' + typeof title + '" was given instead.');
|
||
|
}
|
||
|
this.title = title;
|
||
|
function Context () {}
|
||
|
Context.prototype = parentContext;
|
||
|
this.ctx = new Context();
|
||
|
this.suites = [];
|
||
|
this.tests = [];
|
||
|
this.pending = false;
|
||
|
this._beforeEach = [];
|
||
|
this._beforeAll = [];
|
||
|
this._afterEach = [];
|
||
|
this._afterAll = [];
|
||
|
this.root = !title;
|
||
|
this._timeout = 2000;
|
||
|
this._enableTimeouts = true;
|
||
|
this._slow = 75;
|
||
|
this._bail = false;
|
||
|
this._retries = -1;
|
||
|
this._onlyTests = [];
|
||
|
this._onlySuites = [];
|
||
|
this.delayed = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inherit from `EventEmitter.prototype`.
|
||
|
*/
|
||
|
inherits(Suite, EventEmitter);
|
||
|
|
||
|
/**
|
||
|
* Return a clone of this `Suite`.
|
||
|
*
|
||
|
* @api private
|
||
|
* @return {Suite}
|
||
|
*/
|
||
|
Suite.prototype.clone = function () {
|
||
|
var suite = new Suite(this.title);
|
||
|
debug('clone');
|
||
|
suite.ctx = this.ctx;
|
||
|
suite.timeout(this.timeout());
|
||
|
suite.retries(this.retries());
|
||
|
suite.enableTimeouts(this.enableTimeouts());
|
||
|
suite.slow(this.slow());
|
||
|
suite.bail(this.bail());
|
||
|
return suite;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set or get timeout `ms` or short-hand such as "2s".
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {number|string} ms
|
||
|
* @return {Suite|number} for chaining
|
||
|
*/
|
||
|
Suite.prototype.timeout = function (ms) {
|
||
|
if (!arguments.length) {
|
||
|
return this._timeout;
|
||
|
}
|
||
|
if (ms.toString() === '0') {
|
||
|
this._enableTimeouts = false;
|
||
|
}
|
||
|
if (typeof ms === 'string') {
|
||
|
ms = milliseconds(ms);
|
||
|
}
|
||
|
debug('timeout %d', ms);
|
||
|
this._timeout = parseInt(ms, 10);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set or get number of times to retry a failed test.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {number|string} n
|
||
|
* @return {Suite|number} for chaining
|
||
|
*/
|
||
|
Suite.prototype.retries = function (n) {
|
||
|
if (!arguments.length) {
|
||
|
return this._retries;
|
||
|
}
|
||
|
debug('retries %d', n);
|
||
|
this._retries = parseInt(n, 10) || 0;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set or get timeout to `enabled`.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {boolean} enabled
|
||
|
* @return {Suite|boolean} self or enabled
|
||
|
*/
|
||
|
Suite.prototype.enableTimeouts = function (enabled) {
|
||
|
if (!arguments.length) {
|
||
|
return this._enableTimeouts;
|
||
|
}
|
||
|
debug('enableTimeouts %s', enabled);
|
||
|
this._enableTimeouts = enabled;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set or get slow `ms` or short-hand such as "2s".
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {number|string} ms
|
||
|
* @return {Suite|number} for chaining
|
||
|
*/
|
||
|
Suite.prototype.slow = function (ms) {
|
||
|
if (!arguments.length) {
|
||
|
return this._slow;
|
||
|
}
|
||
|
if (typeof ms === 'string') {
|
||
|
ms = milliseconds(ms);
|
||
|
}
|
||
|
debug('slow %d', ms);
|
||
|
this._slow = ms;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set or get whether to bail after first error.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {boolean} bail
|
||
|
* @return {Suite|number} for chaining
|
||
|
*/
|
||
|
Suite.prototype.bail = function (bail) {
|
||
|
if (!arguments.length) {
|
||
|
return this._bail;
|
||
|
}
|
||
|
debug('bail %s', bail);
|
||
|
this._bail = bail;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Check if this suite or its parent suite is marked as pending.
|
||
|
*
|
||
|
* @api private
|
||
|
*/
|
||
|
Suite.prototype.isPending = function () {
|
||
|
return this.pending || (this.parent && this.parent.isPending());
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Run `fn(test[, done])` before running tests.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {string} title
|
||
|
* @param {Function} fn
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.beforeAll = function (title, fn) {
|
||
|
if (this.isPending()) {
|
||
|
return this;
|
||
|
}
|
||
|
if (typeof title === 'function') {
|
||
|
fn = title;
|
||
|
title = fn.name;
|
||
|
}
|
||
|
title = '"before all" hook' + (title ? ': ' + title : '');
|
||
|
|
||
|
var hook = new Hook(title, fn);
|
||
|
hook.parent = this;
|
||
|
hook.timeout(this.timeout());
|
||
|
hook.retries(this.retries());
|
||
|
hook.enableTimeouts(this.enableTimeouts());
|
||
|
hook.slow(this.slow());
|
||
|
hook.ctx = this.ctx;
|
||
|
this._beforeAll.push(hook);
|
||
|
this.emit('beforeAll', hook);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Run `fn(test[, done])` after running tests.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {string} title
|
||
|
* @param {Function} fn
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.afterAll = function (title, fn) {
|
||
|
if (this.isPending()) {
|
||
|
return this;
|
||
|
}
|
||
|
if (typeof title === 'function') {
|
||
|
fn = title;
|
||
|
title = fn.name;
|
||
|
}
|
||
|
title = '"after all" hook' + (title ? ': ' + title : '');
|
||
|
|
||
|
var hook = new Hook(title, fn);
|
||
|
hook.parent = this;
|
||
|
hook.timeout(this.timeout());
|
||
|
hook.retries(this.retries());
|
||
|
hook.enableTimeouts(this.enableTimeouts());
|
||
|
hook.slow(this.slow());
|
||
|
hook.ctx = this.ctx;
|
||
|
this._afterAll.push(hook);
|
||
|
this.emit('afterAll', hook);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Run `fn(test[, done])` before each test case.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {string} title
|
||
|
* @param {Function} fn
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.beforeEach = function (title, fn) {
|
||
|
if (this.isPending()) {
|
||
|
return this;
|
||
|
}
|
||
|
if (typeof title === 'function') {
|
||
|
fn = title;
|
||
|
title = fn.name;
|
||
|
}
|
||
|
title = '"before each" hook' + (title ? ': ' + title : '');
|
||
|
|
||
|
var hook = new Hook(title, fn);
|
||
|
hook.parent = this;
|
||
|
hook.timeout(this.timeout());
|
||
|
hook.retries(this.retries());
|
||
|
hook.enableTimeouts(this.enableTimeouts());
|
||
|
hook.slow(this.slow());
|
||
|
hook.ctx = this.ctx;
|
||
|
this._beforeEach.push(hook);
|
||
|
this.emit('beforeEach', hook);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Run `fn(test[, done])` after each test case.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {string} title
|
||
|
* @param {Function} fn
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.afterEach = function (title, fn) {
|
||
|
if (this.isPending()) {
|
||
|
return this;
|
||
|
}
|
||
|
if (typeof title === 'function') {
|
||
|
fn = title;
|
||
|
title = fn.name;
|
||
|
}
|
||
|
title = '"after each" hook' + (title ? ': ' + title : '');
|
||
|
|
||
|
var hook = new Hook(title, fn);
|
||
|
hook.parent = this;
|
||
|
hook.timeout(this.timeout());
|
||
|
hook.retries(this.retries());
|
||
|
hook.enableTimeouts(this.enableTimeouts());
|
||
|
hook.slow(this.slow());
|
||
|
hook.ctx = this.ctx;
|
||
|
this._afterEach.push(hook);
|
||
|
this.emit('afterEach', hook);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add a test `suite`.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {Suite} suite
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.addSuite = function (suite) {
|
||
|
suite.parent = this;
|
||
|
suite.timeout(this.timeout());
|
||
|
suite.retries(this.retries());
|
||
|
suite.enableTimeouts(this.enableTimeouts());
|
||
|
suite.slow(this.slow());
|
||
|
suite.bail(this.bail());
|
||
|
this.suites.push(suite);
|
||
|
this.emit('suite', suite);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add a `test` to this suite.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {Test} test
|
||
|
* @return {Suite} for chaining
|
||
|
*/
|
||
|
Suite.prototype.addTest = function (test) {
|
||
|
test.parent = this;
|
||
|
test.timeout(this.timeout());
|
||
|
test.retries(this.retries());
|
||
|
test.enableTimeouts(this.enableTimeouts());
|
||
|
test.slow(this.slow());
|
||
|
test.ctx = this.ctx;
|
||
|
this.tests.push(test);
|
||
|
this.emit('test', test);
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Return the full title generated by recursively concatenating the parent's
|
||
|
* full title.
|
||
|
*
|
||
|
* @api public
|
||
|
* @return {string}
|
||
|
*/
|
||
|
Suite.prototype.fullTitle = function () {
|
||
|
return this.titlePath().join(' ');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Return the title path generated by recursively concatenating the parent's
|
||
|
* title path.
|
||
|
*
|
||
|
* @api public
|
||
|
* @return {string}
|
||
|
*/
|
||
|
Suite.prototype.titlePath = function () {
|
||
|
var result = [];
|
||
|
if (this.parent) {
|
||
|
result = result.concat(this.parent.titlePath());
|
||
|
}
|
||
|
if (!this.root) {
|
||
|
result.push(this.title);
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Return the total number of tests.
|
||
|
*
|
||
|
* @api public
|
||
|
* @return {number}
|
||
|
*/
|
||
|
Suite.prototype.total = function () {
|
||
|
return this.suites.reduce(function (sum, suite) {
|
||
|
return sum + suite.total();
|
||
|
}, 0) + this.tests.length;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Iterates through each suite recursively to find all tests. Applies a
|
||
|
* function in the format `fn(test)`.
|
||
|
*
|
||
|
* @api private
|
||
|
* @param {Function} fn
|
||
|
* @return {Suite}
|
||
|
*/
|
||
|
Suite.prototype.eachTest = function (fn) {
|
||
|
this.tests.forEach(fn);
|
||
|
this.suites.forEach(function (suite) {
|
||
|
suite.eachTest(fn);
|
||
|
});
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This will run the root suite if we happen to be running in delayed mode.
|
||
|
*/
|
||
|
Suite.prototype.run = function run () {
|
||
|
if (this.root) {
|
||
|
this.emit('run');
|
||
|
}
|
||
|
};
|