Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use chaijs/loupe for inspection #1407

Merged
merged 1 commit into from Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/chai/core/assertions.js
Expand Up @@ -1580,7 +1580,7 @@ module.exports = function (chai, _) {
, errorMessage
, shouldThrow = true
, range = (startType === 'date' && finishType === 'date')
? start.toUTCString() + '..' + finish.toUTCString()
? start.toISOString() + '..' + finish.toISOString()
: start + '..' + finish;

if (doLength && objType !== 'map' && objType !== 'set') {
Expand Down
358 changes: 6 additions & 352 deletions lib/chai/utils/inspect.js
Expand Up @@ -2,8 +2,7 @@
// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js

var getName = require('get-func-name');
var getProperties = require('./getProperties');
var getEnumerableProperties = require('./getEnumerableProperties');
var loupe = require('loupe');
var config = require('../config');

module.exports = inspect;
Expand All @@ -24,356 +23,11 @@ module.exports = inspect;
* @name inspect
*/
function inspect(obj, showHidden, depth, colors) {
var ctx = {
var options = {
colors: colors,
depth: (typeof depth === 'undefined' ? 2 : depth),
showHidden: showHidden,
seen: [],
stylize: function (str) { return str; }
truncate: config.truncateThreshold ? config.truncateThreshold : Infinity,
};
return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth));
}

// Returns true if object is a DOM element.
var isDOMElement = function (object) {
if (typeof HTMLElement === 'object') {
return object instanceof HTMLElement;
} else {
return object &&
typeof object === 'object' &&
'nodeType' in object &&
object.nodeType === 1 &&
typeof object.nodeName === 'string';
}
};

function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it
if (value && typeof value.inspect === 'function' &&
// Filter out the util module, it's inspect function is special
value.inspect !== exports.inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
var ret = value.inspect(recurseTimes, ctx);
if (typeof ret !== 'string') {
ret = formatValue(ctx, ret, recurseTimes);
}
return ret;
}

// Primitive types cannot have properties
var primitive = formatPrimitive(ctx, value);
if (primitive) {
return primitive;
}

// If this is a DOM element, try to get the outer HTML.
if (isDOMElement(value)) {
if ('outerHTML' in value) {
return value.outerHTML;
// This value does not have an outerHTML attribute,
// it could still be an XML element
} else {
// Attempt to serialize it
try {
if (document.xmlVersion) {
var xmlSerializer = new XMLSerializer();
return xmlSerializer.serializeToString(value);
} else {
// Firefox 11- do not support outerHTML
// It does, however, support innerHTML
// Use the following to render the element
var ns = "http://www.w3.org/1999/xhtml";
var container = document.createElementNS(ns, '_');

container.appendChild(value.cloneNode(false));
var html = container.innerHTML
.replace('><', '>' + value.innerHTML + '<');
container.innerHTML = '';
return html;
}
} catch (err) {
// This could be a non-native DOM implementation,
// continue with the normal flow:
// printing the element as if it is an object.
}
}
}

// Look up the keys of the object.
var visibleKeys = getEnumerableProperties(value);
var keys = ctx.showHidden ? getProperties(value) : visibleKeys;

var name, nameSuffix;

// Some type of object without properties can be shortcut.
// In IE, errors have a single `stack` property, or if they are vanilla `Error`,
// a `stack` plus `description` property; ignore those for consistency.
if (keys.length === 0 || (isError(value) && (
(keys.length === 1 && keys[0] === 'stack') ||
(keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack')
))) {
if (typeof value === 'function') {
name = getName(value);
nameSuffix = name ? ': ' + name : '';
return ctx.stylize('[Function' + nameSuffix + ']', 'special');
}
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
}
if (isDate(value)) {
return ctx.stylize(Date.prototype.toUTCString.call(value), 'date');
}
if (isError(value)) {
return formatError(value);
}
}

var base = ''
, array = false
, typedArray = false
, braces = ['{', '}'];

if (isTypedArray(value)) {
typedArray = true;
braces = ['[', ']'];
}

// Make Array say that they are Array
if (isArray(value)) {
array = true;
braces = ['[', ']'];
}

// Make functions say that they are functions
if (typeof value === 'function') {
name = getName(value);
nameSuffix = name ? ': ' + name : '';
base = ' [Function' + nameSuffix + ']';
}

// Make RegExps say that they are RegExps
if (isRegExp(value)) {
base = ' ' + RegExp.prototype.toString.call(value);
}

// Make dates with properties first say the date
if (isDate(value)) {
base = ' ' + Date.prototype.toUTCString.call(value);
}

// Make error with message first say the error
if (isError(value)) {
return formatError(value);
}

if (keys.length === 0 && (!array || value.length == 0)) {
return braces[0] + base + braces[1];
}

if (recurseTimes < 0) {
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
} else {
return ctx.stylize('[Object]', 'special');
}
}

