suite.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. 'use strict';
  2. /**
  3. * @module Suite
  4. */
  5. /**
  6. * Module dependencies.
  7. */
  8. var EventEmitter = require('events').EventEmitter;
  9. var Hook = require('./hook');
  10. var utils = require('./utils');
  11. var inherits = utils.inherits;
  12. var debug = require('debug')('mocha:suite');
  13. var milliseconds = require('./ms');
  14. /**
  15. * Expose `Suite`.
  16. */
  17. exports = module.exports = Suite;
  18. /**
  19. * Create a new `Suite` with the given `title` and parent `Suite`. When a suite
  20. * with the same title is already present, that suite is returned to provide
  21. * nicer reporter and more flexible meta-testing.
  22. *
  23. * @memberof Mocha
  24. * @public
  25. * @api public
  26. * @param {Suite} parent
  27. * @param {string} title
  28. * @return {Suite}
  29. */
  30. exports.create = function(parent, title) {
  31. var suite = new Suite(title, parent.ctx);
  32. suite.parent = parent;
  33. title = suite.fullTitle();
  34. parent.addSuite(suite);
  35. return suite;
  36. };
  37. /**
  38. * Initialize a new `Suite` with the given `title` and `ctx`. Derived from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
  39. *
  40. * @memberof Mocha
  41. * @public
  42. * @class
  43. * @param {string} title
  44. * @param {Context} parentContext
  45. */
  46. function Suite(title, parentContext) {
  47. if (!utils.isString(title)) {
  48. throw new Error(
  49. 'Suite `title` should be a "string" but "' +
  50. typeof title +
  51. '" was given instead.'
  52. );
  53. }
  54. this.title = title;
  55. function Context() {}
  56. Context.prototype = parentContext;
  57. this.ctx = new Context();
  58. this.suites = [];
  59. this.tests = [];
  60. this.pending = false;
  61. this._beforeEach = [];
  62. this._beforeAll = [];
  63. this._afterEach = [];
  64. this._afterAll = [];
  65. this.root = !title;
  66. this._timeout = 2000;
  67. this._enableTimeouts = true;
  68. this._slow = 75;
  69. this._bail = false;
  70. this._retries = -1;
  71. this._onlyTests = [];
  72. this._onlySuites = [];
  73. this.delayed = false;
  74. }
  75. /**
  76. * Inherit from `EventEmitter.prototype`.
  77. */
  78. inherits(Suite, EventEmitter);
  79. /**
  80. * Return a clone of this `Suite`.
  81. *
  82. * @api private
  83. * @return {Suite}
  84. */
  85. Suite.prototype.clone = function() {
  86. var suite = new Suite(this.title);
  87. debug('clone');
  88. suite.ctx = this.ctx;
  89. suite.timeout(this.timeout());
  90. suite.retries(this.retries());
  91. suite.enableTimeouts(this.enableTimeouts());
  92. suite.slow(this.slow());
  93. suite.bail(this.bail());
  94. return suite;
  95. };
  96. /**
  97. * Set or get timeout `ms` or short-hand such as "2s".
  98. *
  99. * @api private
  100. * @param {number|string} ms
  101. * @return {Suite|number} for chaining
  102. */
  103. Suite.prototype.timeout = function(ms) {
  104. if (!arguments.length) {
  105. return this._timeout;
  106. }
  107. if (ms.toString() === '0') {
  108. this._enableTimeouts = false;
  109. }
  110. if (typeof ms === 'string') {
  111. ms = milliseconds(ms);
  112. }
  113. debug('timeout %d', ms);
  114. this._timeout = parseInt(ms, 10);
  115. return this;
  116. };
  117. /**
  118. * Set or get number of times to retry a failed test.
  119. *
  120. * @api private
  121. * @param {number|string} n
  122. * @return {Suite|number} for chaining
  123. */
  124. Suite.prototype.retries = function(n) {
  125. if (!arguments.length) {
  126. return this._retries;
  127. }
  128. debug('retries %d', n);
  129. this._retries = parseInt(n, 10) || 0;
  130. return this;
  131. };
  132. /**
  133. * Set or get timeout to `enabled`.
  134. *
  135. * @api private
  136. * @param {boolean} enabled
  137. * @return {Suite|boolean} self or enabled
  138. */
  139. Suite.prototype.enableTimeouts = function(enabled) {
  140. if (!arguments.length) {
  141. return this._enableTimeouts;
  142. }
  143. debug('enableTimeouts %s', enabled);
  144. this._enableTimeouts = enabled;
  145. return this;
  146. };
  147. /**
  148. * Set or get slow `ms` or short-hand such as "2s".
  149. *
  150. * @api private
  151. * @param {number|string} ms
  152. * @return {Suite|number} for chaining
  153. */
  154. Suite.prototype.slow = function(ms) {
  155. if (!arguments.length) {
  156. return this._slow;
  157. }
  158. if (typeof ms === 'string') {
  159. ms = milliseconds(ms);
  160. }
  161. debug('slow %d', ms);
  162. this._slow = ms;
  163. return this;
  164. };
  165. /**
  166. * Set or get whether to bail after first error.
  167. *
  168. * @api private
  169. * @param {boolean} bail
  170. * @return {Suite|number} for chaining
  171. */
  172. Suite.prototype.bail = function(bail) {
  173. if (!arguments.length) {
  174. return this._bail;
  175. }
  176. debug('bail %s', bail);
  177. this._bail = bail;
  178. return this;
  179. };
  180. /**
  181. * Check if this suite or its parent suite is marked as pending.
  182. *
  183. * @api private
  184. */
  185. Suite.prototype.isPending = function() {
  186. return this.pending || (this.parent && this.parent.isPending());
  187. };
  188. /**
  189. * Generic hook-creator.
  190. * @private
  191. * @param {string} title - Title of hook
  192. * @param {Function} fn - Hook callback
  193. * @returns {Hook} A new hook
  194. */
  195. Suite.prototype._createHook = function(title, fn) {
  196. var hook = new Hook(title, fn);
  197. hook.parent = this;
  198. hook.timeout(this.timeout());
  199. hook.retries(this.retries());
  200. hook.enableTimeouts(this.enableTimeouts());
  201. hook.slow(this.slow());
  202. hook.ctx = this.ctx;
  203. hook.file = this.file;
  204. return hook;
  205. };
  206. /**
  207. * Run `fn(test[, done])` before running tests.
  208. *
  209. * @api private
  210. * @param {string} title
  211. * @param {Function} fn
  212. * @return {Suite} for chaining
  213. */
  214. Suite.prototype.beforeAll = function(title, fn) {
  215. if (this.isPending()) {
  216. return this;
  217. }
  218. if (typeof title === 'function') {
  219. fn = title;
  220. title = fn.name;
  221. }
  222. title = '"before all" hook' + (title ? ': ' + title : '');
  223. var hook = this._createHook(title, fn);
  224. this._beforeAll.push(hook);
  225. this.emit('beforeAll', hook);
  226. return this;
  227. };
  228. /**
  229. * Run `fn(test[, done])` after running tests.
  230. *
  231. * @api private
  232. * @param {string} title
  233. * @param {Function} fn
  234. * @return {Suite} for chaining
  235. */
  236. Suite.prototype.afterAll = function(title, fn) {
  237. if (this.isPending()) {
  238. return this;
  239. }
  240. if (typeof title === 'function') {
  241. fn = title;
  242. title = fn.name;
  243. }
  244. title = '"after all" hook' + (title ? ': ' + title : '');
  245. var hook = this._createHook(title, fn);
  246. this._afterAll.push(hook);
  247. this.emit('afterAll', hook);
  248. return this;
  249. };
  250. /**
  251. * Run `fn(test[, done])` before each test case.
  252. *
  253. * @api private
  254. * @param {string} title
  255. * @param {Function} fn
  256. * @return {Suite} for chaining
  257. */
  258. Suite.prototype.beforeEach = function(title, fn) {
  259. if (this.isPending()) {
  260. return this;
  261. }
  262. if (typeof title === 'function') {
  263. fn = title;
  264. title = fn.name;
  265. }
  266. title = '"before each" hook' + (title ? ': ' + title : '');
  267. var hook = this._createHook(title, fn);
  268. this._beforeEach.push(hook);
  269. this.emit('beforeEach', hook);
  270. return this;
  271. };
  272. /**
  273. * Run `fn(test[, done])` after each test case.
  274. *
  275. * @api private
  276. * @param {string} title
  277. * @param {Function} fn
  278. * @return {Suite} for chaining
  279. */
  280. Suite.prototype.afterEach = function(title, fn) {
  281. if (this.isPending()) {
  282. return this;
  283. }
  284. if (typeof title === 'function') {
  285. fn = title;
  286. title = fn.name;
  287. }
  288. title = '"after each" hook' + (title ? ': ' + title : '');
  289. var hook = this._createHook(title, fn);
  290. this._afterEach.push(hook);
  291. this.emit('afterEach', hook);
  292. return this;
  293. };
  294. /**
  295. * Add a test `suite`.
  296. *
  297. * @api private
  298. * @param {Suite} suite
  299. * @return {Suite} for chaining
  300. */
  301. Suite.prototype.addSuite = function(suite) {
  302. suite.parent = this;
  303. suite.timeout(this.timeout());
  304. suite.retries(this.retries());
  305. suite.enableTimeouts(this.enableTimeouts());
  306. suite.slow(this.slow());
  307. suite.bail(this.bail());
  308. this.suites.push(suite);
  309. this.emit('suite', suite);
  310. return this;
  311. };
  312. /**
  313. * Add a `test` to this suite.
  314. *
  315. * @api private
  316. * @param {Test} test
  317. * @return {Suite} for chaining
  318. */
  319. Suite.prototype.addTest = function(test) {
  320. test.parent = this;
  321. test.timeout(this.timeout());
  322. test.retries(this.retries());
  323. test.enableTimeouts(this.enableTimeouts());
  324. test.slow(this.slow());
  325. test.ctx = this.ctx;
  326. this.tests.push(test);
  327. this.emit('test', test);
  328. return this;
  329. };
  330. /**
  331. * Return the full title generated by recursively concatenating the parent's
  332. * full title.
  333. *
  334. * @memberof Mocha.Suite
  335. * @public
  336. * @api public
  337. * @return {string}
  338. */
  339. Suite.prototype.fullTitle = function() {
  340. return this.titlePath().join(' ');
  341. };
  342. /**
  343. * Return the title path generated by recursively concatenating the parent's
  344. * title path.
  345. *
  346. * @memberof Mocha.Suite
  347. * @public
  348. * @api public
  349. * @return {string}
  350. */
  351. Suite.prototype.titlePath = function() {
  352. var result = [];
  353. if (this.parent) {
  354. result = result.concat(this.parent.titlePath());
  355. }
  356. if (!this.root) {
  357. result.push(this.title);
  358. }
  359. return result;
  360. };
  361. /**
  362. * Return the total number of tests.
  363. *
  364. * @memberof Mocha.Suite
  365. * @public
  366. * @api public
  367. * @return {number}
  368. */
  369. Suite.prototype.total = function() {
  370. return (
  371. this.suites.reduce(function(sum, suite) {
  372. return sum + suite.total();
  373. }, 0) + this.tests.length
  374. );
  375. };
  376. /**
  377. * Iterates through each suite recursively to find all tests. Applies a
  378. * function in the format `fn(test)`.
  379. *
  380. * @api private
  381. * @param {Function} fn
  382. * @return {Suite}
  383. */
  384. Suite.prototype.eachTest = function(fn) {
  385. this.tests.forEach(fn);
  386. this.suites.forEach(function(suite) {
  387. suite.eachTest(fn);
  388. });
  389. return this;
  390. };
  391. /**
  392. * This will run the root suite if we happen to be running in delayed mode.
  393. */
  394. Suite.prototype.run = function run() {
  395. if (this.root) {
  396. this.emit('run');
  397. }
  398. };