mocha 7.0.1 → 7.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
@@ -19,7 +19,6 @@ var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
19
19
  var STATE_FAILED = Runnable.constants.STATE_FAILED;
20
20
  var STATE_PASSED = Runnable.constants.STATE_PASSED;
21
21
  var dQuote = utils.dQuote;
22
- var ngettext = utils.ngettext;
23
22
  var sQuote = utils.sQuote;
24
23
  var stackFilter = utils.stackTraceFilter();
25
24
  var stringify = utils.stringify;
@@ -27,6 +26,7 @@ var type = utils.type;
27
26
  var errors = require('./errors');
28
27
  var createInvalidExceptionError = errors.createInvalidExceptionError;
29
28
  var createUnsupportedError = errors.createUnsupportedError;
29
+ var createFatalError = errors.createFatalError;
30
30
 
31
31
  /**
32
32
  * Non-enumerable globals.
@@ -109,7 +109,19 @@ var constants = utils.defineConstants(
109
109
  /**
110
110
  * Emitted when {@link Test} execution has failed, but will retry
111
111
  */
112
- EVENT_TEST_RETRY: 'retry'
112
+ EVENT_TEST_RETRY: 'retry',
113
+ /**
114
+ * Initial state of Runner
115
+ */
116
+ STATE_IDLE: 'idle',
117
+ /**
118
+ * State set to this value when the Runner has started running
119
+ */
120
+ STATE_RUNNING: 'running',
121
+ /**
122
+ * State set to this value when the Runner has stopped
123
+ */
124
+ STATE_STOPPED: 'stopped'
113
125
  }
114
126
  );
115
127
 
@@ -121,20 +133,36 @@ module.exports = Runner;
121
133
  * @extends external:EventEmitter
122
134
  * @public
123
135
  * @class
124
- * @param {Suite} suite Root suite
125
- * @param {boolean} [delay] Whether or not to delay execution of root suite
126
- * until ready.
136
+ * @param {Suite} suite - Root suite
137
+ * @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility).
138
+ * @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
139
+ * @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
127
140
  */
