mocha 7.2.0 → 8.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/bin/mocha +17 -2
  3. package/browser-entry.js +26 -9
  4. package/lib/browser/growl.js +2 -1
  5. package/lib/browser/highlight-tags.js +39 -0
  6. package/lib/browser/parse-query.js +24 -0
  7. package/lib/browser/progress.js +4 -0
  8. package/lib/browser/template.html +7 -5
  9. package/lib/cli/cli.js +2 -2
  10. package/lib/cli/collect-files.js +15 -9
  11. package/lib/cli/config.js +0 -1
  12. package/lib/cli/init.js +1 -2
  13. package/lib/cli/lookup-files.js +145 -0
  14. package/lib/cli/node-flags.js +2 -2
  15. package/lib/cli/options.js +11 -87
  16. package/lib/cli/run-helpers.js +54 -16
  17. package/lib/cli/run-option-metadata.js +4 -2
  18. package/lib/cli/run.js +61 -14
  19. package/lib/cli/watch-run.js +211 -51
  20. package/lib/context.js +0 -15
  21. package/lib/errors.js +26 -3
  22. package/lib/esm-utils.js +11 -6
  23. package/lib/hook.js +24 -0
  24. package/lib/interfaces/common.js +19 -11
  25. package/lib/mocha.js +137 -121
  26. package/lib/mocharc.json +0 -1
  27. package/lib/nodejs/buffered-worker-pool.js +174 -0
  28. package/lib/{growl.js → nodejs/growl.js} +3 -2
  29. package/lib/nodejs/parallel-buffered-runner.js +295 -0
  30. package/lib/nodejs/reporters/parallel-buffered.js +133 -0
  31. package/lib/nodejs/serializer.js +404 -0
  32. package/lib/nodejs/worker.js +154 -0
  33. package/lib/pending.js +4 -0
  34. package/lib/reporters/base.js +25 -12
  35. package/lib/reporters/landing.js +3 -3
  36. package/lib/reporters/tap.js +1 -2
  37. package/lib/reporters/xunit.js +3 -2
  38. package/lib/runnable.js +18 -30
  39. package/lib/runner.js +58 -64
  40. package/lib/suite.js +32 -24
  41. package/lib/test.js +28 -1
  42. package/lib/utils.js +19 -206
  43. package/mocha.js +25522 -18248
  44. package/mocha.js.map +1 -0
  45. package/package.json +52 -42
  46. package/lib/browser/tty.js +0 -13
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const debug = require('debug')('mocha:cli:watch');
3
4
  const path = require('path');
4
5
  const chokidar = require('chokidar');
5
6
  const Context = require('../context');
@@ -12,37 +13,164 @@ const collectFiles = require('./collect-files');
12
13
  * @private
13
14
  */
14
15
 
