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

fix: handle pretty formatting of wide arrays and array-like objects #12402

Merged
merged 11 commits into from Feb 16, 2022
17 changes: 17 additions & 0 deletions packages/jest-matcher-utils/src/__tests__/index.test.ts
Expand Up @@ -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()', () => {
Expand Down
18 changes: 14 additions & 4 deletions packages/jest-matcher-utils/src/index.ts
Expand Up @@ -89,28 +89,38 @@ 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,
});
} catch {
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 =>
Expand Down
1 change: 1 addition & 0 deletions packages/jest-schemas/src/index.ts
Expand Up @@ -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()),
Expand Down
1 change: 1 addition & 0 deletions packages/pretty-format/README.md
Expand Up @@ -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 |
Expand Down
53 changes: 53 additions & 0 deletions packages/pretty-format/src/__tests__/prettyFormat.test.ts
Expand Up @@ -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 = [
{
Expand Down
27 changes: 23 additions & 4 deletions packages/pretty-format/src/collections.ts
Expand Up @@ -43,6 +43,7 @@ export function printIteratorEntries(
separator: string = ': ',
): string {
let result = '';
let width = 0;
let current = iterator.next();

if (!current.done) {
Expand All @@ -51,6 +52,13 @@ export function printIteratorEntries(
const indentationNext = indentation + config.indent;

while (!current.done) {
result += indentationNext;

if (width++ === config.maxWidth) {
result += '...';
SimenB marked this conversation as resolved.
Show resolved Hide resolved
break;
}

const name = printer(
current.value[0],
config,
Expand All @@ -66,7 +74,7 @@ export function printIteratorEntries(
refs,
);

result += indentationNext + name + separator + value;
result += name + separator + value;

current = iterator.next();

Expand Down Expand Up @@ -97,6 +105,7 @@ export function printIteratorValues(
printer: Printer,
): string {
let result = '';
let width = 0;
let current = iterator.next();

if (!current.done) {
Expand All @@ -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 += '...';
SimenB marked this conversation as resolved.
Show resolved Hide resolved
break;
}

result += printer(current.value, config, indentationNext, depth, refs);

current = iterator.next();

Expand Down Expand Up @@ -147,6 +161,11 @@ export function printListItems(
for (let i = 0; i < list.length; i++) {
result += indentationNext;

if (i === config.maxWidth) {
result += '...';
SimenB marked this conversation as resolved.
Show resolved Hide resolved
break;
}

if (i in list) {
result += printer(list[i], config, indentationNext, depth, refs);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/pretty-format/src/index.ts
Expand Up @@ -404,6 +404,7 @@ export const DEFAULT_OPTIONS: Options = {
highlight: false,
indent: 2,
maxDepth: Infinity,
maxWidth: Infinity,
min: false,
plugins: [],
printBasicPrototype: true,
Expand Down Expand Up @@ -506,6 +507,10 @@ const getConfig = (options?: OptionsReceived): Config => ({
options && options.maxDepth !== undefined
? options.maxDepth
: DEFAULT_OPTIONS.maxDepth,
maxWidth:
options && options.maxWidth !== undefined
? options.maxWidth
: DEFAULT_OPTIONS.maxWidth,
SimenB marked this conversation as resolved.
Show resolved Hide resolved
min: options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min,
plugins:
options && options.plugins !== undefined
Expand Down
1 change: 1 addition & 0 deletions packages/pretty-format/src/types.ts
Expand Up @@ -45,6 +45,7 @@ export type Config = {
escapeString: boolean;
indent: string;
maxDepth: number;
maxWidth: number;
min: boolean;
plugins: Plugins;
printBasicPrototype: boolean;
Expand Down