mocha 8.1.0 → 8.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/runner.js CHANGED
@@ -8,7 +8,6 @@ var util = require('util');
8
8
  var EventEmitter = require('events').EventEmitter;
9
9
  var Pending = require('./pending');
10
10
  var utils = require('./utils');
11
- var inherits = utils.inherits;
12
11
  var debug = require('debug')('mocha:runner');
13
12
  var Runnable = require('./runnable');
14
13
  var Suite = require('./suite');
@@ -24,7 +23,7 @@ var dQuote = utils.dQuote;
24
23
  var sQuote = utils.sQuote;
25
24
  var stackFilter = utils.stackTraceFilter();
26
25
  var stringify = utils.stringify;
27
- var type = utils.type;
26
+
28
27
  var errors = require('./errors');
29
28
  var createInvalidExceptionError = errors.createInvalidExceptionError;
30
29
  var createUnsupportedError = errors.createUnsupportedError;
@@ -128,54 +127,59 @@ var constants = utils.defineConstants(
128
127
  }
129
128
  );
130
129
 
131
- module.exports = Runner;
132
-
133
- /**
134
- * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
135
- *
136
- * @extends external:EventEmitter
137
- * @public
138
- * @class
139
- * @param {Suite} suite - Root suite
140
- * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
141
- * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
142
- * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
143
- */
144
- function Runner(suite, opts) {
145
- if (opts === undefined) {
146
- opts = {};
147
- }
148
- if (typeof opts === 'boolean') {
149
- this._delay = opts;
150
- opts = {};
151
- } else {
152
- this._delay = opts.delay;
153
- }
154
- var self = this;
155
- this._globals = [];
156
- this._abort = false;
157
- this.suite = suite;
158
- this._opts = opts;
159
- this.state = constants.STATE_IDLE;
160
- this.total = suite.total();
161
- this.failures = 0;
162
- this._eventListeners = [];
163
- this.on(constants.EVENT_TEST_END, function(test) {
164
- if (test.type === 'test' && test.retriedTest() && test.parent) {
165
- var idx =
166
- test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
167
- if (idx > -1) test.parent.tests[idx] = test;
130
+ class Runner extends EventEmitter {
131
+ /**
132
+ * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}.
133
+ *
134
+ * @extends external:EventEmitter
135
+ * @public
136
+ * @class
137
+ * @param {Suite} suite - Root suite
138
+ * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
139
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
140
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
141
+ */
142
+ constructor(suite, opts) {
143
+ super();
144
+ if (opts === undefined) {
145
+ opts = {};
168
146
  }
169
- self.checkGlobals(test);
170
- });
171
- this.on(constants.EVENT_HOOK_END, function(hook) {
172
- self.checkGlobals(hook);
173
- });
174
- this._defaultGrep = /.*/;
175
- this.grep(this._defaultGrep);
176
- this.globals(this.globalProps());
147
+ if (typeof opts === 'boolean') {
148
+ // TODO: deprecate this
149
+ this._delay = opts;
150
+ opts = {};
151
+ } else {
152
+ this._delay = opts.delay;
153
+ }
154
+ var self = this;
155
+ this._globals = [];
156
+ this._abort = false;
157
+ this.suite = suite;
158
+ this._opts = opts;
159
+ this.state = constants.STATE_IDLE;
160
+ this.total = suite.total();
161
+ this.failures = 0;
162
+ /**
163
+ * @type {Map<EventEmitter,Map<string,Set<EventListener>>>}
164
+ */
165
+ this._eventListeners = new Map();
166
+ this.on(constants.EVENT_TEST_END, function(test) {
167
+ if (test.type === 'test' && test.retriedTest() && test.parent) {
168
+ var idx =
169
+ test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
170
+ if (idx > -1) test.parent.tests[idx] = test;
171
+ }
172
+ self.checkGlobals(test);
173
+ });
174
+ this.on(constants.EVENT_HOOK_END, function(hook) {
175
+ self.checkGlobals(hook);
176
+ });
177
+ this._defaultGrep = /.*/;
178
+ this.grep(this._defaultGrep);
179
+ this.globals(this.globalProps());
177
180
 
178
- this.uncaught = this._uncaught.bind(this);
181
+ this.uncaught = this._uncaught.bind(this);
182
+ }
179
183
  }
180
184
 
181
185
  /**
@@ -186,11 +190,6 @@ function Runner(suite, opts) {
186
190
  */
187
191
  Runner.immediately = global.setImmediate || process.nextTick;
188
192
 
189
- /**
190
- * Inherit from `EventEmitter.prototype`.
191
- */
192
- inherits(Runner, EventEmitter);
193
-
194
193
  /**
195
194
  * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
196
195
  * @param {EventEmitter} target - The `EventEmitter`
@@ -199,33 +198,62 @@ inherits(Runner, EventEmitter);
199
198
  * @private
200
199
  */
201
200
  Runner.prototype._addEventListener = function(target, eventName, listener) {
201
+ debug(
202
+ '_addEventListener(): adding for event %s; %d current listeners',
203
+ eventName,
204
+ target.listenerCount(eventName)
205
+ );
206
+ /* istanbul ignore next */
207
+ if (
208
+ this._eventListeners.has(target) &&
209
+ this._eventListeners.get(target).has(eventName) &&
210
+ this._eventListeners
211
+ .get(target)
212
+ .get(eventName)
213
+ .has(listener)
214
+ ) {
215
+ debug(
216
+ 'warning: tried to attach duplicate event listener for %s',
217
+ eventName
218
+ );
219
+ return;
220
+ }
202
221
  target.on(eventName, listener);
203
- this._eventListeners.push([target, eventName, listener]);
222
+ const targetListeners = this._eventListeners.has(target)
223
+ ? this._eventListeners.get(target)
224
+ : new Map();
225
+ const targetEventListeners = targetListeners.has(eventName)
226
+ ? targetListeners.get(eventName)
227
+ : new Set();
228
+ targetEventListeners.add(listener);
229
+ targetListeners.set(eventName, targetEventListeners);
230
+ this._eventListeners.set(target, targetListeners);
204
231
  };
205
232
 
206
233
  /**
207
234
  * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
208
235
  * @param {EventEmitter} target - The `EventEmitter`
209
- * @param {string} eventName - The event anme
236
+ * @param {string} eventName - The event name
210
237
  * @param {function} listener - Listener function
211
238
  * @private
212
239
  */
213
240
  Runner.prototype._removeEventListener = function(target, eventName, listener) {
214
- var eventListenerIndex = -1;
215
- for (var i = 0; i < this._eventListeners.length; i++) {
216
- var eventListenerDescriptor = this._eventListeners[i];
217
- if (
218
- eventListenerDescriptor[0] === target &&
219
- eventListenerDescriptor[1] === eventName &&
220
- eventListenerDescriptor[2] === listener
221
- ) {
222
- eventListenerIndex = i;
223
- break;
241
+ target.removeListener(eventName, listener);
242
+
243
+ if (this._eventListeners.has(target)) {
244
+ const targetListeners = this._eventListeners.get(target);
245
+ if (targetListeners.has(eventName)) {
246
+ const targetEventListeners = targetListeners.get(eventName);
247
+ targetEventListeners.delete(listener);
248
+ if (!targetEventListeners.size) {
249
+ targetListeners.delete(eventName);
250
+ }
224
251
  }
225
- }
226
- if (eventListenerIndex !== -1) {
227
- var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0];
228
- removedListener[0].removeListener(removedListener[1], removedListener[2]);
252
+ if (!targetListeners.size) {
253
+ this._eventListeners.delete(target);
254
+ }
255
+ } else {
256
+ debug('trying to remove listener for untracked object %s', target);
229
257
  }
230
258
  };
231
259
 
@@ -235,12 +263,14 @@ Runner.prototype._removeEventListener = function(target, eventName, listener) {
235
263
  */
236
264
  Runner.prototype.dispose = function() {
237
265
  this.removeAllListeners();
238
- this._eventListeners.forEach(function(eventListenerDescriptor) {
239
- eventListenerDescriptor[0].removeListener(
240
- eventListenerDescriptor[1],
241
- eventListenerDescriptor[2]
242
- );
266
+ this._eventListeners.forEach((targetListeners, target) => {
267
+ targetListeners.forEach((targetEventListeners, eventName) => {
268
+ targetEventListeners.forEach(listener => {
269
+ target.removeListener(eventName, listener);
270
+ });
271
+ });
243
272
  });
273
+ this._eventListeners.clear();
244
274
  };
245
275
 
246
276
  /**
@@ -976,73 +1006,119 @@ Runner.prototype._uncaught = function(err) {
976
1006
  * @memberof Runner
977
1007
  * @param {Function} fn - Callback when finished
978
1008
  * @param {{files: string[], options: Options}} [opts] - For subclasses
979
- * @return {Runner} Runner instance.
1009
+ * @returns {Runner} Runner instance.
980
1010
  */
981
- Runner.prototype.run = function(fn, opts) {
982
- var self = this;
1011
+ Runner.prototype.run = function(fn, opts = {}) {
983
1012
  var rootSuite = this.suite;
1013
+ var options = opts.options || {};
984
1014
 
1015
+ debug('run(): got options: %O', options);
985
1016
  fn = fn || function() {};
986
1017
 
987
- function start() {
1018
+ const end = () => {
1019
+ debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END);
1020
+ this.emit(constants.EVENT_RUN_END);
1021
+ };
1022
+
1023
+ const begin = () => {
1024
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
1025
+ this.emit(constants.EVENT_RUN_BEGIN);
1026
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
1027
+
1028
+ this.runSuite(rootSuite, async () => {
1029
+ end();
1030
+ });
1031
+ };
1032
+
1033
+ const prepare = () => {
988
1034
  debug('run(): starting');
989
1035
  // If there is an `only` filter
990
1036
  if (rootSuite.hasOnly()) {
991
1037
  rootSuite.filterOnly();
992
1038
  debug('run(): filtered exclusive Runnables');
993
1039
  }
994
- self.state = constants.STATE_RUNNING;
995
- if (self._delay) {
996
- self.emit(constants.EVENT_DELAY_END);
1040
+ this.state = constants.STATE_RUNNING;
1041
+ if (this._delay) {
1042
+ this.emit(constants.EVENT_DELAY_END);
997
1043
  debug('run(): "delay" ended');
998
1044
  }
999
- debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
1000
- self.emit(constants.EVENT_RUN_BEGIN);
1001
- debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
1002
1045
 
1003
- self.runSuite(rootSuite, function() {
1004
- debug(
1005
- 'run(): root suite completed; emitting %s',
1006
- constants.EVENT_RUN_END
1007
- );
1008
- self.emit(constants.EVENT_RUN_END);
1009
- debug('run(): emitted %s', constants.EVENT_RUN_END);
1010
- });
1011
- }
1046
+ return begin();
1047
+ };
1012
1048
 
1013
1049
  // references cleanup to avoid memory leaks
1014
1050
  if (this._opts.cleanReferencesAfterRun) {
1015
- this.on(constants.EVENT_SUITE_END, function(suite) {
1051
+ this.on(constants.EVENT_SUITE_END, suite => {
1016
1052
  suite.cleanReferences();
1017
1053
  });
1018
1054
  }
1019
1055
 
1020
1056
  // callback
1021
1057
  this.on(constants.EVENT_RUN_END, function() {
1022
- self.state = constants.STATE_STOPPED;
1023
- debug(constants.EVENT_RUN_END);
1058
+ this.state = constants.STATE_STOPPED;
1024
1059
  debug('run(): emitted %s', constants.EVENT_RUN_END);
1025
- fn(self.failures);
1060
+ fn(this.failures);
1026
1061
  });
1027
1062
 
1028
- self._removeEventListener(process, 'uncaughtException', self.uncaught);
1029
- self._addEventListener(process, 'uncaughtException', self.uncaught);
1063
+ this._removeEventListener(process, 'uncaughtException', this.uncaught);
1064
+ this._removeEventListener(process, 'unhandledRejection', this.uncaught);
1065
+ this._addEventListener(process, 'uncaughtException', this.uncaught);
1066
+ this._addEventListener(process, 'unhandledRejection', this.uncaught);
1030
1067
 
1031
1068
  if (this._delay) {
1032
1069
  // for reporters, I guess.
1033
1070
  // might be nice to debounce some dots while we wait.
1034
1071
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
1035
- rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
1072
+ rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare);
1036
1073
  debug('run(): waiting for green light due to --delay');
1037
1074
  } else {
1038
- Runner.immediately(function() {
1039
- start();
1040
- });
1075
+ Runner.immediately(prepare);
1041
1076
  }
1042
1077
 
1043
1078
  return this;
1044
1079
  };
1045
1080
 
1081
+ /**
1082
+ * Toggle partial object linking behavior; used for building object references from
1083
+ * unique ID's. Does nothing in serial mode, because the object references already exist.
1084
+ * Subclasses can implement this (e.g., `ParallelBufferedRunner`)
1085
+ * @abstract
1086
+ * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable
1087
+ * @returns {Runner}
1088
+ * @chainable
1089
+ * @public
1090
+ * @example
1091
+ * // this reporter needs proper object references when run in parallel mode
1092
+ * class MyReporter() {
1093
+ * constructor(runner) {
1094
+ * this.runner.linkPartialObjects(true)
1095
+ * .on(EVENT_SUITE_BEGIN, suite => {
1096
+ // this Suite may be the same object...
1097
+ * })
1098
+ * .on(EVENT_TEST_BEGIN, test => {
1099
+ * // ...as the `test.parent` property
1100
+ * });
1101
+ * }
1102
+ * }
1103
+ */
1104
+ Runner.prototype.linkPartialObjects = function(value) {
1105
+ return this;
1106
+ };
1107
+
1108
+ /*
1109
+ * Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`.
1110
+ * This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead.
1111
+ * @public
1112
+ * @memberof Runner
1113
+ * @param {Object} [opts] - Options for {@link Runner#run}
1114
+ * @returns {Promise<number>} Failure count
1115
+ */
1116
+ Runner.prototype.runAsync = async function runAsync(opts = {}) {
1117
+ return new Promise(resolve => {
1118
+ this.run(resolve, opts);
1119
+ });
1120
+ };
1121
+
1046
1122
  /**
1047
1123
  * Cleanly abort execution.
1048
1124
  *
@@ -1057,6 +1133,31 @@ Runner.prototype.abort = function() {
1057
1133
  return this;
1058
1134
  };
1059
1135
 
1136
+ /**
1137
+ * Returns `true` if Mocha is running in parallel mode. For reporters.
1138
+ *
1139
+ * Subclasses should return an appropriate value.
1140
+ * @public
1141
+ * @returns {false}
1142
+ */
1143
+ Runner.prototype.isParallelMode = function isParallelMode() {
1144
+ return false;
1145
+ };
1146
+
1147
+ /**
1148
+ * Configures an alternate reporter for worker processes to use. Subclasses
1149
+ * using worker processes should implement this.
1150
+ * @public
1151
+ * @param {string} path - Absolute path to alternate reporter for worker processes to use
1152
+ * @returns {Runner}
1153
+ * @throws When in serial mode
1154
+ * @chainable
1155
+ * @abstract
1156
+ */
1157
+ Runner.prototype.workerReporter = function() {
1158
+ throw createUnsupportedError('workerReporter() not supported in serial mode');
1159
+ };
1160
+
1060
1161
  /**
1061
1162
  * Filter leaks with the given globals flagged as `ok`.
1062
1163
  *
@@ -1122,7 +1223,9 @@ function isError(err) {
1122
1223
  */
1123
1224
  function thrown2Error(err) {
1124
1225
  return new Error(
1125
- 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'
1226
+ `the ${utils.canonicalType(err)} ${stringify(
1227
+ err
1228
+ )} was thrown, throw an Error :)`
1126
1229
  );
1127
1230
  }
1128
1231
 
@@ -1133,3 +1236,5 @@ Runner.constants = constants;
1133
1236
  * @external EventEmitter
1134
1237
  * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
1135
1238
  */
1239
+
1240
+ module.exports = Runner;
package/lib/suite.js CHANGED
@@ -4,14 +4,23 @@
4
4
  * Module dependencies.
5
5
  * @private
6
6
  */
7
- var EventEmitter = require('events').EventEmitter;
8
- var Hook = require('./hook');
9
- var utils = require('./utils');
10
- var inherits = utils.inherits;
11
- var debug = require('debug')('mocha:suite');
12
- var milliseconds = require('ms');
13
- var errors = require('./errors');
14
- var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
7
+ const {EventEmitter} = require('events');
8
+ const Hook = require('./hook');
9
+ var {
10
+ assignNewMochaID,
11
+ clamp,
12
+ constants: utilsConstants,
13
+ createMap,
14
+ defineConstants,
15
+ getMochaID,
16
+ inherits,
17
+ isString
18
+ } = require('./utils');
19
+ const debug = require('debug')('mocha:suite');
20
+ const milliseconds = require('ms');
21
+ const errors = require('./errors');
22
+
23
+ const {MOCHA_ID_PROP_NAME} = utilsConstants;
15
24
 
16
25
  /**
17
26
  * Expose `Suite`.
@@ -47,8 +56,8 @@ Suite.create = function(parent, title) {
47
56
  * @param {boolean} [isRoot=false] - Whether this is the root suite.
48
57
  */
49
58
  function Suite(title, parentContext, isRoot) {
50
- if (!utils.isString(title)) {
51
- throw createInvalidArgumentTypeError(
59
+ if (!isString(title)) {
60
+ throw errors.createInvalidArgumentTypeError(
52
61
  'Suite argument "title" must be a string. Received type "' +
53
62
  typeof title +
54
63
  '"',
@@ -74,11 +83,19 @@ function Suite(title, parentContext, isRoot) {
74
83
  this._bail = false;
75
84
  this._onlyTests = [];
76
85
  this._onlySuites = [];
86
+ assignNewMochaID(this);
87
+
88
+ Object.defineProperty(this, 'id', {
89
+ get() {
90
+ return getMochaID(this);
91
+ }
92
+ });
93
+
77
94
  this.reset();
78
95
 
79
96
  this.on('newListener', function(event) {
80
97
  if (deprecatedEvents[event]) {
81
- utils.deprecate(
98
+ errors.deprecate(
82
99
  'Event "' +
83
100
  event +
84
101
  '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm'
@@ -145,7 +162,7 @@ Suite.prototype.timeout = function(ms) {
145
162
  // Clamp to range
146
163
  var INT_MAX = Math.pow(2, 31) - 1;
147
164
  var range = [0, INT_MAX];
148
- ms = utils.clamp(ms, range);
165
+ ms = clamp(ms, range);
149
166
 
150
167
  debug('timeout %d', ms);
151
168
  this._timeout = parseInt(ms, 10);
@@ -579,11 +596,13 @@ Suite.prototype.serialize = function serialize() {
579
596
  $$fullTitle: this.fullTitle(),
580
597
  $$isPending: this.isPending(),
581
598
  root: this.root,
582
- title: this.title
599
+ title: this.title,
600
+ id: this.id,
601
+ parent: this.parent ? {[MOCHA_ID_PROP_NAME]: this.parent.id} : null
583
602
  };
584
603
  };
585
604
 
586
- var constants = utils.defineConstants(
605
+ var constants = defineConstants(
587
606
  /**
588
607
  * {@link Suite}-related constants.
589
608
  * @public
@@ -671,6 +690,6 @@ var deprecatedEvents = Object.keys(constants)
671
690
  .reduce(function(acc, constant) {
672
691
  acc[constants[constant]] = true;
673
692
  return acc;
674
- }, utils.createMap());
693
+ }, createMap());
675
694
 
676
695
  Suite.constants = constants;
package/lib/test.js CHANGED
@@ -5,6 +5,8 @@ var errors = require('./errors');
5
5
  var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
6
6
  var isString = utils.isString;
7
7
 
8
+ const {MOCHA_ID_PROP_NAME} = utils.constants;
9
+
8
10
  module.exports = Test;
9
11
 
10
12
  /**
@@ -98,12 +100,14 @@ Test.prototype.serialize = function serialize() {
98
100
  duration: this.duration,
99
101
  err: this.err,
100
102
  parent: {
101
- $$fullTitle: this.parent.fullTitle()
103
+ $$fullTitle: this.parent.fullTitle(),
104
+ [MOCHA_ID_PROP_NAME]: this.parent.id
102
105
  },
103
106
  speed: this.speed,
104
107
  state: this.state,
105
108
  title: this.title,
106
109
  type: this.type,
107
- file: this.file
110
+ file: this.file,
111
+ [MOCHA_ID_PROP_NAME]: this.id
108
112
  };
109
113
  };