Skip to content

Commit 3f802da

Browse files
hi-ogawaclaudecodex
authored
refactor!: replace loupe.inspect with pretty-format (#9609)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Codex <noreply@openai.com>
1 parent edacb0f commit 3f802da

34 files changed

Lines changed: 814 additions & 252 deletions

docs/api/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ test.each`
618618
```
619619

620620
::: tip
621-
Vitest processes `$values` with Chai `format` method. If the value is too truncated, you can increase [chaiConfig.truncateThreshold](/config/chaiconfig#chaiconfig-truncatethreshold) in your config file.
621+
Vitest formats interpolated title values with its display formatter. If the value is too truncated, you can increase [taskTitleValueFormatTruncate](/config/tasktitlevalueformattruncate) in your config file.
622622
:::
623623

624624
## test.for

docs/config/chaiconfig.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,4 @@ Influences whether or not the `showDiff` flag should be included in the thrown A
2929
- **Type:** `number`
3030
- **Default:** `40`
3131

32-
Sets length threshold for actual and expected values in assertion errors. If this threshold is exceeded, for example for large data structures, the value is replaced with something like `[ Array(3) ]` or `{ Object (prop1, prop2) }`. Set it to `0` if you want to disable truncating altogether.
33-
34-
This config option affects truncating values in `test.each` titles and inside the assertion error message.
32+
Sets length threshold for actual and expected values in assertion error messages. If this threshold is exceeded, for example for large data structures, the value is replaced with something like `[ Array(3) ]` or `{ Object (prop1, prop2) }`. Set it to `0` if you want to disable truncating altogether.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
title: taskTitleValueFormatTruncate | Config
3+
outline: deep
4+
---
5+
6+
# taskTitleValueFormatTruncate <CRoot /> {#tasktitlevalueformattruncate}
7+
8+
- **Type** `number`
9+
- **Default:** `40`
10+
11+
Sets the length limit for formatted values interpolated into generated task titles.
12+
13+
This affects values inserted by APIs like `test.each` and `test.for`, including both `$value` and `%` placeholder formatting.
14+
15+
Set it to `0` to disable truncation.

packages/browser/src/client/tester/logger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { browserFormat } from 'vitest/internal/browser'
1+
import { format } from 'vitest/internal/browser'
22
import { getConfig } from '../utils'
33
import { rpc } from './rpc'
44
import { getBrowserRunner } from './runner'
@@ -30,7 +30,7 @@ export function setupConsoleLogSpy(): void {
3030

3131
console.dir = (item, options) => {
3232
dir(item, options)
33-
sendLog('stdout', browserFormat(item))
33+
sendLog('stdout', processLog([item]))
3434
}
3535

3636
console.dirxml = (...args) => {
@@ -114,7 +114,7 @@ function stderr(base: (...args: unknown[]) => void) {
114114
}
115115

116116
function processLog(args: unknown[]) {
117-
return browserFormat(...args)
117+
return format(args, { multiline: true })
118118
}
119119

120120
function sendLog(

packages/expect/src/jest-expect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Constructable } from '@vitest/utils'
44
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
55
import type { Assertion, ChaiPlugin } from './types'
66
import { isMockFunction } from '@vitest/spy'
7+
import { inspect } from '@vitest/utils/display'
78
import { assertTypes, ordinal } from '@vitest/utils/helpers'
89
import c from 'tinyrainbow'
910
import { JEST_MATCHERS_OBJECT } from './constants'
@@ -483,7 +484,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
483484
&& (args.length === 1 || jestEquals(expected, value, customTesters))
484485

485486
const valueString
486-
= args.length === 1 ? '' : ` with value ${utils.objDisplay(expected)}`
487+
= args.length === 1 ? '' : ` with value ${inspect(expected, { truncate: 40 })}`
487488

488489
return this.assert(
489490
pass,

packages/pretty-format/USAGE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,16 @@ Object {
5151
| `printBasicPrototype` | `boolean` | `true` | Print `Object` and `Array` prefixes for plain objects and arrays |
5252
| `printFunctionName` | `boolean` | `true` | Include or omit the function name |
5353
| `printShadowRoot` | `boolean` | `true` | Include shadow-root contents when formatting DOM nodes |
54+
| `quoteKeys` | `boolean` | `true` | Always quote object property keys |
55+
| `singleQuote` | `boolean` | `false` | Print strings using single quotes instead of double quotes |
56+
| `spacingInner` | `string` | `\n` | Whitespace after commas between items or entries |
57+
| `spacingOuter` | `string` | `\n` | Whitespace just inside `[]` / `{}` delimiters |
5458

5559
Important:
5660

5761
- `plugins: []` means the package does not auto-enable its built-in plugins by default
5862
- Vitest features opt into their own plugin stacks and option presets
63+
- `min: true` also changes the defaults of other options to `spacingInner: ' '`, `spacingOuter: ''`, and `printBasicPrototype: false`
5964

6065
## Built-in Plugins
6166

packages/pretty-format/src/collections.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function getKeysOfEnumerableProperties(object: Record<string, unknown>, compareK
2121
}
2222
}
2323

24-
return keys as Array<string>
24+
return keys
2525
}
2626

2727
/**
@@ -37,6 +37,7 @@ export function printIteratorEntries(
3737
refs: Refs,
3838
printer: Printer,
3939
separator = ': ',
40+
length?: number,
4041
): string {
4142
let result = ''
4243
let width = 0
@@ -51,7 +52,7 @@ export function printIteratorEntries(
5152
result += indentationNext
5253

5354
if (width++ === config.maxWidth) {
54-
result += '…'
55+
result += typeof length === 'number' ? `…(${length - width + 1})` : '…'
5556
break
5657
}
5758

@@ -100,6 +101,7 @@ export function printIteratorValues(
100101
depth: number,
101102
refs: Refs,
102103
printer: Printer,
104+
length?: number,
103105
): string {
104106
let result = ''
105107
let width = 0
@@ -114,7 +116,7 @@ export function printIteratorValues(
114116
result += indentationNext
115117

116118
if (width++ === config.maxWidth) {
117-
result += '…'
119+
result += typeof length === 'number' ? `…(${length - width + 1})` : '…'
118120
break
119121
}
120122

@@ -163,7 +165,7 @@ export function printListItems(
163165
result += indentationNext
164166

165167
if (i === config.maxWidth) {
166-
result += '…'
168+
result += `…(${length - i})`
167169
break
168170
}
169171

@@ -197,27 +199,37 @@ export function printListItems(
197199
* without surrounding punctuation (for example, braces)
198200
*/
199201
export function printObjectProperties(
200-
val: Record<string, unknown>,
202+
val: Record<string | symbol, unknown>,
201203
config: Config,
202204
indentation: string,
203205
depth: number,
204206
refs: Refs,
205207
printer: Printer,
208+
compareKeysOverride: CompareKeys = config.compareKeys,
206209
): string {
207210
let result = ''
208-
const keys = getKeysOfEnumerableProperties(val, config.compareKeys)
211+
const keys = getKeysOfEnumerableProperties(val, compareKeysOverride)
209212

210213
if (keys.length > 0) {
211214
result += config.spacingOuter
212215

213216
const indentationNext = indentation + config.indent
214217

215218
for (let i = 0; i < keys.length; i++) {
219+
result += indentationNext
220+
221+
if (i === config.maxWidth) {
222+
result += `…(${keys.length - i})`
223+
break
224+
}
225+
216226
const key = keys[i]
217-
const name = printer(key, config, indentationNext, depth, refs)
227+
const name = !config.quoteKeys && isUnquotableKey(key)
228+
? key as string
229+
: printer(key, config, indentationNext, depth, refs)
218230
const value = printer(val[key], config, indentationNext, depth, refs)
219231

220-
result += `${indentationNext + name}: ${value}`
232+
result += `${name}: ${value}`
221233

222234
if (i < keys.length - 1) {
223235
result += `,${config.spacingInner}`
@@ -232,3 +244,11 @@ export function printObjectProperties(
232244

233245
return result
234246
}
247+
248+
// https://github.com/nodejs/node/blob/61102cdbb3d59155ad5bb4fc9419627a31e63f7a/lib/internal/util/inspect.js#L249
249+
// /^[a-zA-Z_][a-zA-Z_0-9]*$/
250+
const keyStrRegExp = /^[a-z_]\w*$/i
251+
252+
function isUnquotableKey(key: string | symbol): boolean {
253+
return typeof key === 'string' && key !== '__proto__' && keyStrRegExp.test(key)
254+
}

packages/pretty-format/src/index.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function printBasicValue(
111111
printFunctionName: boolean,
112112
escapeRegex: boolean,
113113
escapeString: boolean,
114+
singleQuote: boolean,
114115
): string | null {
115116
if (val === true || val === false) {
116117
return `${val}`
@@ -131,10 +132,15 @@ function printBasicValue(
131132
return printBigInt(val)
132133
}
133134
if (typeOf === 'string') {
135+
const q = singleQuote ? '\'' : '"'
134136
if (escapeString) {
135-
return `"${val.replaceAll(/"|\\/g, '\\$&')}"`
137+
// escape quote in each case, e.g.
138+
// it's me -> 'it\'s me'
139+
// say "hi" -> "say \"hi\""
140+
const escapePattern = singleQuote ? /['\\]/g : /["\\]/g
141+
return `${q}${val.replaceAll(escapePattern, '\\$&')}${q}`
136142
}
137-
return `"${val}"`
143+
return `${q}${val}${q}`
138144
}
139145
if (typeOf === 'function') {
140146
return printFunction(val, printFunctionName)
@@ -229,11 +235,9 @@ function printComplexValue(
229235
return hitMaxDepth
230236
? `[${val.constructor.name}]`
231237
: `${
232-
min
238+
!config.printBasicPrototype && val.constructor.name === 'Array'
233239
? ''
234-
: !config.printBasicPrototype && val.constructor.name === 'Array'
235-
? ''
236-
: `${val.constructor.name} `
240+
: `${val.constructor.name} `
237241
}[${printListItems(val, config, indentation, depth, refs, printer)}]`
238242
}
239243
if (toStringed === '[object Map]') {
@@ -247,6 +251,7 @@ function printComplexValue(
247251
refs,
248252
printer,
249253
' => ',
254+
val.size,
250255
)}}`
251256
}
252257
if (toStringed === '[object Set]') {
@@ -259,6 +264,7 @@ function printComplexValue(
259264
depth,
260265
refs,
261266
printer,
267+
val.size,
262268
)}}`
263269
}
264270

@@ -267,11 +273,9 @@ function printComplexValue(
267273
return hitMaxDepth || isWindow(val)
268274
? `[${getConstructorName(val)}]`
269275
: `${
270-
min
276+
!config.printBasicPrototype && getConstructorName(val) === 'Object'
271277
? ''
272-
: !config.printBasicPrototype && getConstructorName(val) === 'Object'
273-
? ''
274-
: `${getConstructorName(val)} `
278+
: `${getConstructorName(val)} `
275279
}{${printObjectProperties(
276280
val,
277281
config,
@@ -300,13 +304,14 @@ const ErrorPlugin: NewPlugin = {
300304
const name = val.name !== 'Error' ? val.name : getConstructorName(val as any)
301305
return hitMaxDepth
302306
? `[${name}]`
303-
: `${name} {${printIteratorEntries(
304-
Object.entries(entries).values(),
307+
: `${name} {${printObjectProperties(
308+
entries,
305309
config,
306310
indentation,
307311
depth,
308312
refs,
309313
printer,
314+
null,
310315
)}}`
311316
},
312317
}
@@ -392,6 +397,7 @@ function printer(
392397
config.printFunctionName,
393398
config.escapeRegex,
394399
config.escapeString,
400+
config.singleQuote,
395401
)
396402
if (basicResult !== null) {
397403
result = basicResult
@@ -453,6 +459,10 @@ export const DEFAULT_OPTIONS: Options = {
453459
printFunctionName: true,
454460
printShadowRoot: true,
455461
theme: DEFAULT_THEME,
462+
singleQuote: false,
463+
quoteKeys: true,
464+
spacingInner: '\n',
465+
spacingOuter: '\n',
456466
} satisfies Options
457467

458468
function validateOptions(options: OptionsReceived) {
@@ -525,11 +535,13 @@ function getConfig(options?: OptionsReceived): Config {
525535
maxWidth: options?.maxWidth ?? DEFAULT_OPTIONS.maxWidth,
526536
min: options?.min ?? DEFAULT_OPTIONS.min,
527537
plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins,
528-
printBasicPrototype: options?.printBasicPrototype ?? true,
538+
printBasicPrototype: options?.printBasicPrototype ?? !options?.min,
529539
printFunctionName: getPrintFunctionName(options),
530540
printShadowRoot: options?.printShadowRoot ?? true,
531-
spacingInner: options?.min ? ' ' : '\n',
532-
spacingOuter: options?.min ? '' : '\n',
541+
spacingInner: options?.spacingInner ?? (options?.min ? ' ' : '\n'),
542+
spacingOuter: options?.spacingOuter ?? (options?.min ? '' : '\n'),
543+
singleQuote: options?.singleQuote ?? DEFAULT_OPTIONS.singleQuote,
544+
quoteKeys: options?.quoteKeys ?? DEFAULT_OPTIONS.quoteKeys,
533545
maxOutputLength: options?.maxOutputLength ?? DEFAULT_OPTIONS.maxOutputLength,
534546
_outputLengthPerDepth: [],
535547
}
@@ -547,25 +559,26 @@ function createIndent(indent: number): string {
547559
export function format(val: unknown, options?: OptionsReceived): string {
548560
if (options) {
549561
validateOptions(options)
550-
if (options.plugins) {
551-
const plugin = findPlugin(options.plugins, val)
552-
if (plugin !== null) {
553-
return printPlugin(plugin, val, getConfig(options), '', 0, [])
554-
}
555-
}
562+
}
563+
564+
const config = getConfig(options)
565+
const plugin = findPlugin(config.plugins, val)
566+
if (plugin !== null) {
567+
return printPlugin(plugin, val, config, '', 0, [])
556568
}
557569

558570
const basicResult = printBasicValue(
559571
val,
560-
getPrintFunctionName(options),
561-
getEscapeRegex(options),
562-
getEscapeString(options),
572+
config.printFunctionName,
573+
config.escapeRegex,
574+
config.escapeString,
575+
config.singleQuote,
563576
)
564577
if (basicResult !== null) {
565578
return basicResult
566579
}
567580

568-
return printComplexValue(val, getConfig(options), '', 0, [])
581+
return printComplexValue(val, config, '', 0, [])
569582
}
570583

571584
export type {

0 commit comments

Comments
 (0)