diff --git a/lib/utils.js b/lib/utils.js index bb2c99c48f..c7947cb05d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -90,7 +90,7 @@ exports.map = function(arr, fn, scope) { * @param {number} start * @return {number} */ -exports.indexOf = function(arr, obj, start) { +var indexOf = exports.indexOf = function(arr, obj, start) { for (var i = start || 0, l = arr.length; i < l; i++) { if (arr[i] === obj) { return i; @@ -108,7 +108,7 @@ exports.indexOf = function(arr, obj, start) { * @param {Object} val Initial value. * @return {*} */ -exports.reduce = function(arr, fn, val) { +var reduce = exports.reduce = function(arr, fn, val) { var rval = val; for (var i = 0, l = arr.length; i < l; i++) { @@ -314,7 +314,7 @@ exports.trim = function(str) { * @return {Object} */ exports.parseQuery = function(qs) { - return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair) { + return reduce(qs.replace('?', '').split('&'), function(obj, pair) { var i = pair.indexOf('='); var key = pair.slice(0, i); var val = pair.slice(++i); @@ -367,13 +367,11 @@ exports.highlightTags = function(name) { * * @api private * @param {*} value The value to inspect. - * @param {string} [type] The type of the value, if known. + * @param {string} typeHint The type of the value * @returns {string} */ -function emptyRepresentation(value, type) { - type = type || exports.type(value); - - switch (type) { +function emptyRepresentation(value, typeHint) { + switch (typeHint) { case 'function': return '[Function]'; case 'object': @@ -392,7 +390,7 @@ function emptyRepresentation(value, type) { * @api private * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString * @param {*} value The value to test. - * @returns {string} + * @returns {string} Computed type * @example * type({}) // 'object' * type([]) // 'array' @@ -404,8 +402,9 @@ function emptyRepresentation(value, type) { * type(/foo/) // 'regexp' * type('type') // 'string' * type(global) // 'global' + * type(new String('foo') // 'object' */ -exports.type = function type(value) { +var type = exports.type = function type(value) { if (value === undefined) { return 'undefined'; } else if (value === null) { @@ -434,25 +433,36 @@ exports.type = function type(value) { * @return {string} */ exports.stringify = function(value) { - var type = exports.type(value); + var typeHint = type(value); + + if (!~indexOf(['object', 'array', 'function'], typeHint)) { + if (typeHint === 'buffer') { + var json = value.toJSON(); + // Based on the toJSON result + return jsonStringify(json.data && json.type ? json.data : json, 2) + .replace(/,(\n|$)/g, '$1'); + } - if (!~exports.indexOf(['object', 'array', 'function'], type)) { - if (type !== 'buffer') { + // IE7/IE8 has a bizarre String constructor; needs to be coerced + // into an array and back to obj. + if (typeHint === 'string' && typeof value === 'object') { + value = reduce(value.split(''), function(acc, char, idx) { + acc[idx] = char; + return acc; + }, {}); + typeHint = 'object'; + } else { return jsonStringify(value); } - var json = value.toJSON(); - // Based on the toJSON result - return jsonStringify(json.data && json.type ? json.data : json, 2) - .replace(/,(\n|$)/g, '$1'); } for (var prop in value) { if (Object.prototype.hasOwnProperty.call(value, prop)) { - return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1'); + return jsonStringify(exports.canonicalize(value, null, typeHint), 2).replace(/,(\n|$)/g, '$1'); } } - return emptyRepresentation(value, type); + return emptyRepresentation(value, typeHint); }; /** @@ -481,7 +491,7 @@ function jsonStringify(object, spaces, depth) { } function _stringify(val) { - switch (exports.type(val)) { + switch (type(val)) { case 'null': case 'undefined': val = '[' + val + ']'; @@ -564,14 +574,15 @@ exports.isBuffer = function(value) { * @see {@link exports.stringify} * @param {*} value Thing to inspect. May or may not have properties. * @param {Array} [stack=[]] Stack of seen values + * @param {string} [typeHint] Type hint * @return {(Object|Array|Function|string|undefined)} */ -exports.canonicalize = function(value, stack) { +exports.canonicalize = function canonicalize(value, stack, typeHint) { var canonicalizedObj; /* eslint-disable no-unused-vars */ var prop; /* eslint-enable no-unused-vars */ - var type = exports.type(value); + typeHint = typeHint || type(value); function withStack(value, fn) { stack.push(value); fn(); @@ -580,11 +591,11 @@ exports.canonicalize = function(value, stack) { stack = stack || []; - if (exports.indexOf(stack, value) !== -1) { + if (indexOf(stack, value) !== -1) { return '[Circular]'; } - switch (type) { + switch (typeHint) { case 'undefined': case 'buffer': case 'null': @@ -605,7 +616,7 @@ exports.canonicalize = function(value, stack) { } /* eslint-enable guard-for-in */ if (!canonicalizedObj) { - canonicalizedObj = emptyRepresentation(value, type); + canonicalizedObj = emptyRepresentation(value, typeHint); break; } /* falls through */ @@ -750,7 +761,7 @@ exports.stackTraceFilter = function() { return function(stack) { stack = stack.split('\n'); - stack = exports.reduce(stack, function(list, line) { + stack = reduce(stack, function(list, line) { if (isMochaInternal(line)) { return list; } diff --git a/test/acceptance/utils.spec.js b/test/acceptance/utils.spec.js index a2383a739f..cab0f42b01 100644 --- a/test/acceptance/utils.spec.js +++ b/test/acceptance/utils.spec.js @@ -77,6 +77,10 @@ describe('lib/utils', function () { var stringify = utils.stringify; + it('should return an object representation of a string created with a String constructor', function() { + expect(stringify(new String('foo'))).to.equal('{\n "0": "f"\n "1": "o"\n "2": "o"\n}'); + }); + it('should return Buffer with .toJSON representation', function() { expect(stringify(new Buffer([0x01]))).to.equal('[\n 1\n]'); expect(stringify(new Buffer([0x01, 0x02]))).to.equal('[\n 1\n 2\n]');