mocha 7.0.1 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/mocha.js CHANGED
@@ -14,9 +14,14 @@ var utils = require('./utils');
14
14
  var mocharc = require('./mocharc.json');
15
15
  var errors = require('./errors');
16
16
  var Suite = require('./suite');
17
+ var esmUtils = utils.supportsEsModules() ? require('./esm-utils') : undefined;
17
18
  var createStatsCollector = require('./stats-collector');
18
19
  var createInvalidReporterError = errors.createInvalidReporterError;
19
20
  var createInvalidInterfaceError = errors.createInvalidInterfaceError;
21
+ var createMochaInstanceAlreadyDisposedError =
22
+ errors.createMochaInstanceAlreadyDisposedError;
23
+ var createMochaInstanceAlreadyRunningError =
24
+ errors.createMochaInstanceAlreadyRunningError;
20
25
  var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE;
21
26
  var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE;
22
27
  var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE;
@@ -24,12 +29,36 @@ var sQuote = utils.sQuote;
24
29
 
25
30
  exports = module.exports = Mocha;
26
31
 
32
+ /**
33
+ * A Mocha instance is a finite state machine.
34
+ * These are the states it can be in.
35
+ */
36
+ var mochaStates = utils.defineConstants({
37
+ /**
38
+ * Initial state of the mocha instance
39
+ */
40
+ INIT: 'init',
41
+ /**
42
+ * Mocha instance is running tests
43
+ */
44
+ RUNNING: 'running',
45
+ /**
46
+ * Mocha instance is done running tests and references to test functions and hooks are cleaned.
47
+ * You can reset this state by unloading the test files.
48
+ */
49
+ REFERENCES_CLEANED: 'referencesCleaned',
50
+ /**
51
+ * Mocha instance is disposed and can no longer be used.
52
+ */
53
+ DISPOSED: 'disposed'
54
+ });
55
+
27
56
  /**
28
57
  * To require local UIs and reporters when running in node.
29
58
  */
30
59
 
31
- if (!process.browser) {
32
- var cwd = process.cwd();
60
+ if (!process.browser && typeof module.paths !== 'undefined') {
61
+ var cwd = utils.cwd();
33
62
  module.paths.push(cwd, path.join(cwd, 'node_modules'));
34
63
  }
35
64
 
@@ -89,6 +118,8 @@ exports.Test = require('./test');
89
118
  * @param {number} [options.slow] - Slow threshold value.
90
119
  * @param {number|string} [options.timeout] - Timeout threshold value.
91
120
  * @param {string} [options.ui] - Interface name.
121
+ * @param {MochaRootHookObject} [options.rootHooks] - Hooks to bootstrap the root
122
+ * suite with
92
123
  */
93
124
  function Mocha(options) {
94
125
  options = utils.assign({}, mocharc, options || {});
@@ -96,6 +127,7 @@ function Mocha(options) {
96
127
  this.options = options;
97
128
  // root suite
98
129
  this.suite = new exports.Suite('', new exports.Context(), true);
130
+ this._cleanReferencesAfterRun = true;
99
131
 
100
132
  this.grep(options.grep)
101
133
  .fgrep(options.fgrep)
@@ -135,6 +167,10 @@ function Mocha(options) {
135
167
  this[opt]();
136
168
  }
137
169
  }, this);
170
+
171
+ if (options.rootHooks) {
172
+ this.rootHooks(options.rootHooks);
173
+ }
138
174
  }
139
175
 
