From 6094810c9d571686447c2320ba8c150add414e1b Mon Sep 17 00:00:00 2001 From: Ryan Wilson-Perkin Date: Wed, 16 Feb 2022 04:29:09 -0500 Subject: [PATCH] fix: handle pretty formatting of wide arrays and array-like objects (#12402) --- CHANGELOG.md | 2 + .../src/__tests__/index.test.ts | 17 ++++++ packages/jest-matcher-utils/src/index.ts | 18 +++++-- packages/jest-schemas/src/index.ts | 1 + packages/pretty-format/README.md | 1 + .../src/__tests__/prettyFormat.test.ts | 53 +++++++++++++++++++ packages/pretty-format/src/collections.ts | 27 ++++++++-- packages/pretty-format/src/index.ts | 53 ++++++------------- packages/pretty-format/src/types.ts | 1 + 9 files changed, 128 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 877a2233c742..407ebae86ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392)) - `[@jes/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384)) - `[jest-worker]` [**BREAKING**] Allow only absolute `workerPath` ([#12343](https://github.com/facebook/jest/pull/12343)) +- `[pretty-format]` New `maxWidth` parameter ([#12402](https://github.com/facebook/jest/pull/12402)) ### Fixes @@ -23,6 +24,7 @@ - `[jest-config]` Pass `moduleTypes` to `ts-node` to enforce CJS when transpiling ([#12397](https://github.com/facebook/jest/pull/12397)) - `[jest-environment-jsdom]` Make `jsdom` accessible to extending environments again ([#12232](https://github.com/facebook/jest/pull/12232)) - `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125)) +- `[jest-matcher-utils]` Pass maxWidth to `pretty-format` to avoid printing every element in arrays by default ([#12402](https://github.com/facebook/jest/pull/12402)) ### Chore & Maintenance diff --git a/packages/jest-matcher-utils/src/__tests__/index.test.ts b/packages/jest-matcher-utils/src/__tests__/index.test.ts index 9ed9cc9e17e3..37fb9fafc130 100644 --- a/packages/jest-matcher-utils/src/__tests__/index.test.ts +++ b/packages/jest-matcher-utils/src/__tests__/index.test.ts @@ -97,6 +97,23 @@ describe('stringify()', () => { expect(stringify(big)).toBe(prettyFormat(big, {maxDepth: 1, min: true})); expect(stringify(small)).toBe(prettyFormat(small, {min: true})); }); + + test('reduces maxWidth if stringifying very large arrays', () => { + const big: any = []; + const small: any = []; + const testString = Array(1000).join('x'); + + for (let i = 0; i < 100; i += 1) { + big[i] = testString; + } + + for (let i = 0; i < 3; i += 1) { + small[i] = testString; + } + + expect(stringify(big)).toBe(prettyFormat(big, {maxWidth: 5, min: true})); + expect(stringify(small)).toBe(prettyFormat(small, {min: true})); + }); }); describe('ensureNumbers()', () => { diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index 7210d37c624c..cdbad1a6c79f 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -89,13 +89,18 @@ export const SUGGEST_TO_CONTAIN_EQUAL = chalk.dim( 'Looks like you wanted to test for object/array equality with the stricter `toContain` matcher. You probably need to use `toContainEqual` instead.', ); -export const stringify = (object: unknown, maxDepth: number = 10): string => { +export const stringify = ( + object: unknown, + maxDepth: number = 10, + maxWidth: number = 10, +): string => { const MAX_LENGTH = 10000; let result; try { result = prettyFormat(object, { maxDepth, + maxWidth, min: true, plugins: PLUGINS, }); @@ -103,14 +108,19 @@ export const stringify = (object: unknown, maxDepth: number = 10): string => { result = prettyFormat(object, { callToJSON: false, maxDepth, + maxWidth, min: true, plugins: PLUGINS, }); } - return result.length >= MAX_LENGTH && maxDepth > 1 - ? stringify(object, Math.floor(maxDepth / 2)) - : result; + if (result.length >= MAX_LENGTH && maxDepth > 1) { + return stringify(object, Math.floor(maxDepth / 2), maxWidth); + } else if (result.length >= MAX_LENGTH && maxWidth > 1) { + return stringify(object, maxDepth, Math.floor(maxWidth / 2)); + } else { + return result; + } }; export const highlightTrailingWhitespace = (text: string): string => diff --git a/packages/jest-schemas/src/index.ts b/packages/jest-schemas/src/index.ts index b3f0a32eebe8..d0f6f4010165 100644 --- a/packages/jest-schemas/src/index.ts +++ b/packages/jest-schemas/src/index.ts @@ -15,6 +15,7 @@ const RawSnapshotFormat = Type.Partial( highlight: Type.Readonly(Type.Boolean()), indent: Type.Readonly(Type.Number({minimum: 0})), maxDepth: Type.Readonly(Type.Number({minimum: 0})), + maxWidth: Type.Readonly(Type.Number({minimum: 0})), min: Type.Readonly(Type.Boolean()), printBasicPrototype: Type.Readonly(Type.Boolean()), printFunctionName: Type.Readonly(Type.Boolean()), diff --git a/packages/pretty-format/README.md b/packages/pretty-format/README.md index c5cb0041dbfa..3dae52a159fe 100755 --- a/packages/pretty-format/README.md +++ b/packages/pretty-format/README.md @@ -75,6 +75,7 @@ console.log(prettyFormat(onClick, options)); | `highlight` | `boolean` | `false` | highlight syntax with colors in terminal (some plugins) | | `indent` | `number` | `2` | spaces in each level of indentation | | `maxDepth` | `number` | `Infinity` | levels to print in arrays, objects, elements, and so on | +| `maxWidth` | `number` | `Infinity` | number of elements to print in arrays, sets, and so on | | `min` | `boolean` | `false` | minimize added space: no indentation nor line breaks | | `plugins` | `array` | `[]` | plugins to serialize application-specific data types | | `printBasicPrototype` | `boolean` | `false` | print the prototype for plain objects and arrays | diff --git a/packages/pretty-format/src/__tests__/prettyFormat.test.ts b/packages/pretty-format/src/__tests__/prettyFormat.test.ts index ab9d0b9da214..73b9237d54b6 100644 --- a/packages/pretty-format/src/__tests__/prettyFormat.test.ts +++ b/packages/pretty-format/src/__tests__/prettyFormat.test.ts @@ -562,6 +562,59 @@ describe('prettyFormat()', () => { ); }); + describe('maxWidth option', () => { + it('applies to arrays', () => { + const val = Array(1_000_000).fill('x'); + expect(prettyFormat(val, {maxWidth: 5})).toEqual( + [ + 'Array [', + ' "x",', + ' "x",', + ' "x",', + ' "x",', + ' "x",', + ' …', + ']', + ].join('\n'), + ); + }); + + it('applies to sets', () => { + const val = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + expect(prettyFormat(val, {maxWidth: 5})).toEqual( + ['Set {', ' 1,', ' 2,', ' 3,', ' 4,', ' 5,', ' …', '}'].join( + '\n', + ), + ); + }); + + it('applies to maps', () => { + const val = new Map(); + val.set('a', 1); + val.set('b', 2); + val.set('c', 3); + val.set('d', 4); + val.set('e', 5); + val.set('f', 6); + val.set('g', 7); + val.set('h', 8); + val.set('i', 9); + val.set('j', 10); + expect(prettyFormat(val, {maxWidth: 5})).toEqual( + [ + 'Map {', + ' "a" => 1,', + ' "b" => 2,', + ' "c" => 3,', + ' "d" => 4,', + ' "e" => 5,', + ' …', + '}', + ].join('\n'), + ); + }); + }); + it('can customize the max depth', () => { const val = [ { diff --git a/packages/pretty-format/src/collections.ts b/packages/pretty-format/src/collections.ts index 97fae10163ba..0495f35be557 100644 --- a/packages/pretty-format/src/collections.ts +++ b/packages/pretty-format/src/collections.ts @@ -43,6 +43,7 @@ export function printIteratorEntries( separator: string = ': ', ): string { let result = ''; + let width = 0; let current = iterator.next(); if (!current.done) { @@ -51,6 +52,13 @@ export function printIteratorEntries( const indentationNext = indentation + config.indent; while (!current.done) { + result += indentationNext; + + if (width++ === config.maxWidth) { + result += '…'; + break; + } + const name = printer( current.value[0], config, @@ -66,7 +74,7 @@ export function printIteratorEntries( refs, ); - result += indentationNext + name + separator + value; + result += name + separator + value; current = iterator.next(); @@ -97,6 +105,7 @@ export function printIteratorValues( printer: Printer, ): string { let result = ''; + let width = 0; let current = iterator.next(); if (!current.done) { @@ -105,9 +114,14 @@ export function printIteratorValues( const indentationNext = indentation + config.indent; while (!current.done) { - result += - indentationNext + - printer(current.value, config, indentationNext, depth, refs); + result += indentationNext; + + if (width++ === config.maxWidth) { + result += '…'; + break; + } + + result += printer(current.value, config, indentationNext, depth, refs); current = iterator.next(); @@ -147,6 +161,11 @@ export function printListItems( for (let i = 0; i < list.length; i++) { result += indentationNext; + if (i === config.maxWidth) { + result += '…'; + break; + } + if (i in list) { result += printer(list[i], config, indentationNext, depth, refs); } diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 9543e6b354a8..3d9d4ce90564 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -404,6 +404,7 @@ export const DEFAULT_OPTIONS: Options = { highlight: false, indent: 2, maxDepth: Infinity, + maxWidth: Infinity, min: false, plugins: [], printBasicPrototype: true, @@ -465,56 +466,34 @@ const getColorsEmpty = (): Colors => }, Object.create(null)); const getPrintFunctionName = (options?: OptionsReceived) => - options && options.printFunctionName !== undefined - ? options.printFunctionName - : DEFAULT_OPTIONS.printFunctionName; + options?.printFunctionName ?? DEFAULT_OPTIONS.printFunctionName; const getEscapeRegex = (options?: OptionsReceived) => - options && options.escapeRegex !== undefined - ? options.escapeRegex - : DEFAULT_OPTIONS.escapeRegex; + options?.escapeRegex ?? DEFAULT_OPTIONS.escapeRegex; const getEscapeString = (options?: OptionsReceived) => - options && options.escapeString !== undefined - ? options.escapeString - : DEFAULT_OPTIONS.escapeString; + options?.escapeString ?? DEFAULT_OPTIONS.escapeString; const getConfig = (options?: OptionsReceived): Config => ({ - callToJSON: - options && options.callToJSON !== undefined - ? options.callToJSON - : DEFAULT_OPTIONS.callToJSON, - colors: - options && options.highlight - ? getColorsHighlight(options) - : getColorsEmpty(), + callToJSON: options?.callToJSON ?? DEFAULT_OPTIONS.callToJSON, + colors: options?.highlight ? getColorsHighlight(options) : getColorsEmpty(), compareKeys: - options && typeof options.compareKeys === 'function' + typeof options?.compareKeys === 'function' ? options.compareKeys : DEFAULT_OPTIONS.compareKeys, escapeRegex: getEscapeRegex(options), escapeString: getEscapeString(options), - indent: - options && options.min - ? '' - : createIndent( - options && options.indent !== undefined - ? options.indent - : DEFAULT_OPTIONS.indent, - ), - maxDepth: - options && options.maxDepth !== undefined - ? options.maxDepth - : DEFAULT_OPTIONS.maxDepth, - min: options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min, - plugins: - options && options.plugins !== undefined - ? options.plugins - : DEFAULT_OPTIONS.plugins, + indent: options?.min + ? '' + : createIndent(options?.indent ?? DEFAULT_OPTIONS.indent), + maxDepth: options?.maxDepth ?? DEFAULT_OPTIONS.maxDepth, + maxWidth: options?.maxWidth ?? DEFAULT_OPTIONS.maxWidth, + min: options?.min ?? DEFAULT_OPTIONS.min, + plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins, printBasicPrototype: options?.printBasicPrototype ?? true, printFunctionName: getPrintFunctionName(options), - spacingInner: options && options.min ? ' ' : '\n', - spacingOuter: options && options.min ? '' : '\n', + spacingInner: options?.min ? ' ' : '\n', + spacingOuter: options?.min ? '' : '\n', }); function createIndent(indent: number): string { diff --git a/packages/pretty-format/src/types.ts b/packages/pretty-format/src/types.ts index c26b77aee0f7..62e9a0b917ff 100644 --- a/packages/pretty-format/src/types.ts +++ b/packages/pretty-format/src/types.ts @@ -45,6 +45,7 @@ export type Config = { escapeString: boolean; indent: string; maxDepth: number; + maxWidth: number; min: boolean; plugins: Plugins; printBasicPrototype: boolean;