mocha 8.1.1 → 8.2.1

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.
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const logSymbols = require('log-symbols');
3
4
  const debug = require('debug')('mocha:cli:watch');
4
5
  const path = require('path');
5
6
  const chokidar = require('chokidar');
@@ -32,6 +33,7 @@ exports.watchParallelRun = (
32
33
  fileCollectParams
33
34
  ) => {
34
35
  debug('creating parallel watcher');
36
+
35
37
  return createWatcher(mocha, {
36
38
  watchFiles,
37
39
  watchIgnore,
@@ -39,6 +41,9 @@ exports.watchParallelRun = (
39
41
  // I don't know why we're cloning the root suite.
40
42
  const rootSuite = mocha.suite.clone();
41
43
 
44
+ // ensure we aren't leaking event listeners
45
+ mocha.dispose();
46
+
42
47
  // this `require` is needed because the require cache has been cleared. the dynamic
43
48
  // exports set via the below call to `mocha.ui()` won't work properly if a
44
49
  // test depends on this module (see `required-tokens.spec.js`).
@@ -68,9 +73,6 @@ exports.watchParallelRun = (
68
73
  newMocha.lazyLoadFiles(true);
69
74
  return newMocha;
70
75
  },
71
- afterRun({watcher}) {
72
- blastCache(watcher);
73
- },
74
76
  fileCollectParams
75
77
  });
76
78
  };
@@ -91,7 +93,6 @@ exports.watchParallelRun = (
91
93
  */
92
94
  exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
93
95
  debug('creating serial watcher');
94
- // list of all test files
95
96
 
96
97
  return createWatcher(mocha, {
97
98
  watchFiles,
@@ -102,6 +103,9 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
102
103
  // I don't know why we're cloning the root suite.
103
104
  const rootSuite = mocha.suite.clone();
104
105
 
106
+ // ensure we aren't leaking event listeners
107
+ mocha.dispose();
108
+
105
109
  // this `require` is needed because the require cache has been cleared. the dynamic
106
110
  // exports set via the below call to `mocha.ui()` won't work properly if a
107
111
  // test depends on this module (see `required-tokens.spec.js`).
@@ -128,9 +132,6 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
128
132
 
129
133
  return newMocha;
130
134
  },
131
- afterRun({watcher}) {
132
- blastCache(watcher);
133
- },
134
135
  fileCollectParams
135
136
  });
136
137
  };
@@ -141,7 +142,6 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
141
142
  * @param {Object} opts
142
143
  * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before
143
144
  * `mocha.run()`
144
- * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
145
145
  * @param {string[]} [opts.watchFiles] - List of paths and patterns to watch. If
146
146
  * not provided all files with an extension included in
147
147
  * `fileCollectionParams.extension` are watched. See first argument of
@@ -155,13 +155,17 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
155
155
  */
