mocha 5.1.1 → 6.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +686 -984
  2. package/README.md +2 -1
  3. package/{images → assets/growl}/error.png +0 -0
  4. package/{images → assets/growl}/ok.png +0 -0
  5. package/bin/_mocha +4 -595
  6. package/bin/mocha +121 -61
  7. package/bin/options.js +6 -39
  8. package/browser-entry.js +21 -17
  9. package/lib/browser/growl.js +165 -2
  10. package/lib/browser/progress.js +11 -11
  11. package/lib/{template.html → browser/template.html} +0 -0
  12. package/lib/browser/tty.js +2 -2
  13. package/lib/cli/cli.js +68 -0
  14. package/lib/cli/commands.js +13 -0
  15. package/lib/cli/config.js +79 -0
  16. package/lib/cli/index.js +9 -0
  17. package/lib/cli/init.js +37 -0
  18. package/lib/cli/node-flags.js +69 -0
  19. package/lib/cli/one-and-dones.js +70 -0
  20. package/lib/cli/options.js +330 -0
  21. package/lib/cli/run-helpers.js +337 -0
  22. package/lib/cli/run-option-metadata.js +76 -0
  23. package/lib/cli/run.js +297 -0
  24. package/lib/context.js +14 -14
  25. package/lib/errors.js +141 -0
  26. package/lib/growl.js +136 -0
  27. package/lib/hook.js +5 -16
  28. package/lib/interfaces/bdd.js +16 -13
  29. package/lib/interfaces/common.js +62 -18
  30. package/lib/interfaces/exports.js +5 -8
  31. package/lib/interfaces/qunit.js +10 -10
  32. package/lib/interfaces/tdd.js +12 -11
  33. package/lib/mocha.js +477 -256
  34. package/lib/mocharc.json +10 -0
  35. package/lib/pending.js +1 -5
  36. package/lib/reporters/base.js +95 -117
  37. package/lib/reporters/doc.js +23 -9
  38. package/lib/reporters/dot.js +19 -13
  39. package/lib/reporters/html.js +82 -47
  40. package/lib/reporters/json-stream.js +43 -23
  41. package/lib/reporters/json.js +32 -23
  42. package/lib/reporters/landing.js +16 -9
  43. package/lib/reporters/list.js +19 -11
  44. package/lib/reporters/markdown.js +18 -12
  45. package/lib/reporters/min.js +8 -4
  46. package/lib/reporters/nyan.js +42 -35
  47. package/lib/reporters/progress.js +12 -7
  48. package/lib/reporters/spec.js +23 -12
  49. package/lib/reporters/tap.js +250 -32
  50. package/lib/reporters/xunit.js +61 -35
  51. package/lib/runnable.js +152 -95
  52. package/lib/runner.js +296 -248
  53. package/lib/stats-collector.js +83 -0
  54. package/lib/suite.js +294 -75
  55. package/lib/test.js +16 -15
  56. package/lib/utils.js +419 -146
  57. package/mocha.js +4589 -2228
  58. package/package.json +137 -38
  59. package/lib/ms.js +0 -94
  60. package/lib/reporters/base.js.orig +0 -498
  61. package/lib/reporters/json.js.orig +0 -128
package/lib/utils.js CHANGED
@@ -1,47 +1,55 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * @module
4
+ * Various utility functions used throughout Mocha's codebase.
5
+ * @module utils
5
6
  */
6
7
 
7
8
  /**
8
9
  * Module dependencies.
9
10
  */
10
11
 
11
- var debug = require('debug')('mocha:watch');
12
12
  var fs = require('fs');
13
- var glob = require('glob');
14
13
  var path = require('path');
15
- var join = path.join;
14
+ var util = require('util');
15
+ var glob = require('glob');
16
16
  var he = require('he');
17
+ var errors = require('./errors');
18
+ var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
19
+ var createMissingArgumentError = errors.createMissingArgumentError;
20
+
21
+ var assign = (exports.assign = require('object.assign').getPolyfill());
17
22
 
18
23
  /**
19
- * Ignored directories.
24
+ * Inherit the prototype methods from one constructor into another.
25
+ *
26
+ * @param {function} ctor - Constructor function which needs to inherit the
27
+ * prototype.
28
+ * @param {function} superCtor - Constructor function to inherit prototype from.
29
+ * @throws {TypeError} if either constructor is null, or if super constructor
30
+ * lacks a prototype.
20
31
  */
21
-
22
- var ignore = ['node_modules', '.git'];
23
-
24
- exports.inherits = require('util').inherits;
32
+ exports.inherits = util.inherits;
25
33
 
26
34
  /**
27
35
  * Escape special characters in the given string of html.
28
36
  *
29
- * @api private
37
+ * @private
30
38
  * @param {string} html
31
39
  * @return {string}
32
40
  */
