mocha 2.0.1 → 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/lib/mocha.js CHANGED
@@ -80,7 +80,7 @@ function Mocha(options) {
80
80
  this.suite = new exports.Suite('', new exports.Context);
81
81
  this.ui(options.ui);
82
82
  this.bail(options.bail);
83
- this.reporter(options.reporter);
83
+ this.reporter(options.reporter, options.reporterOptions);
84
84
  if (null != options.timeout) this.timeout(options.timeout);
85
85
  this.useColors(options.useColors)
86
86
  if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts);
@@ -131,10 +131,10 @@ Mocha.prototype.addFile = function(file){
131
131
  * Set reporter to `reporter`, defaults to "spec".
132
132
  *
133
133
  * @param {String|Function} reporter name or constructor
134
+ * @param {Object} reporterOptions optional options
134
135
  * @api public
135
136
  */
136
-
137
- Mocha.prototype.reporter = function(reporter){
137
+ Mocha.prototype.reporter = function(reporter, reporterOptions){
138
138
  if ('function' == typeof reporter) {
139
139
  this._reporter = reporter;
140
140
  } else {
@@ -149,6 +149,7 @@ Mocha.prototype.reporter = function(reporter){
149
149
  if (!_reporter) throw new Error('invalid reporter "' + reporter + '"');
150
150
  this._reporter = _reporter;
151
151
  }
152
+ this.options.reporterOptions = reporterOptions;
152
153
  return this;
153
154
  };
154
155
 
@@ -297,9 +298,9 @@ Mocha.prototype.globals = function(globals){
297
298
  */
298
299
 
299
300
  Mocha.prototype.useColors = function(colors){
300
- this.options.useColors = arguments.length && colors != undefined
301
- ? colors
302
- : true;
301
+ if (colors !== undefined) {
302
+ this.options.useColors = colors;
303
+ }
303
304
  return this;
304
305
  };
305
306
 
@@ -401,7 +402,18 @@ Mocha.prototype.run = function(fn){
401
402
  if (options.grep) runner.grep(options.grep, options.invert);
402
403
  if (options.globals) runner.globals(options.globals);
403
404
  if (options.growl) this._growl(runner, reporter);
404
- exports.reporters.Base.useColors = options.useColors;
405
+ if (options.useColors !== undefined) {
406
+ exports.reporters.Base.useColors = options.useColors;
407
+ }
405
408
  exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
406
- return runner.run(fn);
409
+
410
+ function done(failures) {
411
+ if (reporter.done) {
412
+ reporter.done(failures, fn);
413
+ } else {
414
+ fn(failures);
415
+ }
416
+ }
417
+
418
+ return runner.run(done);
407
419
  };
@@ -97,7 +97,7 @@ if ('win32' == process.platform) {
97
97
  */
98
98
 
99
99
  var color = exports.color = function(type, str) {
100
- if (!exports.useColors) return str;
100
+ if (!exports.useColors) return String(str);
101
101
  return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
102
102
  };
103
103
 
@@ -154,7 +154,7 @@ exports.cursor = {
154
154
  */
155
155
 
156
156
  exports.list = function(failures){
157
- console.error();
157
+ console.log();
158
158
  failures.forEach(function(test, i){
159
159
  // format
160
160
  var fmt = color('error title', ' %s) %s:\n')
@@ -178,13 +178,13 @@ exports.list = function(failures){
178
178
 
179
179
  // explicitly show diff
180
180
  if (err.showDiff && sameType(actual, expected)) {
181
- escape = false;
182
- err.actual = actual = utils.stringify(actual);
183
- err.expected = expected = utils.stringify(expected);
184
- }
185
181
 
186
- // actual / expected diff
187
- if (err.showDiff && 'string' == typeof actual && 'string' == typeof expected) {
182
+ if ('string' !== typeof actual) {
183
+ escape = false;
184
+ err.actual = actual = utils.stringify(actual);
185
+ err.expected = expected = utils.stringify(expected);
186
+ }
187
+
188
188
  fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
189
189
  var match = message.match(/^([^:]+): expected/);
190
190
  msg = '\n ' + color('error message', match ? match[1] : msg);
@@ -200,7 +200,7 @@ exports.list = function(failures){
200
200
  stack = stack.slice(index ? index + 1 : index)
201
201
  .replace(/^/gm, ' ');
202
202
 
203
- console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
203
+ console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
204
204
  });
205
205
  };
206
206
 
@@ -305,11 +305,10 @@ Base.prototype.epilogue = function(){
305
305
  if (stats.failures) {
306
306
  fmt = color('fail', ' %d failing');
307
307
 
308
- console.error(fmt,
309
- stats.failures);
308
+ console.log(fmt, stats.failures);
310
309
 
311
310
  Base.list(this.failures);
312
- console.error();
311
+ console.log();
313
312
  }
314
313
 
315
314
  console.log();
@@ -135,7 +135,7 @@ function HTML(runner) {
135
135
  } else if (test.pending) {
136
136
  var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
137
137
  } else {
138
- var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle()));
138
+ var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
139
139
  var str = test.err.stack || test.err.toString();
140
140
 
141
141
  // FF / Opera do not add the message
@@ -183,7 +183,7 @@ function HTML(runner) {
183
183
  */
184
184
  var makeUrl = function makeUrl(s) {
185
185
  var search = window.location.search;
186
- return (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
186
+ return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
187
187
  };
188
188
 
189
189
  /**
@@ -5,6 +5,12 @@
5
5
  var Base = require('./base')
6
6
  , utils = require('../utils');
7
7
 
8
+ /**
9
+ * Constants
10
+ */
11
+
12
+ var SUITE_PREFIX = '$';
13
+
8
14
  /**
9
15
  * Expose `Markdown`.
10
16
  */
@@ -35,8 +41,9 @@ function Markdown(runner) {
35
41
  }
36
42
 
37
43
  function mapTOC(suite, obj) {
38
- var ret = obj;
39
- obj = obj[suite.title] = obj[suite.title] || { suite: suite };
44
+ var ret = obj,
45
+ key = SUITE_PREFIX + suite.title;
46
+ obj = obj[key] = obj[key] || { suite: suite };
40
47
  suite.suites.forEach(function(suite){
41
48
  mapTOC(suite, obj);
42
49
  });
@@ -49,11 +56,13 @@ function Markdown(runner) {
49
56
  var link;
50
57
  for (var key in obj) {
51
58
  if ('suite' == key) continue;
52
- if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
53
- if (key) buf += Array(level).join(' ') + link;
59
+ if (key !== SUITE_PREFIX) {
60
+ link = ' - [' + key.substring(1) + ']';
61
+ link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
62
+ buf += Array(level).join(' ') + link;
63
+ }
54
64
  buf += stringifyTOC(obj[key], level);
55
65
  }
56
- --level;
57
66
  return buf;
58
67
  }
59
68
 
@@ -2,8 +2,7 @@
2
2
  * Module dependencies.
3
3
  */
4
4
 
5
- var Base = require('./base')
6
- , color = Base.color;
5
+ var Base = require('./base');
7
6
 
8
7
  /**
9
8
  * Expose `Dot`.
@@ -80,17 +79,16 @@ NyanCat.prototype.draw = function(){
80
79
 
81
80
  NyanCat.prototype.drawScoreboard = function(){
82
81
  var stats = this.stats;
83
- var colors = Base.colors;
84
82
 
85
- function draw(color, n) {
83
+ function draw(type, n) {
86
84
  write(' ');
87
- write('\u001b[' + color + 'm' + n + '\u001b[0m');
85
+ write(Base.color(type, n));
88
86
  write('\n');
89
87
  }
90
88
 
91
- draw(colors.green, stats.passes);
92
- draw(colors.fail, stats.failures);
93
- draw(colors.pending, stats.pending);
89
+ draw('green', stats.passes);
90
+ draw('fail', stats.failures);
91
+ draw('pending', stats.pending);
94
92
  write('\n');
95
93
 
96
94
  this.cursorUp(this.numberOfLines);
@@ -140,26 +138,26 @@ NyanCat.prototype.drawRainbow = function(){
140
138
  NyanCat.prototype.drawNyanCat = function() {
141
139
  var self = this;
142
140
  var startWidth = this.scoreboardWidth + this.trajectories[0].length;
143
- var color = '\u001b[' + startWidth + 'C';
141
+ var dist = '\u001b[' + startWidth + 'C';
144
142
  var padding = '';
145
143
 
146
- write(color);
144
+ write(dist);
147
145
  write('_,------,');
148
146
  write('\n');
149
147
 
150
- write(color);
148
+ write(dist);
151
149
  padding = self.tick ? ' ' : ' ';
152
150
  write('_|' + padding + '/\\_/\\ ');
153
151
  write('\n');
154
152
 
155
- write(color);
153
+ write(dist);
156
154
  padding = self.tick ? '_' : '__';
157
155
  var tail = self.tick ? '~' : '^';
158
156
  var face;
159
157
  write(tail + '|' + padding + this.face() + ' ');
160
158
  write('\n');
161
159
 
162
- write(color);
160
+ write(dist);
163
161
  padding = self.tick ? ' ' : ' ';
164
162
  write(padding + '"" "" ');
165
163
  write('\n');
@@ -240,6 +238,8 @@ NyanCat.prototype.generateColors = function(){
240
238
  */
241
239
 
242
240
  NyanCat.prototype.rainbowify = function(str){
241
+ if (!Base.useColors)
242
+ return str;
243
243
  var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
244
244
  this.colorIndex += 1;
245
245
  return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
@@ -4,6 +4,7 @@
4
4
 
5
5
  var Base = require('./base')
6
6
  , utils = require('../utils')
7
+ , fs = require('fs')
7
8
  , escape = utils.escape;
8
9
 
9
10
  /**
@@ -29,12 +30,19 @@ exports = module.exports = XUnit;
29
30
  * @api public
30
31
  */
31
32
 
32
- function XUnit(runner) {
33
+ function XUnit(runner, options) {
33
34
  Base.call(this, runner);
34
35
  var stats = this.stats
35
36
  , tests = []
36
37
  , self = this;
37
38
 
39
+ if (options.reporterOptions && options.reporterOptions.output) {
40
+ if (! fs.createWriteStream) {
41
+ throw new Error('file output not supported in browser');
42
+ }
43
+ self.fileStream = fs.createWriteStream(options.reporterOptions.output);
44
+ }
45
+
38
46
  runner.on('pending', function(test){
39
47
  tests.push(test);
40
48
  });
@@ -48,7 +56,7 @@ function XUnit(runner) {
48
56
  });
49
57
 
50
58
  runner.on('end', function(){
51
- console.log(tag('testsuite', {
59
+ self.write(tag('testsuite', {
52
60
  name: 'Mocha Tests'
53
61
  , tests: stats.tests
54
62
  , failures: stats.failures
@@ -58,22 +66,46 @@ function XUnit(runner) {
58
66
  , time: (stats.duration / 1000) || 0
59
67
  }, false));
60
68
 
61
- tests.forEach(test);
62
- console.log('</testsuite>');
69
+ tests.forEach(function(t) { self.test(t); });
70
+ self.write('</testsuite>');
63
71
  });
64
72
  }
65
73
 
74
+ /**
75
+ * Override done to close the stream (if it's a file).
76
+ */
77
+ XUnit.prototype.done = function(failures, fn) {
78
+ if (this.fileStream) {
79
+ this.fileStream.end(function() {
80
+ fn(failures);
81
+ });
82
+ } else {
83
+ fn(failures);
84
+ }
85
+ };
86
+
66
87
  /**
67
88
  * Inherit from `Base.prototype`.
68
89
  */
69
90
 
70
91
  XUnit.prototype.__proto__ = Base.prototype;
71
92
 
93
+ /**
94
+ * Write out the given line
95
+ */
96
+ XUnit.prototype.write = function(line) {
97
+ if (this.fileStream) {
98
+ this.fileStream.write(line + '\n');
99
+ } else {
100
+ console.log(line);
101
+ }
102
+ };
103
+
72
104
  /**
73
105
  * Output tag for the given `test.`
74
106
  */
75
107
 
76
- function test(test) {
108
+ XUnit.prototype.test = function(test, ostream) {
77
109
  var attrs = {
78
110
  classname: test.parent.fullTitle()
79
111
  , name: test.title
@@ -82,13 +114,13 @@ function test(test) {
82
114
 
83
115
  if ('failed' == test.state) {
84
116
  var err = test.err;
85
- console.log(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
117
+ this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
86
118
  } else if (test.pending) {
87
- console.log(tag('testcase', attrs, false, tag('skipped', {}, true)));
119
+ this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
88
120
  } else {
89
- console.log(tag('testcase', attrs, true) );
121
+ this.write(tag('testcase', attrs, true) );
90
122
  }
91
- }
123
+ };
92
124
 
93
125
  /**
94
126
  * HTML tag helper.
package/lib/runnable.js CHANGED
@@ -4,7 +4,8 @@
4
4
 
5
5
  var EventEmitter = require('events').EventEmitter
6
6
  , debug = require('debug')('mocha:runnable')
7
- , milliseconds = require('./ms');
7
+ , milliseconds = require('./ms')
8
+ , utils = require('./utils');
8
9
 
9
10
  /**
10
11
  * Save timer references to avoid Sinon interfering (see GH-237).
@@ -226,7 +227,7 @@ Runnable.prototype.run = function(fn){
226
227
  done();
227
228
  });
228
229
  } catch (err) {
229
- done(err);
230
+ done(utils.getError(err));
230
231
  }
231
232
  return;
232
233
  }
@@ -243,7 +244,7 @@ Runnable.prototype.run = function(fn){
243
244
  callFn(this.fn);
244
245
  }
245
246
  } catch (err) {
246
- done(err);
247
+ done(utils.getError(err));
247
248
  }
248
249
 
249
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;
@@ -538,7 +539,7 @@ Runner.prototype.uncaught = function(err){
538
539
  }.call(err) ? err : ( err.message || err ));
539
540
  } else {
540
541
  debug('uncaught undefined exception');
541
- err = new Error('Caught undefined error, did you throw without specifying what?');
542
+ err = utils.undefinedError();
542
543
  }
543
544
  err.uncaught = true;
544
545
 
package/lib/suite.js CHANGED
@@ -98,7 +98,7 @@ Suite.prototype.clone = function(){
98
98
 
99
99
  Suite.prototype.timeout = function(ms){
100
100
  if (0 == arguments.length) return this._timeout;
101
- if (ms === 0) this._enableTimeouts = false;
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);
@@ -139,7 +139,7 @@ Suite.prototype.slow = function(ms){
139
139
  /**
140
140
  * Sets whether to bail after first error.
141
141
  *
142
- * @parma {Boolean} bail
142
+ * @param {Boolean} bail
143
143
  * @return {Suite|Number} for chaining
144
144
  * @api private
145
145
  */
package/lib/utils.js CHANGED
@@ -292,54 +292,192 @@ exports.highlightTags = function(name) {
292
292
  }
293
293
  };
294
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
+ };
322
+
323
+ /**
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
+ };
295
349
 
296
350
  /**
297
- * Stringify `obj`.
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().
298
359
  *
299
- * @param {Object} obj
300
- * @return {String}
360
+ * @see exports.type
361
+ * @param {*} value
362
+ * @return {string}
301
363
  * @api private
302
364
  */
303
365
 
304
- exports.stringify = function(obj) {
305
- if (obj instanceof RegExp) return obj.toString();
306
- return JSON.stringify(exports.canonicalize(obj), null, 2).replace(/,(\n|$)/g, '$1');
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);
307
389
  };
308
390
 
309
391
  /**
310
- * Return a new object that has the keys in sorted order.
311
- * @param {Object} obj
312
- * @param {Array} [stack]
313
- * @return {Object}
392
+ * Return if obj is a Buffer
393
+ * @param {Object} arg
394
+ * @return {Boolean}
314
395
  * @api private
315
396
  */
397
+ exports.isBuffer = function (arg) {
398
+ return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg);
399
+ };
316
400
 
317
- exports.canonicalize = function(obj, stack) {
318
- stack = stack || [];
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
+ */
319
418
 
320
- if (exports.indexOf(stack, obj) !== -1) return '[Circular]';
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
+ };
321
428
 
322
- var canonicalizedObj;
429
+ stack = stack || [];
323
430
 
324
- if ({}.toString.call(obj) === '[object Array]') {
325
- stack.push(obj);
326
- canonicalizedObj = exports.map(obj, function (item) {
327
- return exports.canonicalize(item, stack);
328
- });
329
- stack.pop();
330
- } else if (typeof obj === 'object' && obj !== null) {
331
- stack.push(obj);
332
- canonicalizedObj = {};
333
- exports.forEach(exports.keys(obj).sort(), function (key) {
334
- canonicalizedObj[key] = exports.canonicalize(obj[key], stack);
335
- });
336
- stack.pop();
337
- } else {
338
- canonicalizedObj = obj;
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();
339
477
  }
340
478
 
341
479
  return canonicalizedObj;
342
- };
480
+ };
343
481
 
344
482
  /**
345
483
  * Lookup file names at the given `path`.
@@ -386,3 +524,25 @@ exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
386
524
 
387
525
  return files;
388
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
+ };
548
+