mocha 1.21.4 → 2.1.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/README.md +226 -0
- package/bin/_mocha +26 -47
- package/bin/mocha +8 -1
- package/index.js +1 -2
- package/lib/browser/debug.js +0 -1
- package/lib/browser/diff.js +16 -1
- package/lib/browser/escape-string-regexp.js +11 -0
- package/lib/browser/events.js +1 -2
- package/lib/browser/glob.js +0 -0
- package/lib/browser/progress.js +6 -6
- package/lib/browser/tty.js +0 -1
- package/lib/context.js +0 -1
- package/lib/hook.js +0 -1
- package/lib/interfaces/bdd.js +4 -4
- package/lib/interfaces/exports.js +1 -2
- package/lib/interfaces/index.js +0 -1
- package/lib/interfaces/qunit.js +2 -2
- package/lib/interfaces/tdd.js +4 -4
- package/lib/mocha.js +32 -9
- package/lib/ms.js +1 -1
- package/lib/reporters/base.js +11 -13
- package/lib/reporters/doc.js +0 -1
- package/lib/reporters/dot.js +0 -1
- package/lib/reporters/html-cov.js +1 -2
- package/lib/reporters/html.js +13 -5
- package/lib/reporters/index.js +0 -1
- package/lib/reporters/json-cov.js +1 -2
- package/lib/reporters/json-stream.js +4 -3
- package/lib/reporters/json.js +7 -2
- package/lib/reporters/landing.js +3 -4
- package/lib/reporters/list.js +0 -1
- package/lib/reporters/markdown.js +15 -6
- package/lib/reporters/min.js +0 -1
- package/lib/reporters/nyan.js +14 -14
- package/lib/reporters/spec.js +0 -1
- package/lib/reporters/tap.js +0 -1
- package/lib/reporters/templates/coverage.jade +2 -1
- package/lib/reporters/templates/style.html +1 -1
- package/lib/reporters/xunit.js +41 -10
- package/lib/runnable.js +8 -6
- package/lib/runner.js +17 -9
- package/lib/suite.js +3 -3
- package/lib/test.js +0 -1
- package/lib/utils.js +247 -49
- package/mocha.js +487 -172
- package/package.json +20 -13
- package/Readme.md +0 -203
package/lib/runnable.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Module dependencies.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
var EventEmitter = require('events').EventEmitter
|
|
7
6
|
, debug = require('debug')('mocha:runnable')
|
|
8
|
-
, milliseconds = require('./ms')
|
|
7
|
+
, milliseconds = require('./ms')
|
|
8
|
+
, utils = require('./utils');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Save timer references to avoid Sinon interfering (see GH-237).
|
|
@@ -46,6 +46,7 @@ function Runnable(title, fn) {
|
|
|
46
46
|
this._slow = 75;
|
|
47
47
|
this._enableTimeouts = true;
|
|
48
48
|
this.timedOut = false;
|
|
49
|
+
this._trace = new Error('done() called multiple times')
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -154,6 +155,7 @@ Runnable.prototype.resetTimeout = function(){
|
|
|
154
155
|
if (!this._enableTimeouts) return;
|
|
155
156
|
this.clearTimeout();
|
|
156
157
|
this.timer = setTimeout(function(){
|
|
158
|
+
if (!self._enableTimeouts) return;
|
|
157
159
|
self.callback(new Error('timeout of ' + ms + 'ms exceeded'));
|
|
158
160
|
self.timedOut = true;
|
|
159
161
|
}, ms);
|
|
@@ -190,14 +192,14 @@ Runnable.prototype.run = function(fn){
|
|
|
190
192
|
function multiple(err) {
|
|
191
193
|
if (emitted) return;
|
|
192
194
|
emitted = true;
|
|
193
|
-
self.emit('error', err || new Error('done() called multiple times'));
|
|
195
|
+
self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
// finished
|
|
197
199
|
function done(err) {
|
|
198
200
|
var ms = self.timeout();
|
|
199
201
|
if (self.timedOut) return;
|
|
200
|
-
if (finished) return multiple(err);
|
|
202
|
+
if (finished) return multiple(err || self._trace);
|
|
201
203
|
self.clearTimeout();
|
|
202
204
|
self.duration = new Date - start;
|
|
203
205
|
finished = true;
|
|
@@ -225,7 +227,7 @@ Runnable.prototype.run = function(fn){
|
|
|
225
227
|
done();
|
|
226
228
|
});
|
|
227
229
|
} catch (err) {
|
|
228
|
-
done(err);
|
|
230
|
+
done(utils.getError(err));
|
|
229
231
|
}
|
|
230
232
|
return;
|
|
231
233
|
}
|
|
@@ -242,7 +244,7 @@ Runnable.prototype.run = function(fn){
|
|
|
242
244
|
callFn(this.fn);
|
|
243
245
|
}
|
|
244
246
|
} catch (err) {
|
|
245
|
-
done(err);
|
|
247
|
+
done(utils.getError(err));
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
function callFn(fn) {
|
package/lib/runner.js
CHANGED
|
@@ -19,7 +19,9 @@ var globals = [
|
|
|
19
19
|
'setInterval',
|
|
20
20
|
'clearInterval',
|
|
21
21
|
'XMLHttpRequest',
|
|
22
|
-
'Date'
|
|
22
|
+
'Date',
|
|
23
|
+
'setImmediate',
|
|
24
|
+
'clearImmediate'
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -244,7 +246,6 @@ Runner.prototype.hook = function(name, fn){
|
|
|
244
246
|
function next(i) {
|
|
245
247
|
var hook = hooks[i];
|
|
246
248
|
if (!hook) return fn();
|
|
247
|
-
if (self.failures && suite.bail()) return fn();
|
|
248
249
|
self.currentRunnable = hook;
|
|
249
250
|
|
|
250
251
|
hook.ctx.currentTest = self.test;
|
|
@@ -533,18 +534,25 @@ Runner.prototype.runSuite = function(suite, fn){
|
|
|
533
534
|
|
|
534
535
|
Runner.prototype.uncaught = function(err){
|
|
535
536
|
if (err) {
|
|
536
|
-
debug('uncaught exception %s', err
|
|
537
|
+
debug('uncaught exception %s', err !== function () {
|
|
538
|
+
return this;
|
|
539
|
+
}.call(err) ? err : ( err.message || err ));
|
|
537
540
|
} else {
|
|
538
541
|
debug('uncaught undefined exception');
|
|
539
|
-
err =
|
|
542
|
+
err = utils.undefinedError();
|
|
540
543
|
}
|
|
541
|
-
|
|
542
|
-
var runnable = this.currentRunnable;
|
|
543
|
-
if (!runnable || 'failed' == runnable.state) return;
|
|
544
|
-
runnable.clearTimeout();
|
|
545
544
|
err.uncaught = true;
|
|
545
|
+
|
|
546
|
+
var runnable = this.currentRunnable;
|
|
547
|
+
if (!runnable) return;
|
|
548
|
+
|
|
549
|
+
var wasAlreadyDone = runnable.state;
|
|
546
550
|
this.fail(runnable, err);
|
|
547
551
|
|
|
552
|
+
runnable.clearTimeout();
|
|
553
|
+
|
|
554
|
+
if (wasAlreadyDone) return;
|
|
555
|
+
|
|
548
556
|
// recover from test
|
|
549
557
|
if ('test' == runnable.type) {
|
|
550
558
|
this.emit('test end', runnable);
|
|
@@ -604,7 +612,7 @@ Runner.prototype.run = function(fn){
|
|
|
604
612
|
Runner.prototype.abort = function(){
|
|
605
613
|
debug('aborting');
|
|
606
614
|
this._abort = true;
|
|
607
|
-
}
|
|
615
|
+
};
|
|
608
616
|
|
|
609
617
|
/**
|
|
610
618
|
* Filter leaks with the given globals flagged as `ok`.
|
package/lib/suite.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Module dependencies.
|
|
4
3
|
*/
|
|
@@ -99,6 +98,7 @@ Suite.prototype.clone = function(){
|
|
|
99
98
|
|
|
100
99
|
Suite.prototype.timeout = function(ms){
|
|
101
100
|
if (0 == arguments.length) return this._timeout;
|
|
101
|
+
if (ms.toString() === '0') this._enableTimeouts = false;
|
|
102
102
|
if ('string' == typeof ms) ms = milliseconds(ms);
|
|
103
103
|
debug('timeout %d', ms);
|
|
104
104
|
this._timeout = parseInt(ms, 10);
|
|
@@ -118,7 +118,7 @@ Suite.prototype.enableTimeouts = function(enabled){
|
|
|
118
118
|
debug('enableTimeouts %s', enabled);
|
|
119
119
|
this._enableTimeouts = enabled;
|
|
120
120
|
return this;
|
|
121
|
-
}
|
|
121
|
+
};
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* Set slow `ms` or short-hand such as "2s".
|
|
@@ -139,7 +139,7 @@ Suite.prototype.slow = function(ms){
|
|
|
139
139
|
/**
|
|
140
140
|
* Sets whether to bail after first error.
|
|
141
141
|
*
|
|
142
|
-
* @
|
|
142
|
+
* @param {Boolean} bail
|
|
143
143
|
* @return {Suite|Number} for chaining
|
|
144
144
|
* @api private
|
|
145
145
|
*/
|
package/lib/test.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
var fs = require('fs')
|
|
6
6
|
, path = require('path')
|
|
7
|
+
, basename = path.basename
|
|
8
|
+
, exists = fs.existsSync || path.existsSync
|
|
9
|
+
, glob = require('glob')
|
|
7
10
|
, join = path.join
|
|
8
11
|
, debug = require('debug')('mocha:watch');
|
|
9
12
|
|
|
@@ -224,18 +227,6 @@ exports.clean = function(str) {
|
|
|
224
227
|
return exports.trim(str);
|
|
225
228
|
};
|
|
226
229
|
|
|
227
|
-
/**
|
|
228
|
-
* Escape regular expression characters in `str`.
|
|
229
|
-
*
|
|
230
|
-
* @param {String} str
|
|
231
|
-
* @return {String}
|
|
232
|
-
* @api private
|
|
233
|
-
*/
|
|
234
|
-
|
|
235
|
-
exports.escapeRegexp = function(str){
|
|
236
|
-
return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
237
|
-
};
|
|
238
|
-
|
|
239
230
|
/**
|
|
240
231
|
* Trim the given `str`.
|
|
241
232
|
*
|
|
@@ -295,56 +286,263 @@ function highlight(js) {
|
|
|
295
286
|
*/
|
|
296
287
|
|
|
297
288
|
exports.highlightTags = function(name) {
|
|
298
|
-
var code = document.getElementsByTagName(name);
|
|
289
|
+
var code = document.getElementById('mocha').getElementsByTagName(name);
|
|
299
290
|
for (var i = 0, len = code.length; i < len; ++i) {
|
|
300
291
|
code[i].innerHTML = highlight(code[i].innerHTML);
|
|
301
292
|
}
|
|
302
293
|
};
|
|
303
294
|
|
|
295
|
+
/**
|
|
296
|
+
* If a value could have properties, and has none, this function is called, which returns
|
|
297
|
+
* a string representation of the empty value.
|
|
298
|
+
*
|
|
299
|
+
* Functions w/ no properties return `'[Function]'`
|
|
300
|
+
* Arrays w/ length === 0 return `'[]'`
|
|
301
|
+
* Objects w/ no properties return `'{}'`
|
|
302
|
+
* All else: return result of `value.toString()`
|
|
303
|
+
*
|
|
304
|
+
* @param {*} value Value to inspect
|
|
305
|
+
* @param {string} [type] The type of the value, if known.
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
var emptyRepresentation = function emptyRepresentation(value, type) {
|
|
309
|
+
type = type || exports.type(value);
|
|
310
|
+
|
|
311
|
+
switch(type) {
|
|
312
|
+
case 'function':
|
|
313
|
+
return '[Function]';
|
|
314
|
+
case 'object':
|
|
315
|
+
return '{}';
|
|
316
|
+
case 'array':
|
|
317
|
+
return '[]';
|
|
318
|
+
default:
|
|
319
|
+
return value.toString();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
304
322
|
|
|
305
323
|
/**
|
|
306
|
-
*
|
|
324
|
+
* Takes some variable and asks `{}.toString()` what it thinks it is.
|
|
325
|
+
* @param {*} value Anything
|
|
326
|
+
* @example
|
|
327
|
+
* type({}) // 'object'
|
|
328
|
+
* type([]) // 'array'
|
|
329
|
+
* type(1) // 'number'
|
|
330
|
+
* type(false) // 'boolean'
|
|
331
|
+
* type(Infinity) // 'number'
|
|
332
|
+
* type(null) // 'null'
|
|
333
|
+
* type(new Date()) // 'date'
|
|
334
|
+
* type(/foo/) // 'regexp'
|
|
335
|
+
* type('type') // 'string'
|
|
336
|
+
* type(global) // 'global'
|
|
337
|
+
* @api private
|
|
338
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
|
|
339
|
+
* @returns {string}
|
|
340
|
+
*/
|
|
341
|
+
exports.type = function type(value) {
|
|
342
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
|
|
343
|
+
return 'buffer';
|
|
344
|
+
}
|
|
345
|
+
return Object.prototype.toString.call(value)
|
|
346
|
+
.replace(/^\[.+\s(.+?)\]$/, '$1')
|
|
347
|
+
.toLowerCase();
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @summary Stringify `value`.
|
|
352
|
+
* @description Different behavior depending on type of value.
|
|
353
|
+
* - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
|
|
354
|
+
* - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
|
|
355
|
+
* - If `value` is an *empty* object, function, or array, return result of function
|
|
356
|
+
* {@link emptyRepresentation}.
|
|
357
|
+
* - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
|
|
358
|
+
* JSON.stringify().
|
|
307
359
|
*
|
|
308
|
-
* @
|
|
309
|
-
* @
|
|
360
|
+
* @see exports.type
|
|
361
|
+
* @param {*} value
|
|
362
|
+
* @return {string}
|
|
310
363
|
* @api private
|
|
311
364
|
*/
|
|
312
365
|
|
|
313
|
-
exports.stringify = function(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
366
|
+
exports.stringify = function(value) {
|
|
367
|
+
var prop,
|
|
368
|
+
type = exports.type(value);
|
|
369
|
+
|
|
370
|
+
if (type === 'null' || type === 'undefined') {
|
|
371
|
+
return '[' + type + ']';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (type === 'date') {
|
|
375
|
+
return '[Date: ' + value.toISOString() + ']';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!~exports.indexOf(['object', 'array', 'function'], type)) {
|
|
379
|
+
return value.toString();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (prop in value) {
|
|
383
|
+
if (value.hasOwnProperty(prop)) {
|
|
384
|
+
return JSON.stringify(exports.canonicalize(value), null, 2).replace(/,(\n|$)/g, '$1');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return emptyRepresentation(value, type);
|
|
389
|
+
};
|
|
317
390
|
|
|
318
391
|
/**
|
|
319
|
-
* Return
|
|
320
|
-
* @param {Object}
|
|
321
|
-
* @return {
|
|
392
|
+
* Return if obj is a Buffer
|
|
393
|
+
* @param {Object} arg
|
|
394
|
+
* @return {Boolean}
|
|
322
395
|
* @api private
|
|
323
396
|
*/
|
|
397
|
+
exports.isBuffer = function (arg) {
|
|
398
|
+
return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @summary Return a new Thing that has the keys in sorted order. Recursive.
|
|
403
|
+
* @description If the Thing...
|
|
404
|
+
* - has already been seen, return string `'[Circular]'`
|
|
405
|
+
* - is `undefined`, return string `'[undefined]'`
|
|
406
|
+
* - is `null`, return value `null`
|
|
407
|
+
* - is some other primitive, return the value
|
|
408
|
+
* - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
|
|
409
|
+
* - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
|
|
410
|
+
* - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
|
|
411
|
+
*
|
|
412
|
+
* @param {*} value Thing to inspect. May or may not have properties.
|
|
413
|
+
* @param {Array} [stack=[]] Stack of seen values
|
|
414
|
+
* @return {(Object|Array|Function|string|undefined)}
|
|
415
|
+
* @see {@link exports.stringify}
|
|
416
|
+
* @api private
|
|
417
|
+
*/
|
|
418
|
+
|
|
419
|
+
exports.canonicalize = function(value, stack) {
|
|
420
|
+
var canonicalizedObj,
|
|
421
|
+
type = exports.type(value),
|
|
422
|
+
prop,
|
|
423
|
+
withStack = function withStack(value, fn) {
|
|
424
|
+
stack.push(value);
|
|
425
|
+
fn();
|
|
426
|
+
stack.pop();
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
stack = stack || [];
|
|
430
|
+
|
|
431
|
+
if (exports.indexOf(stack, value) !== -1) {
|
|
432
|
+
return '[Circular]';
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
switch(type) {
|
|
436
|
+
case 'undefined':
|
|
437
|
+
canonicalizedObj = '[undefined]';
|
|
438
|
+
break;
|
|
439
|
+
case 'buffer':
|
|
440
|
+
case 'null':
|
|
441
|
+
canonicalizedObj = value;
|
|
442
|
+
break;
|
|
443
|
+
case 'array':
|
|
444
|
+
withStack(value, function () {
|
|
445
|
+
canonicalizedObj = exports.map(value, function (item) {
|
|
446
|
+
return exports.canonicalize(item, stack);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
case 'date':
|
|
451
|
+
canonicalizedObj = '[Date: ' + value.toISOString() + ']';
|
|
452
|
+
break;
|
|
453
|
+
case 'function':
|
|
454
|
+
for (prop in value) {
|
|
455
|
+
canonicalizedObj = {};
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
if (!canonicalizedObj) {
|
|
459
|
+
canonicalizedObj = emptyRepresentation(value, type);
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
/* falls through */
|
|
463
|
+
case 'object':
|
|
464
|
+
canonicalizedObj = canonicalizedObj || {};
|
|
465
|
+
withStack(value, function () {
|
|
466
|
+
exports.forEach(exports.keys(value).sort(), function (key) {
|
|
467
|
+
canonicalizedObj[key] = exports.canonicalize(value[key], stack);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
break;
|
|
471
|
+
case 'number':
|
|
472
|
+
case 'boolean':
|
|
473
|
+
canonicalizedObj = value;
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
canonicalizedObj = value.toString();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return canonicalizedObj;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Lookup file names at the given `path`.
|
|
484
|
+
*/
|
|
485
|
+
exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
|
|
486
|
+
var files = [];
|
|
487
|
+
var re = new RegExp('\\.(' + extensions.join('|') + ')$');
|
|
488
|
+
|
|
489
|
+
if (!exists(path)) {
|
|
490
|
+
if (exists(path + '.js')) {
|
|
491
|
+
path += '.js';
|
|
492
|
+
} else {
|
|
493
|
+
files = glob.sync(path);
|
|
494
|
+
if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'");
|
|
495
|
+
return files;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
var stat = fs.statSync(path);
|
|
501
|
+
if (stat.isFile()) return path;
|
|
502
|
+
}
|
|
503
|
+
catch (ignored) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
fs.readdirSync(path).forEach(function(file){
|
|
508
|
+
file = join(path, file);
|
|
509
|
+
try {
|
|
510
|
+
var stat = fs.statSync(file);
|
|
511
|
+
if (stat.isDirectory()) {
|
|
512
|
+
if (recursive) {
|
|
513
|
+
files = files.concat(lookupFiles(file, extensions, recursive));
|
|
514
|
+
}
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (ignored) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return;
|
|
522
|
+
files.push(file);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return files;
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Generate an undefined error with a message warning the user.
|
|
530
|
+
*
|
|
531
|
+
* @return {Error}
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
exports.undefinedError = function(){
|
|
535
|
+
return new Error('Caught undefined error, did you throw without specifying what?');
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Generate an undefined error if `err` is not defined.
|
|
540
|
+
*
|
|
541
|
+
* @param {Error} err
|
|
542
|
+
* @return {Error}
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
exports.getError = function(err){
|
|
546
|
+
return err || exports.undefinedError();
|
|
547
|
+
};
|
|
324
548
|
|
|
325
|
-
exports.canonicalize = function(obj, stack) {
|
|
326
|
-
stack = stack || [];
|
|
327
|
-
|
|
328
|
-
if (exports.indexOf(stack, obj) !== -1) return '[Circular]';
|
|
329
|
-
|
|
330
|
-
var canonicalizedObj;
|
|
331
|
-
|
|
332
|
-
if ({}.toString.call(obj) === '[object Array]') {
|
|
333
|
-
stack.push(obj);
|
|
334
|
-
canonicalizedObj = exports.map(obj, function(item) {
|
|
335
|
-
return exports.canonicalize(item, stack);
|
|
336
|
-
});
|
|
337
|
-
stack.pop();
|
|
338
|
-
} else if (typeof obj === 'object' && obj !== null) {
|
|
339
|
-
stack.push(obj);
|
|
340
|
-
canonicalizedObj = {};
|
|
341
|
-
exports.forEach(exports.keys(obj).sort(), function(key) {
|
|
342
|
-
canonicalizedObj[key] = exports.canonicalize(obj[key], stack);
|
|
343
|
-
});
|
|
344
|
-
stack.pop();
|
|
345
|
-
} else {
|
|
346
|
-
canonicalizedObj = obj;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return canonicalizedObj;
|
|
350
|
-
}
|