mocha 9.2.0 → 10.0.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.
File without changes
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Exports Yargs commands
5
- * @see https://git.io/fpJ0G
5
+ * @see https://github.com/yargs/yargs/blob/main/docs/advanced.md
6
6
  * @private
7
7
  * @module
8
8
  */
package/lib/cli/config.js CHANGED
@@ -30,10 +30,6 @@ exports.CONFIG_FILES = [
30
30
  '.mocharc.json'
31
31
  ];
32
32
 
33
- const isModuleNotFoundError = err =>
34
- err.code === 'MODULE_NOT_FOUND' ||
35
- err.message.indexOf('Cannot find module') !== -1;
36
-
37
33
  /**
38
34
  * Parsers for various config filetypes. Each accepts a filepath and
39
35
  * returns an object (but could throw)
@@ -41,17 +37,16 @@ const isModuleNotFoundError = err =>
41
37
  const parsers = (exports.parsers = {
42
38
  yaml: filepath => require('js-yaml').load(fs.readFileSync(filepath, 'utf8')),
43
39
  js: filepath => {
44
- const cwdFilepath = path.resolve(filepath);
40
+ let cwdFilepath;
45
41
  try {
46
- debug('parsers: load using cwd-relative path: "%s"', cwdFilepath);
42
+ debug('parsers: load cwd-relative path: "%s"', path.resolve(filepath));
43
+ cwdFilepath = require.resolve(path.resolve(filepath)); // evtl. throws
47
44
  return require(cwdFilepath);
48
45
  } catch (err) {
49
- if (isModuleNotFoundError(err)) {
50
- debug('parsers: retry load as module-relative path: "%s"', filepath);
51
- return require(filepath);
52
- } else {
53
- throw err; // rethrow
54
- }
46
+ if (cwdFilepath) throw err;
47
+
48
+ debug('parsers: retry load as module-relative path: "%s"', filepath);
49
+ return require(filepath);
55
50
  }
56
51
  },
57
52
  json: filepath =>
@@ -225,18 +225,18 @@ exports.validateLegacyPlugin = (opts, pluginType, map = {}) => {
225
225
 
226
226
  // if this exists, then it's already loaded, so nothing more to do.
227
227
  if (!map[pluginId]) {
228
+ let foundId;
228
229
  try {
229
- map[pluginId] = require(pluginId);
230
+ foundId = require.resolve(pluginId);
231
+ map[pluginId] = require(foundId);
230
232
  } catch (err) {
231
- if (err.code === 'MODULE_NOT_FOUND') {
232
- // Try to load reporters from a path (absolute or relative)
233
- try {
234
- map[pluginId] = require(path.resolve(pluginId));
235
- } catch (err) {
236
- throw createUnknownError(err);
237
- }
238
- } else {
239
- throw createUnknownError(err);
233
+ if (foundId) throw createUnknownError(err);
234
+
235
+ // Try to load reporters from a cwd-relative path
236
+ try {
237
+ map[pluginId] = require(path.resolve(pluginId));
238
+ } catch (e) {
239
+ throw createUnknownError(e);
240
240
  }
241
241
  }
242
242
  }
@@ -39,7 +39,6 @@ const TYPES = (exports.types = {
39
39
  'forbid-only',
40
40
  'forbid-pending',
41
41
  'full-trace',
42
- 'growl',
43
42
  'inline-diffs',
44
43
  'invert',
45
44
  'list-interfaces',
@@ -76,7 +75,6 @@ exports.aliases = {
76
75
  fgrep: ['f'],
77
76
  global: ['globals'],
78
77
  grep: ['g'],
79
- growl: ['G'],
80
78
  ignore: ['exclude'],
81
79
  invert: ['i'],
82
80
  jobs: ['j'],
package/lib/cli/run.js CHANGED
@@ -141,10 +141,6 @@ exports.builder = yargs =>
141
141
  group: GROUPS.FILTERS,
142
142
  requiresArg: true
143
143
  },
144
- growl: {
145
- description: 'Enable Growl notifications',
146
- group: GROUPS.OUTPUT
147
- },
148
144
  ignore: {
149
145
  defaultDescription: '(none)',
150
146
  description: 'Ignore file(s) or glob pattern(s)',
@@ -333,7 +329,7 @@ exports.builder = yargs =>
333
329
  if (argv.compilers) {
334
330
  throw createUnsupportedError(
335
331
  `--compilers is DEPRECATED and no longer supported.
336
- See https://git.io/vdcSr for migration information.`
332
+ See https://github.com/mochajs/mocha/wiki/compilers-deprecation for migration information.`
337
333
  );
338
334
  }
339
335
 
package/lib/mocha.js CHANGED
@@ -9,14 +9,12 @@
9
9
  var escapeRe = require('escape-string-regexp');
10
10
  var path = require('path');
11
11
  var builtinReporters = require('./reporters');
12
- var growl = require('./nodejs/growl');
13
12
  var utils = require('./utils');
14
13
  var mocharc = require('./mocharc.json');
15
14
  var Suite = require('./suite');
16
15
  var esmUtils = require('./nodejs/esm-utils');
17
16
  var createStatsCollector = require('./stats-collector');
18
17
  const {
19
- warn,
20
18
  createInvalidReporterError,
21
19
  createInvalidInterfaceError,
22
20
  createMochaInstanceAlreadyDisposedError,
@@ -166,7 +164,6 @@ exports.run = function (...args) {
166
164
  * @param {boolean} [options.fullTrace] - Full stacktrace upon failure?
167
165
  * @param {string[]} [options.global] - Variables expected in global scope.
168
166
  * @param {RegExp|string} [options.grep] - Test filter given regular expression.
169
- * @param {boolean} [options.growl] - Enable desktop notifications?
170
167
  * @param {boolean} [options.inlineDiffs] - Display inline diffs?
171
168
  * @param {boolean} [options.invert] - Invert test filter matches?
172
169
  * @param {boolean} [options.noHighlighting] - Disable syntax highlighting?
@@ -196,7 +193,7 @@ function Mocha(options = {}) {
196
193
  .ui(options.ui)
197
194
  .reporter(
198
195
  options.reporter,
199
- options.reporterOption || options.reporterOptions // for backwards compability
196
+ options.reporterOption || options.reporterOptions // for backwards compatibility
200
197
  )
201
198
  .slow(options.slow)
202
199
  .global(options.global);
@@ -223,7 +220,6 @@ function Mocha(options = {}) {
223
220
  'forbidOnly',
224
221
  'forbidPending',
225
222
  'fullTrace',
226
- 'growl',
227
223
  'inlineDiffs',
228
224
  'invert'
229
225
  ].forEach(function (opt) {
@@ -335,35 +331,26 @@ Mocha.prototype.reporter = function (reporterName, reporterOptions) {
335
331
  }
336
332
  // Try to load reporters from process.cwd() and node_modules
337
333
  if (!reporter) {
334
+ let foundReporter;
338
335
  try {
339
- reporter = require(reporterName);
336
+ foundReporter = require.resolve(reporterName);
337
+ reporter = require(foundReporter);
340
338
  } catch (err) {
341
- if (err.code === 'MODULE_NOT_FOUND') {
342
- // Try to load reporters from a path (absolute or relative)
343
- try {
344
- reporter = require(path.resolve(utils.cwd(), reporterName));
345
- } catch (_err) {
346
- _err.code === 'MODULE_NOT_FOUND'
347
- ? warn(`'${reporterName}' reporter not found`)
348
- : warn(
349
- `'${reporterName}' reporter blew up with error:\n ${err.stack}`
350
- );
351
- }
352
- } else {
353
- warn(`'${reporterName}' reporter blew up with error:\n ${err.stack}`);
339
+ if (foundReporter) {
340
+ throw createInvalidReporterError(err.message, foundReporter);
341
+ }
342
+ // Try to load reporters from a cwd-relative path
343
+ try {
344
+ reporter = require(path.resolve(reporterName));
345
+ } catch (e) {
346
+ throw createInvalidReporterError(e.message, reporterName);
354
347
  }
355
348
  }
356
349
  }
357
- if (!reporter) {
358
- throw createInvalidReporterError(
359
- `invalid reporter '${reporterName}'`,
360
- reporterName
361
- );
362
- }
363
350
  this._reporter = reporter;
364
351
  }
365
352
  this.options.reporterOption = reporterOptions;
366
- // alias option name is used in public reporters xunit/tap/progress
353
+ // alias option name is used in built-in reporters xunit/tap/progress
367
354
  this.options.reporterOptions = reporterOptions;
368
355
  return this;
369
356
  };
@@ -478,7 +465,7 @@ Mocha.prototype.loadFilesAsync = function () {
478
465
  Mocha.unloadFile = function (file) {
479
466
  if (utils.isBrowser()) {
480
467
  throw createUnsupportedError(
481
- 'unloadFile() is only suported in a Node.js environment'
468
+ 'unloadFile() is only supported in a Node.js environment'
482
469
  );
483
470
  }
484
471
  return require('./nodejs/file-unloader').unloadFile(file);
@@ -657,49 +644,6 @@ Mocha.prototype.fullTrace = function (fullTrace) {
657
644
  return this;
658
645
  };
659
646
 
660
- /**
661
- * Enables desktop notification support if prerequisite software installed.
662
- *
663
- * @public
664
- * @see [CLI option](../#-growl-g)
665
- * @return {Mocha} this
666
- * @chainable
667
- */
668
- Mocha.prototype.growl = function () {
669
- this.options.growl = this.isGrowlCapable();
670
- if (!this.options.growl) {
671
- var detail = utils.isBrowser()
672
- ? 'notification support not available in this browser...'
673
- : 'notification support prerequisites not installed...';
674
- console.error(detail + ' cannot enable!');
675
- }
676
- return this;
677
- };
678
-
679
- /**
680
- * @summary
681
- * Determines if Growl support seems likely.
682
- *
683
- * @description
684
- * <strong>Not available when run in browser.</strong>
685
- *
686
- * @private
687
- * @see {@link Growl#isCapable}
688
- * @see {@link Mocha#growl}
689
- * @return {boolean} whether Growl support can be expected
690
- */
691
- Mocha.prototype.isGrowlCapable = growl.isCapable;
692
-
693
- /**
694
- * Implements desktop notifications using a pseudo-reporter.
695
- *
696
- * @private
697
- * @see {@link Mocha#growl}
698
- * @see {@link Growl#notify}
699
- * @param {Runner} runner - Runner instance.
700
- */
701
- Mocha.prototype._growl = growl.notify;
702
-
703
647
  /**
704
648
  * Specifies whitelist of variable names to be expected in global scope.
705
649
  *
@@ -723,7 +667,7 @@ Mocha.prototype.global = function (global) {
723
667
  });
724
668
  return this;
725
669
  };
726
- // for backwards compability, 'globals' is an alias of 'global'
670
+ // for backwards compatibility, 'globals' is an alias of 'global'
727
671
  Mocha.prototype.globals = Mocha.prototype.global;
728
672
 
729
673
  /**
@@ -1047,9 +991,6 @@ Mocha.prototype.run = function (fn) {
1047
991
  if (options.global) {
1048
992
  runner.globals(options.global);
1049
993
  }
1050
- if (options.growl) {
1051
- this._growl(runner);
1052
- }
1053
994
  if (options.color !== undefined) {
1054
995
  exports.reporters.Base.useColors = options.color;
1055
996
  }
@@ -10,7 +10,7 @@ const formattedImport = async file => {
10
10
  // the location of the syntax error in the error thrown.
11
11
  // This is problematic because the user can't see what file has the problem,
12
12
  // so we add the file location to the error.
13
- // This `if` should be removed once Node.js fixes the problem.
13
+ // TODO: remove once Node.js fixes the problem.
14
14
  if (
15
15
  err instanceof SyntaxError &&
16
16
  err.message &&
@@ -30,64 +30,52 @@ const formattedImport = async file => {
30
30
  return import(file);
31
31
  };
32
32
 
33
- const hasStableEsmImplementation = (() => {
34
- const [major, minor] = process.version.split('.');
35
- // ESM is stable from v12.22.0 onward
36
- // https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules
37
- const majorNumber = parseInt(major.slice(1), 10);
38
- const minorNumber = parseInt(minor, 10);
39
- return majorNumber > 12 || (majorNumber === 12 && minorNumber >= 22);
40
- })();
41
-
42
- exports.requireOrImport = hasStableEsmImplementation
43
- ? async file => {
44
- if (path.extname(file) === '.mjs') {
45
- return formattedImport(file);
46
- }
33
+ exports.requireOrImport = async file => {
34
+ if (path.extname(file) === '.mjs') {
35
+ return formattedImport(file);
36
+ }
37
+ try {
38
+ return dealWithExports(await formattedImport(file));
39
+ } catch (err) {
40
+ if (
41
+ err.code === 'ERR_MODULE_NOT_FOUND' ||
42
+ err.code === 'ERR_UNKNOWN_FILE_EXTENSION' ||
43
+ err.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
44
+ ) {
47
45
  try {
48
- return dealWithExports(await formattedImport(file));
49
- } catch (err) {
46
+ // Importing a file usually works, but the resolution of `import` is the ESM
47
+ // resolution algorithm, and not the CJS resolution algorithm. We may have
48
+ // failed because we tried the ESM resolution, so we try to `require` it.
49
+ return require(file);
50
+ } catch (requireErr) {
50
51
  if (
51
- err.code === 'ERR_MODULE_NOT_FOUND' ||
52
- err.code === 'ERR_UNKNOWN_FILE_EXTENSION' ||
53
- err.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
52
+ requireErr.code === 'ERR_REQUIRE_ESM' ||
53
+ (requireErr instanceof SyntaxError &&
54
+ requireErr
55
+ .toString()
56
+ .includes('Cannot use import statement outside a module'))
54
57
  ) {
55
- try {
56
- // Importing a file usually works, but the resolution of `import` is the ESM
57
- // resolution algorithm, and not the CJS resolution algorithm. So in this case
58
- // if we fail, we may have failed because we tried the ESM resolution and failed
59
- // So we try to `require` it
60
- return require(file);
61
- } catch (requireErr) {
62
- if (
63
- requireErr.code === 'ERR_REQUIRE_ESM' ||
64
- (requireErr instanceof SyntaxError &&
65
- requireErr
66
- .toString()
67
- .includes('Cannot use import statement outside a module'))
68
- ) {
69
- // ERR_REQUIRE_ESM happens when the test file is a JS file, but via type:module is actually ESM,
70
- // AND has an import to a file that doesn't exist.
71
- // This throws an `ERR_MODULE_NOT_FOUND` error above,
72
- // and when we try to `require` it here, it throws an `ERR_REQUIRE_ESM`.
73
- // What we want to do is throw the original error (the `ERR_MODULE_NOT_FOUND`),
74
- // and not the `ERR_REQUIRE_ESM` error, which is a red herring.
75
- //
76
- // SyntaxError happens when in an edge case: when we're using an ESM loader that loads
77
- // a `test.ts` file (i.e. unrecognized extension), and that file includes an unknown
78
- // import (which thows an ERR_MODULE_NOT_FOUND). require-ing it will throw the
79
- // syntax error, because we cannot require a file that has import-s.
80
- throw err;
81
- } else {
82
- throw requireErr;
83
- }
84
- }
85
- } else {
58
+ // ERR_REQUIRE_ESM happens when the test file is a JS file, but via type:module is actually ESM,
59
+ // AND has an import to a file that doesn't exist.
60
+ // This throws an `ERR_MODULE_NOT_FOUND` error above,
61
+ // and when we try to `require` it here, it throws an `ERR_REQUIRE_ESM`.
62
+ // What we want to do is throw the original error (the `ERR_MODULE_NOT_FOUND`),
63
+ // and not the `ERR_REQUIRE_ESM` error, which is a red herring.
64
+ //
65
+ // SyntaxError happens when in an edge case: when we're using an ESM loader that loads
66
+ // a `test.ts` file (i.e. unrecognized extension), and that file includes an unknown
67
+ // import (which throws an ERR_MODULE_NOT_FOUND). `require`-ing it will throw the
68
+ // syntax error, because we cannot require a file that has `import`-s.
86
69
  throw err;
70
+ } else {
71
+ throw requireErr;
87
72
  }
88
73
  }
74
+ } else {
75
+ throw err;
89
76
  }
90
- : implementationOfRequireOrImportForUnstableEsm;
77
+ }
78
+ };
91
79
 
92
80
  function dealWithExports(module) {
93
81
  if (module.default) {
@@ -104,21 +92,3 @@ exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
104
92
  postLoadFunc(file, result);
105
93
  }
106
94
  };
107
-
108
- /* istanbul ignore next */
109
- async function implementationOfRequireOrImportForUnstableEsm(file) {
110
- if (path.extname(file) === '.mjs') {
111
- return formattedImport(file);
112
- }
113
- // This is currently the only known way of figuring out whether a file is CJS or ESM in
114
- // Node.js that doesn't necessitate calling `import` first.
115
- try {
116
- return require(file);
117
- } catch (err) {
118
- if (err.code === 'ERR_REQUIRE_ESM') {
119
- return formattedImport(file);
120
- } else {
121
- throw err;
122
- }
123
- }
124
- }
@@ -271,8 +271,9 @@ class ParallelBufferedRunner extends Runner {
271
271
  *
272
272
  * @param {Function} callback - Called with an exit code corresponding to
273
273
  * number of test failures.
274
- * @param {{files: string[], options: Options}} opts - Files to run and
275
- * command-line options, respectively.
274
+ * @param {Object} [opts] - options
275
+ * @param {string[]} opts.files - Files to run
276
+ * @param {Options} opts.options - command-line options
276
277
  */
277
278
  run(callback, {files, options = {}} = {}) {
278
279
  /**
@@ -56,6 +56,11 @@ exports.useColors =
56
56
 
57
57
  exports.inlineDiffs = false;
58
58
 
59
+ /**
60
+ * Truncate diffs longer than this value to avoid slow performance
61
+ */
62
+ exports.maxDiffSize = 8192;
63
+
59
64
  /**
60
65
  * Default color map.
61
66
  */
@@ -188,18 +193,23 @@ function stringifyDiffObjs(err) {
188
193
  * @param {string} expected
189
194
  * @return {string} Diff
190
195
  */
196
+
191
197
  var generateDiff = (exports.generateDiff = function (actual, expected) {
192
198
  try {
193
- const diffSize = 2048;
194
- if (actual.length > diffSize) {
195
- actual = actual.substring(0, diffSize) + ' ... Lines skipped';
199
+ var maxLen = exports.maxDiffSize;
200
+ var skipped = 0;
201
+ if (maxLen > 0) {
202
+ skipped = Math.max(actual.length - maxLen, expected.length - maxLen);
203
+ actual = actual.slice(0, maxLen);
204
+ expected = expected.slice(0, maxLen);
196
205
  }
197
- if (expected.length > diffSize) {
198
- expected = expected.substring(0, diffSize) + ' ... Lines skipped';
199
- }
200
- return exports.inlineDiffs
206
+ let result = exports.inlineDiffs
201
207
  ? inlineDiff(actual, expected)
202
208
  : unifiedDiff(actual, expected);
209
+ if (skipped > 0) {
210
+ result = `${result}\n [mocha] output truncated to ${maxLen} characters, see "maxDiffSize" reporter-option\n`;
211
+ }
212
+ return result;
203
213
  } catch (err) {
204
214
  var msg =
205
215
  '\n ' +
@@ -318,6 +328,12 @@ function Base(runner, options) {
318
328
  this.runner = runner;
319
329
  this.stats = runner.stats; // assigned so Reporters keep a closer reference
320
330
 
331
+ var maxDiffSizeOpt =
332
+ this.options.reporterOption && this.options.reporterOption.maxDiffSize;
333
+ if (maxDiffSizeOpt !== undefined && !isNaN(Number(maxDiffSizeOpt))) {
334
+ exports.maxDiffSize = Number(maxDiffSizeOpt);
335
+ }
336
+
321
337
  runner.on(EVENT_TEST_PASS, function (test) {
322
338
  if (test.duration > test.slow()) {
323
339
  test.speed = 'slow';
@@ -181,7 +181,7 @@ function HTML(runner, options) {
181
181
  if (indexOfMessage === -1) {
182
182
  stackString = test.err.stack;
183
183
  } else {
184
- stackString = test.err.stack.substr(
184
+ stackString = test.err.stack.slice(
185
185
  test.err.message.length + indexOfMessage
186
186
  );
187
187
  }
package/lib/runner.js CHANGED
@@ -135,27 +135,15 @@ class Runner extends EventEmitter {
135
135
  * @public
136
136
  * @class
137
137
  * @param {Suite} suite - Root suite
138
- * @param {Object|boolean} [opts] - Options. If `boolean` (deprecated), whether to delay execution of root suite until ready.
138
+ * @param {Object} [opts] - Settings object
139
139
  * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
140
140
  * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
141
141
  * @param {boolean} [opts.dryRun] - Whether to report tests without running them.
142
142
  * @param {boolean} [opts.failZero] - Whether to fail test run if zero tests encountered.
143
143
  */
144
- constructor(suite, opts) {
144
+ constructor(suite, opts = {}) {
145
145
  super();
146
- if (opts === undefined) {
147
- opts = {};
148
- }
149
- if (typeof opts === 'boolean') {
150
- // TODO: remove this
151
- require('./errors').deprecate(
152
- '"Runner(suite: Suite, delay: boolean)" is deprecated. Use "Runner(suite: Suite, {delay: boolean})" instead.'
153
- );
154
- this._delay = opts;
155
- opts = {};
156
- } else {
157
- this._delay = opts.delay;
158
- }
146
+
159
147
  var self = this;
160
148
  this._globals = [];
161
149
  this._abort = false;
@@ -655,7 +643,7 @@ Runner.prototype.parents = function () {
655
643
  * @private
656
644
  */
657
645
  Runner.prototype.runTest = function (fn) {
658
- if (this._opts.dryRun) return fn();
646
+ if (this._opts.dryRun) return Runner.immediately(fn);
659
647
 
660
648
  var self = this;
661
649
  var test = this.test;
@@ -1031,7 +1019,9 @@ Runner.prototype._uncaught = function (err) {
1031
1019
  * @public
1032
1020
  * @memberof Runner
1033
1021
  * @param {Function} fn - Callback when finished
1034
- * @param {{files: string[], options: Options}} [opts] - For subclasses
1022
+ * @param {Object} [opts] - For subclasses
1023
+ * @param {string[]} opts.files - Files to run
1024
+ * @param {Options} opts.options - command-line options
1035
1025
  * @returns {Runner} Runner instance.
1036
1026
  */
1037
1027
  Runner.prototype.run = function (fn, opts = {}) {
@@ -1064,7 +1054,7 @@ Runner.prototype.run = function (fn, opts = {}) {
1064
1054
  debug('run(): filtered exclusive Runnables');
1065
1055
  }
1066
1056
  this.state = constants.STATE_RUNNING;
1067
- if (this._delay) {
1057
+ if (this._opts.delay) {
1068
1058
  this.emit(constants.EVENT_DELAY_END);
1069
1059
  debug('run(): "delay" ended');
1070
1060
  }
@@ -1091,7 +1081,7 @@ Runner.prototype.run = function (fn, opts = {}) {
1091
1081
  this._addEventListener(process, 'uncaughtException', this.uncaught);
1092
1082
  this._addEventListener(process, 'unhandledRejection', this.unhandled);
1093
1083
 
1094
- if (this._delay) {
1084
+ if (this._opts.delay) {
1095
1085
  // for reporters, I guess.
1096
1086
  // might be nice to debounce some dots while we wait.
1097
1087
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);