mocha 7.1.2 → 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.
@@ -3,6 +3,7 @@
3
3
  var Suite = require('../suite');
4
4
  var errors = require('../errors');
5
5
  var createMissingArgumentError = errors.createMissingArgumentError;
6
+ var createUnsupportedError = errors.createUnsupportedError;
6
7
 
7
8
  /**
8
9
  * Functions common to more than one interface.
@@ -126,14 +127,14 @@ module.exports = function(suites, context, mocha) {
126
127
  suites.unshift(suite);
127
128
  if (opts.isOnly) {
128
129
  if (mocha.options.forbidOnly && shouldBeTested(suite)) {
129
- throw new Error('`.only` forbidden');
130
+ throw createUnsupportedError('`.only` forbidden');
130
131
  }
131
132
 
132
133
  suite.parent.appendOnlySuite(suite);
133
134
  }
134
135
  if (suite.pending) {
135
136
  if (mocha.options.forbidPending && shouldBeTested(suite)) {
136
- throw new Error('Pending test forbidden');
137
+ throw createUnsupportedError('Pending test forbidden');
137
138
  }
138
139
  }
139
140
  if (typeof opts.fn === 'function') {
@@ -165,7 +166,9 @@ module.exports = function(suites, context, mocha) {
165
166
  * @returns {*}
166
167
  */
167
168
  only: function(mocha, test) {
168
- test.parent.appendOnlyTest(test);
169
+ if (mocha.options.forbidOnly)
170
+ throw createUnsupportedError('`.only` forbidden');
171
+ test.markOnly();
169
172
  return test;
170
173
  },
171
174
 
package/lib/mocha.js CHANGED
@@ -18,6 +18,10 @@ var esmUtils = utils.supportsEsModules() ? require('./esm-utils') : undefined;
18
18
  var createStatsCollector = require('./stats-collector');
19
19
  var createInvalidReporterError = errors.createInvalidReporterError;
20
20
  var createInvalidInterfaceError = errors.createInvalidInterfaceError;
21
+ var createMochaInstanceAlreadyDisposedError =
22
+ errors.createMochaInstanceAlreadyDisposedError;
23
+ var createMochaInstanceAlreadyRunningError =
24
+ errors.createMochaInstanceAlreadyRunningError;
21
25
  var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE;
22
26
  var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE;
23
27
  var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE;
@@ -25,12 +29,36 @@ var sQuote = utils.sQuote;
25
29
 
26
30
  exports = module.exports = Mocha;
27
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
+
28
56
  /**
29
57
  * To require local UIs and reporters when running in node.
30
58
  */
31
59
 
32
- if (!process.browser) {
33
- var cwd = process.cwd();
60
+ if (!process.browser && typeof module.paths !== 'undefined') {
61
+ var cwd = utils.cwd();
34
62
  module.paths.push(cwd, path.join(cwd, 'node_modules'));
35
63
  }
36
64
 
@@ -90,6 +118,8 @@ exports.Test = require('./test');
90
118
  * @param {number} [options.slow] - Slow threshold value.
91
119
  * @param {number|string} [options.timeout] - Timeout threshold value.
92
120
  * @param {string} [options.ui] - Interface name.
121
+ * @param {MochaRootHookObject} [options.rootHooks] - Hooks to bootstrap the root
122
+ * suite with
93
123
  */
94
124
  function Mocha(options) {
95
125
  options = utils.assign({}, mocharc, options || {});
@@ -97,6 +127,7 @@ function Mocha(options) {
97
127
  this.options = options;
98
128
  // root suite
99
129
  this.suite = new exports.Suite('', new exports.Context(), true);
130
+ this._cleanReferencesAfterRun = true;
100
131
 
101
132
  this.grep(options.grep)
102
133
  .fgrep(options.fgrep)
@@ -136,6 +167,10 @@ function Mocha(options) {
136
167
  this[opt]();
137
168
  }
138
169
  }, this);
170
+
171
+ if (options.rootHooks) {
172
+ this.rootHooks(options.rootHooks);
173
+ }
139
174
  }
140
175
 
141
176
  /**
@@ -202,24 +237,24 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
202
237
  _reporter = require(reporter);
203
238
  } catch (err) {
204
239
  if (
205
- err.code !== 'MODULE_NOT_FOUND' ||
206
- err.message.indexOf('Cannot find module') !== -1
240
+ err.code === 'MODULE_NOT_FOUND' ||
241
+ err.message.indexOf('Cannot find module') >= 0
207
242
  ) {
208
243
  // Try to load reporters from a path (absolute or relative)
209
244
  try {
210
- _reporter = require(path.resolve(process.cwd(), reporter));
245
+ _reporter = require(path.resolve(utils.cwd(), reporter));
211
246
  } catch (_err) {
212
- _err.code !== 'MODULE_NOT_FOUND' ||
213
- _err.message.indexOf('Cannot find module') !== -1
214
- ? console.warn(sQuote(reporter) + ' reporter not found')
215
- : 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(
216
251
  sQuote(reporter) +
217
252
  ' reporter blew up with error:\n' +
218
253
  err.stack
219
254
  );
220
255
  }
221
256
  } else {
222
- console.warn(
257
+ utils.warn(
223
258
  sQuote(reporter) + ' reporter blew up with error:\n' + err.stack
224
259
  );
225
260
  }
@@ -388,7 +423,18 @@ Mocha.unloadFile = function(file) {
388
423
  * @chainable
389
424
  */
