From 187566a70aa4b6aa5f74952b504bbeddb5854aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 22 Aug 2022 13:14:22 +0200 Subject: [PATCH] feat(pretty-format): allow to opt out from sorting object keys with `compareKeys: null` (#12443) --- CHANGELOG.md | 1 + packages/jest-schemas/src/index.ts | 1 + packages/pretty-format/README.md | 58 +++++++++---------- .../src/__tests__/prettyFormat.test.ts | 10 +++- packages/pretty-format/src/collections.ts | 4 +- packages/pretty-format/src/index.ts | 9 ++- packages/pretty-format/src/types.ts | 5 +- 7 files changed, 52 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bfd8443c7f0..60c206aee52e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - `[@jest/test-result, @jest/types]` [**BREAKING**] Replace `Bytes` and `Milliseconds` types with `number` ([#13155](https://github.com/facebook/jest/pull/13155)) - `[jest-worker]` Adds `workerIdleMemoryLimit` option which is used as a check for worker memory leaks >= Node 16.11.0 and recycles child workers as required ([#13056](https://github.com/facebook/jest/pull/13056), [#13105](https://github.com/facebook/jest/pull/13105), [#13106](https://github.com/facebook/jest/pull/13106), [#13107](https://github.com/facebook/jest/pull/13107)) - `[pretty-format]` [**BREAKING**] Remove `ConvertAnsi` plugin in favour of `jest-serializer-ansi-escapes` ([#13040](https://github.com/facebook/jest/pull/13040)) +- `[pretty-format]` Allow to opt out from sorting object keys with `compareKeys: null` ([#12443](https://github.com/facebook/jest/pull/12443)) ### Fixes diff --git a/packages/jest-schemas/src/index.ts b/packages/jest-schemas/src/index.ts index d0f6f4010165..420d784b96dd 100644 --- a/packages/jest-schemas/src/index.ts +++ b/packages/jest-schemas/src/index.ts @@ -10,6 +10,7 @@ import {Static, Type} from '@sinclair/typebox'; const RawSnapshotFormat = Type.Partial( Type.Object({ callToJSON: Type.Readonly(Type.Boolean()), + compareKeys: Type.Readonly(Type.Null()), escapeRegex: Type.Readonly(Type.Boolean()), escapeString: Type.Readonly(Type.Boolean()), highlight: Type.Readonly(Type.Boolean()), diff --git a/packages/pretty-format/README.md b/packages/pretty-format/README.md index 55dca1a88015..0cc1bcff0f50 100755 --- a/packages/pretty-format/README.md +++ b/packages/pretty-format/README.md @@ -66,21 +66,21 @@ console.log(prettyFormat(onClick, options)); ``` -| key | type | default | description | -| :-------------------- | :-------- | :--------- | :------------------------------------------------------ | -| `callToJSON` | `boolean` | `true` | call `toJSON` method (if it exists) on objects | -| `compareKeys` | `function`| `undefined`| compare function used when sorting object keys | -| `escapeRegex` | `boolean` | `false` | escape special characters in regular expressions | -| `escapeString` | `boolean` | `true` | escape special characters in strings | -| `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 | -| `printFunctionName` | `boolean` | `true` | include or omit the name of a function | -| `theme` | `object` | | colors to highlight syntax in terminal | +| key | type | default | description | +| :-------------------- | :--------------- | :---------- | :-------------------------------------------------------------------------------------- | +| `callToJSON` | `boolean` | `true` | call `toJSON` method (if it exists) on objects | +| `compareKeys` | `function\|null` | `undefined` | compare function used when sorting object keys, `null` can be used to skip over sorting | +| `escapeRegex` | `boolean` | `false` | escape special characters in regular expressions | +| `escapeString` | `boolean` | `true` | escape special characters in strings | +| `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 | +| `printFunctionName` | `boolean` | `true` | include or omit the name of a function | +| `theme` | `object` | | colors to highlight syntax in terminal | Property values of `theme` are from [ansi-styles colors](https://github.com/chalk/ansi-styles#colors) @@ -206,20 +206,20 @@ Write `serialize` to return a string, given the arguments: ### config -| key | type | description | -| :------------------ | :-------- | :------------------------------------------------------ | -| `callToJSON` | `boolean` | call `toJSON` method (if it exists) on objects | -| `compareKeys` | `function`| compare function used when sorting object keys | -| `colors` | `Object` | escape codes for colors to highlight syntax | -| `escapeRegex` | `boolean` | escape special characters in regular expressions | -| `escapeString` | `boolean` | escape special characters in strings | -| `indent` | `string` | spaces in each level of indentation | -| `maxDepth` | `number` | levels to print in arrays, objects, elements, and so on | -| `min` | `boolean` | minimize added space: no indentation nor line breaks | -| `plugins` | `array` | plugins to serialize application-specific data types | -| `printFunctionName` | `boolean` | include or omit the name of a function | -| `spacingInner` | `string` | spacing to separate items in a list | -| `spacingOuter` | `string` | spacing to enclose a list of items | +| key | type | description | +| :------------------ | :--------------- | :-------------------------------------------------------------------------------------- | +| `callToJSON` | `boolean` | call `toJSON` method (if it exists) on objects | +| `compareKeys` | `function\|null` | compare function used when sorting object keys, `null` can be used to skip over sorting | +| `colors` | `Object` | escape codes for colors to highlight syntax | +| `escapeRegex` | `boolean` | escape special characters in regular expressions | +| `escapeString` | `boolean` | escape special characters in strings | +| `indent` | `string` | spaces in each level of indentation | +| `maxDepth` | `number` | levels to print in arrays, objects, elements, and so on | +| `min` | `boolean` | minimize added space: no indentation nor line breaks | +| `plugins` | `array` | plugins to serialize application-specific data types | +| `printFunctionName` | `boolean` | include or omit the name of a function | +| `spacingInner` | `string` | spacing to separate items in a list | +| `spacingOuter` | `string` | spacing to enclose a list of items | Each property of `colors` in `config` corresponds to a property of `theme` in `options`: diff --git a/packages/pretty-format/src/__tests__/prettyFormat.test.ts b/packages/pretty-format/src/__tests__/prettyFormat.test.ts index 3e7937160096..0d2662ef93ba 100644 --- a/packages/pretty-format/src/__tests__/prettyFormat.test.ts +++ b/packages/pretty-format/src/__tests__/prettyFormat.test.ts @@ -334,7 +334,7 @@ describe('prettyFormat()', () => { expect(prettyFormat(val)).toEqual('Object {\n "a": 2,\n "b": 1,\n}'); }); - it('prints an object with keys in their original order', () => { + it('prints an object with keys in their original order with the appropriate comparing function', () => { // eslint-disable-next-line sort-keys const val = {b: 1, a: 2}; const compareKeys = () => 0; @@ -343,6 +343,14 @@ describe('prettyFormat()', () => { ); }); + it('prints an object with keys in their original order with compareKeys set to null', () => { + // eslint-disable-next-line sort-keys + const val = {b: 1, a: 2}; + expect(prettyFormat(val, {compareKeys: null})).toEqual( + 'Object {\n "b": 1,\n "a": 2,\n}', + ); + }); + it('prints an object with keys sorted in reverse order', () => { const val = {a: 1, b: 2}; const compareKeys = (a: string, b: string) => (a > b ? -1 : 1); diff --git a/packages/pretty-format/src/collections.ts b/packages/pretty-format/src/collections.ts index df4736bcbd2e..0741737ce660 100644 --- a/packages/pretty-format/src/collections.ts +++ b/packages/pretty-format/src/collections.ts @@ -12,7 +12,9 @@ const getKeysOfEnumerableProperties = ( object: Record, compareKeys: CompareKeys, ) => { - const keys: Array = Object.keys(object).sort(compareKeys); + const rawKeys = Object.keys(object); + const keys: Array = + compareKeys !== null ? rawKeys.sort(compareKeys) : rawKeys; if (Object.getOwnPropertySymbols) { Object.getOwnPropertySymbols(object).forEach(symbol => { diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 7c26aaa05684..b2031173216a 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -400,7 +400,10 @@ const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME) as Array< keyof typeof DEFAULT_THEME >; -export const DEFAULT_OPTIONS: Options = { +// could be replaced by `satisfies` operator in the future: https://github.com/microsoft/TypeScript/issues/47920 +const toOptionsSubtype = (options: T) => options; + +export const DEFAULT_OPTIONS = toOptionsSubtype({ callToJSON: true, compareKeys: undefined, escapeRegex: false, @@ -414,7 +417,7 @@ export const DEFAULT_OPTIONS: Options = { printBasicPrototype: true, printFunctionName: true, theme: DEFAULT_THEME, -}; +}); function validateOptions(options: OptionsReceived) { Object.keys(options).forEach(key => { @@ -482,7 +485,7 @@ const getConfig = (options?: OptionsReceived): Config => ({ callToJSON: options?.callToJSON ?? DEFAULT_OPTIONS.callToJSON, colors: options?.highlight ? getColorsHighlight(options) : getColorsEmpty(), compareKeys: - typeof options?.compareKeys === 'function' + typeof options?.compareKeys === 'function' || options?.compareKeys === null ? options.compareKeys : DEFAULT_OPTIONS.compareKeys, escapeRegex: getEscapeRegex(options), diff --git a/packages/pretty-format/src/types.ts b/packages/pretty-format/src/types.ts index 62e9a0b917ff..7b3b57017d1e 100644 --- a/packages/pretty-format/src/types.ts +++ b/packages/pretty-format/src/types.ts @@ -20,7 +20,7 @@ type Print = (arg0: unknown) => string; export type Theme = Options['theme']; -export type CompareKeys = ((a: string, b: string) => number) | undefined; +export type CompareKeys = ((a: string, b: string) => number) | null | undefined; type RequiredOptions = Required; @@ -30,7 +30,8 @@ export interface Options theme: Required; } -export interface PrettyFormatOptions extends SnapshotFormat { +export interface PrettyFormatOptions + extends Omit { compareKeys?: CompareKeys; plugins?: Plugins; }