33
- exports.escape = function (html) {
34
- return he.encode(String(html), { useNamedReferences: false });
41
+ exports.escape = function(html) {
42
+ return he.encode(String(html), {useNamedReferences: false});
35
43
  };
36
44
 
37
45
  /**
38
46
  * Test if the given obj is type of string.
39
47
  *
40
- * @api private
48
+ * @private
41
49
  * @param {Object} obj
42
50
  * @return {boolean}
43
51
  */
44
- exports.isString = function (obj) {
52
+ exports.isString = function(obj) {
45
53
  return typeof obj === 'string';
46
54
  };
47
55
 
@@ -49,15 +57,16 @@ exports.isString = function (obj) {
49
57
  * Watch the given `files` for changes
50
58
  * and invoke `fn(file)` on modification.
51
59
  *
52
- * @api private
60
+ * @private
53
61
  * @param {Array} files
54
62
  * @param {Function} fn
55
63
  */
56
- exports.watch = function (files, fn) {
57
- var options = { interval: 100 };
58
- files.forEach(function (file) {
64
+ exports.watch = function(files, fn) {
65
+ var options = {interval: 100};
66
+ var debug = require('debug')('mocha:watch');
67
+ files.forEach(function(file) {
59
68
  debug('file %s', file);
60
- fs.watchFile(file, options, function (curr, prev) {
69
+ fs.watchFile(file, options, function(curr, prev) {
61
70
  if (prev.mtime < curr.mtime) {
62
71
  fn(file);
63
72
  }
@@ -66,39 +75,52 @@ exports.watch = function (files, fn) {
66
75
  };
67
76
 
68
77
  /**
69
- * Ignored files.
78
+ * Predicate to screen `pathname` for further consideration.
70
79
  *
71
- * @api private
72
- * @param {string} path
73
- * @return {boolean}
80
+ * @description
81
+ * Returns <code>false</code> for pathname referencing:
82
+ * <ul>
83
+ * <li>'npm' package installation directory
84
+ * <li>'git' version control directory
85
+ * </ul>
86
+ *
87
+ * @private
88
+ * @param {string} pathname - File or directory name to screen
89
+ * @return {boolean} whether pathname should be further considered
90
+ * @example
91
+ * ['node_modules', 'test.js'].filter(considerFurther); // => ['test.js']
74
92
  */
75
- function ignored (path) {
76
- return !~ignore.indexOf(path);
93
+ function considerFurther(pathname) {
94
+ var ignore = ['node_modules', '.git'];
95
+
96
+ return !~ignore.indexOf(pathname);
77
97
  }
78
98
 
79
99
  /**
80
100
  * Lookup files in the given `dir`.
81
101
  *
82
- * @api private
102
+ * @description
103
+ * Filenames are returned in _traversal_ order by the OS/filesystem.
104
+ * **Make no assumption that the names will be sorted in any fashion.**
105
+ *
106
+ * @private
83
107
  * @param {string} dir
84
- * @param {string[]} [ext=['.js']]
108
+ * @param {string[]} [exts=['js']]
85
109
  * @param {Array} [ret=[]]
86
110
  * @return {Array}
87
111
  */
88
- exports.files = function (dir, ext, ret) {
112
+ exports.files = function(dir, exts, ret) {
89
113
  ret = ret || [];
90
- ext = ext || ['js'];
91
-
92
- var re = new RegExp('\\.(' + ext.join('|') + ')$');
114
+ exts = exts || ['js'];
93
115
 
94
116
  fs.readdirSync(dir)
95
- .filter(ignored)
96
- .forEach(function (path) {
97
- path = join(dir, path);
98
- if (fs.lstatSync(path).isDirectory()) {
99
- exports.files(path, ext, ret);
100
- } else if (path.match(re)) {
101
- ret.push(path);
117
+ .filter(considerFurther)
118
+ .forEach(function(dirent) {
119
+ var pathname = path.join(dir, dirent);
120
+ if (fs.lstatSync(pathname).isDirectory()) {
121
+ exports.files(pathname, exts, ret);
122
+ } else if (hasMatchingExtname(pathname, exts)) {
123
+ ret.push(pathname);
102
124
  }
103
125
  });
104
126
 
@@ -108,11 +130,11 @@ exports.files = function (dir, ext, ret) {
108
130
  /**
109
131
  * Compute a slug from the given `str`.
110
132
  *
111
- * @api private
133
+ * @private
112
134
  * @param {string} str
113
135
  * @return {string}
114
136
  */
115
- exports.slug = function (str) {
137
+ exports.slug = function(str) {
116
138
  return str
117
139
  .toLowerCase()
118
140
  .replace(/ +/g, '-')
@@ -125,15 +147,22 @@ exports.slug = function (str) {
125
147
  * @param {string} str
126
148
  * @return {string}
127
149
  */
128
- exports.clean = function (str) {
150
+ exports.clean = function(str) {
129
151
  str = str
130
- .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '')
152
+ .replace(/\r\n?|[\n\u2028\u2029]/g, '\n')
153
+ .replace(/^\uFEFF/, '')
131
154
  // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content
132
- .replace(/^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, '$1$2$3');
155
+ .replace(
156
+ /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/,
157
+ '$1$2$3'
158
+ );
133
159
 
134
160
  var spaces = str.match(/^\n?( *)/)[1].length;
135
161
  var tabs = str.match(/^\n?(\t*)/)[1].length;
136
- var re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}', 'gm');
162
+ var re = new RegExp(
163
+ '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}',
164
+ 'gm'
165
+ );
137
166
 
138
167
  str = str.replace(re, '');
139
168
 
@@ -143,31 +172,34 @@ exports.clean = function (str) {
143
172
  /**
144
173
  * Parse the given `qs`.
145
174
  *
146
- * @api private
175
+ * @private
147
176
  * @param {string} qs
148
177
  * @return {Object}
149
178
  */
150
- exports.parseQuery = function (qs) {
151
- return qs.replace('?', '').split('&').reduce(function (obj, pair) {
152
- var i = pair.indexOf('=');
153
- var key = pair.slice(0, i);
154
- var val = pair.slice(++i);
155
-
156
- // Due to how the URLSearchParams API treats spaces
157
- obj[key] = decodeURIComponent(val.replace(/\+/g, '%20'));
158
-
159
- return obj;
160
- }, {});
179
+ exports.parseQuery = function(qs) {
180
+ return qs
181
+ .replace('?', '')
182
+ .split('&')
183
+ .reduce(function(obj, pair) {
184
+ var i = pair.indexOf('=');
185
+ var key = pair.slice(0, i);
186
+ var val = pair.slice(++i);
187
+
188
+ // Due to how the URLSearchParams API treats spaces
189
+ obj[key] = decodeURIComponent(val.replace(/\+/g, '%20'));
190
+
191
+ return obj;
192
+ }, {});
161
193
  };
162
194
 
163
195
  /**
164
196
  * Highlight the given string of `js`.
165
197
  *
166
- * @api private
198
+ * @private
167
199
  * @param {string} js
168
200
  * @return {string}
169
201
  */
170
- function highlight (js) {
202
+ function highlight(js) {
171
203
  return js
172
204
  .replace(/</g, '&lt;')
173
205
  .replace(/>/g, '&gt;')
@@ -175,17 +207,23 @@ function highlight (js) {
175
207
  .replace(/('.*?')/gm, '<span class="string">$1</span>')
176
208
  .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
177
209
  .replace(/(\d+)/gm, '<span class="number">$1</span>')
178
- .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
179
- .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>');
210
+ .replace(
211
+ /\bnew[ \t]+(\w+)/gm,
212
+ '<span class="keyword">new</span> <span class="init">$1</span>'
213
+ )
214
+ .replace(
215
+ /\b(function|new|throw|return|var|if|else)\b/gm,
216
+ '<span class="keyword">$1</span>'
217
+ );
180
218
  }
181
219
 
182
220
  /**
183
221
  * Highlight the contents of tag `name`.
184
222
  *
185
- * @api private
223
+ * @private
186
224
  * @param {string} name
187
225
  */
188
- exports.highlightTags = function (name) {
226
+ exports.highlightTags = function(name) {
189
227
  var code = document.getElementById('mocha').getElementsByTagName(name);
190
228
  for (var i = 0, len = code.length; i < len; ++i) {
191
229
  code[i].innerHTML = highlight(code[i].innerHTML);
@@ -201,12 +239,12 @@ exports.highlightTags = function (name) {
201
239
  * Objects w/ no properties return `'{}'`
202
240
  * All else: return result of `value.toString()`
203
241
  *
204
- * @api private
242
+ * @private
205
243
  * @param {*} value The value to inspect.
206
244
  * @param {string} typeHint The type of the value
207
245
  * @returns {string}
208
246
  */
209
- function emptyRepresentation (value, typeHint) {
247
+ function emptyRepresentation(value, typeHint) {
210
248
  switch (typeHint) {
211
249
  case 'function':
212
250
  return '[Function]';
@@ -223,7 +261,7 @@ function emptyRepresentation (value, typeHint) {
223
261
  * Takes some variable and asks `Object.prototype.toString()` what it thinks it
224
262
  * is.
225
263
  *
226
- * @api private
264
+ * @private
227
265
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
228
266
  * @param {*} value The value to test.
229
267
  * @returns {string} Computed type
@@ -240,7 +278,7 @@ function emptyRepresentation (value, typeHint) {
240
278
  * type(global) // 'global'
241
279
  * type(new String('foo') // 'object'
242
280
  */
243
- var type = exports.type = function type (value) {
281
+ var type = (exports.type = function type(value) {
244
282
  if (value === undefined) {
245
283
  return 'undefined';
246
284
  } else if (value === null) {
@@ -248,10 +286,11 @@ var type = exports.type = function type (value) {
248
286
  } else if (Buffer.isBuffer(value)) {
249
287
  return 'buffer';
250
288
  }
251
- return Object.prototype.toString.call(value)
289
+ return Object.prototype.toString
290
+ .call(value)
252
291
  .replace(/^\[.+\s(.+?)]$/, '$1')
253
292
  .toLowerCase();
254
- };
293
+ });
255
294
 
256
295
  /**
257
296
  * Stringify `value`. Different behavior depending on type of value:
@@ -263,26 +302,28 @@ var type = exports.type = function type (value) {
263
302
  * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
264
303
  * JSON.stringify().
265
304
  *
266
- * @api private
305
+ * @private
267
306
  * @see exports.type
268
307
  * @param {*} value
269
308
  * @return {string}
270
309
  */
271
- exports.stringify = function (value) {
310
+ exports.stringify = function(value) {
272
311
  var typeHint = type(value);
273
312
 
274
313
  if (!~['object', 'array', 'function'].indexOf(typeHint)) {
275
314
  if (typeHint === 'buffer') {
276
315
  var json = Buffer.prototype.toJSON.call(value);
277
316
  // Based on the toJSON result
278
- return jsonStringify(json.data && json.type ? json.data : json, 2)
279
- .replace(/,(\n|$)/g, '$1');
317
+ return jsonStringify(
318
+ json.data && json.type ? json.data : json,
319
+ 2
320
+ ).replace(/,(\n|$)/g, '$1');
280
321
  }
281
322
 
282
323
  // IE7/IE8 has a bizarre String constructor; needs to be coerced
283
324
  // into an array and back to obj.
284
325
  if (typeHint === 'string' && typeof value === 'object') {
285
- value = value.split('').reduce(function (acc, char, idx) {
326
+ value = value.split('').reduce(function(acc, char, idx) {
286
327
  acc[idx] = char;
287
328
  return acc;
288
329
  }, {});
@@ -294,7 +335,10 @@ exports.stringify = function (value) {
294
335
 
295
336
  for (var prop in value) {
296
337
  if (Object.prototype.hasOwnProperty.call(value, prop)) {
297
- return jsonStringify(exports.canonicalize(value, null, typeHint), 2).replace(/,(\n|$)/g, '$1');
338
+ return jsonStringify(
339
+ exports.canonicalize(value, null, typeHint),
340
+ 2
341
+ ).replace(/,(\n|$)/g, '$1');
298
342
  }
299
343
  }
300
344
 
@@ -304,13 +348,13 @@ exports.stringify = function (value) {
304
348
  /**
305
349
  * like JSON.stringify but more sense.
306
350
  *
307
- * @api private
351
+ * @private
308
352
  * @param {Object} object
309
353
  * @param {number=} spaces
310
354
  * @param {number=} depth
311
355
  * @returns {*}
312
356
  */
313
- function jsonStringify (object, spaces, depth) {
357
+ function jsonStringify(object, spaces, depth) {
314
358
  if (typeof spaces === 'undefined') {
315
359
  // primitive types
316
360
  return _stringify(object);
@@ -320,13 +364,16 @@ function jsonStringify (object, spaces, depth) {
320
364
  var space = spaces * depth;
321
365
  var str = Array.isArray(object) ? '[' : '{';
322
366
  var end = Array.isArray(object) ? ']' : '}';
323
- var length = typeof object.length === 'number' ? object.length : Object.keys(object).length;
367
+ var length =
368
+ typeof object.length === 'number'
369
+ ? object.length
370
+ : Object.keys(object).length;
324
371
  // `.repeat()` polyfill
325
- function repeat (s, n) {
372
+ function repeat(s, n) {
326
373
  return new Array(n).join(s);
327
374
  }
328
375
 
329
- function _stringify (val) {
376
+ function _stringify(val) {
330
377
  switch (type(val)) {
331
378
  case 'null':
332
379
  case 'undefined':
@@ -340,9 +387,10 @@ function jsonStringify (object, spaces, depth) {
340
387
  case 'regexp':
341
388
  case 'symbol':
342
389
  case 'number':
343
- val = val === 0 && (1 / val) === -Infinity // `-0`
344
- ? '-0'
345
- : val.toString();
390
+ val =
391
+ val === 0 && 1 / val === -Infinity // `-0`
392
+ ? '-0'
393
+ : val.toString();
346
394
  break;
347
395
  case 'date':
348
396
  var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString();
@@ -355,9 +403,10 @@ function jsonStringify (object, spaces, depth) {
355
403
  val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
356
404
  break;
357
405
  default:
358
- val = (val === '[Function]' || val === '[Circular]')
359
- ? val
360
- : JSON.stringify(val); // string
406
+ val =
407
+ val === '[Function]' || val === '[Circular]'
408
+ ? val
409
+ : JSON.stringify(val); // string
361
410
  }
362
411
  return val;
363
412
  }
@@ -367,15 +416,19 @@ function jsonStringify (object, spaces, depth) {
367
416
  continue; // not my business
368
417
  }
369
418
  --length;
370
- str += '\n ' + repeat(' ', space) +
419
+ str +=
420
+ '\n ' +
421
+ repeat(' ', space) +
371
422
  (Array.isArray(object) ? '' : '"' + i + '": ') + // key
372
423
  _stringify(object[i]) + // value
373
424
  (length ? ',' : ''); // comma
374
425
  }
375
426
 
376
- return str +
427
+ return (
428
+ str +
377
429
  // [], {}
378
- (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end);
430
+ (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end)
431
+ );
379
432
  }
380
433
 
381
434
  /**
@@ -390,20 +443,20 @@ function jsonStringify (object, spaces, depth) {
390
443
  * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
391
444
  * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
392
445
  *
393
- * @api private
446
+ * @private
394
447
  * @see {@link exports.stringify}
395
448
  * @param {*} value Thing to inspect. May or may not have properties.
396
449
  * @param {Array} [stack=[]] Stack of seen values
397
450
  * @param {string} [typeHint] Type hint
398
451
  * @return {(Object|Array|Function|string|undefined)}
399
452
  */
400
- exports.canonicalize = function canonicalize (value, stack, typeHint) {
453
+ exports.canonicalize = function canonicalize(value, stack, typeHint) {
401
454
  var canonicalizedObj;
402
455
  /* eslint-disable no-unused-vars */
403
456
  var prop;
404
457
  /* eslint-enable no-unused-vars */
405
458
  typeHint = typeHint || type(value);
406
- function withStack (value, fn) {
459
+ function withStack(value, fn) {
407
460
  stack.push(value);
408
461
  fn();
409
462
  stack.pop();
@@ -422,8 +475,8 @@ exports.canonicalize = function canonicalize (value, stack, typeHint) {
422
475
  canonicalizedObj = value;
423
476
  break;
424
477
  case 'array':
425
- withStack(value, function () {
426
- canonicalizedObj = value.map(function (item) {
478
+ withStack(value, function() {
479
+ canonicalizedObj = value.map(function(item) {
427
480
  return exports.canonicalize(item, stack);
428
481
  });
429
482
  });
@@ -442,10 +495,12 @@ exports.canonicalize = function canonicalize (value, stack, typeHint) {
442
495
  /* falls through */
443
496
  case 'object':
444
497
  canonicalizedObj = canonicalizedObj || {};
445
- withStack(value, function () {
446
- Object.keys(value).sort().forEach(function (key) {
447
- canonicalizedObj[key] = exports.canonicalize(value[key], stack);
448
- });
498
+ withStack(value, function() {
499
+ Object.keys(value)
500
+ .sort()
501
+ .forEach(function(key) {
502
+ canonicalizedObj[key] = exports.canonicalize(value[key], stack);
503
+ });
449
504
  });
450
505
  break;
451
506
  case 'date':
@@ -462,34 +517,82 @@ exports.canonicalize = function canonicalize (value, stack, typeHint) {
462
517
  return canonicalizedObj;
463
518
  };
464
519
 
520
+ /**
521
+ * Determines if pathname has a matching file extension.
522
+ *
523
+ * @private
524
+ * @param {string} pathname - Pathname to check for match.
525
+ * @param {string[]} exts - List of file extensions (sans period).
526
+ * @return {boolean} whether file extension matches.
527
+ * @example
528
+ * hasMatchingExtname('foo.html', ['js', 'css']); // => false
529
+ */
530
+ function hasMatchingExtname(pathname, exts) {
531
+ var suffix = path.extname(pathname).slice(1);
532
+ return exts.some(function(element) {
533
+ return suffix === element;
534
+ });
535
+ }
536
+
537
+ /**
538
+ * Determines if pathname would be a "hidden" file (or directory) on UN*X.
539
+ *
540
+ * @description
541
+ * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during
542
+ * typical usage. Dotfiles, plain-text configuration files, are prime examples.
543
+ *
544
+ * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names}
545
+ *
546
+ * @private
547
+ * @param {string} pathname - Pathname to check for match.
548
+ * @return {boolean} whether pathname would be considered a hidden file.
549
+ * @example
550
+ * isHiddenOnUnix('.profile'); // => true
551
+ */
552
+ function isHiddenOnUnix(pathname) {
553
+ return path.basename(pathname)[0] === '.';
554
+ }
555
+
465
556
  /**
466
557
  * Lookup file names at the given `path`.
467
558
  *
468
- * @memberof Mocha.utils
559
+ * @description
560
+ * Filenames are returned in _traversal_ order by the OS/filesystem.
561
+ * **Make no assumption that the names will be sorted in any fashion.**
562
+ *
469
563
  * @public
470
- * @api public
471
- * @param {string} filepath Base path to start searching from.
472
- * @param {string[]} extensions File extensions to look for.
473
- * @param {boolean} recursive Whether or not to recurse into subdirectories.
564
+ * @memberof Mocha.utils
565
+ * @todo Fix extension handling
566
+ * @param {string} filepath - Base path to start searching from.
567
+ * @param {string[]} extensions - File extensions to look for.
568
+ * @param {boolean} recursive - Whether to recurse into subdirectories.
474
569
  * @return {string[]} An array of paths.
570
+ * @throws {Error} if no files match pattern.
571
+ * @throws {TypeError} if `filepath` is directory and `extensions` not provided.
475
572
  */
476
- exports.lookupFiles = function lookupFiles (filepath, extensions, recursive) {
573
+ exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
477
574
  var files = [];
575
+ var stat;
478
576
 
479
577
  if (!fs.existsSync(filepath)) {
480
578
  if (fs.existsSync(filepath + '.js')) {
481
579
  filepath += '.js';
482
580
  } else {
581
+ // Handle glob
483
582
  files = glob.sync(filepath);
484
583
  if (!files.length) {
485
- throw new Error("cannot resolve path (or pattern) '" + filepath + "'");
584
+ throw createNoFilesMatchPatternError(
585
+ 'Cannot find any files matching pattern ' + exports.dQuote(filepath),
586
+ filepath
587
+ );
486
588
  }
487
589
  return files;
488
590
  }
489
591
  }
490
592
 
593
+ // Handle file
491
594
  try {
492
- var stat = fs.statSync(filepath);
595
+ stat = fs.statSync(filepath);
493
596
  if (stat.isFile()) {
494
597
  return filepath;
495
598
  }
@@ -498,13 +601,16 @@ exports.lookupFiles = function lookupFiles (filepath, extensions, recursive) {
498
601
  return;
499
602
  }
500
603
 
501
- fs.readdirSync(filepath).forEach(function (file) {
502
- file = path.join(filepath, file);
604
+ // Handle directory
605
+ fs.readdirSync(filepath).forEach(function(dirent) {
606
+ var pathname = path.join(filepath, dirent);
607
+ var stat;
608
+
503
609
  try {
504
- var stat = fs.statSync(file);
610
+ stat = fs.statSync(pathname);
505
611
  if (stat.isDirectory()) {
506
612
  if (recursive) {
507
- files = files.concat(lookupFiles(file, extensions, recursive));
613
+ files = files.concat(lookupFiles(pathname, extensions, recursive));
508
614
  }
509
615
  return;
510
616
  }
@@ -512,35 +618,73 @@ exports.lookupFiles = function lookupFiles (filepath, extensions, recursive) {
512
618
  // ignore error
513
619
  return;
514
620
  }
515
- var re = new RegExp('\\.(?:' + extensions.join('|') + ')$');
516
- if (!stat.isFile() || !re.test(file) || path.basename(file)[0] === '.') {
621
+ if (!extensions) {
622
+ throw createMissingArgumentError(
623
+ util.format(
624
+ 'Argument %s required when argument %s is a directory',
625
+ exports.sQuote('extensions'),
626
+ exports.sQuote('filepath')
627
+ ),
628
+ 'extensions',
629
+ 'array'
630
+ );
631
+ }
632
+
633
+ if (
634
+ !stat.isFile() ||
635
+ !hasMatchingExtname(pathname, extensions) ||
636
+ isHiddenOnUnix(pathname)
637
+ ) {
517
638
  return;
518
639
  }
519
- files.push(file);
640
+ files.push(pathname);
520
641
  });
521
642
 
522
643
  return files;
523
644
  };
524
645
 
525
646
  /**
526
- * Generate an undefined error with a message warning the user.
527
- *
528
- * @return {Error}
647
+ * process.emitWarning or a polyfill
648
+ * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options
649
+ * @ignore
529
650
  */
651
+ function emitWarning(msg, type) {
652
+ if (process.emitWarning) {
653
+ process.emitWarning(msg, type);
654
+ } else {
655
+ process.nextTick(function() {
656
+ console.warn(type + ': ' + msg);
657
+ });
658
+ }
659
+ }
530
660
 
531
- exports.undefinedError = function () {
532
- return new Error('Caught undefined error, did you throw without specifying what?');
661
+ /**
662
+ * Show a deprecation warning. Each distinct message is only displayed once.
663
+ * Ignores empty messages.
664
+ *
665
+ * @param {string} [msg] - Warning to print
666
+ * @private
667
+ */
668
+ exports.deprecate = function deprecate(msg) {
669
+ msg = String(msg);
670
+ if (msg && !deprecate.cache[msg]) {
671
+ deprecate.cache[msg] = true;
672
+ emitWarning(msg, 'DeprecationWarning');
673
+ }
533
674
  };
675
+ exports.deprecate.cache = {};
534
676
 
535
677
  /**
536
- * Generate an undefined error if `err` is not defined.
678
+ * Show a generic warning.
679
+ * Ignores empty messages.
537
680
  *
538
- * @param {Error} err
539
- * @return {Error}
681
+ * @param {string} [msg] - Warning to print
682
+ * @private
540
683
  */
541
-
542
- exports.getError = function (err) {
543
- return err || exports.undefinedError();
684
+ exports.warn = function warn(msg) {
685
+ if (msg) {
686
+ emitWarning(msg);
687
+ }
544
688
  };
545
689
 
546
690
  /**
@@ -552,9 +696,9 @@ exports.getError = function (err) {
552
696
  * (i.e: strip Mocha and internal node functions from stack trace).
553
697
  * @returns {Function}
554
698
  */
555
- exports.stackTraceFilter = function () {
699
+ exports.stackTraceFilter = function() {
556
700
  // TODO: Replace with `process.browser`
557
- var is = typeof document === 'undefined' ? { node: true } : { browser: true };
701
+ var is = typeof document === 'undefined' ? {node: true} : {browser: true};
558
702
  var slash = path.sep;
559
703
  var cwd;
560
704
  if (is.node) {
@@ -562,30 +706,33 @@ exports.stackTraceFilter = function () {
562
706
  } else {
563
707
  cwd = (typeof location === 'undefined'
564
708
  ? window.location
565
- : location).href.replace(/\/[^/]*$/, '/');
709
+ : location
710
+ ).href.replace(/\/[^/]*$/, '/');
566
711
  slash = '/';
567
712
  }
568
713
 
569
- function isMochaInternal (line) {
570
- return (~line.indexOf('node_modules' + slash + 'mocha' + slash)) ||
571
- (~line.indexOf('node_modules' + slash + 'mocha.js')) ||
572
- (~line.indexOf('bower_components' + slash + 'mocha.js')) ||
573
- (~line.indexOf(slash + 'mocha.js'));
714
+ function isMochaInternal(line) {
715
+ return (
716
+ ~line.indexOf('node_modules' + slash + 'mocha' + slash) ||
717
+ ~line.indexOf(slash + 'mocha.js')
718
+ );
574
719
  }
575
720
 
576
- function isNodeInternal (line) {
577
- return (~line.indexOf('(timers.js:')) ||
578
- (~line.indexOf('(events.js:')) ||
579
- (~line.indexOf('(node.js:')) ||
580
- (~line.indexOf('(module.js:')) ||
581
- (~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
582
- false;
721
+ function isNodeInternal(line) {
722
+ return (
723
+ ~line.indexOf('(timers.js:') ||
724
+ ~line.indexOf('(events.js:') ||
725
+ ~line.indexOf('(node.js:') ||
726
+ ~line.indexOf('(module.js:') ||
727
+ ~line.indexOf('GeneratorFunctionPrototype.next (native)') ||
728
+ false
729
+ );
583
730
  }
584
731
 
585
- return function (stack) {
732
+ return function(stack) {
586
733
  stack = stack.split('\n');
587
734
 
588
- stack = stack.reduce(function (list, line) {
735
+ stack = stack.reduce(function(list, line) {
589
736
  if (isMochaInternal(line)) {
590
737
  return list;
591
738
  }
@@ -595,7 +742,7 @@ exports.stackTraceFilter = function () {
595
742
  }
596
743
 
597
744
  // Clean up cwd(absolute)
598
- if (/\(?.+:\d+:\d+\)?$/.test(line)) {
745
+ if (/:\d+:\d+\)?$/.test(line)) {
599
746
  line = line.replace('(' + cwd, '(');
600
747
  }
601
748
 
@@ -609,16 +756,142 @@ exports.stackTraceFilter = function () {
609
756
 
610
757
  /**
611
758
  * Crude, but effective.
612
- * @api
759
+ * @public
613
760
  * @param {*} value
614
761
  * @returns {boolean} Whether or not `value` is a Promise
615
762
  */
616
- exports.isPromise = function isPromise (value) {
617
- return typeof value === 'object' && typeof value.then === 'function';
763
+ exports.isPromise = function isPromise(value) {
764
+ return (
765
+ typeof value === 'object' &&
766
+ value !== null &&
767
+ typeof value.then === 'function'
768
+ );
769
+ };
770
+
771
+ /**
772
+ * Clamps a numeric value to an inclusive range.
773
+ *
774
+ * @param {number} value - Value to be clamped.
775
+ * @param {numer[]} range - Two element array specifying [min, max] range.
776
+ * @returns {number} clamped value
777
+ */
778
+ exports.clamp = function clamp(value, range) {
779
+ return Math.min(Math.max(value, range[0]), range[1]);
780
+ };
781
+
782
+ /**
783
+ * Single quote text by combining with undirectional ASCII quotation marks.
784
+ *
785
+ * @description
786
+ * Provides a simple means of markup for quoting text to be used in output.
787
+ * Use this to quote names of variables, methods, and packages.
788
+ *
789
+ * <samp>package 'foo' cannot be found</samp>
790
+ *
791
+ * @private
792
+ * @param {string} str - Value to be quoted.
793
+ * @returns {string} quoted value
794
+ * @example
795
+ * sQuote('n') // => 'n'
796
+ */
797
+ exports.sQuote = function(str) {
798
+ return "'" + str + "'";
799
+ };
800
+
801
+ /**
802
+ * Double quote text by combining with undirectional ASCII quotation marks.
803
+ *
804
+ * @description
805
+ * Provides a simple means of markup for quoting text to be used in output.
806
+ * Use this to quote names of datatypes, classes, pathnames, and strings.
807
+ *
808
+ * <samp>argument 'value' must be "string" or "number"</samp>
809
+ *
810
+ * @private
811
+ * @param {string} str - Value to be quoted.
812
+ * @returns {string} quoted value
813
+ * @example
814
+ * dQuote('number') // => "number"
815
+ */
816
+ exports.dQuote = function(str) {
817
+ return '"' + str + '"';
818
+ };
819
+
820
+ /**
821
+ * Provides simplistic message translation for dealing with plurality.
822
+ *
823
+ * @description
824
+ * Use this to create messages which need to be singular or plural.
825
+ * Some languages have several plural forms, so _complete_ message clauses
826
+ * are preferable to generating the message on the fly.
827
+ *
828
+ * @private
829
+ * @param {number} n - Non-negative integer
830
+ * @param {string} msg1 - Message to be used in English for `n = 1`
831
+ * @param {string} msg2 - Message to be used in English for `n = 0, 2, 3, ...`
832
+ * @returns {string} message corresponding to value of `n`
833
+ * @example
834
+ * var sprintf = require('util').format;
835
+ * var pkgs = ['one', 'two'];
836
+ * var msg = sprintf(
837
+ * ngettext(
838
+ * pkgs.length,
839
+ * 'cannot load package: %s',
840
+ * 'cannot load packages: %s'
841
+ * ),
842
+ * pkgs.map(sQuote).join(', ')
843
+ * );
844
+ * console.log(msg); // => cannot load packages: 'one', 'two'
845
+ */
846
+ exports.ngettext = function(n, msg1, msg2) {
847
+ if (typeof n === 'number' && n >= 0) {
848
+ return n === 1 ? msg1 : msg2;
849
+ }
618
850
  };
619
851
 
620
852
  /**
621
853
  * It's a noop.
622
- * @api
854
+ * @public
623
855
  */
624
- exports.noop = function () {};
856
+ exports.noop = function() {};
857
+
858
+ /**
859
+ * Creates a map-like object.
860
+ *
861
+ * @description
862
+ * A "map" is an object with no prototype, for our purposes. In some cases
863
+ * this would be more appropriate than a `Map`, especially if your environment
864
+ * doesn't support it. Recommended for use in Mocha's public APIs.
865
+ *
866
+ * @public
867
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|MDN:Map}
868
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects|MDN:Object.create - Custom objects}
869
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign|MDN:Object.assign}
870
+ * @param {...*} [obj] - Arguments to `Object.assign()`.
871
+ * @returns {Object} An object with no prototype, having `...obj` properties
872
+ */
873
+ exports.createMap = function(obj) {
874
+ return assign.apply(
875
+ null,
876
+ [Object.create(null)].concat(Array.prototype.slice.call(arguments))
877
+ );
878
+ };
879
+
880
+ /**
881
+ * Creates a read-only map-like object.
882
+ *
883
+ * @description
884
+ * This differs from {@link module:utils.createMap createMap} only in that
885
+ * the argument must be non-empty, because the result is frozen.
886
+ *
887
+ * @see {@link module:utils.createMap createMap}
888
+ * @param {...*} [obj] - Arguments to `Object.assign()`.
889
+ * @returns {Object} A frozen object with no prototype, having `...obj` properties
890
+ * @throws {TypeError} if argument is not a non-empty object.
891
+ */
892
+ exports.defineConstants = function(obj) {
893
+ if (type(obj) !== 'object' || !Object.keys(obj).length) {
894
+ throw new TypeError('Invalid argument; expected a non-empty object');
895
+ }
896
+ return Object.freeze(exports.createMap(obj));
897
+ };