390
425
  Mocha.prototype.unloadFiles = function() {
391
- 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;
392
438
  return this;
393
439
  };
394
440
 
@@ -506,6 +552,38 @@ Mocha.prototype.checkLeaks = function(checkLeaks) {
506
552
  return this;
507
553
  };
508
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
+
509
587
  /**
510
588
  * Displays full stack trace upon test failure.
511
589
  *
@@ -856,6 +934,28 @@ Mocha.prototype.forbidPending = function(forbidPending) {
856
934
  return this;
857
935
  };
858
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
+
859
959
  /**
860
960
  * Mocha version as specified by "package.json".
861
961
  *
@@ -896,13 +996,23 @@ Object.defineProperty(Mocha.prototype, 'version', {
896
996
  * mocha.run(failures => process.exitCode = failures ? 1 : 0);
897
997
  */
898
998
  Mocha.prototype.run = function(fn) {
999
+ this._guardRunningStateTransition();
1000
+ this._state = mochaStates.RUNNING;
1001
+ if (this._previousRunner) {
1002
+ this._previousRunner.dispose();
1003
+ this.suite.reset();
1004
+ }
899
1005
  if (this.files.length && !this.loadAsync) {
900
1006
  this.loadFiles();
901
1007
  }
1008
+ var self = this;
902
1009
  var suite = this.suite;
903
1010
  var options = this.options;
904
1011
  options.files = this.files;
905
- var runner = new exports.Runner(suite, options.delay);
1012
+ var runner = new exports.Runner(suite, {
1013
+ delay: options.delay,
1014
+ cleanReferencesAfterRun: this._cleanReferencesAfterRun
1015
+ });
906
1016
  createStatsCollector(runner);
907
1017
  var reporter = new this._reporter(runner, options);
908
1018
  runner.checkLeaks = options.checkLeaks === true;
@@ -927,6 +1037,12 @@ Mocha.prototype.run = function(fn) {
927
1037
  exports.reporters.Base.hideDiff = !options.diff;
928
1038
 
929
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
+ }
930
1046
  fn = fn || utils.noop;
931
1047
  if (reporter.done) {
932
1048
  reporter.done(failures, fn);
@@ -937,3 +1053,46 @@ Mocha.prototype.run = function(fn) {
937
1053
 
938
1054
  return runner.run(done);
939
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
+ */
@@ -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
  *
@@ -268,7 +278,7 @@ Runnable.prototype.run = function(fn) {
268
278
  var start = new Date();
269
279
  var ctx = this.ctx;
270
280
  var finished;
271
- var emitted;
281
+ var errorWasHandled = false;
272
282
 
273
283
  // Sometimes the ctx exists, but it is not runnable
274
284
  if (ctx && ctx.runnable) {
@@ -277,17 +287,11 @@ Runnable.prototype.run = function(fn) {
277
287
 
278
288
  // called multiple times
279
289
  function multiple(err) {
280
- if (emitted) {
290
+ if (errorWasHandled) {
281
291
  return;
282
292
  }
283
- emitted = true;
284
- var msg = 'done() called multiple times';
285
- if (err && err.message) {
286
- err.message += " (and Mocha's " + msg + ')';
287
- self.emit('error', err);
288
- } else {
289
- self.emit('error', new Error(msg));
290
- }
293
+ errorWasHandled = true;
294
+ self.emit('error', createMultipleDoneError(self, err));
291
295
  }
292
296
 
293
297
  // finished
@@ -338,7 +342,7 @@ Runnable.prototype.run = function(fn) {
338
342
  callFnAsync(this.fn);
339
343
  } catch (err) {
340
344
  // handles async runnables which actually run synchronously
341
- emitted = true;
345
+ errorWasHandled = true;
342
346
  if (err instanceof Pending) {
343
347
  return; // done() is already called in this.skip()
344
348
  } else if (this.allowUncaught) {
@@ -357,7 +361,7 @@ Runnable.prototype.run = function(fn) {
357
361
  callFn(this.fn);
358
362
  }
359
363
  } catch (err) {
360
- emitted = true;
364
+ errorWasHandled = true;
361
365
  if (err instanceof Pending) {
362
366
  return done();
363
367
  } else if (this.allowUncaught) {