156
156
  const createWatcher = (
157
157
  mocha,
158
- {watchFiles, watchIgnore, beforeRun, afterRun, fileCollectParams}
158
+ {watchFiles, watchIgnore, beforeRun, fileCollectParams}
159
159
  ) => {
160
160
  if (!watchFiles) {
161
161
  watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
162
162
  }
163
163
 
164
164
  debug('ignoring files matching: %s', watchIgnore);
165
+ let globalFixtureContext;
166
+
167
+ // we handle global fixtures manually
168
+ mocha.enableGlobalSetup(false).enableGlobalTeardown(false);
165
169
 
166
170
  const watcher = chokidar.watch(watchFiles, {
167
171
  ignored: watchIgnore,
@@ -169,11 +173,14 @@ const createWatcher = (
169
173
  });
170
174
 
171
175
  const rerunner = createRerunner(mocha, watcher, {
172
- beforeRun,
173
- afterRun
176
+ beforeRun
174
177
  });
175
178
 
176
- watcher.on('ready', () => {
179
+ watcher.on('ready', async () => {
180
+ if (!globalFixtureContext) {
181
+ debug('triggering global setup');
182
+ globalFixtureContext = await mocha.runGlobalSetup();
183
+ }
177
184
  rerunner.run();
178
185
  });
179
186
 
@@ -185,10 +192,39 @@ const createWatcher = (
185
192
  process.on('exit', () => {
186
193
  showCursor();
187
194
  });
188
- process.on('SIGINT', () => {
195
+
196
+ // this is for testing.
197
+ // win32 cannot gracefully shutdown via a signal from a parent
198
+ // process; a `SIGINT` from a parent will cause the process
199
+ // to immediately exit. during normal course of operation, a user
200
+ // will type Ctrl-C and the listener will be invoked, but this
201
+ // is not possible in automated testing.
202
+ // there may be another way to solve this, but it too will be a hack.
203
+ // for our watch tests on win32 we must _fork_ mocha with an IPC channel
204
+ if (process.connected) {
205
+ process.on('message', msg => {
206
+ if (msg === 'SIGINT') {
207
+ process.emit('SIGINT');
208
+ }
209
+ });
210
+ }
211
+
212
+ let exiting = false;
213
+ process.on('SIGINT', async () => {
189
214
  showCursor();
190
- console.log('\n');
191
- process.exit(128 + 2);
215
+ console.error(`${logSymbols.warning} [mocha] cleaning up, please wait...`);
216
+ if (!exiting) {
217
+ exiting = true;
218
+ if (mocha.hasGlobalTeardownFixtures()) {
219
+ debug('running global teardown');
220
+ try {
221
+ await mocha.runGlobalTeardown(globalFixtureContext);
222
+ } catch (err) {
223
+ console.error(err);
224
+ }
225
+ }
226
+ process.exit(130);
227
+ }
192
228
  });
193
229
 
194
230
  // Keyboard shortcut for restarting when "rs\n" is typed (ala Nodemon)
@@ -212,12 +248,11 @@ const createWatcher = (
212
248
  * @param {FSWatcher} watcher - chokidar `FSWatcher` instance
213
249
  * @param {Object} [opts] - Options!
214
250
  * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before `mocha.run()`
215
- * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
216
251
  * @returns {Rerunner}
217
252
  * @ignore
218
253
  * @private
219
254
  */
220
- const createRerunner = (mocha, watcher, {beforeRun, afterRun} = {}) => {
255
+ const createRerunner = (mocha, watcher, {beforeRun} = {}) => {
221
256
  // Set to a `Runner` when mocha is running. Set to `null` when mocha is not
222
257
  // running.
223
258
  let runner = null;
@@ -226,16 +261,15 @@ const createRerunner = (mocha, watcher, {beforeRun, afterRun} = {}) => {
226
261
  let rerunScheduled = false;
227
262
 
228
263
  const run = () => {
229
- mocha = beforeRun ? beforeRun({mocha, watcher}) : mocha;
230
-
264
+ mocha = beforeRun ? beforeRun({mocha, watcher}) || mocha : mocha;
231
265
  runner = mocha.run(() => {
232
266
  debug('finished watch run');
233
267
  runner = null;
234
- afterRun && afterRun({mocha, watcher});
268
+ blastCache(watcher);
235
269
  if (rerunScheduled) {
236
270
  rerun();
237
271
  } else {
238
- debug('waiting for changes...');
272
+ console.error(`${logSymbols.info} [mocha] waiting for changes...`);
239
273
  }
240
274
  });
241
275
  };
@@ -333,15 +367,6 @@ const blastCache = watcher => {
333
367
  * @returns {Mocha}
334
368
  */
335
369
 
336
- /**
337
- * Callback to be run after `mocha.run()` completes. Typically used to clear
338
- * require cache.
339
- * @callback AfterWatchRun
340
- * @private
341
- * @param {{mocha: Mocha, watcher: FSWatcher}} options
342
- * @returns {void}
343
- */
344
-
345
370
  /**
346
371
  * Object containing run control methods
347
372
  * @typedef {Object} Rerunner
package/lib/errors.js CHANGED
@@ -1,12 +1,57 @@
1
1
  'use strict';
2
2
 
3
- var format = require('util').format;
3
+ const {format} = require('util');
4
4
 
5
5
  /**
6
- * Factory functions to create throwable error objects
7
- * @module Errors
6
+ * Contains error codes, factory functions to create throwable error objects,
7
+ * and warning/deprecation functions.
8
+ * @module
8
9
  */
9
10
 
11
+ /**
12
+ * process.emitWarning or a polyfill
13
+ * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options
14
+ * @ignore
15
+ */
16
+ const emitWarning = (msg, type) => {
17
+ if (process.emitWarning) {
18
+ process.emitWarning(msg, type);
19
+ } else {
20
+ process.nextTick(function() {
21
+ console.warn(type + ': ' + msg);
22
+ });
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Show a deprecation warning. Each distinct message is only displayed once.
28
+ * Ignores empty messages.
29
+ *
30
+ * @param {string} [msg] - Warning to print
31
+ * @private
32
+ */
33
+ const deprecate = msg => {
34
+ msg = String(msg);
35
+ if (msg && !deprecate.cache[msg]) {
36
+ deprecate.cache[msg] = true;
37
+ emitWarning(msg, 'DeprecationWarning');
38
+ }
39
+ };
40
+ deprecate.cache = {};
41
+
42
+ /**
43
+ * Show a generic warning.
44
+ * Ignores empty messages.
45
+ *
46
+ * @param {string} [msg] - Warning to print
47
+ * @private
48
+ */
49
+ const warn = msg => {
50
+ if (msg) {
51
+ emitWarning(msg);
52
+ }
53
+ };
54
+
10
55
  /**
11
56
  * When Mocha throw exceptions (or otherwise errors), it attempts to assign a
12
57
  * `code` property to the `Error` object, for easier handling. These are the
@@ -71,9 +116,21 @@ var constants = {
71
116
  /**
72
117
  * Use of `only()` w/ `--forbid-only` results in this error.
73
118
  */
74
- FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY'
119
+ FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY',
120
+
121
+ /**
122
+ * To be thrown when a user-defined plugin implementation (e.g., `mochaHooks`) is invalid
123
+ */
124
+ INVALID_PLUGIN_IMPLEMENTATION: 'ERR_MOCHA_INVALID_PLUGIN_IMPLEMENTATION',
125
+
126
+ /**
127
+ * To be thrown when a builtin or third-party plugin definition (the _definition_ of `mochaHooks`) is invalid
128
+ */
129
+ INVALID_PLUGIN_DEFINITION: 'ERR_MOCHA_INVALID_PLUGIN_DEFINITION'
75
130
  };
76
131
 
132
+ const MOCHA_ERRORS = new Set(Object.values(constants));
133
+
77
134
  /**
78
135
  * Creates an error object to be thrown when no files to be tested could be found using specified pattern.
79
136
  *
@@ -221,7 +278,7 @@ function createFatalError(message, value) {
221
278
  * @public
222
279
  * @returns {Error}
223
280
  */
224
- function createInvalidPluginError(message, pluginType, pluginId) {
281
+ function createInvalidLegacyPluginError(message, pluginType, pluginId) {
225
282
  switch (pluginType) {
226
283
  case 'reporter':
227
284
  return createInvalidReporterError(message, pluginId);
@@ -232,6 +289,21 @@ function createInvalidPluginError(message, pluginType, pluginId) {
232
289
  }
233
290
  }
234
291
 
292
+ /**
293
+ * **DEPRECATED**. Use {@link createInvalidLegacyPluginError} instead Dynamically creates a plugin-type-specific error based on plugin type
294
+ * @deprecated
295
+ * @param {string} message - Error message
296
+ * @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
297
+ * @param {string} [pluginId] - Name/path of plugin, if any
298
+ * @throws When `pluginType` is not known
299
+ * @public
300
+ * @returns {Error}
301
+ */
302
+ function createInvalidPluginError(...args) {
303
+ deprecate('Use createInvalidLegacyPluginError() instead');
304
+ return createInvalidLegacyPluginError(...args);
305
+ }
306
+
235
307
  /**
236
308
  * Creates an error object to be thrown when a mocha object's `run` method is executed while it is already disposed.
237
309
  * @param {string} message The error message to be displayed.
@@ -315,20 +387,70 @@ function createForbiddenExclusivityError(mocha) {
315
387
  return err;
316
388
  }
317
389
 
390
+ /**
391
+ * Creates an error object to be thrown when a plugin definition is invalid
392
+ * @param {string} msg - Error message
393
+ * @param {PluginDefinition} [pluginDef] - Problematic plugin definition
394
+ * @public
395
+ * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
396
+ */
397
+ function createInvalidPluginDefinitionError(msg, pluginDef) {
398
+ const err = new Error(msg);
399
+ err.code = constants.INVALID_PLUGIN_DEFINITION;
400
+ err.pluginDef = pluginDef;
401
+ return err;
402
+ }
403
+
404
+ /**
405
+ * Creates an error object to be thrown when a plugin implementation (user code) is invalid
406
+ * @param {string} msg - Error message
407
+ * @param {Object} [opts] - Plugin definition and user-supplied implementation
408
+ * @param {PluginDefinition} [opts.pluginDef] - Plugin Definition
409
+ * @param {*} [opts.pluginImpl] - Plugin Implementation (user-supplied)
410
+ * @public
411
+ * @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION}
412
+ */
413
+ function createInvalidPluginImplementationError(
414
+ msg,
415
+ {pluginDef, pluginImpl} = {}
416
+ ) {
417
+ const err = new Error(msg);
418
+ err.code = constants.INVALID_PLUGIN_IMPLEMENTATION;
419
+ err.pluginDef = pluginDef;
420
+ err.pluginImpl = pluginImpl;
421
+ return err;
422
+ }
423
+
424
+ /**
425
+ * Returns `true` if an error came out of Mocha.
426
+ * _Can suffer from false negatives, but not false positives._
427
+ * @public
428
+ * @param {*} err - Error, or anything
429
+ * @returns {boolean}
430
+ */
431
+ const isMochaError = err =>
432
+ Boolean(err && typeof err === 'object' && MOCHA_ERRORS.has(err.code));
433
+
318
434
  module.exports = {
319
- createInvalidArgumentTypeError: createInvalidArgumentTypeError,
320
- createInvalidArgumentValueError: createInvalidArgumentValueError,
321
- createInvalidExceptionError: createInvalidExceptionError,
322
- createInvalidInterfaceError: createInvalidInterfaceError,
323
- createInvalidReporterError: createInvalidReporterError,
324
- createMissingArgumentError: createMissingArgumentError,
325
- createNoFilesMatchPatternError: createNoFilesMatchPatternError,
326
- createUnsupportedError: createUnsupportedError,
327
- createInvalidPluginError: createInvalidPluginError,
328
- createMochaInstanceAlreadyDisposedError: createMochaInstanceAlreadyDisposedError,
329
- createMochaInstanceAlreadyRunningError: createMochaInstanceAlreadyRunningError,
330
- createFatalError: createFatalError,
331
- createMultipleDoneError: createMultipleDoneError,
332
- createForbiddenExclusivityError: createForbiddenExclusivityError,
333
- constants: constants
435
+ constants,
436
+ createFatalError,
437
+ createForbiddenExclusivityError,
438
+ createInvalidArgumentTypeError,
439
+ createInvalidArgumentValueError,
440
+ createInvalidExceptionError,
441
+ createInvalidInterfaceError,
442
+ createInvalidLegacyPluginError,
443
+ createInvalidPluginDefinitionError,
444
+ createInvalidPluginError,
445
+ createInvalidPluginImplementationError,
446
+ createInvalidReporterError,
447
+ createMissingArgumentError,
448
+ createMochaInstanceAlreadyDisposedError,
449
+ createMochaInstanceAlreadyRunningError,
450
+ createMultipleDoneError,
451
+ createNoFilesMatchPatternError,
452
+ createUnsupportedError,
453
+ deprecate,
454
+ isMochaError,
455
+ warn
334
456
  };
package/lib/hook.js CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var Runnable = require('./runnable');
4
- var inherits = require('./utils').inherits;
4
+ const {inherits, constants} = require('./utils');
5
+ const {MOCHA_ID_PROP_NAME} = constants;
5
6
 
6
7
  /**
7
8
  * Expose `Hook`.
@@ -63,16 +64,20 @@ Hook.prototype.serialize = function serialize() {
63
64
  return {
64
65
  $$isPending: this.isPending(),
65
66
  $$titlePath: this.titlePath(),
66
- ctx: {
67
- currentTest: {
68
- title: this.ctx && this.ctx.currentTest && this.ctx.currentTest.title
69
- }
70
- },
67
+ ctx:
68
+ this.ctx && this.ctx.currentTest
69
+ ? {
70
+ currentTest: {
71
+ title: this.ctx.currentTest.title,
72
+ [MOCHA_ID_PROP_NAME]: this.ctx.currentTest.id
73
+ }
74
+ }
75
+ : {},
71
76
  parent: {
72
- root: this.parent.root,
73
- title: this.parent.title
77
+ [MOCHA_ID_PROP_NAME]: this.parent.id
74
78
  },
75
79
  title: this.title,
76
- type: this.type
80
+ type: this.type,
81
+ [MOCHA_ID_PROP_NAME]: this.id
77
82
  };
78
83
  };