/** * Module dependencies. */ var EventEmitter = require('events').EventEmitter , debug = require('debug')('suite') , utils = require('./utils') , Hook = require('./hook'); /** * 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. * * @param {Suite} parent * @param {String} title * @return {Suite} * @api public */ exports.create = function(parent, title){ var suite = new Suite(title); suite.parent = parent; title = suite.fullTitle(); parent.addSuite(suite); return suite; }; /** * Initialize a new `Suite` with the given `title`. * * @param {String} title * @api private */ function Suite(title) { this.title = title; this.suites = []; this.tests = []; this._beforeEach = []; this._beforeAll = []; this._afterEach = []; this._afterAll = []; this.root = !title; this._timeout = 2000; this._bail = false; } /** * Inherit from `EventEmitter.prototype`. */ Suite.prototype.__proto__ = EventEmitter.prototype; /** * Return a clone of this `Suite`. * * @return {Suite} * @api private */ Suite.prototype.clone = function(){ var suite = new Suite(this.title); debug('clone'); suite.timeout(this.timeout()); suite.bail(this.bail()); return suite; }; /** * Set timeout `ms` or short-hand such as "2s". * * @param {Number|String} ms * @return {Suite|Number} for chaining * @api private */ Suite.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; debug('timeout %d', ms); this._timeout = parseInt(ms, 10); return this; }; /** * Sets whether to bail after first error. * * @parma {Boolean} bail * @return {Suite|Number} for chaining * @api private */ Suite.prototype.bail = function(bail){ if (0 == arguments.length) return this._bail; debug('bail %s', bail); this._bail = bail; return this; }; /** * Run `fn(test[, done])` before running tests. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.beforeAll = function(fn){ var hook = new Hook('"before all" hook', fn); hook.parent = this; hook.timeout(this.timeout()); this._beforeAll.push(hook); this.emit('beforeAll', hook); return this; }; /** * Run `fn(test[, done])` after running tests. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.afterAll = function(fn){ var hook = new Hook('"after all" hook', fn); hook.parent = this; hook.timeout(this.timeout()); this._afterAll.push(hook); this.emit('afterAll', hook); return this; }; /** * Run `fn(test[, done])` before each test case. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.beforeEach = function(fn){ var hook = new Hook('"before each" hook', fn); hook.parent = this; hook.timeout(this.timeout()); this._beforeEach.push(hook); this.emit('beforeEach', hook); return this; }; /** * Run `fn(test[, done])` after each test case. * * @param {Function} fn * @return {Suite} for chaining * @api private */ Suite.prototype.afterEach = function(fn){ var hook = new Hook('"after each" hook', fn); hook.parent = this; hook.timeout(this.timeout()); this._afterEach.push(hook); this.emit('afterEach', hook); return this; }; /** * Add a test `suite`. * * @param {Suite} suite * @return {Suite} for chaining * @api private */ Suite.prototype.addSuite = function(suite){ suite.parent = this; suite.timeout(this.timeout()); suite.bail(this.bail()); this.suites.push(suite); this.emit('suite', suite); return this; }; /** * Add a `test` to this suite. * * @param {Test} test * @return {Suite} for chaining * @api private */ Suite.prototype.addTest = function(test){ test.parent = this; test.timeout(this.timeout()); this.tests.push(test); this.emit('test', test); return this; }; /** * Return the full title generated by recursively * concatenating the parent's full title. * * @return {String} * @api public */ 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. * * @return {Number} * @api public */ Suite.prototype.total = function(){ return utils.reduce(this.suites, function(sum, suite){ return sum + suite.total(); }, 0) + this.tests.length; };