mocha 9.1.2

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 (76) hide show
  1. package/CHANGELOG.md +1015 -0
  2. package/LICENSE +22 -0
  3. package/README.md +70 -0
  4. package/assets/growl/error.png +0 -0
  5. package/assets/growl/ok.png +0 -0
  6. package/bin/_mocha +10 -0
  7. package/bin/mocha +142 -0
  8. package/browser-entry.js +216 -0
  9. package/index.js +3 -0
  10. package/lib/browser/growl.js +169 -0
  11. package/lib/browser/highlight-tags.js +39 -0
  12. package/lib/browser/parse-query.js +24 -0
  13. package/lib/browser/progress.js +123 -0
  14. package/lib/browser/template.html +20 -0
  15. package/lib/cli/cli.js +89 -0
  16. package/lib/cli/collect-files.js +92 -0
  17. package/lib/cli/commands.js +13 -0
  18. package/lib/cli/config.js +105 -0
  19. package/lib/cli/index.js +3 -0
  20. package/lib/cli/init.js +36 -0
  21. package/lib/cli/lookup-files.js +145 -0
  22. package/lib/cli/node-flags.js +85 -0
  23. package/lib/cli/one-and-dones.js +69 -0
  24. package/lib/cli/options.js +261 -0
  25. package/lib/cli/run-helpers.js +243 -0
  26. package/lib/cli/run-option-metadata.js +117 -0
  27. package/lib/cli/run.js +379 -0
  28. package/lib/cli/watch-run.js +380 -0
  29. package/lib/context.js +86 -0
  30. package/lib/errors.js +563 -0
  31. package/lib/hook.js +89 -0
  32. package/lib/interfaces/bdd.js +111 -0
  33. package/lib/interfaces/common.js +193 -0
  34. package/lib/interfaces/exports.js +60 -0
  35. package/lib/interfaces/index.js +6 -0
  36. package/lib/interfaces/qunit.js +98 -0
  37. package/lib/interfaces/tdd.js +106 -0
  38. package/lib/mocha.js +1374 -0
  39. package/lib/mocharc.json +10 -0
  40. package/lib/nodejs/buffered-worker-pool.js +172 -0
  41. package/lib/nodejs/esm-utils.js +109 -0
  42. package/lib/nodejs/file-unloader.js +15 -0
  43. package/lib/nodejs/growl.js +137 -0
  44. package/lib/nodejs/parallel-buffered-runner.js +433 -0
  45. package/lib/nodejs/reporters/parallel-buffered.js +165 -0
  46. package/lib/nodejs/serializer.js +412 -0
  47. package/lib/nodejs/worker.js +151 -0
  48. package/lib/pending.js +16 -0
  49. package/lib/plugin-loader.js +286 -0
  50. package/lib/reporters/base.js +537 -0
  51. package/lib/reporters/doc.js +95 -0
  52. package/lib/reporters/dot.js +81 -0
  53. package/lib/reporters/html.js +390 -0
  54. package/lib/reporters/index.js +19 -0
  55. package/lib/reporters/json-stream.js +92 -0
  56. package/lib/reporters/json.js +162 -0
  57. package/lib/reporters/landing.js +116 -0
  58. package/lib/reporters/list.js +78 -0
  59. package/lib/reporters/markdown.js +112 -0
  60. package/lib/reporters/min.js +52 -0
  61. package/lib/reporters/nyan.js +276 -0
  62. package/lib/reporters/progress.js +104 -0
  63. package/lib/reporters/spec.js +99 -0
  64. package/lib/reporters/tap.js +293 -0
  65. package/lib/reporters/xunit.js +217 -0
  66. package/lib/runnable.js +476 -0
  67. package/lib/runner.js +1269 -0
  68. package/lib/stats-collector.js +83 -0
  69. package/lib/suite.js +695 -0
  70. package/lib/test.js +113 -0
  71. package/lib/utils.js +641 -0
  72. package/mocha-es2018.js +19816 -0
  73. package/mocha.css +325 -0
  74. package/mocha.js +30844 -0
  75. package/mocha.js.map +1 -0
  76. package/package.json +200 -0
