Skip to content

Commit

Permalink
fix: handle pretty formatting of wide arrays and array-like objects (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanwilsonperkin committed Feb 16, 2022
1 parent adf8526 commit 6094810
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 45 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand All @@ -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

Expand Down
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 += '…';
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 += '…';
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 += '…';
break;
}

if (i in list) {
result += printer(list[i], config, indentationNext, depth, refs);
}
Expand Down
53 changes: 16 additions & 37 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 @@ -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 {
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

0 comments on commit 6094810

Please sign in to comment.