pixelnode/node_modules/mocha/lib/suite.js

396 lines
8.3 KiB
JavaScript

/**
* 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) {
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.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 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 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 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 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;
};
/**
* Sets 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() {
if (this.parent) {
var full = this.parent.fullTitle();
if (full) {
return full + ' ' + this.title;
}
}
return this.title;
};
/**
* Return the total number of tests.
*
* @api public
* @return {number}
*/
Suite.prototype.total = function() {
return utils.reduce(this.suites, 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) {
utils.forEach(this.tests, fn);
utils.forEach(this.suites, 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');
}
};