128
- function Runner(suite, delay) {
141
+ function Runner(suite, opts) {
142
+ if (opts === undefined) {
143
+ opts = {};
144
+ }
145
+ if (typeof opts === 'boolean') {
146
+ this._delay = opts;
147
+ opts = {};
148
+ } else {
149
+ this._delay = opts.delay;
150
+ }
129
151
  var self = this;
130
152
  this._globals = [];
131
153
  this._abort = false;
132
- this._delay = delay;
133
154
  this.suite = suite;
134
- this.started = false;
155
+ this._opts = opts;
156
+ this.state = constants.STATE_IDLE;
135
157
  this.total = suite.total();
136
158
  this.failures = 0;
159
+ this._eventListeners = [];
137
160
  this.on(constants.EVENT_TEST_END, function(test) {
161
+ if (test.type === 'test' && test.retriedTest() && test.parent) {
162
+ var idx =
163
+ test.parent.tests && test.parent.tests.indexOf(test.retriedTest());
164
+ if (idx > -1) test.parent.tests[idx] = test;
165
+ }
138
166
  self.checkGlobals(test);
139
167
  });
140
168
  this.on(constants.EVENT_HOOK_END, function(hook) {
@@ -143,6 +171,8 @@ function Runner(suite, delay) {
143
171
  this._defaultGrep = /.*/;
144
172
  this.grep(this._defaultGrep);
145
173
  this.globals(this.globalProps());
174
+
175
+ this.uncaught = this._uncaught.bind(this);
146
176
  }
147
177
 
148
178
  /**
@@ -158,6 +188,56 @@ Runner.immediately = global.setImmediate || process.nextTick;
158
188
  */
159
189
  inherits(Runner, EventEmitter);
160
190
 
191
+ /**
192
+ * Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed.
193
+ * @param {EventEmitter} target - The `EventEmitter`
194
+ * @param {string} eventName - The event name
195
+ * @param {string} fn - Listener function
196
+ */
197
+ Runner.prototype._addEventListener = function(target, eventName, listener) {
198
+ target.on(eventName, listener);
199
+ this._eventListeners.push([target, eventName, listener]);
200
+ };
201
+
202
+ /**
203
+ * Replacement for `target.removeListener(eventName, listener)` that also updates the bookkeeping.
204
+ * @param {EventEmitter} target - The `EventEmitter`
205
+ * @param {string} eventName - The event anme
206
+ * @param {function} listener - Listener function
207
+ */
208
+ Runner.prototype._removeEventListener = function(target, eventName, listener) {
209
+ var eventListenerIndex = -1;
210
+ for (var i = 0; i < this._eventListeners.length; i++) {
211
+ var eventListenerDescriptor = this._eventListeners[i];
212
+ if (
213
+ eventListenerDescriptor[0] === target &&
214
+ eventListenerDescriptor[1] === eventName &&
215
+ eventListenerDescriptor[2] === listener
216
+ ) {
217
+ eventListenerIndex = i;
218
+ break;
219
+ }
220
+ }
221
+ if (eventListenerIndex !== -1) {
222
+ var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0];
223
+ removedListener[0].removeListener(removedListener[1], removedListener[2]);
224
+ }
225
+ };
226
+
227
+ /**
228
+ * Removes all event handlers set during a run on this instance.
229
+ * Remark: this does _not_ clean/dispose the tests or suites themselves.
230
+ */
231
+ Runner.prototype.dispose = function() {
232
+ this.removeAllListeners();
233
+ this._eventListeners.forEach(function(eventListenerDescriptor) {
234
+ eventListenerDescriptor[0].removeListener(
235
+ eventListenerDescriptor[1],
236
+ eventListenerDescriptor[2]
237
+ );
238
+ });
239
+ };
240
+
161
241
  /**
162
242
  * Run tests with full titles matching `re`. Updates runner.total
163
243
  * with number of tests matched.
@@ -169,7 +249,7 @@ inherits(Runner, EventEmitter);
169
249
  * @return {Runner} Runner instance.
170
250
  */
171
251
  Runner.prototype.grep = function(re, invert) {
172
- debug('grep %s', re);
252
+ debug('grep(): setting to %s', re);
173
253
  this._grep = re;
174
254
  this._invert = invert;
175
255
  this.total = this.grepTotal(this.suite);
@@ -234,7 +314,7 @@ Runner.prototype.globals = function(arr) {
234
314
  if (!arguments.length) {
235
315
  return this._globals;
236
316
  }
237
- debug('globals %j', arr);
317
+ debug('globals(): setting to %O', arr);
238
318
  this._globals = this._globals.concat(arr);
239
319
  return this;
240
320
  };
@@ -266,12 +346,8 @@ Runner.prototype.checkGlobals = function(test) {
266
346
  this._globals = this._globals.concat(leaks);
267
347
 
268
348
  if (leaks.length) {
269
- var format = ngettext(
270
- leaks.length,
271
- 'global leak detected: %s',
272
- 'global leaks detected: %s'
273
- );
274
- var error = new Error(util.format(format, leaks.map(sQuote).join(', ')));
349
+ var msg = 'global leak(s) detected: %s';
350
+ var error = new Error(util.format(msg, leaks.map(sQuote).join(', ')));
275
351
  this.fail(test, error);
276
352
  }
277
353
  };
@@ -287,8 +363,18 @@ Runner.prototype.fail = function(test, err) {
287
363
  if (test.isPending()) {
288
364
  return;
289
365
  }
366
+ if (this.state === constants.STATE_STOPPED) {
367
+ if (err.code === errors.constants.MULTIPLE_DONE) {
368
+ throw err;
369
+ }
370
+ throw createFatalError(
371
+ 'Test failed after root suite execution completed!',
372
+ err
373
+ );
374
+ }
290
375
 
291
376
  ++this.failures;
377
+ debug('total number of failures: %d', this.failures);
292
378
  test.state = STATE_FAILED;
293
379
 
294
380
  if (!isError(err)) {
@@ -376,7 +462,7 @@ Runner.prototype.hook = function(name, fn) {
376
462
  self.emit(constants.EVENT_HOOK_BEGIN, hook);
377
463
 
378
464
  if (!hook.listeners('error').length) {
379
- hook.on('error', function(err) {
465
+ self._addEventListener(hook, 'error', function(err) {
380
466
  self.failHook(hook, err);
381
467
  });
382
468
  }
@@ -519,18 +605,10 @@ Runner.prototype.runTest = function(fn) {
519
605
  return;
520
606
  }
521
607
 
522
- var suite = this.parents().reverse()[0] || this.suite;
523
- if (this.forbidOnly && suite.hasOnly()) {
524
- fn(new Error('`.only` forbidden'));
525
- return;
526
- }
527
608
  if (this.asyncOnly) {
528
609
  test.asyncOnly = true;
529
610
  }
530
- test.on('error', function(err) {
531
- if (err instanceof Pending) {
532
- return;
533
- }
611
+ this._addEventListener(test, 'error', function(err) {
534
612
  self.fail(test, err);
535
613
  });
536
614
  if (this.allowUncaught) {
@@ -725,9 +803,10 @@ Runner.prototype.runSuite = function(suite, fn) {
725
803
  var self = this;
726
804
  var total = this.grepTotal(suite);
727
805
 
728
- debug('run suite %s', suite.fullTitle());
806
+ debug('runSuite(): running %s', suite.fullTitle());
729
807
 
730
808
  if (!total || (self.failures && suite._bail)) {
809
+ debug('runSuite(): bailing');
731
810
  return fn();
732
811
  }
733
812
 
@@ -793,21 +872,49 @@ Runner.prototype.runSuite = function(suite, fn) {
793
872
  /**
794
873
  * Handle uncaught exceptions within runner.
795
874
  *
796
- * @param {Error} err
875
+ * This function is bound to the instance as `Runner#uncaught` at instantiation
876
+ * time. It's intended to be listening on the `Process.uncaughtException` event.
877
+ * In order to not leak EE listeners, we need to ensure no more than a single
878
+ * `uncaughtException` listener exists per `Runner`. The only way to do
879
+ * this--because this function needs the context (and we don't have lambdas)--is
880
+ * to use `Function.prototype.bind`. We need strict equality to unregister and
881
+ * _only_ unregister the _one_ listener we set from the
882
+ * `Process.uncaughtException` event; would be poor form to just remove
883
+ * everything. See {@link Runner#run} for where the event listener is registered
884
+ * and unregistered.
885
+ * @param {Error} err - Some uncaught error
797
886
  * @private
798
887
  */
799
- Runner.prototype.uncaught = function(err) {
888
+ Runner.prototype._uncaught = function(err) {
889
+ // this is defensive to prevent future developers from mis-calling this function.
890
+ // it's more likely that it'd be called with the incorrect context--say, the global
891
+ // `process` object--than it would to be called with a context that is not a "subclass"
892
+ // of `Runner`.
893
+ if (!(this instanceof Runner)) {
894
+ throw createFatalError(
895
+ 'Runner#uncaught() called with invalid context',
896
+ this
897
+ );
898
+ }
800
899
  if (err instanceof Pending) {
900
+ debug('uncaught(): caught a Pending');
801
901
  return;
802
902
  }
803
- if (this.allowUncaught) {
903
+ // browser does not exit script when throwing in global.onerror()
904
+ if (this.allowUncaught && !process.browser) {
905
+ debug('uncaught(): bubbling exception due to --allow-uncaught');
906
+ throw err;
907
+ }
908
+
909
+ if (this.state === constants.STATE_STOPPED) {
910
+ debug('uncaught(): throwing after run has completed!');
804
911
  throw err;
805
912
  }
806
913
 
807
914
  if (err) {
808
- debug('uncaught exception %O', err);
915
+ debug('uncaught(): got truthy exception %O', err);
809
916
  } else {
810
- debug('uncaught undefined/falsy exception');
917
+ debug('uncaught(): undefined/falsy exception');
811
918
  err = createInvalidExceptionError(
812
919
  'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger',
813
920
  err
@@ -816,6 +923,7 @@ Runner.prototype.uncaught = function(err) {
816
923
 
817
924
  if (!isError(err)) {
818
925
  err = thrown2Error(err);
926
+ debug('uncaught(): converted "error" %o to Error', err);
819
927
  }
820
928
  err.uncaught = true;
821
929
 
@@ -823,12 +931,15 @@ Runner.prototype.uncaught = function(err) {
823
931
 
824
932
  if (!runnable) {
825
933
  runnable = new Runnable('Uncaught error outside test suite');
934
+ debug('uncaught(): no current Runnable; created a phony one');
826
935
  runnable.parent = this.suite;
827
936
 
828
- if (this.started) {
937
+ if (this.state === constants.STATE_RUNNING) {
938
+ debug('uncaught(): failing gracefully');
829
939
  this.fail(runnable, err);
830
940
  } else {
831
941
  // Can't recover from this failure
942
+ debug('uncaught(): test run has not yet started; unrecoverable');
832
943
  this.emit(constants.EVENT_RUN_BEGIN);
833
944
  this.fail(runnable, err);
834
945
  this.emit(constants.EVENT_RUN_END);
@@ -840,9 +951,11 @@ Runner.prototype.uncaught = function(err) {
840
951
  runnable.clearTimeout();
841
952
 
842
953
  if (runnable.isFailed()) {
954
+ debug('uncaught(): Runnable has already failed');
843
955
  // Ignore error if already failed
844
956
  return;
845
957
  } else if (runnable.isPending()) {
958
+ debug('uncaught(): pending Runnable wound up failing!');
846
959
  // report 'pending test' retrospectively as failed
847
960
  runnable.isPending = alwaysFalse;
848
961
  this.fail(runnable, err);
@@ -853,25 +966,15 @@ Runner.prototype.uncaught = function(err) {
853
966
  // we cannot recover gracefully if a Runnable has already passed
854
967
  // then fails asynchronously
855
968
  if (runnable.isPassed()) {
969
+ debug('uncaught(): Runnable has already passed; bailing gracefully');
856
970
  this.fail(runnable, err);
857
971
  this.abort();
858
972
  } else {
859
- debug(runnable);
973
+ debug('uncaught(): forcing Runnable to complete with Error');
860
974
  return runnable.callback(err);
861
975
  }
862
976
  };
863
977
 
864
- /**
865
- * Handle uncaught exceptions after runner's end event.
866
- *
867
- * @param {Error} err
868
- * @private
869
- */
870
- Runner.prototype.uncaughtEnd = function uncaughtEnd(err) {
871
- if (err instanceof Pending) return;
872
- throw err;
873
- };
874
-
875
978
  /**
876
979
  * Run the root suite and invoke `fn(failures)`
877
980
  * on completion.
@@ -887,53 +990,60 @@ Runner.prototype.run = function(fn) {
887
990
 
888
991
  fn = fn || function() {};
889
992
 
890
- function uncaught(err) {
891
- self.uncaught(err);
892
- }
893
-
894
993
  function start() {
994
+ debug('run(): starting');
895
995
  // If there is an `only` filter
896
996
  if (rootSuite.hasOnly()) {
897
997
  rootSuite.filterOnly();
998
+ debug('run(): filtered exclusive Runnables');
898
999
  }
899
- self.started = true;
1000
+ self.state = constants.STATE_RUNNING;
900
1001
  if (self._delay) {
901
1002
  self.emit(constants.EVENT_DELAY_END);
1003
+ debug('run(): "delay" ended');
902
1004
  }
1005
+ debug('run(): emitting %s', constants.EVENT_RUN_BEGIN);
903
1006
  self.emit(constants.EVENT_RUN_BEGIN);
1007
+ debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);
904
1008
 
905
1009
  self.runSuite(rootSuite, function() {
906
- debug('finished running');
1010
+ debug(
1011
+ 'run(): root suite completed; emitting %s',
1012
+ constants.EVENT_RUN_END
1013
+ );
907
1014
  self.emit(constants.EVENT_RUN_END);
1015
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
908
1016
  });
909
1017
  }
910
1018
 
911
- debug(constants.EVENT_RUN_BEGIN);
912
-
913
1019
  // references cleanup to avoid memory leaks
914
- this.on(constants.EVENT_SUITE_END, function(suite) {
915
- suite.cleanReferences();
916
- });
1020
+ if (this._opts.cleanReferencesAfterRun) {
1021
+ this.on(constants.EVENT_SUITE_END, function(suite) {
1022
+ suite.cleanReferences();
1023
+ });
1024
+ }
917
1025
 
918
1026
  // callback
919
1027
  this.on(constants.EVENT_RUN_END, function() {
1028
+ self.state = constants.STATE_STOPPED;
920
1029
  debug(constants.EVENT_RUN_END);
921
- process.removeListener('uncaughtException', uncaught);
922
- process.on('uncaughtException', self.uncaughtEnd);
1030
+ debug('run(): emitted %s', constants.EVENT_RUN_END);
923
1031
  fn(self.failures);
924
1032
  });
925
1033
 
926
- // uncaught exception
927
- process.removeListener('uncaughtException', self.uncaughtEnd);
928
- process.on('uncaughtException', uncaught);
1034
+ self._removeEventListener(process, 'uncaughtException', self.uncaught);
1035
+ self._addEventListener(process, 'uncaughtException', self.uncaught);
929
1036
 
930
1037
  if (this._delay) {
931
1038
  // for reporters, I guess.
932
1039
  // might be nice to debounce some dots while we wait.
933
1040
  this.emit(constants.EVENT_DELAY_BEGIN, rootSuite);
934
1041
  rootSuite.once(EVENT_ROOT_SUITE_RUN, start);
1042
+ debug('run(): waiting for green light due to --delay');
935
1043
  } else {
936
- start();
1044
+ Runner.immediately(function() {
1045
+ start();
1046
+ });
937
1047
  }
938
1048
 
939
1049
  return this;
@@ -947,7 +1057,7 @@ Runner.prototype.run = function(fn) {
947
1057
  * @return {Runner} Runner instance.
948
1058
  */
949
1059
  Runner.prototype.abort = function() {
950
- debug('aborting');
1060
+ debug('abort(): aborting');
951
1061
  this._abort = true;
952
1062
 
953
1063
  return this;
package/lib/suite.js CHANGED
@@ -61,20 +61,20 @@ function Suite(title, parentContext, isRoot) {
61
61
  this.ctx = new Context();
62
62
  this.suites = [];
63
63
  this.tests = [];
64
+ this.root = isRoot === true;
64
65
  this.pending = false;
66
+ this._retries = -1;
65
67
  this._beforeEach = [];
66
68
  this._beforeAll = [];
67
69
  this._afterEach = [];
68
70
  this._afterAll = [];
69
- this.root = isRoot === true;
70
71
  this._timeout = 2000;
71
72
  this._enableTimeouts = true;
72
73
  this._slow = 75;
73
74
  this._bail = false;
74
- this._retries = -1;
75
75
  this._onlyTests = [];
76
76
  this._onlySuites = [];
77
- this.delayed = false;
77
+ this.reset();
78
78
 
79
79
  this.on('newListener', function(event) {
80
80
  if (deprecatedEvents[event]) {
@@ -92,6 +92,22 @@ function Suite(title, parentContext, isRoot) {
92
92
  */
93
93
  inherits(Suite, EventEmitter);
94
94
 
95
+ /**
96
+ * Resets the state initially or for a next run.
97
+ */
98
+ Suite.prototype.reset = function() {
99
+ this.delayed = false;
100
+ function doReset(thingToReset) {
101
+ thingToReset.reset();
102
+ }
103
+ this.suites.forEach(doReset);
104
+ this.tests.forEach(doReset);
105
+ this._beforeEach.forEach(doReset);
106
+ this._afterEach.forEach(doReset);
107
+ this._beforeAll.forEach(doReset);
108
+ this._afterAll.forEach(doReset);
109
+ };
110
+
95
111
  /**
96
112
  * Return a clone of this `Suite`.
97
113
  *
@@ -511,6 +527,16 @@ Suite.prototype.getHooks = function getHooks(name) {
511
527
  return this['_' + name];
512
528
  };
513
529
 
530
+ /**
531
+ * cleans all references from this suite and all child suites.
532
+ */
533
+ Suite.prototype.dispose = function() {
534
+ this.suites.forEach(function(suite) {
535
+ suite.dispose();
536
+ });
537
+ this.cleanReferences();
538
+ };
539
+
514
540
  /**
515
541
  * Cleans up the references to all the deferred functions
516
542
  * (before/after/beforeEach/afterEach) and tests of a Suite.
package/lib/test.js CHANGED
@@ -26,9 +26,9 @@ function Test(title, fn) {
26
26
  'string'
27
27
  );
28
28
  }
29
- Runnable.call(this, title, fn);
30
- this.pending = !fn;
31
29
  this.type = 'test';
30
+ Runnable.call(this, title, fn);
31
+ this.reset();
32
32
  }
33
33
 
34
34
  /**
@@ -36,6 +36,36 @@ function Test(title, fn) {
36
36
  */
37
37
  utils.inherits(Test, Runnable);
38
38
 
39
+ /**
40
+ * Resets the state initially or for a next run.
41
+ */
42
+ Test.prototype.reset = function() {
43
+ Runnable.prototype.reset.call(this);
44
+ this.pending = !this.fn;
45
+ delete this.state;
46
+ };
47
+
48
+ /**
49
+ * Set or get retried test
50
+ *
51
+ * @private
52
+ */
53
+ Test.prototype.retriedTest = function(n) {
54
+ if (!arguments.length) {
55
+ return this._retriedTest;
56
+ }
57
+ this._retriedTest = n;
58
+ };
59
+
60
+ /**
61
+ * Add test to the list of tests marked `only`.
62
+ *
63
+ * @private
64
+ */
65
+ Test.prototype.markOnly = function() {
66
+ this.parent.appendOnlyTest(this);
67
+ };
68
+
39
69
  Test.prototype.clone = function() {
40
70
  var test = new Test(this.title, this.fn);
41
71
  test.timeout(this.timeout());
@@ -43,6 +73,7 @@ Test.prototype.clone = function() {
43
73
  test.enableTimeouts(this.enableTimeouts());
44
74
  test.retries(this.retries());
45
75
  test.currentRetry(this.currentRetry());
76
+ test.retriedTest(this.retriedTest() || this);
46
77
  test.globals(this.globals());
47
78
  test.parent = this.parent;
48
79
  test.file = this.file;
package/lib/utils.js CHANGED
@@ -63,8 +63,9 @@ exports.isString = function(obj) {
63
63
  exports.slug = function(str) {
64
64
  return str
65
65
  .toLowerCase()
66
- .replace(/ +/g, '-')
67
- .replace(/[^-\w]/g, '');
66
+ .replace(/\s+/g, '-')
67
+ .replace(/[^-\w]/g, '')
68
+ .replace(/-{2,}/g, '-');
68
69
  };
69
70
 
70
71
  /**
@@ -637,7 +638,7 @@ exports.stackTraceFilter = function() {
637
638
  var slash = path.sep;
638
639
  var cwd;
639
640
  if (is.node) {
640
- cwd = process.cwd() + slash;
641
+ cwd = exports.cwd() + slash;
641
642
  } else {
642
643
  cwd = (typeof location === 'undefined'
643
644
  ? window.location
@@ -753,38 +754,6 @@ exports.dQuote = function(str) {
753
754
  return '"' + str + '"';
754
755
  };
755
756
 
756
- /**
757
- * Provides simplistic message translation for dealing with plurality.
758
- *
759
- * @description
760
- * Use this to create messages which need to be singular or plural.
761
- * Some languages have several plural forms, so _complete_ message clauses
762
- * are preferable to generating the message on the fly.
763
- *
764
- * @private
765
- * @param {number} n - Non-negative integer
766
- * @param {string} msg1 - Message to be used in English for `n = 1`
767
- * @param {string} msg2 - Message to be used in English for `n = 0, 2, 3, ...`
768
- * @returns {string} message corresponding to value of `n`
769
- * @example
770
- * var sprintf = require('util').format;
771
- * var pkgs = ['one', 'two'];
772
- * var msg = sprintf(
773
- * ngettext(
774
- * pkgs.length,
775
- * 'cannot load package: %s',
776
- * 'cannot load packages: %s'
777
- * ),
778
- * pkgs.map(sQuote).join(', ')
779
- * );
780
- * console.log(msg); // => cannot load packages: 'one', 'two'
781
- */
782
- exports.ngettext = function(n, msg1, msg2) {
783
- if (typeof n === 'number' && n >= 0) {
784
- return n === 1 ? msg1 : msg2;
785
- }
786
- };
787
-
788
757
  /**
789
758
  * It's a noop.
790
759
  * @public
@@ -831,3 +800,35 @@ exports.defineConstants = function(obj) {
831
800
  }
832
801
  return Object.freeze(exports.createMap(obj));
833
802
  };
803
+
804
+ /**
805
+ * Whether current version of Node support ES modules
806
+ *
807
+ * @description
808
+ * Versions prior to 10 did not support ES Modules, and version 10 has an old incompatibile version of ESM.
809
+ * This function returns whether Node.JS has ES Module supports that is compatible with Mocha's needs,
810
+ * which is version >=12.11.
811
+ *
812
+ * @returns {Boolean} whether the current version of Node.JS supports ES Modules in a way that is compatible with Mocha
813
+ */
814
+ exports.supportsEsModules = function() {
815
+ if (!process.browser && process.versions && process.versions.node) {
816
+ var versionFields = process.versions.node.split('.');
817
+ var major = +versionFields[0];
818
+ var minor = +versionFields[1];
819
+
820
+ if (major >= 13 || (major === 12 && minor >= 11)) {
821
+ return true;
822
+ }
823
+ }
824
+ };
825
+
826
+ /**
827
+ * Returns current working directory
828
+ *
829
+ * Wrapper around `process.cwd()` for isolation
830
+ * @private
831
+ */
832
+ exports.cwd = function cwd() {
833
+ return process.cwd();
834
+ };