ctx.seen.push(value);

var output;
if (array) {
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
} else if (typedArray) {
return formatTypedArray(value);
} else {
output = keys.map(function(key) {
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
});
}

ctx.seen.pop();

return reduceToSingleString(output, base, braces);
}

function formatPrimitive(ctx, value) {
switch (typeof value) {
case 'undefined':
return ctx.stylize('undefined', 'undefined');

case 'string':
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
.replace(/'/g, "\\'")
.replace(/\\"/g, '"') + '\'';
return ctx.stylize(simple, 'string');

case 'number':
if (value === 0 && (1/value) === -Infinity) {
return ctx.stylize('-0', 'number');
}
return ctx.stylize('' + value, 'number');

case 'boolean':
return ctx.stylize('' + value, 'boolean');

case 'symbol':
return ctx.stylize(value.toString(), 'symbol');

case 'bigint':
return ctx.stylize(value.toString() + 'n', 'bigint');
}
// For some reason typeof null is "object", so special case here.
if (value === null) {
return ctx.stylize('null', 'null');
}
}

function formatError(value) {
return '[' + Error.prototype.toString.call(value) + ']';
}

function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
for (var i = 0, l = value.length; i < l; ++i) {
if (Object.prototype.hasOwnProperty.call(value, String(i))) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
String(i), true));
} else {
output.push('');
}
}

keys.forEach(function(key) {
if (!key.match(/^\d+$/)) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, true));
}
});
return output;
}

function formatTypedArray(value) {
var str = '[ ';

for (var i = 0; i < value.length; ++i) {
if (str.length >= config.truncateThreshold - 7) {
str += '...';
break;
}
str += value[i] + ', ';
}
str += ' ]';

// Removing trailing `, ` if the array was not truncated
if (str.indexOf(', ]') !== -1) {
str = str.replace(', ]', ' ]');
}

return str;
}

function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
var name;
var propDescriptor = Object.getOwnPropertyDescriptor(value, key);
var str;

if (propDescriptor) {
if (propDescriptor.get) {
if (propDescriptor.set) {
str = ctx.stylize('[Getter/Setter]', 'special');
} else {
str = ctx.stylize('[Getter]', 'special');
}
} else {
if (propDescriptor.set) {
str = ctx.stylize('[Setter]', 'special');
}
}
}
if (visibleKeys.indexOf(key) < 0) {
name = '[' + key + ']';
}
if (!str) {
if (ctx.seen.indexOf(value[key]) < 0) {
if (recurseTimes === null) {
str = formatValue(ctx, value[key], null);
} else {
str = formatValue(ctx, value[key], recurseTimes - 1);
}
if (str.indexOf('\n') > -1) {
if (array) {
str = str.split('\n').map(function(line) {
return ' ' + line;
}).join('\n').substr(2);
} else {
str = '\n' + str.split('\n').map(function(line) {
return ' ' + line;
}).join('\n');
}
}
} else {
str = ctx.stylize('[Circular]', 'special');
}
}
if (typeof name === 'undefined') {
if (array && key.match(/^\d+$/)) {
return str;
}
name = JSON.stringify('' + key);
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
name = name.substr(1, name.length - 2);
name = ctx.stylize(name, 'name');
} else {
name = name.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'");
name = ctx.stylize(name, 'string');
}
}

return name + ': ' + str;
}

function reduceToSingleString(output, base, braces) {
var length = output.reduce(function(prev, cur) {
return prev + cur.length + 1;
}, 0);

if (length > 60) {
return braces[0] +
(base === '' ? '' : base + '\n ') +
' ' +
output.join(',\n ') +
' ' +
braces[1];
}

return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}

function isTypedArray(ar) {
// Unfortunately there's no way to check if an object is a TypedArray
// We have to check if it's one of these types
return (typeof ar === 'object' && /\w+Array]$/.test(objectToString(ar)));
}

function isArray(ar) {
return Array.isArray(ar) ||
(typeof ar === 'object' && objectToString(ar) === '[object Array]');
}

function isRegExp(re) {
return typeof re === 'object' && objectToString(re) === '[object RegExp]';
}

function isDate(d) {
return typeof d === 'object' && objectToString(d) === '[object Date]';
}

function isError(e) {
return typeof e === 'object' && objectToString(e) === '[object Error]';
}

function objectToString(o) {
return Object.prototype.toString.call(o);
return loupe.inspect(obj, options);
}