16
+ /**
17
+ * Run Mocha in parallel "watch" mode
18
+ * @param {Mocha} mocha - Mocha instance
19
+ * @param {Object} opts - Options
20
+ * @param {string[]} [opts.watchFiles] - List of paths and patterns to
21
+ * watch. If not provided all files with an extension included in
22
+ * `fileCollectionParams.extension` are watched. See first argument of
23
+ * `chokidar.watch`.
24
+ * @param {string[]} opts.watchIgnore - List of paths and patterns to
25
+ * exclude from watching. See `ignored` option of `chokidar`.
26
+ * @param {FileCollectionOptions} fileCollectParams - Parameters that control test
27
+ * @private
28
+ */
29
+ exports.watchParallelRun = (
30
+ mocha,
31
+ {watchFiles, watchIgnore},
32
+ fileCollectParams
33
+ ) => {
34
+ debug('creating parallel watcher');
35
+ return createWatcher(mocha, {
36
+ watchFiles,
37
+ watchIgnore,
38
+ beforeRun({mocha}) {
39
+ // I don't know why we're cloning the root suite.
40
+ const rootSuite = mocha.suite.clone();
41
+
42
+ // this `require` is needed because the require cache has been cleared. the dynamic
43
+ // exports set via the below call to `mocha.ui()` won't work properly if a
44
+ // test depends on this module (see `required-tokens.spec.js`).
45
+ const Mocha = require('../mocha');
46
+
47
+ // ... and now that we've gotten a new module, we need to use it again due
48
+ // to `mocha.ui()` call
49
+ const newMocha = new Mocha(mocha.options);
50
+ // don't know why this is needed
51
+ newMocha.suite = rootSuite;
52
+ // nor this
53
+ newMocha.suite.ctx = new Context();
54
+
55
+ // reset the list of files
56
+ newMocha.files = collectFiles(fileCollectParams);
57
+
58
+ // because we've swapped out the root suite (see the `run` inner function
59
+ // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
60
+ newMocha.ui(newMocha.options.ui);
61
+
62
+ // we need to call `newMocha.rootHooks` to set up rootHooks for the new
63
+ // suite
64
+ newMocha.rootHooks(newMocha.options.rootHooks);
65
+
66
+ // in parallel mode, the main Mocha process doesn't actually load the
67
+ // files. this flag prevents `mocha.run()` from autoloading.
68
+ newMocha.lazyLoadFiles(true);
69
+ return newMocha;
70
+ },
71
+ afterRun({watcher}) {
72
+ blastCache(watcher);
73
+ },
74
+ fileCollectParams
75
+ });
76
+ };
77
+
15
78
  /**
16
79
  * Run Mocha in "watch" mode
17
80
  * @param {Mocha} mocha - Mocha instance
18
81
  * @param {Object} opts - Options
19
82
  * @param {string[]} [opts.watchFiles] - List of paths and patterns to
20
83
  * watch. If not provided all files with an extension included in
21
- * `fileColletionParams.extension` are watched. See first argument of
84
+ * `fileCollectionParams.extension` are watched. See first argument of
22
85
  * `chokidar.watch`.
23
86
  * @param {string[]} opts.watchIgnore - List of paths and patterns to
24
87
  * exclude from watching. See `ignored` option of `chokidar`.
25
- * @param {Object} fileCollectParams - Parameters that control test
88
+ * @param {FileCollectionOptions} fileCollectParams - Parameters that control test
26
89
  * file collection. See `lib/cli/collect-files.js`.
27
- * @param {string[]} fileCollectParams.extension - List of extensions
28
- * to watch if `opts.watchFiles` is not given.
29
90
  * @private
30
91
  */