140
176
  /**
@@ -201,24 +237,24 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
201
237
  _reporter = require(reporter);
202
238
  } catch (err) {
203
239
  if (
204
- err.code !== 'MODULE_NOT_FOUND' ||
205
- err.message.indexOf('Cannot find module') !== -1
240
+ err.code === 'MODULE_NOT_FOUND' ||
241
+ err.message.indexOf('Cannot find module') >= 0
206
242
  ) {
207
243
  // Try to load reporters from a path (absolute or relative)
208
244
  try {
209
- _reporter = require(path.resolve(process.cwd(), reporter));
245
+ _reporter = require(path.resolve(utils.cwd(), reporter));
210
246
  } catch (_err) {
211
- _err.code !== 'MODULE_NOT_FOUND' ||
212
- _err.message.indexOf('Cannot find module') !== -1
213
- ? console.warn(sQuote(reporter) + ' reporter not found')
214
- : console.warn(
247
+ _err.code === 'MODULE_NOT_FOUND' ||
248
+ _err.message.indexOf('Cannot find module') >= 0
249
+ ? utils.warn(sQuote(reporter) + ' reporter not found')
250
+ : utils.warn(
215
251
  sQuote(reporter) +
216
252
  ' reporter blew up with error:\n' +
217
253
  err.stack
218
254
  );
219
255
  }
220
256
  } else {
221
- console.warn(
257
+ utils.warn(
222
258
  sQuote(reporter) + ' reporter blew up with error:\n' + err.stack
223
259
  );
224
260
  }
@@ -290,16 +326,18 @@ Mocha.prototype.ui = function(ui) {
290
326
  };
291
327
 
292
328
  /**
293
- * Loads `files` prior to execution.
329
+ * Loads `files` prior to execution. Does not support ES Modules.
294
330
  *
295
331
  * @description
296
332
  * The implementation relies on Node's `require` to execute
297
333
  * the test interface functions and will be subject to its cache.
334
+ * Supports only CommonJS modules. To load ES modules, use Mocha#loadFilesAsync.
298
335
  *
299
336
  * @private
300
337
  * @see {@link Mocha#addFile}
301
338
  * @see {@link Mocha#run}
302
339
  * @see {@link Mocha#unloadFiles}
340
+ * @see {@link Mocha#loadFilesAsync}
303
341
  * @param {Function} [fn] - Callback invoked upon completion.
304
342
  */
305
343
  Mocha.prototype.loadFiles = function(fn) {
@@ -314,6 +352,49 @@ Mocha.prototype.loadFiles = function(fn) {
314
352
  fn && fn();
315
353
  };
316
354
 
355
+ /**
356
+ * Loads `files` prior to execution. Supports Node ES Modules.
357
+ *
358
+ * @description
359
+ * The implementation relies on Node's `require` and `import` to execute
360
+ * the test interface functions and will be subject to its cache.
361
+ * Supports both CJS and ESM modules.
362
+ *
363
+ * @public
364
+ * @see {@link Mocha#addFile}
365
+ * @see {@link Mocha#run}
366
+ * @see {@link Mocha#unloadFiles}
367
+ * @returns {Promise}
368
+ * @example
369
+ *
370
+ * // loads ESM (and CJS) test files asynchronously, then runs root suite
371
+ * mocha.loadFilesAsync()
372
+ * .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0))
373
+ * .catch(() => process.exitCode = 1);
374
+ */
375
+ Mocha.prototype.loadFilesAsync = function() {
376
+ var self = this;
377
+ var suite = this.suite;
378
+ this.loadAsync = true;
379
+
380
+ if (!esmUtils) {
381
+ return new Promise(function(resolve) {
382
+ self.loadFiles(resolve);
383
+ });
384
+ }
385
+
386
+ return esmUtils.loadFilesAsync(
387
+ this.files,
388
+ function(file) {
389
+ suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self);
390
+ },
391
+ function(file, resultModule) {
392
+ suite.emit(EVENT_FILE_REQUIRE, resultModule, file, self);
393
+ suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self);
394
+ }
395
+ );
396
+ };
397
+
317
398
  /**
318
399
  * Removes a previously loaded file from Node's `require` cache.
319
400
  *
@@ -330,8 +411,9 @@ Mocha.unloadFile = function(file) {
330
411
  * Unloads `files` from Node's `require` cache.
331
412
  *
332
413
  * @description
333
- * This allows files to be "freshly" reloaded, providing the ability
414
+ * This allows required files to be "freshly" reloaded, providing the ability
334
415
  * to reuse a Mocha instance programmatically.
416
+ * Note: does not clear ESM module files from the cache
335
417
  *
336
418
  * <strong>Intended for consumers &mdash; not used internally</strong>
337
419
  *
@@ -341,7 +423,18 @@ Mocha.unloadFile = function(file) {
341
423
  * @chainable
342
424
  */
343
425
  Mocha.prototype.unloadFiles = function() {
344
- this.files.forEach(Mocha.unloadFile);
426
+ if (this._state === mochaStates.DISPOSED) {
427
+ throw createMochaInstanceAlreadyDisposedError(
428
+ 'Mocha instance is already disposed, it cannot be used again.',
429
+ this._cleanReferencesAfterRun,
430
+ this
431
+ );
432
+ }
433
+
434
+ this.files.forEach(function(file) {
435
+ Mocha.unloadFile(file);
436
+ });
437
+ this._state = mochaStates.INIT;
345
438
  return this;
346
439
  };
347
440
 
@@ -459,6 +552,38 @@ Mocha.prototype.checkLeaks = function(checkLeaks) {
459
552
  return this;
460
553
  };
461
554
 
