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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,91 @@
1
+ # 8.2.1 / 2020-11-02
2
+
3
+ Fixed stuff.
4
+
5
+ ## :bug: Fixes
6
+
7
+ - [#4489](https://github.com/mochajs/mocha/issues/4489): Fix problematic handling of otherwise-unhandled `Promise` rejections and erroneous "`done()` called twice" errors ([**@boneskull**](https://github.com/boneskull))
8
+ - [#4496](https://github.com/mochajs/mocha/issues/4496): Avoid `MaxListenersExceededWarning` in watch mode ([**@boneskull**](https://github.com/boneskull))
9
+
10
+ Also thanks to [**@akeating**](https://github.com/akeating) for a documentation fix!
11
+
12
+ # 8.2.0 / 2020-10-16
13
+
14
+ The major feature added in v8.2.0 is addition of support for [_global fixtures_](https://mochajs.org/#global-fixtures).
15
+
16
+ While Mocha has always had the ability to run setup and teardown via a hook (e.g., a `before()` at the top level of a test file) when running tests in serial, Mocha v8.0.0 added support for parallel runs. Parallel runs are _incompatible_ with this strategy; e.g., a top-level `before()` would only run for the file in which it was defined.
17
+
18
+ With [global fixtures](https://mochajs.org/#global-fixtures), Mocha can now perform user-defined setup and teardown _regardless_ of mode, and these fixtures are guaranteed to run _once and only once_. This holds for parallel mode, serial mode, and even "watch" mode (the teardown will run once you hit Ctrl-C, just before Mocha finally exits). Tasks such as starting and stopping servers are well-suited to global fixtures, but not sharing resources--global fixtures do _not_ share context with your test files (but they do share context with each other).
19
+
20
+ Here's a short example of usage:
21
+
22
+ ```js
23
+ // fixtures.js
24
+
25
+ // can be async or not
26
+ exports.mochaGlobalSetup = async function() {
27
+ this.server = await startSomeServer({port: process.env.TEST_PORT});
28
+ console.log(`server running on port ${this.server.port}`);
29
+ };
30
+
31
+ exports.mochaGlobalTeardown = async function() {
32
+ // the context (`this`) is shared, but not with the test files
33
+ await this.server.stop();
34
+ console.log(`server on port ${this.server.port} stopped`);
35
+ };
36
+
37
+ // this file can contain root hook plugins as well!
38
+ // exports.mochaHooks = { ... }
39
+ ```
40
+
41
+ Fixtures are loaded with `--require`, e.g., `mocha --require fixtures.js`.
42
+
43
+ For detailed information, please see the [documentation](https://mochajs.org/#global-fixtures) and this handy-dandy [flowchart](https://mochajs.org/#test-fixture-decision-tree-wizard-thing) to help understand the differences between hooks, root hook plugins, and global fixtures (and when you should use each).
44
+
45
+ ## :tada: Enhancements
46
+
47
+ - [#4308](https://github.com/mochajs/mocha/issues/4308): Support run-once [global setup & teardown fixtures](https://mochajs.org/#global-fixtures) ([**@boneskull**](https://github.com/boneskull))
48
+ - [#4442](https://github.com/mochajs/mocha/issues/4442): Multi-part extensions (e.g., `test.js`) now usable with `--extension` option ([**@jordanstephens**](https://github.com/jordanstephens))
49
+ - [#4472](https://github.com/mochajs/mocha/issues/4472): Leading dots (e.g., `.js`, `.test.js`) now usable with `--extension` option ([**@boneskull**](https://github.com/boneskull))
50
+ - [#4434](https://github.com/mochajs/mocha/issues/4434): Output of `json` reporter now contains `speed` ("fast"/"medium"/"slow") property ([**@wwhurin**](https://github.com/wwhurin))
51
+ - [#4464](https://github.com/mochajs/mocha/issues/4464): Errors thrown by serializer in parallel mode now have error codes ([**@evaline-ju**](https://github.com/evaline-ju))
52
+
53
+ _For implementors of custom reporters:_
54
+
55
+ - [#4409](https://github.com/mochajs/mocha/issues/4409): Parallel mode and custom reporter improvements ([**@boneskull**](https://github.com/boneskull)):
56
+ - Support custom worker-process-only reporters (`Runner.prototype.workerReporter()`); reporters should subclass `ParallelBufferedReporter` in `mocha/lib/nodejs/reporters/parallel-buffered`
57
+ - Allow opt-in of object reference matching for "sufficiently advanced" custom reporters (`Runner.prototype.linkPartialObjects()`); use if strict object equality is needed when consuming `Runner` event data
58
+ - Enable detection of parallel mode (`Runner.prototype.isParallelMode()`)
59
+
60
+ ## :bug: Fixes
61
+
62
+ - [#4476](https://github.com/mochajs/mocha/issues/4476): Workaround for profoundly bizarre issue affecting `npm` v6.x causing some of Mocha's deps to be installed when `mocha` is present in a package's `devDependencies` and `npm install --production` is run the package's working copy ([**@boneskull**](https://github.com/boneskull))
63
+ - [#4465](https://github.com/mochajs/mocha/issues/4465): Worker processes guaranteed (as opposed to "very likely") to exit before Mocha does; fixes a problem when using `nyc` with Mocha in parallel mode ([**@boneskull**](https://github.com/boneskull))
64
+ - [#4419](https://github.com/mochajs/mocha/issues/4419): Restore `lookupFiles()` in `mocha/lib/utils`, which was broken/missing in Mocha v8.1.0; it now prints a deprecation warning (use `const {lookupFiles} = require('mocha/lib/cli')` instead) ([**@boneskull**](https://github.com/boneskull))
65
+
66
+ Thanks to [**@AviVahl**](https://github.com/AviVahl), [**@donghoon-song**](https://github.com/donghoon-song), [**@ValeriaVG**](https://github.com/ValeriaVG), [**@znarf**](https://github.com/znarf), [**@sujin-park**](https://github.com/sujin-park), and [**@majecty**](https://github.com/majecty) for other helpful contributions!
67
+
68
+ # 8.1.3 / 2020-08-28
69
+
70
+ ## :bug: Fixes
71
+
72
+ - [#4425](https://github.com/mochajs/mocha/issues/4425): Restore `Mocha.utils.lookupFiles()` and Webpack compatibility (both broken since v8.1.0); `Mocha.utils.lookupFiles()` is now **deprecated** and will be removed in the next major revision of Mocha; use `require('mocha/lib/cli').lookupFiles` instead ([**@boneskull**](https://github.com/boneskull))
73
+
74
+ # 8.1.2 / 2020-08-25
75
+
76
+ ## :bug: Fixes
77
+
78
+ - [#4418](https://github.com/mochajs/mocha/issues/4418): Fix command-line flag incompatibility in forthcoming Node.js v14.9.0 ([**@boneskull**](https://github.com/boneskull))
79
+ - [#4401](https://github.com/mochajs/mocha/issues/4401): Fix missing global variable in browser ([**@irrationnelle**](https://github.com/irrationnelle))
80
+
81
+ ## :lock: Security Fixes
82
+
83
+ - [#4396](https://github.com/mochajs/mocha/issues/4396): Update many dependencies ([**@GChuf**](https://github.com/GChuf))
84
+
85
+ ## :book: Documentation
86
+
87
+ - Various fixes by [**@sujin-park**](https://github.com/sujin-park), [**@wwhurin**](https://github.com/wwhurin) & [**@Donghoon759**](https://github.com/Donghoon759)
88
+
1
89
  # 8.1.1 / 2020-08-04
2
90
 
3
91
  ## :bug: Fixes
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  <p align="center"><a href="http://travis-ci.org/mochajs/mocha"><img src="https://api.travis-ci.org/mochajs/mocha.svg?branch=master" alt="Build Status"></a> <a href="https://coveralls.io/github/mochajs/mocha"><img src="https://coveralls.io/repos/github/mochajs/mocha/badge.svg" alt="Coverage Status"></a> <a href="https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_shield"><img src="https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=shield" alt="FOSSA Status"></a> <a href="https://gitter.im/mochajs/mocha?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/Join%20Chat.svg" alt="Gitter"></a> <a href="https://github.com/mochajs/mocha#backers"><img src="https://opencollective.com/mochajs/backers/badge.svg" alt="OpenCollective"></a> <a href="https://github.com/mochajs/mocha#sponsors"><img src="https://opencollective.com/mochajs/sponsors/badge.svg" alt="OpenCollective"></a>
8
8
  </p>
9
9
 
10
+ <p align="center"><a href="https://www.npmjs.com/package/mocha"><img src="https://img.shields.io/npm/v/mocha.svg" alt="NPM Version"></a> <a href="https://github.com/mochajs/mocha"><img src="https://img.shields.io/node/v/mocha.svg" alt="Node Version"></a></p>
11
+
10
12
  <p align="center"><br><img alt="Mocha Browser Support h/t SauceLabs" src="https://saucelabs.com/browser-matrix/mochajs.svg" width="354"></p>
11
13
 
12
14
  ## Links
package/browser-entry.js CHANGED
@@ -210,8 +210,8 @@ Mocha.process = process;
210
210
  * Expose mocha.
211
211
  */
212
212
 
213
- mocha.Mocha = Mocha;
214
- mocha.mocha = mocha;
213
+ global.Mocha = Mocha;
214
+ global.mocha = mocha;
215
215
 
216
216
  // this allows test/acceptance/required-tokens.js to pass; thus,
217
217
  // you can now do `const describe = require('mocha').describe` in a
package/lib/cli/cli.js CHANGED
@@ -3,19 +3,24 @@
3
3
  'use strict';
4
4
 
5
5
  /**
6
- * This is where we finally parse and handle arguments passed to the `mocha` executable.
7
- * Option parsing is handled by {@link https://npm.im/yargs yargs}.
8
- * If executed via `node`, this module will run {@linkcode module:lib/cli/cli.main main()}.
9
- *
10
- * @private
11
- * @module
6
+ * Contains CLI entry point and public API for programmatic usage in Node.js.
7
+ * - Option parsing is handled by {@link https://npm.im/yargs yargs}.
8
+ * - If executed via `node`, this module will run {@linkcode module:lib/cli.main main()}.
9
+ * @public
10
+ * @module lib/cli
12
11
  */
13
12
 
14
13
  const debug = require('debug')('mocha:cli:cli');
15
14
  const symbols = require('log-symbols');
16
15
  const yargs = require('yargs/yargs');
17
16
  const path = require('path');
18
- const {loadOptions, YARGS_PARSER_CONFIG} = require('./options');
17
+ const {
18
+ loadRc,
19
+ loadPkgRc,
20
+ loadOptions,
21
+ YARGS_PARSER_CONFIG
22
+ } = require('./options');
23
+ const lookupFiles = require('./lookup-files');
19
24
  const commands = require('./commands');
20
25
  const ansi = require('ansi-colors');
21
26
  const {repository, homepage, version, gitter} = require('../../package.json');
@@ -25,7 +30,8 @@ const {cwd} = require('../utils');
25
30
  * - Accepts an `Array` of arguments
26
31
  * - Modifies {@link https://nodejs.org/api/modules.html#modules_module_paths Node.js' search path} for easy loading of consumer modules
27
32
  * - Sets {@linkcode https://nodejs.org/api/errors.html#errors_error_stacktracelimit Error.stackTraceLimit} to `Infinity`
28
- * @summary Mocha's main entry point from the command-line.
33
+ * @public
34
+ * @summary Mocha's main command-line entry-point.
29
35
  * @param {string[]} argv - Array of arguments to parse, or by default the lovely `process.argv.slice(2)`
30
36
  */
31
37
  exports.main = (argv = process.argv.slice(2)) => {
@@ -71,6 +77,11 @@ exports.main = (argv = process.argv.slice(2)) => {
71
77
  .parse(args._);
72
78
  };
73
79
 
80
+ exports.lookupFiles = lookupFiles;
81
+ exports.loadOptions = loadOptions;
82
+ exports.loadPkgRc = loadPkgRc;
83
+ exports.loadRc = loadRc;
84
+
74
85
  // allow direct execution
75
86
  if (require.main === module) {
76
87
  exports.main();
package/lib/cli/index.js CHANGED
@@ -1,9 +1,3 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * Just exports {@link module:lib/cli/cli} for convenience.
5
- * @private
6
- * @module lib/cli
7
- * @exports module:lib/cli/cli
8
- */
9
3
  module.exports = require('./cli');
@@ -1,4 +1,9 @@
1
1
  'use strict';
2
+ /**
3
+ * Contains `lookupFiles`, which takes some globs/dirs/options and returns a list of files.
4
+ * @module
5
+ * @private
6
+ */
2
7
 
3
8
  var fs = require('fs');
4
9
  var path = require('path');
@@ -8,6 +13,7 @@ var errors = require('../errors');
8
13
  var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
9
14
  var createMissingArgumentError = errors.createMissingArgumentError;
10
15
  var {sQuote, dQuote} = require('../utils');
16
+ const debug = require('debug')('mocha:cli:lookup-files');
11
17
 
12
18
  /**
13
19
  * Determines if pathname would be a "hidden" file (or directory) on UN*X.
@@ -24,26 +30,26 @@ var {sQuote, dQuote} = require('../utils');
24
30
  * @example
25
31
  * isHiddenOnUnix('.profile'); // => true
26
32
  */
27
- function isHiddenOnUnix(pathname) {
28
- return path.basename(pathname)[0] === '.';
29
- }
33
+ const isHiddenOnUnix = pathname => path.basename(pathname).startsWith('.');
30
34
 
31
35
  /**
32
36
  * Determines if pathname has a matching file extension.
33
37
  *
38
+ * Supports multi-part extensions.
39
+ *
34
40
  * @private
35
41
  * @param {string} pathname - Pathname to check for match.
36
- * @param {string[]} exts - List of file extensions (sans period).
37
- * @return {boolean} whether file extension matches.
42
+ * @param {string[]} exts - List of file extensions, w/-or-w/o leading period
43
+ * @return {boolean} `true` if file extension matches.
38
44
  * @example
39
- * hasMatchingExtname('foo.html', ['js', 'css']); // => false
45
+ * hasMatchingExtname('foo.html', ['js', 'css']); // false
46
+ * hasMatchingExtname('foo.js', ['.js']); // true
47
+ * hasMatchingExtname('foo.js', ['js']); // ture
40
48
  */
41
- function hasMatchingExtname(pathname, exts) {
42
- var suffix = path.extname(pathname).slice(1);
43
- return exts.some(function(element) {
44
- return suffix === element;
45
- });
46
- }
49
+ const hasMatchingExtname = (pathname, exts = []) =>
50
+ exts
51
+ .map(ext => (ext.startsWith('.') ? ext : `.${ext}`))
52
+ .some(ext => pathname.endsWith(ext));
47
53
 
48
54
  /**
49
55
  * Lookup file names at the given `path`.
@@ -53,7 +59,7 @@ function hasMatchingExtname(pathname, exts) {
53
59
  * **Make no assumption that the names will be sorted in any fashion.**
54
60
  *
55
61
  * @public
56
- * @memberof Mocha.utils
62
+ * @alias module:lib/cli.lookupFiles
57
63
  * @param {string} filepath - Base path to start searching from.
58
64
  * @param {string[]} [extensions=[]] - File extensions to look for.
59
65
  * @param {boolean} [recursive=false] - Whether to recurse into subdirectories.
@@ -61,27 +67,28 @@ function hasMatchingExtname(pathname, exts) {
61
67
  * @throws {Error} if no files match pattern.
62
68
  * @throws {TypeError} if `filepath` is directory and `extensions` not provided.
63
69
  */
64
- module.exports = function lookupFiles(filepath, extensions, recursive) {
65
- extensions = extensions || [];
66
- recursive = recursive || false;
67
- var files = [];
68
- var stat;
70
+ module.exports = function lookupFiles(
71
+ filepath,
72
+ extensions = [],
73
+ recursive = false
74
+ ) {
75
+ const files = [];
76
+ let stat;
69
77
 
70
78
  if (!fs.existsSync(filepath)) {
71
- var pattern;
79
+ let pattern;
72
80
  if (glob.hasMagic(filepath)) {
73
81
  // Handle glob as is without extensions
74
82
  pattern = filepath;
75
83
  } else {
76
84
  // glob pattern e.g. 'filepath+(.js|.ts)'
77
- var strExtensions = extensions
78
- .map(function(v) {
79
- return '.' + v;
80
- })
85
+ const strExtensions = extensions
86
+ .map(ext => (ext.startsWith('.') ? ext : `.${ext}`))
81
87
  .join('|');
82
- pattern = filepath + '+(' + strExtensions + ')';
88
+ pattern = `${filepath}+(${strExtensions})`;
89
+ debug('looking for files using glob pattern: %s', pattern);
83
90
  }
84
- files = glob.sync(pattern, {nodir: true});
91
+ files.push(...glob.sync(pattern, {nodir: true}));
85
92
  if (!files.length) {
86
93
  throw createNoFilesMatchPatternError(
87
94
  'Cannot find any files matching pattern ' + dQuote(filepath),
@@ -103,20 +110,19 @@ module.exports = function lookupFiles(filepath, extensions, recursive) {
103
110
  }
104
111
 
105
112
  // Handle directory
106
- fs.readdirSync(filepath).forEach(function(dirent) {
107
- var pathname = path.join(filepath, dirent);
108
- var stat;
113
+ fs.readdirSync(filepath).forEach(dirent => {
114
+ const pathname = path.join(filepath, dirent);
115
+ let stat;
109
116
 
110
117
  try {
111
118
  stat = fs.statSync(pathname);
112
119
  if (stat.isDirectory()) {
113
120
  if (recursive) {
114
- files = files.concat(lookupFiles(pathname, extensions, recursive));
121
+ files.push(...lookupFiles(pathname, extensions, recursive));
115
122
  }
116
123
  return;
117
124
  }
118
- } catch (err) {
119
- // ignore error
125
+ } catch (ignored) {
120
126
  return;
121
127
  }
122
128
  if (!extensions.length) {
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const nodeFlags = process.allowedNodeEnvironmentFlags;
10
+ const {isMochaFlag} = require('./run-option-metadata');
10
11
  const unparse = require('yargs-unparser');
11
12
 
12
13
  /**
@@ -43,16 +44,14 @@ exports.isNodeFlag = (flag, bareword = true) => {
43
44
  flag = flag.replace(/^--?/, '');
44
45
  }
45
46
  return (
46
- // treat --require/-r as Mocha flag even though it's also a node flag
47
- !(flag === 'require' || flag === 'r') &&
48
47
  // check actual node flags from `process.allowedNodeEnvironmentFlags`,
49
48
  // then historical support for various V8 and non-`NODE_OPTIONS` flags
50
49
  // and also any V8 flags with `--v8-` prefix
51
- ((nodeFlags && nodeFlags.has(flag)) ||
52
- debugFlags.has(flag) ||
53
- /(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
54
- flag
55
- ))
50
+ (!isMochaFlag(flag) && nodeFlags && nodeFlags.has(flag)) ||
51
+ debugFlags.has(flag) ||
52
+ /(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
53
+ flag
54
+ )
56
55
  );
57
56
  };
58
57
 
@@ -3,7 +3,8 @@
3
3
  /**
4
4
  * Main entry point for handling filesystem-based configuration,
5
5
  * whether that's a config file or `package.json` or whatever.
6
- * @module
6
+ * @module lib/cli/options
7
+ * @private
7
8
  */
8
9
 
9
10
  const fs = require('fs');
@@ -150,7 +151,7 @@ const parse = (args = [], defaultValues = {}, ...configObjects) => {
150
151
  * @param {Object} [args] - Arguments object
151
152
  * @param {string|boolean} [args.config] - Path to config file or `false` to skip
152
153
  * @public
153
- * @memberof module:lib/cli/options
154
+ * @alias module:lib/cli.loadRc
154
155
  * @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.config` is `false`
155
156
  */
156
157
  const loadRc = (args = {}) => {
@@ -167,7 +168,7 @@ module.exports.loadRc = loadRc;
167
168
  * @param {Object} [args] - Arguments object
168
169
  * @param {string|boolean} [args.config] - Path to `package.json` or `false` to skip
169
170
  * @public
170
- * @memberof module:lib/cli/options
171
+ * @alias module:lib/cli.loadPkgRc
171
172
  * @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.package` is `false`
172
173
  */
173
174
  const loadPkgRc = (args = {}) => {
@@ -210,7 +211,7 @@ module.exports.loadPkgRc = loadPkgRc;
210
211
  * @summary Parses options read from `.mocharc.*` and `package.json`.
211
212
  * @param {string|string[]} [argv] - Arguments to parse
212
213
  * @public
213
- * @memberof module:lib/cli/options
214
+ * @alias module:lib/cli.loadOptions
214
215
  * @returns {external:yargsParser.Arguments} Parsed args from everything
215
216
  */
216
217
  const loadOptions = (argv = []) => {
@@ -12,10 +12,10 @@ const path = require('path');
12
12
  const debug = require('debug')('mocha:cli:run:helpers');
13
13
  const {watchRun, watchParallelRun} = require('./watch-run');
14
14
  const collectFiles = require('./collect-files');
15
- const {type} = require('../utils');
16
15
  const {format} = require('util');
17
- const {createInvalidPluginError, createUnsupportedError} = require('../errors');
16
+ const {createInvalidLegacyPluginError} = require('../errors');
18
17
  const {requireOrImport} = require('../esm-utils');
18
+ const PluginLoader = require('../plugin-loader');
19
19
 
20
20
  /**
21
21
  * Exits Mocha when tests + code under test has finished execution (default)
@@ -79,12 +79,12 @@ exports.list = str =>
79
79
  *
80
80
  * Returns array of `mochaHooks` exports, if any.
81
81
  * @param {string[]} requires - Modules to require
82
- * @returns {Promise<MochaRootHookObject|MochaRootHookFunction>} Any root hooks
82
+ * @returns {Promise<object>} Plugin implementations
83
83
  * @private
84
84
  */
85
- exports.handleRequires = async (requires = []) => {
86
- const acc = [];
87
- for (const mod of requires) {
85
+ exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => {
86
+ const pluginLoader = PluginLoader.create({ignore: ignoredPlugins});
87
+ for await (const mod of requires) {
88
88
  let modpath = mod;
89
89
  // this is relative to cwd
90
90
  if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) {
@@ -92,49 +92,18 @@ exports.handleRequires = async (requires = []) => {
92
92
  debug('resolved required file %s to %s', mod, modpath);
93
93
  }
94
94
  const requiredModule = await requireOrImport(modpath);
95
- if (
96
- requiredModule &&
97
- typeof requiredModule === 'object' &&
98
- requiredModule.mochaHooks
99
- ) {
100
- const mochaHooksType = type(requiredModule.mochaHooks);
101
- if (/function$/.test(mochaHooksType) || mochaHooksType === 'object') {
102
- debug('found root hooks in required file %s', mod);
103
- acc.push(requiredModule.mochaHooks);
104
- } else {
105
- throw createUnsupportedError(
106
- 'mochaHooks must be an object or a function returning (or fulfilling with) an object'
107
- );
95
+ if (requiredModule && typeof requiredModule === 'object') {
96
+ if (pluginLoader.load(requiredModule)) {
97
+ debug('found one or more plugin implementations in %s', modpath);
108
98
  }
109
99
  }
110
100
  debug('loaded required module "%s"', mod);
111
101
  }
112
- return acc;
113
- };
114
-
115
- /**
116
- * Loads root hooks as exported via `mochaHooks` from required files.
117
- * These can be sync/async functions returning objects, or just objects.
118
- * Flattens to a single object.
119
- * @param {Array<MochaRootHookObject|MochaRootHookFunction>} rootHooks - Array of root hooks
120
- * @private
121
- * @returns {MochaRootHookObject}
122
- */
123
- exports.loadRootHooks = async rootHooks => {
124
- const rootHookObjects = await Promise.all(
125
- rootHooks.map(async hook => (/function$/.test(type(hook)) ? hook() : hook))
126
- );
127
-
128
- return rootHookObjects.reduce(
129
- (acc, hook) => {
130
- acc.beforeAll = acc.beforeAll.concat(hook.beforeAll || []);
131
- acc.beforeEach = acc.beforeEach.concat(hook.beforeEach || []);
132
- acc.afterAll = acc.afterAll.concat(hook.afterAll || []);
133
- acc.afterEach = acc.afterEach.concat(hook.afterEach || []);
134
- return acc;
135
- },
136
- {beforeAll: [], beforeEach: [], afterAll: [], afterEach: []}
137
- );
102
+ const plugins = await pluginLoader.finalize();
103
+ if (Object.keys(plugins).length) {
104
+ debug('finalized plugin implementations: %O', plugins);
105
+ }
106
+ return plugins;
138
107
  };
139
108
 
140
109
  /**
@@ -172,11 +141,7 @@ const singleRun = async (mocha, {exit}, fileCollectParams) => {
172
141
  */
173
142
  const parallelRun = async (mocha, options, fileCollectParams) => {
174
143
  const files = collectFiles(fileCollectParams);
175
- debug(
176
- 'executing %d test file(s) across %d concurrent jobs',
177
- files.length,
178
- options.jobs
179
- );
144
+ debug('executing %d test file(s) in parallel mode', files.length);
180
145
  mocha.files = files;
181
146
 
182
147
  // note that we DO NOT load any files here; this is handled by the worker
@@ -236,7 +201,7 @@ exports.runMocha = async (mocha, options) => {
236
201
  * name
237
202
  * @private
238
203
  */
239
- exports.validatePlugin = (opts, pluginType, map = {}) => {
204
+ exports.validateLegacyPlugin = (opts, pluginType, map = {}) => {
240
205
  /**
241
206
  * This should be a unique identifier; either a string (present in `map`),
242
207
  * or a resolvable (via `require.resolve`) module ID/path.
@@ -245,14 +210,14 @@ exports.validatePlugin = (opts, pluginType, map = {}) => {
245
210
  const pluginId = opts[pluginType];
246
211
 
247
212
  if (Array.isArray(pluginId)) {
248
- throw createInvalidPluginError(
213
+ throw createInvalidLegacyPluginError(
249
214
  `"--${pluginType}" can only be specified once`,
250
215
  pluginType
251
216
  );
252
217
  }
253
218
 
254
- const unknownError = err =>
255
- createInvalidPluginError(
219
+ const createUnknownError = err =>
220
+ createInvalidLegacyPluginError(
256
221
  format('Could not load %s "%s":\n\n %O', pluginType, pluginId, err),
257
222
  pluginType,
258
223
  pluginId
@@ -268,10 +233,10 @@ exports.validatePlugin = (opts, pluginType, map = {}) => {
268
233
  try {
269
234
  opts[pluginType] = require(path.resolve(pluginId));
270
235
  } catch (err) {
271
- throw unknownError(err);
236
+ throw createUnknownError(err);
272
237
  }
273
238
  } else {
274
- throw unknownError(err);
239
+ throw createUnknownError(err);
275
240
  }
276
241
  }
277
242
  }
@@ -12,7 +12,7 @@
12
12
  * @type {{string:string[]}}
13
13
  * @private
14
14
  */
15
- exports.types = {
15
+ const TYPES = (exports.types = {
16
16
  array: [
17
17
  'extension',
18
18
  'file',
@@ -58,7 +58,7 @@ exports.types = {
58
58
  'slow',
59
59
  'timeout'
60
60
  ]
61
- };
61
+ });
62
62
 
63
63
  /**
64
64
  * Option aliases keyed by canonical option name.
@@ -88,3 +88,26 @@ exports.aliases = {
88
88
  ui: ['u'],
89
89
  watch: ['w']
90
90
  };
91
+
92
+ const ALL_MOCHA_FLAGS = Object.keys(TYPES).reduce((acc, key) => {
93
+ // gets all flags from each of the fields in `types`, adds those,
94
+ // then adds aliases of each flag (if any)
95
+ TYPES[key].forEach(flag => {
96
+ acc.add(flag);
97
+ const aliases = exports.aliases[flag] || [];
98
+ aliases.forEach(alias => {
99
+ acc.add(alias);
100
+ });
101
+ });
102
+ return acc;
103
+ }, new Set());
104
+
105
+ /**
106
+ * Returns `true` if the provided `flag` is known to Mocha.
107
+ * @param {string} flag - Flag to check
108
+ * @returns {boolean} If `true`, this is a Mocha flag
109
+ * @private
110
+ */
111
+ exports.isMochaFlag = flag => {
112
+ return ALL_MOCHA_FLAGS.has(flag.replace(/^--?/, ''));
113
+ };
package/lib/cli/run.js CHANGED
@@ -19,8 +19,7 @@ const {
19
19
  const {
20
20
  list,
21
21
  handleRequires,
22
- validatePlugin,
23
- loadRootHooks,
22
+ validateLegacyPlugin,
24
23
  runMocha
25
24
  } = require('./run-helpers');
26
25
  const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
@@ -339,13 +338,10 @@ exports.builder = yargs =>
339
338
  // currently a failing middleware does not work nicely with yargs' `fail()`.
340
339
  try {
341
340
  // load requires first, because it can impact "plugin" validation
342
- const rawRootHooks = await handleRequires(argv.require);
343
- validatePlugin(argv, 'reporter', Mocha.reporters);
344
- validatePlugin(argv, 'ui', Mocha.interfaces);
345
-
346
- if (rawRootHooks && rawRootHooks.length) {
347
- argv.rootHooks = await loadRootHooks(rawRootHooks);
348
- }
341
+ const plugins = await handleRequires(argv.require);
342
+ validateLegacyPlugin(argv, 'reporter', Mocha.reporters);
343
+ validateLegacyPlugin(argv, 'ui', Mocha.interfaces);
344
+ Object.assign(argv, plugins);
349
345
  } catch (err) {
350
346
  // this could be a bad --require, bad reporter, ui, etc.
351
347
  console.error(`\n${symbols.error} ${ansi.red('ERROR:')}`, err);