123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 |
- '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');
- var errors = require('./errors');
- var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
- /**
- * Expose `Suite`.
- */
- exports = module.exports = Suite;
- /**
- * Create a new `Suite` with the given `title` and parent `Suite`.
- *
- * @public
- * @param {Suite} parent - Parent suite (required!)
- * @param {string} title - Title
- * @return {Suite}
- */
- Suite.create = function(parent, title) {
- var suite = new Suite(title, parent.ctx);
- suite.parent = parent;
- title = suite.fullTitle();
- parent.addSuite(suite);
- return suite;
- };
- /**
- * Constructs a new `Suite` instance with the given `title`, `ctx`, and `isRoot`.
- *
- * @public
- * @class
- * @extends EventEmitter
- * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter}
- * @param {string} title - Suite title.
- * @param {Context} parentContext - Parent context instance.
- * @param {boolean} [isRoot=false] - Whether this is the root suite.
- */
- function Suite(title, parentContext, isRoot) {
- if (!utils.isString(title)) {
- throw createInvalidArgumentTypeError(
- 'Suite argument "title" must be a string. Received type "' +
- typeof title +
- '"',
- 'title',
- 'string'
- );
- }
- 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 = isRoot === true;
- this._timeout = 2000;
- this._enableTimeouts = true;
- this._slow = 75;
- this._bail = false;
- this._retries = -1;
- this._onlyTests = [];
- this._onlySuites = [];
- this.delayed = false;
- this.on('newListener', function(event) {
- if (deprecatedEvents[event]) {
- utils.deprecate(
- 'Event "' +
- event +
- '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm'
- );
- }
- });
- }
- /**
- * Inherit from `EventEmitter.prototype`.
- */
- inherits(Suite, EventEmitter);
- /**
- * Return a clone of this `Suite`.
- *
- * @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".
- *
- * @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.
- *
- * @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`.
- *
- * @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".
- *
- * @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.
- *
- * @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.
- *
- * @private
- */
- Suite.prototype.isPending = function() {
- return this.pending || (this.parent && this.parent.isPending());
- };
- /**
- * Generic hook-creator.
- * @private
- * @param {string} title - Title of hook
- * @param {Function} fn - Hook callback
- * @returns {Hook} A new hook
- */
- Suite.prototype._createHook = function(title, fn) {
- 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;
- hook.file = this.file;
- return hook;
- };
- /**
- * Run `fn(test[, done])` before running tests.
- *
- * @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 = this._createHook(title, fn);
- this._beforeAll.push(hook);
- this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, hook);
- return this;
- };
- /**
- * Run `fn(test[, done])` after running tests.
- *
- * @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 = this._createHook(title, fn);
- this._afterAll.push(hook);
- this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_ALL, hook);
- return this;
- };
- /**
- * Run `fn(test[, done])` before each test case.
- *
- * @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 = this._createHook(title, fn);
- this._beforeEach.push(hook);
- this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, hook);
- return this;
- };
- /**
- * Run `fn(test[, done])` after each test case.
- *
- * @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 = this._createHook(title, fn);
- this._afterEach.push(hook);
- this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_EACH, hook);
- return this;
- };
- /**
- * Add a test `suite`.
- *
- * @private
- * @param {Suite} suite
- * @return {Suite} for chaining
- */
- Suite.prototype.addSuite = function(suite) {
- suite.parent = this;
- suite.root = false;
- 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(constants.EVENT_SUITE_ADD_SUITE, suite);
- return this;
- };
- /**
- * Add a `test` to this suite.
- *
- * @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(constants.EVENT_SUITE_ADD_TEST, test);
- return this;
- };
- /**
- * Return the full title generated by recursively concatenating the parent's
- * full title.
- *
- * @memberof Suite
- * @public
- * @return {string}
- */
- Suite.prototype.fullTitle = function() {
- return this.titlePath().join(' ');
- };
- /**
- * Return the title path generated by recursively concatenating the parent's
- * title path.
- *
- * @memberof Suite
- * @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.
- *
- * @memberof Suite
- * @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)`.
- *
- * @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.
- * @private
- */
- Suite.prototype.run = function run() {
- if (this.root) {
- this.emit(constants.EVENT_ROOT_SUITE_RUN);
- }
- };
- /**
- * Determines whether a suite has an `only` test or suite as a descendant.
- *
- * @private
- * @returns {Boolean}
- */
- Suite.prototype.hasOnly = function hasOnly() {
- return (
- this._onlyTests.length > 0 ||
- this._onlySuites.length > 0 ||
- this.suites.some(function(suite) {
- return suite.hasOnly();
- })
- );
- };
- /**
- * Filter suites based on `isOnly` logic.
- *
- * @private
- * @returns {Boolean}
- */
- Suite.prototype.filterOnly = function filterOnly() {
- if (this._onlyTests.length) {
- // If the suite contains `only` tests, run those and ignore any nested suites.
- this.tests = this._onlyTests;
- this.suites = [];
- } else {
- // Otherwise, do not run any of the tests in this suite.
- this.tests = [];
- this._onlySuites.forEach(function(onlySuite) {
- // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite.
- // Otherwise, all of the tests on this `only` suite should be run, so don't filter it.
- if (onlySuite.hasOnly()) {
- onlySuite.filterOnly();
- }
- });
- // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants.
- var onlySuites = this._onlySuites;
- this.suites = this.suites.filter(function(childSuite) {
- return onlySuites.indexOf(childSuite) !== -1 || childSuite.filterOnly();
- });
- }
- // Keep the suite only if there is something to run
- return this.tests.length > 0 || this.suites.length > 0;
- };
- /**
- * Adds a suite to the list of subsuites marked `only`.
- *
- * @private
- * @param {Suite} suite
- */
- Suite.prototype.appendOnlySuite = function(suite) {
- this._onlySuites.push(suite);
- };
- /**
- * Adds a test to the list of tests marked `only`.
- *
- * @private
- * @param {Test} test
- */
- Suite.prototype.appendOnlyTest = function(test) {
- this._onlyTests.push(test);
- };
- /**
- * Returns the array of hooks by hook name; see `HOOK_TYPE_*` constants.
- * @private
- */
- Suite.prototype.getHooks = function getHooks(name) {
- return this['_' + name];
- };
- /**
- * Cleans up the references to all the deferred functions
- * (before/after/beforeEach/afterEach) and tests of a Suite.
- * These must be deleted otherwise a memory leak can happen,
- * as those functions may reference variables from closures,
- * thus those variables can never be garbage collected as long
- * as the deferred functions exist.
- *
- * @private
- */
- Suite.prototype.cleanReferences = function cleanReferences() {
- function cleanArrReferences(arr) {
- for (var i = 0; i < arr.length; i++) {
- delete arr[i].fn;
- }
- }
- if (Array.isArray(this._beforeAll)) {
- cleanArrReferences(this._beforeAll);
- }
- if (Array.isArray(this._beforeEach)) {
- cleanArrReferences(this._beforeEach);
- }
- if (Array.isArray(this._afterAll)) {
- cleanArrReferences(this._afterAll);
- }
- if (Array.isArray(this._afterEach)) {
- cleanArrReferences(this._afterEach);
- }
- for (var i = 0; i < this.tests.length; i++) {
- delete this.tests[i].fn;
- }
- };
- var constants = utils.defineConstants(
- /**
- * {@link Suite}-related constants.
- * @public
- * @memberof Suite
- * @alias constants
- * @readonly
- * @static
- * @enum {string}
- */
- {
- /**
- * Event emitted after a test file has been loaded Not emitted in browser.
- */
- EVENT_FILE_POST_REQUIRE: 'post-require',
- /**
- * Event emitted before a test file has been loaded. In browser, this is emitted once an interface has been selected.
- */
- EVENT_FILE_PRE_REQUIRE: 'pre-require',
- /**
- * Event emitted immediately after a test file has been loaded. Not emitted in browser.
- */
- EVENT_FILE_REQUIRE: 'require',
- /**
- * Event emitted when `global.run()` is called (use with `delay` option)
- */
- EVENT_ROOT_SUITE_RUN: 'run',
- /**
- * Namespace for collection of a `Suite`'s "after all" hooks
- */
- HOOK_TYPE_AFTER_ALL: 'afterAll',
- /**
- * Namespace for collection of a `Suite`'s "after each" hooks
- */
- HOOK_TYPE_AFTER_EACH: 'afterEach',
- /**
- * Namespace for collection of a `Suite`'s "before all" hooks
- */
- HOOK_TYPE_BEFORE_ALL: 'beforeAll',
- /**
- * Namespace for collection of a `Suite`'s "before all" hooks
- */
- HOOK_TYPE_BEFORE_EACH: 'beforeEach',
- // the following events are all deprecated
- /**
- * Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated
- */
- EVENT_SUITE_ADD_HOOK_AFTER_ALL: 'afterAll',
- /**
- * Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated
- */
- EVENT_SUITE_ADD_HOOK_AFTER_EACH: 'afterEach',
- /**
- * Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated
- */
- EVENT_SUITE_ADD_HOOK_BEFORE_ALL: 'beforeAll',
- /**
- * Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated
- */
- EVENT_SUITE_ADD_HOOK_BEFORE_EACH: 'beforeEach',
- /**
- * Emitted after a child `Suite` has been added to a `Suite`. Deprecated
- */
- EVENT_SUITE_ADD_SUITE: 'suite',
- /**
- * Emitted after a `Test` has been added to a `Suite`. Deprecated
- */
- EVENT_SUITE_ADD_TEST: 'test'
- }
- );
- /**
- * @summary There are no known use cases for these events.
- * @desc This is a `Set`-like object having all keys being the constant's string value and the value being `true`.
- * @todo Remove eventually
- * @type {Object<string,boolean>}
- * @ignore
- */
- var deprecatedEvents = Object.keys(constants)
- .filter(function(constant) {
- return constant.substring(0, 15) === 'EVENT_SUITE_ADD';
- })
- .reduce(function(acc, constant) {
- acc[constants[constant]] = true;
- return acc;
- }, utils.createMap());
- Suite.constants = constants;
|