31
- module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
92
+ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
93
+ debug('creating serial watcher');
94
+ // list of all test files
95
+
96
+ return createWatcher(mocha, {
97
+ watchFiles,
98
+ watchIgnore,
99
+ beforeRun({mocha}) {
100
+ mocha.unloadFiles();
101
+
102
+ // I don't know why we're cloning the root suite.
103
+ const rootSuite = mocha.suite.clone();
104
+
105
+ // this `require` is needed because the require cache has been cleared. the dynamic
106
+ // exports set via the below call to `mocha.ui()` won't work properly if a
107
+ // test depends on this module (see `required-tokens.spec.js`).
108
+ const Mocha = require('../mocha');
109
+
110
+ // ... and now that we've gotten a new module, we need to use it again due
111
+ // to `mocha.ui()` call
112
+ const newMocha = new Mocha(mocha.options);
113
+ // don't know why this is needed
114
+ newMocha.suite = rootSuite;
115
+ // nor this
116
+ newMocha.suite.ctx = new Context();
117
+
118
+ // reset the list of files
119
+ newMocha.files = collectFiles(fileCollectParams);
120
+
121
+ // because we've swapped out the root suite (see the `run` inner function
122
+ // in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
123
+ newMocha.ui(newMocha.options.ui);
124
+
125
+ // we need to call `newMocha.rootHooks` to set up rootHooks for the new
126
+ // suite
127
+ newMocha.rootHooks(newMocha.options.rootHooks);
128
+
129
+ return newMocha;
130
+ },
131
+ afterRun({watcher}) {
132
+ blastCache(watcher);
133
+ },
134
+ fileCollectParams
135
+ });
136
+ };
137
+
138
+ /**
139
+ * Bootstraps a chokidar watcher. Handles keyboard input & signals
140
+ * @param {Mocha} mocha - Mocha instance
141
+ * @param {Object} opts
142
+ * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before
143
+ * `mocha.run()`
144
+ * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
145
+ * @param {string[]} [opts.watchFiles] - List of paths and patterns to watch. If
146
+ * not provided all files with an extension included in
147
+ * `fileCollectionParams.extension` are watched. See first argument of
148
+ * `chokidar.watch`.
149
+ * @param {string[]} [opts.watchIgnore] - List of paths and patterns to exclude
150
+ * from watching. See `ignored` option of `chokidar`.
151
+ * @param {FileCollectionOptions} opts.fileCollectParams - List of extensions to watch if `opts.watchFiles` is not given.
152
+ * @returns {FSWatcher}
153
+ * @ignore
154
+ * @private
155
+ */
156
+ const createWatcher = (
157
+ mocha,
158
+ {watchFiles, watchIgnore, beforeRun, afterRun, fileCollectParams}
159
+ ) => {
32
160
  if (!watchFiles) {
33
161
  watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
34
162
  }
35
163
 
164
+ debug('ignoring files matching: %s', watchIgnore);
165
+
36
166
  const watcher = chokidar.watch(watchFiles, {
37
167
  ignored: watchIgnore,
38
168
  ignoreInitial: true
39
169
  });
40
170
 
41
- const rerunner = createRerunner(mocha, () => {
42
- getWatchedFiles(watcher).forEach(file => {
43
- delete require.cache[file];
44
- });
45
- mocha.files = collectFiles(fileCollectParams);
171
+ const rerunner = createRerunner(mocha, watcher, {
172
+ beforeRun,
173
+ afterRun
46
174
  });
47
175
 
48
176
  watcher.on('ready', () => {
@@ -53,7 +181,6 @@ module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
53
181
  rerunner.scheduleRun();
54
182
  });
55
183
 
56
- console.log();
57
184
  hideCursor();
58
185
  process.on('exit', () => {
59
186
  showCursor();
@@ -74,36 +201,43 @@ module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
74
201
  .toLowerCase();
75
202
  if (str === 'rs') rerunner.scheduleRun();
76
203
  });
204
+
205
+ return watcher;
77
206
  };
78
207
 
79
208
  /**
80
- * Create an object that allows you to rerun tests on the mocha
81
- * instance. `beforeRun` is called everytime before `mocha.run()` is
82
- * called.
209
+ * Create an object that allows you to rerun tests on the mocha instance.
83
210
  *
84
211
  * @param {Mocha} mocha - Mocha instance
85
- * @param {function} beforeRun - Called just before `mocha.run()`
212
+ * @param {FSWatcher} watcher - chokidar `FSWatcher` instance
213
+ * @param {Object} [opts] - Options!
214
+ * @param {BeforeWatchRun} [opts.beforeRun] - Function to call before `mocha.run()`
215
+ * @param {AfterWatchRun} [opts.afterRun] - Function to call after `mocha.run()`
216
+ * @returns {Rerunner}
217
+ * @ignore
218
+ * @private
86
219
  */
87
- const createRerunner = (mocha, beforeRun) => {
220
+ const createRerunner = (mocha, watcher, {beforeRun, afterRun} = {}) => {
88
221
  // Set to a `Runner` when mocha is running. Set to `null` when mocha is not
89
222
  // running.
90
223
  let runner = null;
91
224
 
225
+ // true if a file has changed during a test run
92
226
  let rerunScheduled = false;
93
227
 
94
228
  const run = () => {
95
- try {
96
- beforeRun();
97
- resetMocha(mocha);
98
- runner = mocha.run(() => {
99
- runner = null;
100
- if (rerunScheduled) {
101
- rerun();
102
- }
103
- });
104
- } catch (e) {
105
- console.log(e.stack);
106
- }
229
+ mocha = beforeRun ? beforeRun({mocha, watcher}) : mocha;
230
+
231
+ runner = mocha.run(() => {
232
+ debug('finished watch run');
233
+ runner = null;
234
+ afterRun && afterRun({mocha, watcher});
235
+ if (rerunScheduled) {
236
+ rerun();
237
+ } else {
238
+ debug('waiting for changes...');
239
+ }
240
+ });
107
241
  };
108
242
 
109
243
  const scheduleRun = () => {
@@ -136,32 +270,18 @@ const createRerunner = (mocha, beforeRun) => {
136
270
  *
137
271
  * @param watcher - Instance of a chokidar watcher
138
272
  * @return {string[]} - List of absolute paths
273
+ * @ignore
274
+ * @private
139
275
  */
140
276
  const getWatchedFiles = watcher => {
141
277
  const watchedDirs = watcher.getWatched();
142
- let watchedFiles = [];
143
- Object.keys(watchedDirs).forEach(dir => {
144
- watchedFiles = watchedFiles.concat(
145
- watchedDirs[dir].map(file => path.join(dir, file))
146
- );
147
- });
148
- return watchedFiles;
149
- };
150
-
151
- /**
152
- * Reset the internal state of the mocha instance so that tests can be rerun.
153
- *
154
- * @param {Mocha} mocha - Mocha instance
155
- * @private
156
- */
157
- const resetMocha = mocha => {
158
- mocha.unloadFiles();
159
- mocha.suite = mocha.suite.clone();
160
- mocha.suite.ctx = new Context();
161
- // Registers a callback on `mocha.suite` that wires new context to the DSL
162
- // (e.g. `describe`) that is exposed as globals when the test files are
163
- // reloaded.
164
- mocha.ui(mocha.options.ui);
278
+ return Object.keys(watchedDirs).reduce(
279
+ (acc, dir) => [
280
+ ...acc,
281
+ ...watchedDirs[dir].map(file => path.join(dir, file))
282
+ ],
283
+ []
284
+ );
165
285
  };
166
286
 
167
287
  /**
@@ -189,3 +309,43 @@ const showCursor = () => {
189
309
  const eraseLine = () => {
190
310
  process.stdout.write('\u001b[2K');
191
311
  };
312
+
313
+ /**
314
+ * Blast all of the watched files out of `require.cache`
315
+ * @param {FSWatcher} watcher - chokidar FSWatcher
316
+ * @ignore
317
+ * @private
318
+ */
319
+ const blastCache = watcher => {
320
+ const files = getWatchedFiles(watcher);
321
+ files.forEach(file => {
322
+ delete require.cache[file];
323
+ });
324
+ debug('deleted %d file(s) from the require cache', files.length);
325
+ };
326
+
327
+ /**
328
+ * Callback to be run before `mocha.run()` is called.
329
+ * Optionally, it can return a new `Mocha` instance.
330
+ * @callback BeforeWatchRun
331
+ * @private
332
+ * @param {{mocha: Mocha, watcher: FSWatcher}} options
333
+ * @returns {Mocha}
334
+ */
335
+
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
+ /**
346
+ * Object containing run control methods
347
+ * @typedef {Object} Rerunner
348
+ * @private
349
+ * @property {Function} run - Calls `mocha.run()`
350
+ * @property {Function} scheduleRun - Schedules another call to `run`
351
+ */
package/lib/context.js CHANGED
@@ -45,21 +45,6 @@ Context.prototype.timeout = function(ms) {
45
45
  return this;
46
46
  };
47
47
 
48
- /**
49
- * Set test timeout `enabled`.
50
- *
51
- * @private
52
- * @param {boolean} enabled
53
- * @return {Context} self
54
- */
55
- Context.prototype.enableTimeouts = function(enabled) {
56
- if (!arguments.length) {
57
- return this.runnable().enableTimeouts();
58
- }
59
- this.runnable().enableTimeouts(enabled);
60
- return this;
61
- };
62
-
63
48
  /**
64
49
  * Set or get test slowness threshold `ms`.
65
50
  *
package/lib/errors.js CHANGED
@@ -59,14 +59,19 @@ var constants = {
59
59
  UNSUPPORTED: 'ERR_MOCHA_UNSUPPORTED',
60
60
 
61
61
  /**
62
- * Invalid state transition occuring in `Mocha` instance
62
+ * Invalid state transition occurring in `Mocha` instance
63
63
  */
64
64
  INSTANCE_ALREADY_RUNNING: 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING',
65
65
 
66
66
  /**
67
- * Invalid state transition occuring in `Mocha` instance
67
+ * Invalid state transition occurring in `Mocha` instance
68
68
  */
69
- INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED'
69
+ INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED',
70
+
71
+ /**
72
+ * Use of `only()` w/ `--forbid-only` results in this error.
73
+ */
74
+ FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY'
70
75
  };
71
76
 
72
77
  /**
@@ -293,6 +298,23 @@ function createMultipleDoneError(runnable, originalErr) {
293
298
  return err;
294
299
  }
295
300
 
301
+ /**
302
+ * Creates an error object to be thrown when `.only()` is used with
303
+ * `--forbid-only`.
304
+ * @public
305
+ * @param {Mocha} mocha - Mocha instance
306
+ * @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY}
307
+ */
308
+ function createForbiddenExclusivityError(mocha) {
309
+ var err = new Error(
310
+ mocha.isWorker
311
+ ? '`.only` is not supported in parallel mode'
312
+ : '`.only` forbidden by --forbid-only'
313
+ );
314
+ err.code = constants.FORBIDDEN_EXCLUSIVITY;
315
+ return err;
316
+ }
317
+
296
318
  module.exports = {
297
319
  createInvalidArgumentTypeError: createInvalidArgumentTypeError,
298
320
  createInvalidArgumentValueError: createInvalidArgumentValueError,
@@ -307,5 +329,6 @@ module.exports = {
307
329
  createMochaInstanceAlreadyRunningError: createMochaInstanceAlreadyRunningError,
308
330
  createFatalError: createFatalError,
309
331
  createMultipleDoneError: createMultipleDoneError,
332
+ createForbiddenExclusivityError: createForbiddenExclusivityError,
310
333
  constants: constants
311
334
  };
package/lib/esm-utils.js CHANGED
@@ -1,11 +1,16 @@
1
- const url = require('url');
2
1
  const path = require('path');
2
+ const url = require('url');
3
3
 
4
- const requireOrImport = async file => {
5
- file = path.resolve(file);
4
+ const formattedImport = async file => {
5
+ if (path.isAbsolute(file)) {
6
+ return import(url.pathToFileURL(file));
7
+ }
8
+ return import(file);
9
+ };
6
10
 
11
+ exports.requireOrImport = async file => {
7
12
  if (path.extname(file) === '.mjs') {
8
- return import(url.pathToFileURL(file));
13
+ return formattedImport(file);
9
14
  }
10
15
  // This is currently the only known way of figuring out whether a file is CJS or ESM.
11
16
  // If Node.js or the community establish a better procedure for that, we can fix this code.
@@ -15,7 +20,7 @@ const requireOrImport = async file => {
15
20
  return require(file);
16
21
  } catch (err) {
17
22
  if (err.code === 'ERR_REQUIRE_ESM') {
18
- return import(url.pathToFileURL(file));
23
+ return formattedImport(file);
19
24
  } else {
20
25
  throw err;
21
26
  }
@@ -25,7 +30,7 @@ const requireOrImport = async file => {
25
30
  exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
26
31
  for (const file of files) {
27
32
  preLoadFunc(file);
28
- const result = await requireOrImport(file);
33
+ const result = await exports.requireOrImport(path.resolve(file));
29
34
  postLoadFunc(file, result);
30
35
  }
31
36
  };
package/lib/hook.js CHANGED
@@ -52,3 +52,27 @@ Hook.prototype.error = function(err) {
52
52
 
53
53
  this._error = err;
54
54
  };
55
+
56
+ /**
57
+ * Returns an object suitable for IPC.
58
+ * Functions are represented by keys beginning with `$$`.
59
+ * @private
60
+ * @returns {Object}
61
+ */
62
+ Hook.prototype.serialize = function serialize() {
63
+ return {
64
+ $$isPending: this.isPending(),
65
+ $$titlePath: this.titlePath(),
66
+ ctx: {
67
+ currentTest: {
68
+ title: this.ctx && this.ctx.currentTest && this.ctx.currentTest.title
69
+ }
70
+ },
71
+ parent: {
72
+ root: this.parent.root,
73
+ title: this.parent.title
74
+ },
75
+ title: this.title,
76
+ type: this.type
77
+ };
78
+ };
@@ -1,13 +1,19 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ @module interfaces/common
5
+ */
6
+
3
7
  var Suite = require('../suite');
4
8
  var errors = require('../errors');
5
9
  var createMissingArgumentError = errors.createMissingArgumentError;
6
10
  var createUnsupportedError = errors.createUnsupportedError;
11
+ var createForbiddenExclusivityError = errors.createForbiddenExclusivityError;
7
12
 
8
13
  /**
9
14
  * Functions common to more than one interface.
10
15
  *
16
+ * @private
11
17
  * @param {Suite[]} suites
12
18
  * @param {Context} context
13
19
  * @param {Mocha} mocha
@@ -93,6 +99,9 @@ module.exports = function(suites, context, mocha) {
93
99
  * @returns {Suite}
94
100
  */
95
101
  only: function only(opts) {
102
+ if (mocha.options.forbidOnly) {
103
+ throw createForbiddenExclusivityError(mocha);
104
+ }
96
105
  opts.isOnly = true;
97
106
  return this.create(opts);
98
107
  },
@@ -126,16 +135,14 @@ module.exports = function(suites, context, mocha) {
126
135
  suite.file = opts.file;
127
136
  suites.unshift(suite);
128
137
  if (opts.isOnly) {
129
- if (mocha.options.forbidOnly && shouldBeTested(suite)) {
130
- throw createUnsupportedError('`.only` forbidden');
131
- }
132
-
133
- suite.parent.appendOnlySuite(suite);
138
+ suite.markOnly();
134
139
  }
135
- if (suite.pending) {
136
- if (mocha.options.forbidPending && shouldBeTested(suite)) {
137
- throw createUnsupportedError('Pending test forbidden');
138
- }
140
+ if (
141
+ suite.pending &&
142
+ mocha.options.forbidPending &&
143
+ shouldBeTested(suite)
144
+ ) {
145
+ throw createUnsupportedError('Pending test forbidden');
139
146
  }
140
147
  if (typeof opts.fn === 'function') {
141
148
  opts.fn.call(suite);
@@ -166,8 +173,9 @@ module.exports = function(suites, context, mocha) {
166
173
  * @returns {*}
167
174
  */
168
175
  only: function(mocha, test) {
169
- if (mocha.options.forbidOnly)
170
- throw createUnsupportedError('`.only` forbidden');
176
+ if (mocha.options.forbidOnly) {
177
+ throw createForbiddenExclusivityError(mocha);
178
+ }
171
179
  test.markOnly();
172
180
  return test;
173
181
  },