555
+ /**
556
+ * Enables or disables whether or not to dispose after each test run.
557
+ * Disable this to ensure you can run the test suite multiple times.
558
+ * If disabled, be sure to dispose mocha when you're done to prevent memory leaks.
559
+ * @public
560
+ * @see {@link Mocha#dispose}
561
+ * @param {boolean} cleanReferencesAfterRun
562
+ * @return {Mocha} this
563
+ * @chainable
564
+ */
565
+ Mocha.prototype.cleanReferencesAfterRun = function(cleanReferencesAfterRun) {
566
+ this._cleanReferencesAfterRun = cleanReferencesAfterRun !== false;
567
+ return this;
568
+ };
569
+
570
+ /**
571
+ * Manually dispose this mocha instance. Mark this instance as `disposed` and unable to run more tests.
572
+ * It also removes function references to tests functions and hooks, so variables trapped in closures can be cleaned by the garbage collector.
573
+ * @public
574
+ */
575
+ Mocha.prototype.dispose = function() {
576
+ if (this._state === mochaStates.RUNNING) {
577
+ throw createMochaInstanceAlreadyRunningError(
578
+ 'Cannot dispose while the mocha instance is still running tests.'
579
+ );
580
+ }
581
+ this.unloadFiles();
582
+ this._previousRunner && this._previousRunner.dispose();
583
+ this.suite.dispose();
584
+ this._state = mochaStates.DISPOSED;
585
+ };
586
+
462
587
  /**
463
588
  * Displays full stack trace upon test failure.
464
589
  *
@@ -809,6 +934,28 @@ Mocha.prototype.forbidPending = function(forbidPending) {
809
934
  return this;
810
935
  };
811
936
 
937
+ /**
938
+ * Throws an error if mocha is in the wrong state to be able to transition to a "running" state.
939
+ */
940
+ Mocha.prototype._guardRunningStateTransition = function() {
941
+ if (this._state === mochaStates.RUNNING) {
942
+ throw createMochaInstanceAlreadyRunningError(
943
+ 'Mocha instance is currently running tests, cannot start a next test run until this one is done',
944
+ this
945
+ );
946
+ }
947
+ if (
948
+ this._state === mochaStates.DISPOSED ||
949
+ this._state === mochaStates.REFERENCES_CLEANED
950
+ ) {
951
+ throw createMochaInstanceAlreadyDisposedError(
952
+ 'Mocha instance is already disposed, cannot start a new test run. Please create a new mocha instance. Be sure to set disable `cleanReferencesAfterRun` when you want to reuse the same mocha instance for multiple test runs.',
953
+ this._cleanReferencesAfterRun,
954
+ this
955
+ );
956
+ }
957
+ };
958
+
812
959
  /**
813
960
  * Mocha version as specified by "package.json".
814
961
  *
@@ -842,16 +989,30 @@ Object.defineProperty(Mocha.prototype, 'version', {
842
989
  * @see {@link Mocha#unloadFiles}
843
990
  * @see {@link Runner#run}
844
991
  * @param {DoneCB} [fn] - Callback invoked when test execution completed.
845
- * @return {Runner} runner instance
992
+ * @returns {Runner} runner instance
993
+ * @example
994
+ *
995
+ * // exit with non-zero status if there were test failures
996
+ * mocha.run(failures => process.exitCode = failures ? 1 : 0);
846
997
  */
