@vitest/utils
Advanced tools
+9
-21
| import { PrettyFormatOptions } from '@vitest/pretty-format'; | ||
| type Inspect = (value: unknown, options: Options) => string; | ||
| interface Options { | ||
| showHidden: boolean; | ||
| depth: number; | ||
| colors: boolean; | ||
| customInspect: boolean; | ||
| showProxy: boolean; | ||
| maxArrayLength: number; | ||
| breakLength: number; | ||
| truncate: number; | ||
| seen: unknown[]; | ||
| inspect: Inspect; | ||
| stylize: (value: string, styleType: string) => string; | ||
| } | ||
| type LoupeOptions = Partial<Options>; | ||
| interface StringifyOptions extends PrettyFormatOptions { | ||
@@ -24,8 +9,11 @@ maxLength?: number; | ||
| declare const formatRegExp: RegExp; | ||
| declare function format(...args: unknown[]): string; | ||
| declare function browserFormat(...args: unknown[]): string; | ||
| declare function inspect(obj: unknown, options?: LoupeOptions): string; | ||
| declare function objDisplay(obj: unknown, options?: LoupeOptions): string; | ||
| declare function format(args: unknown[], options?: InspectOptions): string; | ||
| interface InspectOptions extends StringifyOptions { | ||
| truncate?: number; | ||
| multiline?: boolean; | ||
| } | ||
| declare function inspect(obj: unknown, options?: InspectOptions): string; | ||
| declare function truncateString(string: string, maxLength: number): string; | ||
| export { browserFormat, format, formatRegExp, inspect, objDisplay, stringify }; | ||
| export type { LoupeOptions, StringifyOptions }; | ||
| export { format, formatRegExp, inspect, stringify, truncateString }; | ||
| export type { InspectOptions, StringifyOptions }; |
+84
-623
| import { plugins, createDOMElementFilter, format as format$1 } from '@vitest/pretty-format'; | ||
| const ansiColors = { | ||
| bold: ['1', '22'], | ||
| dim: ['2', '22'], | ||
| italic: ['3', '23'], | ||
| underline: ['4', '24'], | ||
| // 5 & 6 are blinking | ||
| inverse: ['7', '27'], | ||
| hidden: ['8', '28'], | ||
| strike: ['9', '29'], | ||
| // 10-20 are fonts | ||
| // 21-29 are resets for 1-9 | ||
| black: ['30', '39'], | ||
| red: ['31', '39'], | ||
| green: ['32', '39'], | ||
| yellow: ['33', '39'], | ||
| blue: ['34', '39'], | ||
| magenta: ['35', '39'], | ||
| cyan: ['36', '39'], | ||
| white: ['37', '39'], | ||
| brightblack: ['30;1', '39'], | ||
| brightred: ['31;1', '39'], | ||
| brightgreen: ['32;1', '39'], | ||
| brightyellow: ['33;1', '39'], | ||
| brightblue: ['34;1', '39'], | ||
| brightmagenta: ['35;1', '39'], | ||
| brightcyan: ['36;1', '39'], | ||
| brightwhite: ['37;1', '39'], | ||
| grey: ['90', '39'], | ||
| }; | ||
| const styles = { | ||
| special: 'cyan', | ||
| number: 'yellow', | ||
| bigint: 'yellow', | ||
| boolean: 'yellow', | ||
| undefined: 'grey', | ||
| null: 'bold', | ||
| string: 'green', | ||
| symbol: 'green', | ||
| date: 'magenta', | ||
| regexp: 'red', | ||
| }; | ||
| const truncator = '…'; | ||
| function colorise(value, styleType) { | ||
| const color = ansiColors[styles[styleType]] || ansiColors[styleType] || ''; | ||
| if (!color) { | ||
| return String(value); | ||
| } | ||
| return `\u001b[${color[0]}m${String(value)}\u001b[${color[1]}m`; | ||
| } | ||
| function normaliseOptions({ showHidden = false, depth = 2, colors = false, customInspect = true, showProxy = false, maxArrayLength = Infinity, breakLength = Infinity, seen = [], | ||
| // eslint-disable-next-line no-shadow | ||
| truncate = Infinity, stylize = String, } = {}, inspect) { | ||
| const options = { | ||
| showHidden: Boolean(showHidden), | ||
| depth: Number(depth), | ||
| colors: Boolean(colors), | ||
| customInspect: Boolean(customInspect), | ||
| showProxy: Boolean(showProxy), | ||
| maxArrayLength: Number(maxArrayLength), | ||
| breakLength: Number(breakLength), | ||
| truncate: Number(truncate), | ||
| seen, | ||
| inspect, | ||
| stylize, | ||
| }; | ||
| if (options.colors) { | ||
| options.stylize = colorise; | ||
| } | ||
| return options; | ||
| } | ||
| function isHighSurrogate(char) { | ||
| return char >= '\ud800' && char <= '\udbff'; | ||
| } | ||
| function truncate(string, length, tail = truncator) { | ||
| string = String(string); | ||
| const tailLength = tail.length; | ||
| const stringLength = string.length; | ||
| if (tailLength > length && stringLength > tailLength) { | ||
| return tail; | ||
| } | ||
| if (stringLength > length && stringLength > tailLength) { | ||
| let end = length - tailLength; | ||
| if (end > 0 && isHighSurrogate(string[end - 1])) { | ||
| end = end - 1; | ||
| } | ||
| return `${string.slice(0, end)}${tail}`; | ||
| } | ||
| return string; | ||
| } | ||
| // eslint-disable-next-line complexity | ||
| function inspectList(list, options, inspectItem, separator = ', ') { | ||
| inspectItem = inspectItem || options.inspect; | ||
| const size = list.length; | ||
| if (size === 0) | ||
| return ''; | ||
| const originalLength = options.truncate; | ||
| let output = ''; | ||
| let peek = ''; | ||
| let truncated = ''; | ||
| for (let i = 0; i < size; i += 1) { | ||
| const last = i + 1 === list.length; | ||
| const secondToLast = i + 2 === list.length; | ||
| truncated = `${truncator}(${list.length - i})`; | ||
| const value = list[i]; | ||
| // If there is more than one remaining we need to account for a separator of `, ` | ||
| options.truncate = originalLength - output.length - (last ? 0 : separator.length); | ||
| const string = peek || inspectItem(value, options) + (last ? '' : separator); | ||
| const nextLength = output.length + string.length; | ||
| const truncatedLength = nextLength + truncated.length; | ||
| // If this is the last element, and adding it would | ||
| // take us over length, but adding the truncator wouldn't - then break now | ||
| if (last && nextLength > originalLength && output.length + truncated.length <= originalLength) { | ||
| break; | ||
| } | ||
| // If this isn't the last or second to last element to scan, | ||
| // but the string is already over length then break here | ||
| if (!last && !secondToLast && truncatedLength > originalLength) { | ||
| break; | ||
| } | ||
| // Peek at the next string to determine if we should | ||
| // break early before adding this item to the output | ||
| peek = last ? '' : inspectItem(list[i + 1], options) + (secondToLast ? '' : separator); | ||
| // If we have one element left, but this element and | ||
| // the next takes over length, the break early | ||
| if (!last && secondToLast && truncatedLength > originalLength && nextLength + peek.length > originalLength) { | ||
| break; | ||
| } | ||
| output += string; | ||
| // If the next element takes us to length - | ||
| // but there are more after that, then we should truncate now | ||
| if (!last && !secondToLast && nextLength + peek.length >= originalLength) { | ||
| truncated = `${truncator}(${list.length - i - 1})`; | ||
| break; | ||
| } | ||
| truncated = ''; | ||
| } | ||
| return `${output}${truncated}`; | ||
| } | ||
| function quoteComplexKey(key) { | ||
| if (key.match(/^[a-zA-Z_][a-zA-Z_0-9]*$/)) { | ||
| return key; | ||
| } | ||
| return JSON.stringify(key) | ||
| .replace(/'/g, "\\'") | ||
| .replace(/\\"/g, '"') | ||
| .replace(/(^"|"$)/g, "'"); | ||
| } | ||
| function inspectProperty([key, value], options) { | ||
| options.truncate -= 2; | ||
| if (typeof key === 'string') { | ||
| key = quoteComplexKey(key); | ||
| } | ||
| else if (typeof key !== 'number') { | ||
| key = `[${options.inspect(key, options)}]`; | ||
| } | ||
| options.truncate -= key.length; | ||
| value = options.inspect(value, options); | ||
| return `${key}: ${value}`; | ||
| } | ||
| function inspectArray(array, options) { | ||
| // Object.keys will always output the Array indices first, so we can slice by | ||
| // `array.length` to get non-index properties | ||
| const nonIndexProperties = Object.keys(array).slice(array.length); | ||
| if (!array.length && !nonIndexProperties.length) | ||
| return '[]'; | ||
| options.truncate -= 4; | ||
| const listContents = inspectList(array, options); | ||
| options.truncate -= listContents.length; | ||
| let propertyContents = ''; | ||
| if (nonIndexProperties.length) { | ||
| propertyContents = inspectList(nonIndexProperties.map(key => [key, array[key]]), options, inspectProperty); | ||
| } | ||
| return `[ ${listContents}${propertyContents ? `, ${propertyContents}` : ''} ]`; | ||
| } | ||
| const getArrayName = (array) => { | ||
| // We need to special case Node.js' Buffers, which report to be Uint8Array | ||
| // @ts-ignore | ||
| if (typeof Buffer === 'function' && array instanceof Buffer) { | ||
| return 'Buffer'; | ||
| } | ||
| if (array[Symbol.toStringTag]) { | ||
| return array[Symbol.toStringTag]; | ||
| } | ||
| return array.constructor.name; | ||
| }; | ||
| function inspectTypedArray(array, options) { | ||
| const name = getArrayName(array); | ||
| options.truncate -= name.length + 4; | ||
| // Object.keys will always output the Array indices first, so we can slice by | ||
| // `array.length` to get non-index properties | ||
| const nonIndexProperties = Object.keys(array).slice(array.length); | ||
| if (!array.length && !nonIndexProperties.length) | ||
| return `${name}[]`; | ||
| // As we know TypedArrays only contain Unsigned Integers, we can skip inspecting each one and simply | ||
| // stylise the toString() value of them | ||
| let output = ''; | ||
| for (let i = 0; i < array.length; i++) { | ||
| const string = `${options.stylize(truncate(array[i], options.truncate), 'number')}${i === array.length - 1 ? '' : ', '}`; | ||
| options.truncate -= string.length; | ||
| if (array[i] !== array.length && options.truncate <= 3) { | ||
| output += `${truncator}(${array.length - array[i] + 1})`; | ||
| break; | ||
| } | ||
| output += string; | ||
| } | ||
| let propertyContents = ''; | ||
| if (nonIndexProperties.length) { | ||
| propertyContents = inspectList(nonIndexProperties.map(key => [key, array[key]]), options, inspectProperty); | ||
| } | ||
| return `${name}[ ${output}${propertyContents ? `, ${propertyContents}` : ''} ]`; | ||
| } | ||
| function inspectDate(dateObject, options) { | ||
| const stringRepresentation = dateObject.toJSON(); | ||
| if (stringRepresentation === null) { | ||
| return 'Invalid Date'; | ||
| } | ||
| const split = stringRepresentation.split('T'); | ||
| const date = split[0]; | ||
| // If we need to - truncate the time portion, but never the date | ||
| return options.stylize(`${date}T${truncate(split[1], options.truncate - date.length - 1)}`, 'date'); | ||
| } | ||
| function inspectFunction(func, options) { | ||
| const functionType = func[Symbol.toStringTag] || 'Function'; | ||
| const name = func.name; | ||
| if (!name) { | ||
| return options.stylize(`[${functionType}]`, 'special'); | ||
| } | ||
| return options.stylize(`[${functionType} ${truncate(name, options.truncate - 11)}]`, 'special'); | ||
| } | ||
| function inspectMapEntry([key, value], options) { | ||
| options.truncate -= 4; | ||
| key = options.inspect(key, options); | ||
| options.truncate -= key.length; | ||
| value = options.inspect(value, options); | ||
| return `${key} => ${value}`; | ||
| } | ||
| // IE11 doesn't support `map.entries()` | ||
| function mapToEntries(map) { | ||
| const entries = []; | ||
| map.forEach((value, key) => { | ||
| entries.push([key, value]); | ||
| }); | ||
| return entries; | ||
| } | ||
| function inspectMap(map, options) { | ||
| if (map.size === 0) | ||
| return 'Map{}'; | ||
| options.truncate -= 7; | ||
| return `Map{ ${inspectList(mapToEntries(map), options, inspectMapEntry)} }`; | ||
| } | ||
| const isNaN = Number.isNaN || (i => i !== i); // eslint-disable-line no-self-compare | ||
| function inspectNumber(number, options) { | ||
| if (isNaN(number)) { | ||
| return options.stylize('NaN', 'number'); | ||
| } | ||
| if (number === Infinity) { | ||
| return options.stylize('Infinity', 'number'); | ||
| } | ||
| if (number === -Infinity) { | ||
| return options.stylize('-Infinity', 'number'); | ||
| } | ||
| if (number === 0) { | ||
| return options.stylize(1 / number === Infinity ? '+0' : '-0', 'number'); | ||
| } | ||
| return options.stylize(truncate(String(number), options.truncate), 'number'); | ||
| } | ||
| function inspectBigInt(number, options) { | ||
| let nums = truncate(number.toString(), options.truncate - 1); | ||
| if (nums !== truncator) | ||
| nums += 'n'; | ||
| return options.stylize(nums, 'bigint'); | ||
| } | ||
| function inspectRegExp(value, options) { | ||
| const flags = value.toString().split('/')[2]; | ||
| const sourceLength = options.truncate - (2 + flags.length); | ||
| const source = value.source; | ||
| return options.stylize(`/${truncate(source, sourceLength)}/${flags}`, 'regexp'); | ||
| } | ||
| // IE11 doesn't support `Array.from(set)` | ||
| function arrayFromSet(set) { | ||
| const values = []; | ||
| set.forEach(value => { | ||
| values.push(value); | ||
| }); | ||
| return values; | ||
| } | ||
| function inspectSet(set, options) { | ||
| if (set.size === 0) | ||
| return 'Set{}'; | ||
| options.truncate -= 7; | ||
| return `Set{ ${inspectList(arrayFromSet(set), options)} }`; | ||
| } | ||
| const stringEscapeChars = new RegExp("['\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5" + | ||
| '\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]', 'g'); | ||
| const escapeCharacters = { | ||
| '\b': '\\b', | ||
| '\t': '\\t', | ||
| '\n': '\\n', | ||
| '\f': '\\f', | ||
| '\r': '\\r', | ||
| "'": "\\'", | ||
| '\\': '\\\\', | ||
| }; | ||
| const hex = 16; | ||
| function escape(char) { | ||
| return (escapeCharacters[char] || | ||
| `\\u${`0000${char.charCodeAt(0).toString(hex)}`.slice(-4)}`); | ||
| } | ||
| function inspectString(string, options) { | ||
| if (stringEscapeChars.test(string)) { | ||
| string = string.replace(stringEscapeChars, escape); | ||
| } | ||
| return options.stylize(`'${truncate(string, options.truncate - 2)}'`, 'string'); | ||
| } | ||
| function inspectSymbol(value) { | ||
| if ('description' in Symbol.prototype) { | ||
| return value.description ? `Symbol(${value.description})` : 'Symbol()'; | ||
| } | ||
| return value.toString(); | ||
| } | ||
| const getPromiseValue = () => 'Promise{…}'; | ||
| function inspectObject$1(object, options) { | ||
| const properties = Object.getOwnPropertyNames(object); | ||
| const symbols = Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(object) : []; | ||
| if (properties.length === 0 && symbols.length === 0) { | ||
| return '{}'; | ||
| } | ||
| options.truncate -= 4; | ||
| options.seen = options.seen || []; | ||
| if (options.seen.includes(object)) { | ||
| return '[Circular]'; | ||
| } | ||
| options.seen.push(object); | ||
| const propertyContents = inspectList(properties.map(key => [key, object[key]]), options, inspectProperty); | ||
| const symbolContents = inspectList(symbols.map(key => [key, object[key]]), options, inspectProperty); | ||
| options.seen.pop(); | ||
| let sep = ''; | ||
| if (propertyContents && symbolContents) { | ||
| sep = ', '; | ||
| } | ||
| return `{ ${propertyContents}${sep}${symbolContents} }`; | ||
| } | ||
| const toStringTag = typeof Symbol !== 'undefined' && Symbol.toStringTag ? Symbol.toStringTag : false; | ||
| function inspectClass(value, options) { | ||
| let name = ''; | ||
| if (toStringTag && toStringTag in value) { | ||
| name = value[toStringTag]; | ||
| } | ||
| name = name || value.constructor.name; | ||
| // Babel transforms anonymous classes to the name `_class` | ||
| if (!name || name === '_class') { | ||
| name = '<Anonymous Class>'; | ||
| } | ||
| options.truncate -= name.length; | ||
| return `${name}${inspectObject$1(value, options)}`; | ||
| } | ||
| function inspectArguments(args, options) { | ||
| if (args.length === 0) | ||
| return 'Arguments[]'; | ||
| options.truncate -= 13; | ||
| return `Arguments[ ${inspectList(args, options)} ]`; | ||
| } | ||
| const errorKeys = [ | ||
| 'stack', | ||
| 'line', | ||
| 'column', | ||
| 'name', | ||
| 'message', | ||
| 'fileName', | ||
| 'lineNumber', | ||
| 'columnNumber', | ||
| 'number', | ||
| 'description', | ||
| 'cause', | ||
| ]; | ||
| function inspectObject(error, options) { | ||
| const properties = Object.getOwnPropertyNames(error).filter(key => errorKeys.indexOf(key) === -1); | ||
| const name = error.name; | ||
| options.truncate -= name.length; | ||
| let message = ''; | ||
| if (typeof error.message === 'string') { | ||
| message = truncate(error.message, options.truncate); | ||
| } | ||
| else { | ||
| properties.unshift('message'); | ||
| } | ||
| message = message ? `: ${message}` : ''; | ||
| options.truncate -= message.length + 5; | ||
| options.seen = options.seen || []; | ||
| if (options.seen.includes(error)) { | ||
| return '[Circular]'; | ||
| } | ||
| options.seen.push(error); | ||
| const propertyContents = inspectList(properties.map(key => [key, error[key]]), options, inspectProperty); | ||
| return `${name}${message}${propertyContents ? ` { ${propertyContents} }` : ''}`; | ||
| } | ||
| function inspectAttribute([key, value], options) { | ||
| options.truncate -= 3; | ||
| if (!value) { | ||
| return `${options.stylize(String(key), 'yellow')}`; | ||
| } | ||
| return `${options.stylize(String(key), 'yellow')}=${options.stylize(`"${value}"`, 'string')}`; | ||
| } | ||
| function inspectNodeCollection(collection, options) { | ||
| return inspectList(collection, options, inspectNode, '\n'); | ||
| } | ||
| function inspectNode(node, options) { | ||
| switch (node.nodeType) { | ||
| case 1: | ||
| return inspectHTML(node, options); | ||
| case 3: | ||
| return options.inspect(node.data, options); | ||
| default: | ||
| return options.inspect(node, options); | ||
| } | ||
| } | ||
| // @ts-ignore (Deno doesn't have Element) | ||
| function inspectHTML(element, options) { | ||
| const properties = element.getAttributeNames(); | ||
| const name = element.tagName.toLowerCase(); | ||
| const head = options.stylize(`<${name}`, 'special'); | ||
| const headClose = options.stylize(`>`, 'special'); | ||
| const tail = options.stylize(`</${name}>`, 'special'); | ||
| options.truncate -= name.length * 2 + 5; | ||
| let propertyContents = ''; | ||
| if (properties.length > 0) { | ||
| propertyContents += ' '; | ||
| propertyContents += inspectList(properties.map((key) => [key, element.getAttribute(key)]), options, inspectAttribute, ' '); | ||
| } | ||
| options.truncate -= propertyContents.length; | ||
| const truncate = options.truncate; | ||
| let children = inspectNodeCollection(element.children, options); | ||
| if (children && children.length > truncate) { | ||
| children = `${truncator}(${element.children.length})`; | ||
| } | ||
| return `${head}${propertyContents}${headClose}${children}${tail}`; | ||
| } | ||
| /* ! | ||
| * loupe | ||
| * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com> | ||
| * MIT Licensed | ||
| */ | ||
| const symbolsSupported = typeof Symbol === 'function' && typeof Symbol.for === 'function'; | ||
| const chaiInspect = symbolsSupported ? Symbol.for('chai/inspect') : '@@chai/inspect'; | ||
| const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); | ||
| const constructorMap = new WeakMap(); | ||
| const stringTagMap = {}; | ||
| const baseTypesMap = { | ||
| undefined: (value, options) => options.stylize('undefined', 'undefined'), | ||
| null: (value, options) => options.stylize('null', 'null'), | ||
| boolean: (value, options) => options.stylize(String(value), 'boolean'), | ||
| Boolean: (value, options) => options.stylize(String(value), 'boolean'), | ||
| number: inspectNumber, | ||
| Number: inspectNumber, | ||
| bigint: inspectBigInt, | ||
| BigInt: inspectBigInt, | ||
| string: inspectString, | ||
| String: inspectString, | ||
| function: inspectFunction, | ||
| Function: inspectFunction, | ||
| symbol: inspectSymbol, | ||
| // A Symbol polyfill will return `Symbol` not `symbol` from typedetect | ||
| Symbol: inspectSymbol, | ||
| Array: inspectArray, | ||
| Date: inspectDate, | ||
| Map: inspectMap, | ||
| Set: inspectSet, | ||
| RegExp: inspectRegExp, | ||
| Promise: getPromiseValue, | ||
| // WeakSet, WeakMap are totally opaque to us | ||
| WeakSet: (value, options) => options.stylize('WeakSet{…}', 'special'), | ||
| WeakMap: (value, options) => options.stylize('WeakMap{…}', 'special'), | ||
| Arguments: inspectArguments, | ||
| Int8Array: inspectTypedArray, | ||
| Uint8Array: inspectTypedArray, | ||
| Uint8ClampedArray: inspectTypedArray, | ||
| Int16Array: inspectTypedArray, | ||
| Uint16Array: inspectTypedArray, | ||
| Int32Array: inspectTypedArray, | ||
| Uint32Array: inspectTypedArray, | ||
| Float32Array: inspectTypedArray, | ||
| Float64Array: inspectTypedArray, | ||
| Generator: () => '', | ||
| DataView: () => '', | ||
| ArrayBuffer: () => '', | ||
| Error: inspectObject, | ||
| HTMLCollection: inspectNodeCollection, | ||
| NodeList: inspectNodeCollection, | ||
| }; | ||
| // eslint-disable-next-line complexity | ||
| const inspectCustom = (value, options, type, inspectFn) => { | ||
| if (chaiInspect in value && typeof value[chaiInspect] === 'function') { | ||
| return value[chaiInspect](options); | ||
| } | ||
| if (nodeInspect in value && typeof value[nodeInspect] === 'function') { | ||
| return value[nodeInspect](options.depth, options, inspectFn); | ||
| } | ||
| if ('inspect' in value && typeof value.inspect === 'function') { | ||
| return value.inspect(options.depth, options); | ||
| } | ||
| if ('constructor' in value && constructorMap.has(value.constructor)) { | ||
| return constructorMap.get(value.constructor)(value, options); | ||
| } | ||
| if (stringTagMap[type]) { | ||
| return stringTagMap[type](value, options); | ||
| } | ||
| return ''; | ||
| }; | ||
| const toString = Object.prototype.toString; | ||
| // eslint-disable-next-line complexity | ||
| function inspect$1(value, opts = {}) { | ||
| const options = normaliseOptions(opts, inspect$1); | ||
| const { customInspect } = options; | ||
| let type = value === null ? 'null' : typeof value; | ||
| if (type === 'object') { | ||
| type = toString.call(value).slice(8, -1); | ||
| } | ||
| // If it is a base value that we already support, then use Loupe's inspector | ||
| if (type in baseTypesMap) { | ||
| return baseTypesMap[type](value, options); | ||
| } | ||
| // If `options.customInspect` is set to true then try to use the custom inspector | ||
| if (customInspect && value) { | ||
| const output = inspectCustom(value, options, type, inspect$1); | ||
| if (output) { | ||
| if (typeof output === 'string') | ||
| return output; | ||
| return inspect$1(output, options); | ||
| } | ||
| } | ||
| const proto = value ? Object.getPrototypeOf(value) : false; | ||
| // If it's a plain Object then use Loupe's inspector | ||
| if (proto === Object.prototype || proto === null) { | ||
| return inspectObject$1(value, options); | ||
| } | ||
| // Specifically account for HTMLElements | ||
| // @ts-ignore | ||
| if (value && typeof HTMLElement === 'function' && value instanceof HTMLElement) { | ||
| return inspectHTML(value, options); | ||
| } | ||
| if ('constructor' in value) { | ||
| // If it is a class, inspect it like an object but add the constructor name | ||
| if (value.constructor !== Object) { | ||
| return inspectClass(value, options); | ||
| } | ||
| // If it is an object with an anonymous prototype, display it as an object. | ||
| return inspectObject$1(value, options); | ||
| } | ||
| // last chance to check if it's an object | ||
| if (value === Object(value)) { | ||
| return inspectObject$1(value, options); | ||
| } | ||
| // We have run out of options! Just stringify the value | ||
| return options.stylize(String(value), type); | ||
| } | ||
| const { AsymmetricMatcher, DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent } = plugins; | ||
@@ -642,19 +68,8 @@ const PLUGINS = [ | ||
| const formatRegExp = /%[sdjifoOc%]/g; | ||
| function baseFormat(args, options = {}) { | ||
| const formatArg = (item, inspecOptions) => { | ||
| if (options.prettifyObject) { | ||
| return stringify(item, undefined, { | ||
| printBasicPrototype: false, | ||
| escapeString: false | ||
| }); | ||
| } | ||
| return inspect(item, inspecOptions); | ||
| }; | ||
| function format(args, options = {}) { | ||
| const formatArg = (item) => inspect(item, options); | ||
| if (typeof args[0] !== "string") { | ||
| const objects = []; | ||
| for (let i = 0; i < args.length; i++) { | ||
| objects.push(formatArg(args[i], { | ||
| depth: 0, | ||
| colors: false | ||
| })); | ||
| objects.push(formatArg(args[i])); | ||
| } | ||
@@ -686,6 +101,3 @@ return objects.join(" "); | ||
| } | ||
| return formatArg(value, { | ||
| depth: 0, | ||
| colors: false | ||
| }); | ||
| return formatArg(value); | ||
| } | ||
@@ -712,6 +124,3 @@ return String(value); | ||
| case "%f": return Number.parseFloat(String(args[i++])).toString(); | ||
| case "%o": return formatArg(args[i++], { | ||
| showHidden: true, | ||
| showProxy: true | ||
| }); | ||
| case "%o": | ||
| case "%O": return formatArg(args[i++]); | ||
@@ -743,37 +152,89 @@ case "%c": { | ||
| } | ||
| function format(...args) { | ||
| return baseFormat(args); | ||
| function inspect(obj, options) { | ||
| const { truncate, multiline, ...stringifyOptions } = options ?? {}; | ||
| const prettyFormatOptions = { | ||
| singleQuote: true, | ||
| quoteKeys: false, | ||
| min: true, | ||
| spacingInner: " ", | ||
| spacingOuter: " ", | ||
| printBasicPrototype: false, | ||
| compareKeys: null, | ||
| ...multiline ? { | ||
| min: false, | ||
| spacingInner: undefined, | ||
| spacingOuter: undefined | ||
| } : {} | ||
| }; | ||
| const threshold = truncate ?? 0; | ||
| const formatted = stringify(obj, undefined, { | ||
| ...prettyFormatOptions, | ||
| ...stringifyOptions, | ||
| maxLength: threshold || undefined | ||
| }); | ||
| if (threshold === 0 || formatted.length <= threshold) { | ||
| return formatted; | ||
| } | ||
| // if stringify's adaptive maxDepth (down to 1) fails to truncate enough, | ||
| // - for known types (e.g. string, object, array, etc), apply best effort truncation. | ||
| // - for other values, fallback to maxDepth = 0 which should can show minimal output. | ||
| const type = Object.prototype.toString.call(obj); | ||
| if (typeof obj === "string") { | ||
| let end = threshold - 1; | ||
| if (end > 0 && isHighSurrogate(formatted[end - 1])) { | ||
| end = end - 1; | ||
| } | ||
| return `'${formatted.slice(1, end)}…'`; | ||
| } | ||
| if (type === "[object Array]" || type === "[object Object]" || type === "[object Set]" || type === "[object Map]") { | ||
| return stringifyByMaxWidth(obj, threshold, { | ||
| ...prettyFormatOptions, | ||
| ...stringifyOptions, | ||
| maxDepth: 1 | ||
| }); | ||
| } | ||
| return stringify(obj, undefined, { | ||
| ...prettyFormatOptions, | ||
| ...stringifyOptions, | ||
| maxDepth: 0 | ||
| }); | ||
| } | ||
| function browserFormat(...args) { | ||
| return baseFormat(args, { prettifyObject: true }); | ||
| function truncateString(string, maxLength) { | ||
| if (string.length <= maxLength) { | ||
| return string; | ||
| } | ||
| let end = maxLength - 1; | ||
| if (isHighSurrogate(string[end - 1])) { | ||
| end = end - 1; | ||
| } | ||
| return `${string.slice(0, end)}…`; | ||
| } | ||
| function inspect(obj, options = {}) { | ||
| if (options.truncate === 0) { | ||
| options.truncate = Number.POSITIVE_INFINITY; | ||
| function stringifyByMaxWidth(object, threshold, options) { | ||
| function evaluate(x) { | ||
| return stringify(object, undefined, { | ||
| ...options, | ||
| maxWidth: x | ||
| }); | ||
| } | ||
| return inspect$1(obj, options); | ||
| const opt = binarySearch(0, threshold, (x) => evaluate(x).length <= threshold); | ||
| return evaluate(opt); | ||
| } | ||
| function objDisplay(obj, options = {}) { | ||
| if (typeof options.truncate === "undefined") { | ||
| options.truncate = 40; | ||
| } | ||
| const str = inspect(obj, options); | ||
| const type = Object.prototype.toString.call(obj); | ||
| if (options.truncate && str.length >= options.truncate) { | ||
| if (type === "[object Function]") { | ||
| const fn = obj; | ||
| return !fn.name ? "[Function]" : `[Function: ${fn.name}]`; | ||
| } else if (type === "[object Array]") { | ||
| return `[ Array(${obj.length}) ]`; | ||
| } else if (type === "[object Object]") { | ||
| const keys = Object.keys(obj); | ||
| const kstr = keys.length > 2 ? `${keys.splice(0, 2).join(", ")}, ...` : keys.join(", "); | ||
| return `{ Object (${kstr}) }`; | ||
| // find max(x \in [x, y) | f(x) = true) | ||
| // if f(x0) is false, then returns x0. | ||
| function binarySearch(x0, x1, f) { | ||
| while (x0 + 1 < x1) { | ||
| const x = Math.floor((x0 + x1) / 2); | ||
| if (f(x)) { | ||
| x0 = x; | ||
| } else { | ||
| return str; | ||
| x1 = x; | ||
| } | ||
| } | ||
| return str; | ||
| return x0; | ||
| } | ||
| // https://github.com/chaijs/loupe/pull/79 | ||
| function isHighSurrogate(char) { | ||
| return char >= "\ud800" && char <= "\udbff"; | ||
| } | ||
| export { browserFormat, format, formatRegExp, inspect, objDisplay, stringify }; | ||
| export { format, formatRegExp, inspect, stringify, truncateString }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export { LoupeOptions, StringifyOptions } from './display.js'; | ||
| export { StringifyOptions } from './display.js'; | ||
| export { DeferPromise } from './helpers.js'; | ||
@@ -3,0 +3,0 @@ export { SafeTimers } from './timers.js'; |
+3
-4
| { | ||
| "name": "@vitest/utils", | ||
| "type": "module", | ||
| "version": "4.1.5", | ||
| "version": "5.0.0-beta.1", | ||
| "description": "Shared Vitest utility functions", | ||
@@ -89,3 +89,3 @@ "license": "MIT", | ||
| "tinyrainbow": "^3.1.0", | ||
| "@vitest/pretty-format": "4.1.5" | ||
| "@vitest/pretty-format": "5.0.0-beta.1" | ||
| }, | ||
@@ -96,4 +96,3 @@ "devDependencies": { | ||
| "@types/estree": "^1.0.8", | ||
| "diff-sequences": "^29.6.3", | ||
| "loupe": "^3.2.1" | ||
| "diff-sequences": "^29.6.3" | ||
| }, | ||
@@ -100,0 +99,0 @@ "scripts": { |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
4
-20%146934
-11.95%4105
-11.47%1
Infinity%+ Added
- Removed