diff --git a/packages/browser/src/client/logger.ts b/packages/browser/src/client/logger.ts index 6b4fc854fcbe..c65eaa3f3290 100644 --- a/packages/browser/src/client/logger.ts +++ b/packages/browser/src/client/logger.ts @@ -4,7 +4,7 @@ import { importId } from './utils' const { Date, console } = globalThis export async function setupConsoleLogSpy() { - const { stringify, format, utilInspect } = await importId('vitest/utils') as typeof import('vitest/utils') + const { stringify, format, inspect } = await importId('vitest/utils') as typeof import('vitest/utils') const { log, info, error, dir, dirxml, trace, time, timeEnd, timeLog, warn, debug, count, countReset } = console const formatInput = (input: unknown) => { if (input instanceof Node) @@ -42,7 +42,7 @@ export async function setupConsoleLogSpy() { console.warn = stderr(warn) console.dir = (item, options) => { - sendLog('stdout', utilInspect(item, options)) + sendLog('stdout', inspect(item, options)) return dir(item, options) } diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index 615d0d83ea3f..0fc271f726f6 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -298,7 +298,7 @@ function formatTitle(template: string, items: any[], idx: number) { let formatted = format(template, ...items.slice(0, count)) if (isObject(items[0])) { formatted = formatted.replace(/\$([$\w_.]+)/g, - (_, key) => objDisplay(objectAttr(items[0], key), runner?.config?.chaiConfig) as unknown as string, + (_, key) => objDisplay(objectAttr(items[0], key), { truncate: runner?.config?.chaiConfig?.truncateThreshold }) as unknown as string, // https://github.com/chaijs/chai/pull/1490 ) } diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts index 95a637ec2359..9bc5ef729f96 100644 --- a/packages/utils/src/display.ts +++ b/packages/utils/src/display.ts @@ -1,36 +1,99 @@ -// eslint-disable-next-line unicorn/prefer-node-protocol -import * as util from 'util' - +// since this is already part of Vitest via Chai, we can just reuse it without increasing the size of bundle // @ts-expect-error doesn't have types -import loupeImport from 'loupe' +import { inspect as loupe } from 'loupe' interface LoupeOptions { - truncateThreshold?: number + showHidden?: boolean | undefined + depth?: number | null | undefined + colors?: boolean | undefined + customInspect?: boolean | undefined + showProxy?: boolean | undefined + maxArrayLength?: number | null | undefined + maxStringLength?: number | null | undefined + breakLength?: number | undefined + compact?: boolean | number | undefined + sorted?: boolean | ((a: string, b: string) => number) | undefined + getters?: 'get' | 'set' | boolean | undefined + numericSeparator?: boolean | undefined + truncate?: number } -const loupe = (typeof loupeImport.default === 'function' ? loupeImport.default : loupeImport) +const formatRegExp = /%[sdj%]/g -export function format(...args: any[]) { - return util.format(...args) -} +export function format(...args: unknown[]) { + if (typeof args[0] !== 'string') { + const objects = [] + for (let i = 0; i < args.length; i++) + objects.push(inspect(args[i])) + return objects.join(' ') + } + + const len = args.length + let i = 1 + const template = args[0] + let str = String(template).replace(formatRegExp, (x) => { + if (x === '%%') + return '%' + if (i >= len) + return x + switch (x) { + case '%s': { + const value = args[i++] + if (typeof value === 'bigint') + return `${value.toString()}n` + if (typeof value === 'number' && value === 0 && 1 / value < 0) + return '-0' + if (typeof value === 'object' && value !== null) + return inspect(value, { depth: 0, colors: false, compact: 3 }) + return String(value) + } + case '%d': { + const value = args[i++] + if (typeof value === 'bigint') + return `${value.toString()}n` + return Number(value).toString() + } + case '%i': { + const value = args[i++] + if (typeof value === 'bigint') + return `${value.toString()}n` + return Number.parseInt(String(value)).toString() + } + case '%f': return Number.parseFloat(String(args[i++])).toString() + case '%o': return inspect(args[i++], { showHidden: true, showProxy: true }) + case '%O': return inspect(args[i++]) + case '%c': return '' + case '%j': + try { + return JSON.stringify(args[i++]) + } + catch (_) { + return '[Circular]' + } + default: + return x + } + }) + + for (let x = args[i]; i < len; x = args[++i]) { + if (x === null || typeof x !== 'object') + str += ` ${x}` -export function utilInspect(item: unknown, options?: util.InspectOptions) { - return util.inspect(item, options) + else + str += ` ${inspect(x)}` + } + return str } -// chai utils -export function loupeInspect(obj: unknown, options: LoupeOptions = {}): string { - return loupe(obj, { - depth: 2, - truncate: options.truncateThreshold === 0 - ? Infinity - : (options.truncateThreshold ?? 40), - }) +export function inspect(obj: unknown, options: LoupeOptions = {}) { + if (options.truncate === 0) + options.truncate = Infinity + return loupe(obj, options) } export function objDisplay(obj: unknown, options: LoupeOptions = {}): string { - const truncateThreshold = options.truncateThreshold ?? 40 - const str = loupeInspect(obj, options) + const truncateThreshold = options.truncate ?? 40 + const str = inspect(obj, options) const type = Object.prototype.toString.call(obj) if (truncateThreshold && str.length >= truncateThreshold) {