847
998
  Mocha.prototype.run = function(fn) {
848
- if (this.files.length) {
999
+ this._guardRunningStateTransition();
1000
+ this._state = mochaStates.RUNNING;
1001
+ if (this._previousRunner) {
1002
+ this._previousRunner.dispose();
1003
+ this.suite.reset();
1004
+ }
1005
+ if (this.files.length && !this.loadAsync) {
849
1006
  this.loadFiles();
850
1007
  }
1008
+ var self = this;
851
1009
  var suite = this.suite;
852
1010
  var options = this.options;
853
1011
  options.files = this.files;
854
- var runner = new exports.Runner(suite, options.delay);
1012
+ var runner = new exports.Runner(suite, {
1013
+ delay: options.delay,
1014
+ cleanReferencesAfterRun: this._cleanReferencesAfterRun
1015
+ });
855
1016
  createStatsCollector(runner);
856
1017
  var reporter = new this._reporter(runner, options);
857
1018
  runner.checkLeaks = options.checkLeaks === true;
@@ -876,6 +1037,12 @@ Mocha.prototype.run = function(fn) {
876
1037
  exports.reporters.Base.hideDiff = !options.diff;
877
1038
 
878
1039
  function done(failures) {
1040
+ self._previousRunner = runner;
1041
+ if (self._cleanReferencesAfterRun) {
1042
+ self._state = mochaStates.REFERENCES_CLEANED;
1043
+ } else {
1044
+ self._state = mochaStates.INIT;
1045
+ }
879
1046
  fn = fn || utils.noop;
880
1047
  if (reporter.done) {
881
1048
  reporter.done(failures, fn);
@@ -886,3 +1053,46 @@ Mocha.prototype.run = function(fn) {
886
1053
 
887
1054
  return runner.run(done);
888
1055
  };
1056
+
1057
+ /**
1058
+ * Assigns hooks to the root suite
1059
+ * @param {MochaRootHookObject} [hooks] - Hooks to assign to root suite
1060
+ * @chainable
1061
+ */
1062
+ Mocha.prototype.rootHooks = function rootHooks(hooks) {
1063
+ if (utils.type(hooks) === 'object') {
1064
+ var beforeAll = [].concat(hooks.beforeAll || []);
1065
+ var beforeEach = [].concat(hooks.beforeEach || []);
1066
+ var afterAll = [].concat(hooks.afterAll || []);
1067
+ var afterEach = [].concat(hooks.afterEach || []);
1068
+ var rootSuite = this.suite;
1069
+ beforeAll.forEach(function(hook) {
1070
+ rootSuite.beforeAll(hook);
1071
+ });
1072
+ beforeEach.forEach(function(hook) {
1073
+ rootSuite.beforeEach(hook);
1074
+ });
1075
+ afterAll.forEach(function(hook) {
1076
+ rootSuite.afterAll(hook);
1077
+ });
1078
+ afterEach.forEach(function(hook) {
1079
+ rootSuite.afterEach(hook);
1080
+ });
1081
+ }
1082
+ return this;
1083
+ };
1084
+
1085
+ /**
1086
+ * An alternative way to define root hooks that works with parallel runs.
1087
+ * @typedef {Object} MochaRootHookObject
1088
+ * @property {Function|Function[]} [beforeAll] - "Before all" hook(s)
1089
+ * @property {Function|Function[]} [beforeEach] - "Before each" hook(s)
1090
+ * @property {Function|Function[]} [afterAll] - "After all" hook(s)
1091
+ * @property {Function|Function[]} [afterEach] - "After each" hook(s)
1092
+ */
1093
+
1094
+ /**
1095
+ * An function that returns a {@link MochaRootHookObject}, either sync or async.
1096
+ * @callback MochaRootHookFunction
1097
+ * @returns {MochaRootHookObject|Promise<MochaRootHookObject>}
1098
+ */
package/lib/mocharc.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "diff": true,
3
- "extension": ["js"],
3
+ "extension": ["js", "cjs", "mjs"],
4
4
  "opts": "./test/mocha.opts",
5
5
  "package": "./package.json",
6
6
  "reporter": "spec",
@@ -62,6 +62,7 @@ function Doc(runner, options) {
62
62
 
63
63
  runner.on(EVENT_TEST_PASS, function(test) {
64
64
  Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.title));
65
+ Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.file));
65
66
  var code = utils.escape(utils.clean(test.body));
66
67
  Base.consoleLog('%s <dd><pre><code>%s</code></pre></dd>', indent(), code);
67
68
  });
@@ -72,6 +73,11 @@ function Doc(runner, options) {
72
73
  indent(),
73
74
  utils.escape(test.title)
74
75
  );
76
+ Base.consoleLog(
77
+ '%s <dt class="error">%s</dt>',
78
+ indent(),
79
+ utils.escape(test.file)
80
+ );
75
81
  var code = utils.escape(utils.clean(test.body));
76
82
  Base.consoleLog(
77
83
  '%s <dd class="error"><pre><code>%s</code></pre></dd>',
@@ -82,6 +82,7 @@ function clean(test) {
82
82
  return {
83
83
  title: test.title,
84
84
  fullTitle: test.fullTitle(),
85
+ file: test.file,
85
86
  duration: test.duration,
86
87
  currentRetry: test.currentRetry()
87
88
  };
@@ -87,6 +87,7 @@ function clean(test) {
87
87
  return {
88
88
  title: test.title,
89
89
  fullTitle: test.fullTitle(),
90
+ file: test.file,
90
91
  duration: test.duration,
91
92
  currentRetry: test.currentRetry(),
92
93
  err: cleanCycles(err)
@@ -98,6 +98,14 @@ function Landing(runner, options) {
98
98
  process.stdout.write('\n');
99
99
  self.epilogue();
100
100
  });
101
+
102
+ // if cursor is hidden when we ctrl-C, then it will remain hidden unless...
103
+ process.once('SIGINT', function() {
104
+ cursor.show();
105
+ process.nextTick(function() {
106
+ process.kill(process.pid, 'SIGINT');
107
+ });
108
+ });
101
109
  }
102
110
 
103
111
  /**
package/lib/runnable.js CHANGED
@@ -5,8 +5,9 @@ var Pending = require('./pending');
5
5
  var debug = require('debug')('mocha:runnable');
6
6
  var milliseconds = require('ms');
7
7
  var utils = require('./utils');
8
- var createInvalidExceptionError = require('./errors')
9
- .createInvalidExceptionError;
8
+ var errors = require('./errors');
9
+ var createInvalidExceptionError = errors.createInvalidExceptionError;
10
+ var createMultipleDoneError = errors.createMultipleDoneError;
10
11
 
11
12
  /**
12
13
  * Save timer references to avoid Sinon interfering (see GH-237).
@@ -36,10 +37,8 @@ function Runnable(title, fn) {
36
37
  this._timeout = 2000;
37
38
  this._slow = 75;
38
39
  this._enableTimeouts = true;
39
- this.timedOut = false;
40
40
  this._retries = -1;
41
- this._currentRetry = 0;
42
- this.pending = false;
41
+ this.reset();
43
42
  }
44
43
 
45
44
  /**
@@ -47,6 +46,17 @@ function Runnable(title, fn) {
47
46
  */
48
47
  utils.inherits(Runnable, EventEmitter);
49
48
 
49
+ /**
50
+ * Resets the state initially or for a next run.
51
+ */
52
+ Runnable.prototype.reset = function() {
53
+ this.timedOut = false;
54
+ this._currentRetry = 0;
55
+ this.pending = false;
56
+ delete this.state;
57
+ delete this.err;
58
+ };
59
+
50
60
  /**
51
61
  * Get current timeout value in msecs.
52
62
  *
@@ -222,31 +232,6 @@ Runnable.prototype.clearTimeout = function() {
222
232
  clearTimeout(this.timer);
223
233
  };
224
234
 
225
- /**
226
- * Inspect the runnable void of private properties.
227
- *
228
- * @private
229
- * @return {string}
230
- */
231
- Runnable.prototype.inspect = function() {
232
- return JSON.stringify(
233
- this,
234
- function(key, val) {
235
- if (key[0] === '_') {
236
- return;
237
- }
238
- if (key === 'parent') {
239
- return '#<Suite>';
240
- }
241
- if (key === 'ctx') {
242
- return '#<Context>';
243
- }
244
- return val;
245
- },
246
- 2
247
- );
248
- };
249
-
250
235
  /**
251
236
  * Reset the timeout.
252
237
  *
@@ -293,7 +278,7 @@ Runnable.prototype.run = function(fn) {
293
278
  var start = new Date();
294
279
  var ctx = this.ctx;
295
280
  var finished;
296
- var emitted;
281
+ var errorWasHandled = false;
297
282
 
298
283
  // Sometimes the ctx exists, but it is not runnable
299
284
  if (ctx && ctx.runnable) {
@@ -302,17 +287,11 @@ Runnable.prototype.run = function(fn) {
302
287
 
303
288
  // called multiple times
304
289
  function multiple(err) {
305
- if (emitted) {
290
+ if (errorWasHandled) {
306
291
  return;
307
292
  }
308
- emitted = true;
309
- var msg = 'done() called multiple times';
310
- if (err && err.message) {
311
- err.message += " (and Mocha's " + msg + ')';
312
- self.emit('error', err);
313
- } else {
314
- self.emit('error', new Error(msg));
315
- }
293
+ errorWasHandled = true;
294
+ self.emit('error', createMultipleDoneError(self, err));
316
295
  }
317
296
 
318
297
  // finished
@@ -363,7 +342,7 @@ Runnable.prototype.run = function(fn) {
363
342
  callFnAsync(this.fn);
364
343
  } catch (err) {
365
344
  // handles async runnables which actually run synchronously
366
- emitted = true;
345
+ errorWasHandled = true;
367
346
  if (err instanceof Pending) {
368
347
  return; // done() is already called in this.skip()
369
348
  } else if (this.allowUncaught) {
@@ -382,7 +361,7 @@ Runnable.prototype.run = function(fn) {
382
361
  callFn(this.fn);
383
362
  }
384
363
  } catch (err) {
385
- emitted = true;
364
+ errorWasHandled = true;
386
365
  if (err instanceof Pending) {
387
366
  return done();
388
367
  } else if (this.allowUncaught) {