@@ -0,0 +1,390 @@
1
+ 'use strict';
2
+
3
+ /* eslint-env browser */
4
+ /**
5
+ * @module HTML
6
+ */
7
+ /**
8
+ * Module dependencies.
9
+ */
10
+
11
+ var Base = require('./base');
12
+ var utils = require('../utils');
13
+ var Progress = require('../browser/progress');
14
+ var escapeRe = require('escape-string-regexp');
15
+ var constants = require('../runner').constants;
16
+ var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
17
+ var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
18
+ var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN;
19
+ var EVENT_SUITE_END = constants.EVENT_SUITE_END;
20
+ var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
21
+ var escape = utils.escape;
22
+
23
+ /**
24
+ * Save timer references to avoid Sinon interfering (see GH-237).
25
+ */
26
+
27
+ var Date = global.Date;
28
+
29
+ /**
30
+ * Expose `HTML`.
31
+ */
32
+
33
+ exports = module.exports = HTML;
34
+
35
+ /**
36
+ * Stats template.
37
+ */
38
+
39
+ var statsTemplate =
40
+ '<ul id="mocha-stats">' +
41
+ '<li class="progress"><canvas width="40" height="40"></canvas></li>' +
42
+ '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>' +
43
+ '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>' +
44
+ '<li class="duration">duration: <em>0</em>s</li>' +
45
+ '</ul>';
46
+
47
+ var playIcon = '&#x2023;';
48
+
49
+ /**
50
+ * Constructs a new `HTML` reporter instance.
51
+ *
52
+ * @public
53
+ * @class
54
+ * @memberof Mocha.reporters
55
+ * @extends Mocha.reporters.Base
56
+ * @param {Runner} runner - Instance triggers reporter actions.
57
+ * @param {Object} [options] - runner options
58
+ */
59
+ function HTML(runner, options) {
60
+ Base.call(this, runner, options);
61
+
62
+ var self = this;
63
+ var stats = this.stats;
64
+ var stat = fragment(statsTemplate);
65
+ var items = stat.getElementsByTagName('li');
66
+ var passes = items[1].getElementsByTagName('em')[0];
67
+ var passesLink = items[1].getElementsByTagName('a')[0];
68
+ var failures = items[2].getElementsByTagName('em')[0];
69
+ var failuresLink = items[2].getElementsByTagName('a')[0];
70
+ var duration = items[3].getElementsByTagName('em')[0];
71
+ var canvas = stat.getElementsByTagName('canvas')[0];
72
+ var report = fragment('<ul id="mocha-report"></ul>');
73
+ var stack = [report];
74
+ var progress;
75
+ var ctx;
76
+ var root = document.getElementById('mocha');
77
+
78
+ if (canvas.getContext) {
79
+ var ratio = window.devicePixelRatio || 1;
80
+ canvas.style.width = canvas.width;
81
+ canvas.style.height = canvas.height;
82
+ canvas.width *= ratio;
83
+ canvas.height *= ratio;
84
+ ctx = canvas.getContext('2d');
85
+ ctx.scale(ratio, ratio);
86
+ progress = new Progress();
87
+ }
88
+
89
+ if (!root) {
90
+ return error('#mocha div missing, add it to your document');
91
+ }
92
+
93
+ // pass toggle
94
+ on(passesLink, 'click', function(evt) {
95
+ evt.preventDefault();
96
+ unhide();
97
+ var name = /pass/.test(report.className) ? '' : ' pass';
98
+ report.className = report.className.replace(/fail|pass/g, '') + name;
99
+ if (report.className.trim()) {
100
+ hideSuitesWithout('test pass');
101
+ }
102
+ });
103
+
104
+ // failure toggle
105
+ on(failuresLink, 'click', function(evt) {
106
+ evt.preventDefault();
107
+ unhide();
108
+ var name = /fail/.test(report.className) ? '' : ' fail';
109
+ report.className = report.className.replace(/fail|pass/g, '') + name;
110
+ if (report.className.trim()) {
111
+ hideSuitesWithout('test fail');
112
+ }
113
+ });
114
+
115
+ root.appendChild(stat);
116
+ root.appendChild(report);
117
+
118
+ if (progress) {
119
+ progress.size(40);
120
+ }
121
+
122
+ runner.on(EVENT_SUITE_BEGIN, function(suite) {
123
+ if (suite.root) {
124
+ return;
125
+ }
126
+
127
+ // suite
128
+ var url = self.suiteURL(suite);
129
+ var el = fragment(
130
+ '<li class="suite"><h1><a href="%s">%s</a></h1></li>',
131
+ url,
132
+ escape(suite.title)
133
+ );
134
+
135
+ // container
136
+ stack[0].appendChild(el);
137
+ stack.unshift(document.createElement('ul'));
138
+ el.appendChild(stack[0]);
139
+ });
140
+
141
+ runner.on(EVENT_SUITE_END, function(suite) {
142
+ if (suite.root) {
143
+ updateStats();
144
+ return;
145
+ }
146
+ stack.shift();
147
+ });
148
+
149
+ runner.on(EVENT_TEST_PASS, function(test) {
150
+ var url = self.testURL(test);
151
+ var markup =
152
+ '<li class="test pass %e"><h2>%e<span class="duration">%ems</span> ' +
153
+ '<a href="%s" class="replay">' +
154
+ playIcon +
155
+ '</a></h2></li>';
156
+ var el = fragment(markup, test.speed, test.title, test.duration, url);
157
+ self.addCodeToggle(el, test.body);
158
+ appendToStack(el);
159
+ updateStats();
160
+ });
161
+
162
+ runner.on(EVENT_TEST_FAIL, function(test) {
163
+ var el = fragment(
164
+ '<li class="test fail"><h2>%e <a href="%e" class="replay">' +
165
+ playIcon +
166
+ '</a></h2></li>',
167
+ test.title,
168
+ self.testURL(test)
169
+ );
170
+ var stackString; // Note: Includes leading newline
171
+ var message = test.err.toString();
172
+
173
+ // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
174
+ // check for the result of the stringifying.
175
+ if (message === '[object Error]') {
176
+ message = test.err.message;
177
+ }
178
+
179
+ if (test.err.stack) {
180
+ var indexOfMessage = test.err.stack.indexOf(test.err.message);
181
+ if (indexOfMessage === -1) {
182
+ stackString = test.err.stack;
183
+ } else {
184
+ stackString = test.err.stack.substr(
185
+ test.err.message.length + indexOfMessage
186
+ );
187
+ }
188
+ } else if (test.err.sourceURL && test.err.line !== undefined) {
189
+ // Safari doesn't give you a stack. Let's at least provide a source line.
190
+ stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
191
+ }
192
+
193
+ stackString = stackString || '';
194
+
195
+ if (test.err.htmlMessage && stackString) {
196
+ el.appendChild(
197
+ fragment(
198
+ '<div class="html-error">%s\n<pre class="error">%e</pre></div>',
199
+ test.err.htmlMessage,
200
+ stackString
201
+ )
202
+ );
203
+ } else if (test.err.htmlMessage) {
204
+ el.appendChild(
205
+ fragment('<div class="html-error">%s</div>', test.err.htmlMessage)
206
+ );
207
+ } else {
208
+ el.appendChild(
209
+ fragment('<pre class="error">%e%e</pre>', message, stackString)
210
+ );
211
+ }
212
+
213
+ self.addCodeToggle(el, test.body);
214
+ appendToStack(el);
215
+ updateStats();
216
+ });
217
+
218
+ runner.on(EVENT_TEST_PENDING, function(test) {
219
+ var el = fragment(
220
+ '<li class="test pass pending"><h2>%e</h2></li>',
221
+ test.title
222
+ );
223
+ appendToStack(el);
224
+ updateStats();
225
+ });
226
+
227
+ function appendToStack(el) {
228
+ // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
229
+ if (stack[0]) {
230
+ stack[0].appendChild(el);
231
+ }
232
+ }
233
+
234
+ function updateStats() {
235
+ // TODO: add to stats
236
+ var percent = ((stats.tests / runner.total) * 100) | 0;
237
+ if (progress) {
238
+ progress.update(percent).draw(ctx);
239
+ }
240
+
241
+ // update stats
242
+ var ms = new Date() - stats.start;
243
+ text(passes, stats.passes);
244
+ text(failures, stats.failures);
245
+ text(duration, (ms / 1000).toFixed(2));
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Makes a URL, preserving querystring ("search") parameters.
251
+ *
252
+ * @param {string} s
253
+ * @return {string} A new URL.
254
+ */
255
+ function makeUrl(s) {
256
+ var search = window.location.search;
257
+
258
+ // Remove previous grep query parameter if present
259
+ if (search) {
260
+ search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
261
+ }
262
+
263
+ return (
264
+ window.location.pathname +
265
+ (search ? search + '&' : '?') +
266
+ 'grep=' +
267
+ encodeURIComponent(escapeRe(s))
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Provide suite URL.
273
+ *
274
+ * @param {Object} [suite]
275
+ */
276
+ HTML.prototype.suiteURL = function(suite) {
277
+ return makeUrl(suite.fullTitle());
278
+ };
279
+
280
+ /**
281
+ * Provide test URL.
282
+ *
283
+ * @param {Object} [test]
284
+ */
285
+ HTML.prototype.testURL = function(test) {
286
+ return makeUrl(test.fullTitle());
287
+ };
288
+
289
+ /**
290
+ * Adds code toggle functionality for the provided test's list element.
291
+ *
292
+ * @param {HTMLLIElement} el
293
+ * @param {string} contents
294
+ */
295
+ HTML.prototype.addCodeToggle = function(el, contents) {
296
+ var h2 = el.getElementsByTagName('h2')[0];
297
+
298
+ on(h2, 'click', function() {
299
+ pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
300
+ });
301
+
302
+ var pre = fragment('<pre><code>%e</code></pre>', utils.clean(contents));
303
+ el.appendChild(pre);
304
+ pre.style.display = 'none';
305
+ };
306
+
307
+ /**
308
+ * Display error `msg`.
309
+ *
310
+ * @param {string} msg
311
+ */
312
+ function error(msg) {
313
+ document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
314
+ }
315
+
316
+ /**
317
+ * Return a DOM fragment from `html`.
318
+ *
319
+ * @param {string} html
320
+ */
321
+ function fragment(html) {
322
+ var args = arguments;
323
+ var div = document.createElement('div');
324
+ var i = 1;
325
+
326
+ div.innerHTML = html.replace(/%([se])/g, function(_, type) {
327
+ switch (type) {
328
+ case 's':
329
+ return String(args[i++]);
330
+ case 'e':
331
+ return escape(args[i++]);
332
+ // no default
333
+ }
334
+ });
335
+
336
+ return div.firstChild;
337
+ }
338
+
339
+ /**
340
+ * Check for suites that do not have elements
341
+ * with `classname`, and hide them.
342
+ *
343
+ * @param {text} classname
344
+ */
345
+ function hideSuitesWithout(classname) {
346
+ var suites = document.getElementsByClassName('suite');
347
+ for (var i = 0; i < suites.length; i++) {
348
+ var els = suites[i].getElementsByClassName(classname);
349
+ if (!els.length) {
350
+ suites[i].className += ' hidden';
351
+ }
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Unhide .hidden suites.
357
+ */
358
+ function unhide() {
359
+ var els = document.getElementsByClassName('suite hidden');
360
+ while (els.length > 0) {
361
+ els[0].className = els[0].className.replace('suite hidden', 'suite');
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Set an element's text contents.
367
+ *
368
+ * @param {HTMLElement} el
369
+ * @param {string} contents
370
+ */
371
+ function text(el, contents) {
372
+ if (el.textContent) {
373
+ el.textContent = contents;
374
+ } else {
375
+ el.innerText = contents;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Listen on `event` with callback `fn`.
381
+ */
382
+ function on(el, event, fn) {
383
+ if (el.addEventListener) {
384
+ el.addEventListener(event, fn, false);
385
+ } else {
386
+ el.attachEvent('on' + event, fn);
387
+ }
388
+ }
389
+
390
+ HTML.browserOnly = true;
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ // Alias exports to a their normalized format Mocha#reporter to prevent a need
4
+ // for dynamic (try/catch) requires, which Browserify doesn't handle.
5
+ exports.Base = exports.base = require('./base');
6
+ exports.Dot = exports.dot = require('./dot');
7
+ exports.Doc = exports.doc = require('./doc');
8
+ exports.TAP = exports.tap = require('./tap');
9
+ exports.JSON = exports.json = require('./json');
10
+ exports.HTML = exports.html = require('./html');
11
+ exports.List = exports.list = require('./list');
12
+ exports.Min = exports.min = require('./min');
13
+ exports.Spec = exports.spec = require('./spec');
14
+ exports.Nyan = exports.nyan = require('./nyan');
15
+ exports.XUnit = exports.xunit = require('./xunit');
16
+ exports.Markdown = exports.markdown = require('./markdown');
17
+ exports.Progress = exports.progress = require('./progress');
18
+ exports.Landing = exports.landing = require('./landing');
19
+ exports.JSONStream = exports['json-stream'] = require('./json-stream');
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+ /**
3
+ * @module JSONStream
4
+ */
5
+ /**
6
+ * Module dependencies.
7
+ */
8
+
9
+ var Base = require('./base');
10
+ var constants = require('../runner').constants;
11
+ var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
12
+ var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
13
+ var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN;
14
+ var EVENT_RUN_END = constants.EVENT_RUN_END;
15
+
16
+ /**
17
+ * Expose `JSONStream`.
18
+ */
19
+
20
+ exports = module.exports = JSONStream;
21
+
22
+ /**
23
+ * Constructs a new `JSONStream` reporter instance.
24
+ *
25
+ * @public
26
+ * @class
27
+ * @memberof Mocha.reporters
28
+ * @extends Mocha.reporters.Base
29
+ * @param {Runner} runner - Instance triggers reporter actions.
30
+ * @param {Object} [options] - runner options
31
+ */
32
+ function JSONStream(runner, options) {
33
+ Base.call(this, runner, options);
34
+
35
+ var self = this;
36
+ var total = runner.total;
37
+
38
+ runner.once(EVENT_RUN_BEGIN, function() {
39
+ writeEvent(['start', {total: total}]);
40
+ });
41
+
42
+ runner.on(EVENT_TEST_PASS, function(test) {
43
+ writeEvent(['pass', clean(test)]);
44
+ });
45
+
46
+ runner.on(EVENT_TEST_FAIL, function(test, err) {
47
+ test = clean(test);
48
+ test.err = err.message;
49
+ test.stack = err.stack || null;
50
+ writeEvent(['fail', test]);
51
+ });
52
+
53
+ runner.once(EVENT_RUN_END, function() {
54
+ writeEvent(['end', self.stats]);
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Mocha event to be written to the output stream.
60
+ * @typedef {Array} JSONStream~MochaEvent
61
+ */
62
+
63
+ /**
64
+ * Writes Mocha event to reporter output stream.
65
+ *
66
+ * @private
67
+ * @param {JSONStream~MochaEvent} event - Mocha event to be output.
68
+ */
69
+ function writeEvent(event) {
70
+ process.stdout.write(JSON.stringify(event) + '\n');
71
+ }
72
+
73
+ /**
74
+ * Returns an object literal representation of `test`
75
+ * free of cyclic properties, etc.
76
+ *
77
+ * @private
78
+ * @param {Test} test - Instance used as data source.
79
+ * @return {Object} object containing pared-down test instance data
80
+ */
81
+ function clean(test) {
82
+ return {
83
+ title: test.title,
84
+ fullTitle: test.fullTitle(),
85
+ file: test.file,
86
+ duration: test.duration,
87
+ currentRetry: test.currentRetry(),
88
+ speed: test.speed
89
+ };
90
+ }
91
+
92
+ JSONStream.description = 'newline delimited JSON events';
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+ /**
3
+ * @module JSON
4
+ */
5
+ /**
6
+ * Module dependencies.
7
+ */
8
+
9
+ var Base = require('./base');
10
+ var fs = require('fs');
11
+ var path = require('path');
12
+ const createUnsupportedError = require('../errors').createUnsupportedError;
13
+ const utils = require('../utils');
14
+ var constants = require('../runner').constants;
15
+ var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
16
+ var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
17
+ var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
18
+ var EVENT_TEST_END = constants.EVENT_TEST_END;
19
+ var EVENT_RUN_END = constants.EVENT_RUN_END;
20
+
21
+ /**
22
+ * Expose `JSON`.
23
+ */
24
+
25
+ exports = module.exports = JSONReporter;
26
+
27
+ /**
28
+ * Constructs a new `JSON` reporter instance.
29
+ *
30
+ * @public
31
+ * @class JSON
32
+ * @memberof Mocha.reporters
33
+ * @extends Mocha.reporters.Base
34
+ * @param {Runner} runner - Instance triggers reporter actions.
35
+ * @param {Object} [options] - runner options
36
+ */
37
+ function JSONReporter(runner, options = {}) {
38
+ Base.call(this, runner, options);
39
+
40
+ var self = this;
41
+ var tests = [];
42
+ var pending = [];
43
+ var failures = [];
44
+ var passes = [];
45
+ var output;
46
+
47
+ if (options.reporterOption && options.reporterOption.output) {
48
+ if (utils.isBrowser()) {
49
+ throw createUnsupportedError('file output not supported in browser');
50
+ }
51
+ output = options.reporterOption.output;
52
+ }
53
+
54
+ runner.on(EVENT_TEST_END, function(test) {
55
+ tests.push(test);
56
+ });
57
+
58
+ runner.on(EVENT_TEST_PASS, function(test) {
59
+ passes.push(test);
60
+ });
61
+
62
+ runner.on(EVENT_TEST_FAIL, function(test) {
63
+ failures.push(test);
64
+ });
65
+
66
+ runner.on(EVENT_TEST_PENDING, function(test) {
67
+ pending.push(test);
68
+ });
69
+
70
+ runner.once(EVENT_RUN_END, function() {
71
+ var obj = {
72
+ stats: self.stats,
73
+ tests: tests.map(clean),
74
+ pending: pending.map(clean),
75
+ failures: failures.map(clean),
76
+ passes: passes.map(clean)
77
+ };
78
+
79
+ runner.testResults = obj;
80
+
81
+ var json = JSON.stringify(obj, null, 2);
82
+ if (output) {
83
+ try {
84
+ fs.mkdirSync(path.dirname(output), {recursive: true});
85
+ fs.writeFileSync(output, json);
86
+ } catch (err) {
87
+ console.error(
88
+ `${Base.symbols.err} [mocha] writing output to "${output}" failed: ${err.message}\n`
89
+ );
90
+ process.stdout.write(json);
91
+ }
92
+ } else {
93
+ process.stdout.write(json);
94
+ }
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Return a plain-object representation of `test`
100
+ * free of cyclic properties etc.
101
+ *
102
+ * @private
103
+ * @param {Object} test
104
+ * @return {Object}
105
+ */
106
+ function clean(test) {
107
+ var err = test.err || {};
108
+ if (err instanceof Error) {
109
+ err = errorJSON(err);
110
+ }
111
+
112
+ return {
113
+ title: test.title,
114
+ fullTitle: test.fullTitle(),
115
+ file: test.file,
116
+ duration: test.duration,
117
+ currentRetry: test.currentRetry(),
118
+ speed: test.speed,
119
+ err: cleanCycles(err)
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Replaces any circular references inside `obj` with '[object Object]'
125
+ *
126
+ * @private
127
+ * @param {Object} obj
128
+ * @return {Object}
129
+ */
130
+ function cleanCycles(obj) {
131
+ var cache = [];
132
+ return JSON.parse(
133
+ JSON.stringify(obj, function(key, value) {
134
+ if (typeof value === 'object' && value !== null) {
135
+ if (cache.indexOf(value) !== -1) {
136
+ // Instead of going in a circle, we'll print [object Object]
137
+ return '' + value;
138
+ }
139
+ cache.push(value);
140
+ }
141
+
142
+ return value;
143
+ })
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Transform an Error object into a JSON object.
149
+ *
150
+ * @private
151
+ * @param {Error} err
152
+ * @return {Object}
153
+ */
154
+ function errorJSON(err) {
155
+ var res = {};
156
+ Object.getOwnPropertyNames(err).forEach(function(key) {
157
+ res[key] = err[key];
158
+ }, err);
159
+ return res;
160
+ }
161
+
162
+ JSONReporter.description = 'single JSON object';