From 7e633217fb61ee8ca2be7f62fdaa7d1f3ac88368 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 2 Aug 2017 14:15:27 -0400 Subject: [PATCH 1/3] Factor out common code for collections in pretty-format --- .../src/__tests__/asymmetric_matcher.test.js | 4 +- packages/pretty-format/src/collections.js | 177 ++++++++++++++ packages/pretty-format/src/index.js | 226 ++++-------------- .../src/plugins/asymmetric_matcher.js | 43 +++- .../pretty-format/src/plugins/lib/markup.js | 83 +++---- .../src/plugins/react_element.js | 6 +- .../src/plugins/react_test_component.js | 6 +- types/PrettyFormat.js | 3 +- 8 files changed, 307 insertions(+), 241 deletions(-) create mode 100644 packages/pretty-format/src/collections.js diff --git a/packages/pretty-format/src/__tests__/asymmetric_matcher.test.js b/packages/pretty-format/src/__tests__/asymmetric_matcher.test.js index 87fbffb131c1..774ae075757f 100644 --- a/packages/pretty-format/src/__tests__/asymmetric_matcher.test.js +++ b/packages/pretty-format/src/__tests__/asymmetric_matcher.test.js @@ -196,7 +196,7 @@ describe(`indent option`, () => { describe(`maxDepth option`, () => { test(`matchers as leaf nodes`, () => { - options.maxDepth = 3; + options.maxDepth = 2; const val = { // ++depth === 1 nested: [ @@ -229,7 +229,7 @@ describe(`maxDepth option`, () => { }`); }); test(`matchers as internal nodes`, () => { - options.maxDepth = 3; + options.maxDepth = 2; const val = [ // ++depth === 1 expect.arrayContaining([ diff --git a/packages/pretty-format/src/collections.js b/packages/pretty-format/src/collections.js new file mode 100644 index 000000000000..44d4e8bd2ef2 --- /dev/null +++ b/packages/pretty-format/src/collections.js @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ + +import type {Config, Printer, Refs} from 'types/PrettyFormat'; + +// Return entries (for examples, of a map) +// with spacing, indentation, and separating punctuation (comma) +// without surrounding punctuation (for example, braces) +export function printIteratorEntries( + iterator: Iterator<[any, any]>, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + separator: string = ': ', +): string { + let result = ''; + let current = iterator.next(); + + if (!current.done) { + result += config.spacingOuter; + + const indentationNext = indentation + config.indent; + + while (!current.done) { + const name = printer( + current.value[0], + config, + indentationNext, + depth, + refs, + ); + const value = printer( + current.value[1], + config, + indentationNext, + depth, + refs, + ); + + result += indentationNext + name + separator + value; + + current = iterator.next(); + + if (!current.done) { + result += ',' + config.spacingInner; + } else if (!config.min) { + result += ','; + } + } + + result += config.spacingOuter + indentation; + } + + return result; +} + +// Return values (for examples, of a set) +// with spacing, indentation, and separating punctuation (comma) +// without surrounding punctuation (braces or brackets) +export function printIteratorValues( + iterator: Iterator, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = ''; + let current = iterator.next(); + + if (!current.done) { + result += config.spacingOuter; + + const indentationNext = indentation + config.indent; + + while (!current.done) { + result += + indentationNext + + printer(current.value, config, indentationNext, depth, refs); + + current = iterator.next(); + + if (!current.done) { + result += ',' + config.spacingInner; + } else if (!config.min) { + result += ','; + } + } + + result += config.spacingOuter + indentation; + } + + return result; +} + +// Return items (for examples, of an array) +// with spacing, indentation, and separating punctuation (comma) +// without surrounding punctuation (for example, brackets) +export function printListItems( + list: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = ''; + + if (list.length) { + result += config.spacingOuter; + + const indentationNext = indentation + config.indent; + + for (let i = 0; i < list.length; i++) { + result += + indentationNext + + printer(list[i], config, indentationNext, depth, refs); + + if (i < list.length - 1) { + result += ',' + config.spacingInner; + } else if (!config.min) { + result += ','; + } + } + + result += config.spacingOuter + indentation; + } + + return result; +} + +// Return properties of an object +// with spacing, indentation, and separating punctuation (comma) +// without surrounding punctuation (for example, braces) +export function printObjectProperties( + keys: Array, + val: Object, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = ''; + if (keys.length) { + result += config.spacingOuter; + + const indentationNext = indentation + config.indent; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const name = printer(key, config, indentationNext, depth, refs); + const value = printer(val[key], config, indentationNext, depth, refs); + + result += indentationNext + name + ': ' + value; + + if (i < keys.length - 1) { + result += ',' + config.spacingInner; + } else if (!config.min) { + result += ','; + } + } + + result += config.spacingOuter + indentation; + } + + return result; +} diff --git a/packages/pretty-format/src/index.js b/packages/pretty-format/src/index.js index 140de3b86211..b487a733ad8c 100644 --- a/packages/pretty-format/src/index.js +++ b/packages/pretty-format/src/index.js @@ -22,6 +22,13 @@ import type { import style from 'ansi-styles'; +import { + printIteratorEntries, + printIteratorValues, + printListItems, + printObjectProperties, +} from './collections'; + import AsymmetricMatcher from './plugins/asymmetric_matcher'; import ConvertAnsi from './plugins/convert_ansi'; import HTMLElement from './plugins/html_element'; @@ -152,162 +159,6 @@ function printBasicValue( return null; } -function printListItems( - list: any, - config: Config, - indentation: string, - depth: number, - refs: Refs, -): string { - let result = ''; - - if (list.length) { - result += config.spacingOuter; - - const indentationNext = indentation + config.indent; - - for (let i = 0; i < list.length; i++) { - result += - indentationNext + print(list[i], config, indentationNext, depth, refs); - - if (i < list.length - 1) { - result += ',' + config.spacingInner; - } else if (!config.min) { - result += ','; - } - } - - result += config.spacingOuter + indentation; - } - - return result; -} - -function printMapEntries( - val: Map, - config: Config, - indentation: string, - depth: number, - refs: Refs, -): string { - let result = ''; - const iterator = val.entries(); - let current = iterator.next(); - - if (!current.done) { - result += config.spacingOuter; - - const indentationNext = indentation + config.indent; - - while (!current.done) { - const name = print( - current.value[0], - config, - indentationNext, - depth, - refs, - ); - const value = print( - current.value[1], - config, - indentationNext, - depth, - refs, - ); - - result += indentationNext + name + ' => ' + value; - - current = iterator.next(); - - if (!current.done) { - result += ',' + config.spacingInner; - } else if (!config.min) { - result += ','; - } - } - - result += config.spacingOuter + indentation; - } - - return result; -} - -function printObjectProperties( - val: Object, - config: Config, - indentation: string, - depth: number, - refs: Refs, -): string { - let result = ''; - let keys = Object.keys(val).sort(); - const symbols = getSymbols(val); - - if (symbols.length) { - keys = keys.filter(key => !isSymbol(key)).concat(symbols); - } - - if (keys.length) { - result += config.spacingOuter; - - const indentationNext = indentation + config.indent; - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const name = print(key, config, indentationNext, depth, refs); - const value = print(val[key], config, indentationNext, depth, refs); - - result += indentationNext + name + ': ' + value; - - if (i < keys.length - 1) { - result += ',' + config.spacingInner; - } else if (!config.min) { - result += ','; - } - } - - result += config.spacingOuter + indentation; - } - - return result; -} - -function printSetValues( - val: Set, - config: Config, - indentation: string, - depth: number, - refs: Refs, -): string { - let result = ''; - const iterator = val.values(); - let current = iterator.next(); - - if (!current.done) { - result += config.spacingOuter; - - const indentationNext = indentation + config.indent; - - while (!current.done) { - result += - indentationNext + - print(current.value, config, indentationNext, depth, refs); - - current = iterator.next(); - - if (!current.done) { - result += ',' + config.spacingInner; - } else if (!config.min) { - result += ','; - } - } - - result += config.spacingOuter + indentation; - } - - return result; -} - function printComplexValue( val: any, config: Config, @@ -330,7 +181,7 @@ function printComplexValue( val.toJSON && typeof val.toJSON === 'function' ) { - return print(val.toJSON(), config, indentation, depth, refs); + return printer(val.toJSON(), config, indentation, depth, refs); } const toStringed = toString.call(val); @@ -339,7 +190,7 @@ function printComplexValue( ? '[Arguments]' : (min ? '' : 'Arguments ') + '[' + - printListItems(val, config, indentation, depth, refs) + + printListItems(val, config, indentation, depth, refs, printer) + ']'; } if (isToStringedArrayType(toStringed)) { @@ -347,28 +198,65 @@ function printComplexValue( ? '[' + val.constructor.name + ']' : (min ? '' : val.constructor.name + ' ') + '[' + - printListItems(val, config, indentation, depth, refs) + + printListItems(val, config, indentation, depth, refs, printer) + ']'; } if (toStringed === '[object Map]') { return hitMaxDepth ? '[Map]' - : 'Map {' + printMapEntries(val, config, indentation, depth, refs) + '}'; + : 'Map {' + + printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + ' => ', + ) + + '}'; } if (toStringed === '[object Set]') { return hitMaxDepth ? '[Set]' - : 'Set {' + printSetValues(val, config, indentation, depth, refs) + '}'; + : 'Set {' + + printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + ) + + '}'; } return hitMaxDepth ? '[' + (val.constructor ? val.constructor.name : 'Object') + ']' : (min ? '' : (val.constructor ? val.constructor.name : 'Object') + ' ') + '{' + - printObjectProperties(val, config, indentation, depth, refs) + + printObjectProperties( + getKeysAndSymbols(val), + val, + config, + indentation, + depth, + refs, + printer, + ) + '}'; } +function getKeysAndSymbols(val) { + const keys = Object.keys(val).sort(); + const symbols = getSymbols(val); + + if (symbols.length) { + return keys.filter(key => !isSymbol(key)).concat(symbols); + } + return keys; +} + function printPlugin( plugin: Plugin, val: any, @@ -378,18 +266,10 @@ function printPlugin( refs: Refs, ): string { const printed = plugin.serialize - ? plugin.serialize( - val, - config, - (valChild, indentationChild, depthChild, refsChild) => - print(valChild, config, indentationChild, depthChild, refsChild), - indentation, - depth, - refs, - ) + ? plugin.serialize(val, config, indentation, depth, refs, printer) : plugin.print( val, - valChild => print(valChild, config, indentation, depth, refs), + valChild => printer(valChild, config, indentation, depth, refs), str => { const indentationNext = indentation + config.indent; return ( @@ -422,7 +302,7 @@ function findPlugin(plugins: Plugins, val: any) { return null; } -function print( +function printer( val: any, config: Config, indentation: string, diff --git a/packages/pretty-format/src/plugins/asymmetric_matcher.js b/packages/pretty-format/src/plugins/asymmetric_matcher.js index 7b8f6a7c8860..d43fdc6fb0ca 100644 --- a/packages/pretty-format/src/plugins/asymmetric_matcher.js +++ b/packages/pretty-format/src/plugins/asymmetric_matcher.js @@ -10,19 +10,18 @@ import type {Config, NewPlugin, Printer, Refs} from 'types/PrettyFormat'; +import {printListItems, printObjectProperties} from '../collections'; + const asymmetricMatcher = Symbol.for('jest.asymmetricMatcher'); const SPACE = ' '; -class ArrayContaining extends Array {} -class ObjectContaining extends Object {} - export const serialize = ( val: any, config: Config, - print: Printer, indentation: string, depth: number, refs: Refs, + printer: Printer, ): string => { const stringedValue = val.toString(); @@ -30,10 +29,12 @@ export const serialize = ( if (++depth > config.maxDepth) { return '[' + stringedValue + ']'; } - const array = ArrayContaining.from(val.sample); return ( - (config.min ? stringedValue + SPACE : '') + - print(array, indentation, depth, refs) + stringedValue + + SPACE + + '[' + + printListItems(val.sample, config, indentation, depth, refs, printer) + + ']' ); } @@ -41,19 +42,37 @@ export const serialize = ( if (++depth > config.maxDepth) { return '[' + stringedValue + ']'; } - const object = Object.assign(new ObjectContaining(), val.sample); return ( - (config.min ? stringedValue + SPACE : '') + - print(object, indentation, depth, refs) + stringedValue + + SPACE + + '{' + + printObjectProperties( + Object.keys(val.sample).sort(), + val.sample, + config, + indentation, + depth, + refs, + printer, + ) + + '}' ); } if (stringedValue === 'StringMatching') { - return stringedValue + SPACE + print(val.sample, indentation, depth, refs); + return ( + stringedValue + + SPACE + + printer(val.sample, config, indentation, depth, refs) + ); } if (stringedValue === 'StringContaining') { - return stringedValue + SPACE + print(val.sample, indentation, depth, refs); + return ( + stringedValue + + SPACE + + printer(val.sample, config, indentation, depth, refs) + ); } return val.toAsymmetricMatcher(); diff --git a/packages/pretty-format/src/plugins/lib/markup.js b/packages/pretty-format/src/plugins/lib/markup.js index 1f9473872f33..ac475805169a 100644 --- a/packages/pretty-format/src/plugins/lib/markup.js +++ b/packages/pretty-format/src/plugins/lib/markup.js @@ -12,69 +12,58 @@ import type {Config, Printer, Refs} from 'types/PrettyFormat'; import escapeHTML from './escape_html'; -// Return spacing and indentation that precedes the property. -const printProp = ( - key: string, - value: any, - config: Config, - printer: Printer, - indentation: string, - depth: number, - refs: Refs, -): string => { - const indentationNext = indentation + config.indent; - let printed = printer(value, indentationNext, depth, refs); - - if (typeof value !== 'string') { - if (printed.indexOf('\n') !== -1) { - printed = - config.spacingOuter + - indentationNext + - printed + - config.spacingOuter + - indentation; - } - printed = '{' + printed + '}'; - } - - const colors = config.colors; - return ( - config.spacingInner + - indentation + - colors.prop.open + - key + - colors.prop.close + - '=' + - colors.value.open + - printed + - colors.value.close - ); -}; - // Return empty string if keys is empty. export const printProps = ( keys: Array, props: Object, config: Config, - printer: Printer, indentation: string, depth: number, refs: Refs, -): string => - keys - .map(key => - printProp(key, props[key], config, printer, indentation, depth, refs), - ) + printer: Printer, +): string => { + const indentationNext = indentation + config.indent; + const colors = config.colors; + return keys + .map(key => { + const value = props[key]; + let printed = printer(value, config, indentationNext, depth, refs); + + if (typeof value !== 'string') { + if (printed.indexOf('\n') !== -1) { + printed = + config.spacingOuter + + indentationNext + + printed + + config.spacingOuter + + indentation; + } + printed = '{' + printed + '}'; + } + + return ( + config.spacingInner + + indentation + + colors.prop.open + + key + + colors.prop.close + + '=' + + colors.value.open + + printed + + colors.value.close + ); + }) .join(''); +}; // Return empty string if children is empty. export const printChildren = ( children: Array, config: Config, - printer: Printer, indentation: string, depth: number, refs: Refs, + printer: Printer, ): string => { const colors = config.colors; return children @@ -84,7 +73,7 @@ export const printChildren = ( indentation + (typeof child === 'string' ? colors.content.open + escapeHTML(child) + colors.content.close - : printer(child, indentation, depth, refs)), + : printer(child, config, indentation, depth, refs)), ) .join(''); }; diff --git a/packages/pretty-format/src/plugins/react_element.js b/packages/pretty-format/src/plugins/react_element.js index 53043ce109ae..b4c6749641a1 100644 --- a/packages/pretty-format/src/plugins/react_element.js +++ b/packages/pretty-format/src/plugins/react_element.js @@ -40,10 +40,10 @@ const getType = element => { export const serialize = ( element: React$Element<*>, config: Config, - printer: Printer, indentation: string, depth: number, refs: Refs, + printer: Printer, ): string => printElement( getType(element), @@ -51,18 +51,18 @@ export const serialize = ( Object.keys(element.props).filter(key => key !== 'children').sort(), element.props, config, - printer, indentation + config.indent, depth, refs, + printer, ), printChildren( getChildren(element.props.children), config, - printer, indentation + config.indent, depth, refs, + printer, ), config, indentation, diff --git a/packages/pretty-format/src/plugins/react_test_component.js b/packages/pretty-format/src/plugins/react_test_component.js index 2b8ef287d93f..3393c6a321ca 100644 --- a/packages/pretty-format/src/plugins/react_test_component.js +++ b/packages/pretty-format/src/plugins/react_test_component.js @@ -23,10 +23,10 @@ const testSymbol = Symbol.for('react.test.json'); export const serialize = ( object: ReactTestObject, config: Config, - printer: Printer, indentation: string, depth: number, refs: Refs, + printer: Printer, ): string => printElement( object.type, @@ -38,20 +38,20 @@ export const serialize = ( // $FlowFixMe object.props, config, - printer, indentation + config.indent, depth, refs, + printer, ) : '', object.children ? printChildren( object.children, config, - printer, indentation + config.indent, depth, refs, + printer, ) : '', config, diff --git a/types/PrettyFormat.js b/types/PrettyFormat.js index 5a5358f63ac9..04d834308fdd 100644 --- a/types/PrettyFormat.js +++ b/types/PrettyFormat.js @@ -75,6 +75,7 @@ export type Config = {| export type Printer = ( val: any, + config: Config, indentation: string, depth: number, refs: Refs, @@ -86,10 +87,10 @@ export type NewPlugin = {| serialize: ( val: any, config: Config, - printer: Printer, indentation: string, depth: number, refs: Refs, + printer: Printer, ) => string, test: Test, |}; From 0efb1adf10a99deb241ba6d03abae631154663a7 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 2 Aug 2017 16:07:30 -0400 Subject: [PATCH 2/3] Fix pretty lint error --- packages/pretty-format/src/collections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pretty-format/src/collections.js b/packages/pretty-format/src/collections.js index 44d4e8bd2ef2..75d1a98ed389 100644 --- a/packages/pretty-format/src/collections.js +++ b/packages/pretty-format/src/collections.js @@ -142,7 +142,7 @@ export function printListItems( // with spacing, indentation, and separating punctuation (comma) // without surrounding punctuation (for example, braces) export function printObjectProperties( - keys: Array, + keys: Array, val: Object, config: Config, indentation: string, From 09c312f2890697ff0da49f55acaac5588065aede Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 2 Aug 2017 16:37:32 -0400 Subject: [PATCH 3/3] Improve comments and put printObjectProperties back together --- packages/pretty-format/src/collections.js | 31 ++++++++++++++----- packages/pretty-format/src/index.js | 26 +--------------- .../src/plugins/asymmetric_matcher.js | 1 - 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/packages/pretty-format/src/collections.js b/packages/pretty-format/src/collections.js index 75d1a98ed389..82fa62fecb1a 100644 --- a/packages/pretty-format/src/collections.js +++ b/packages/pretty-format/src/collections.js @@ -10,8 +10,14 @@ import type {Config, Printer, Refs} from 'types/PrettyFormat'; -// Return entries (for examples, of a map) -// with spacing, indentation, and separating punctuation (comma) +const getSymbols = Object.getOwnPropertySymbols || (obj => []); + +const isSymbol = key => + // $FlowFixMe string literal `symbol`. This value is not a valid `typeof` return value + typeof key === 'symbol' || toString.call(key) === '[object Symbol]'; + +// Return entries (for example, of a map) +// with spacing, indentation, and comma // without surrounding punctuation (for example, braces) export function printIteratorEntries( iterator: Iterator<[any, any]>, @@ -20,6 +26,9 @@ export function printIteratorEntries( depth: number, refs: Refs, printer: Printer, + // Too bad, so sad that separator for ECMAScript Map has been ' => ' + // What a distracting diff if you change a data structure to/from + // ECMAScript Object or Immutable.Map/OrderedMap which use the default. separator: string = ': ', ): string { let result = ''; @@ -63,8 +72,8 @@ export function printIteratorEntries( return result; } -// Return values (for examples, of a set) -// with spacing, indentation, and separating punctuation (comma) +// Return values (for example, of a set) +// with spacing, indentation, and comma // without surrounding punctuation (braces or brackets) export function printIteratorValues( iterator: Iterator, @@ -102,8 +111,8 @@ export function printIteratorValues( return result; } -// Return items (for examples, of an array) -// with spacing, indentation, and separating punctuation (comma) +// Return items (for example, of an array) +// with spacing, indentation, and comma // without surrounding punctuation (for example, brackets) export function printListItems( list: any, @@ -139,10 +148,9 @@ export function printListItems( } // Return properties of an object -// with spacing, indentation, and separating punctuation (comma) +// with spacing, indentation, and comma // without surrounding punctuation (for example, braces) export function printObjectProperties( - keys: Array, val: Object, config: Config, indentation: string, @@ -151,6 +159,13 @@ export function printObjectProperties( printer: Printer, ): string { let result = ''; + let keys = Object.keys(val).sort(); + const symbols = getSymbols(val); + + if (symbols.length) { + keys = keys.filter(key => !isSymbol(key)).concat(symbols); + } + if (keys.length) { result += config.spacingOuter; diff --git a/packages/pretty-format/src/index.js b/packages/pretty-format/src/index.js index b487a733ad8c..d4b6647f5367 100644 --- a/packages/pretty-format/src/index.js +++ b/packages/pretty-format/src/index.js @@ -45,12 +45,6 @@ const symbolToString = Symbol.prototype.toString; const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/; const NEWLINE_REGEXP = /\n/gi; -const getSymbols = Object.getOwnPropertySymbols || (obj => []); - -const isSymbol = key => - // $FlowFixMe string literal `symbol`. This value is not a valid `typeof` return value - typeof key === 'symbol' || toString.call(key) === '[object Symbol]'; - function isToStringedArrayType(toStringed: string): boolean { return ( toStringed === '[object Array]' || @@ -235,28 +229,10 @@ function printComplexValue( ? '[' + (val.constructor ? val.constructor.name : 'Object') + ']' : (min ? '' : (val.constructor ? val.constructor.name : 'Object') + ' ') + '{' + - printObjectProperties( - getKeysAndSymbols(val), - val, - config, - indentation, - depth, - refs, - printer, - ) + + printObjectProperties(val, config, indentation, depth, refs, printer) + '}'; } -function getKeysAndSymbols(val) { - const keys = Object.keys(val).sort(); - const symbols = getSymbols(val); - - if (symbols.length) { - return keys.filter(key => !isSymbol(key)).concat(symbols); - } - return keys; -} - function printPlugin( plugin: Plugin, val: any, diff --git a/packages/pretty-format/src/plugins/asymmetric_matcher.js b/packages/pretty-format/src/plugins/asymmetric_matcher.js index d43fdc6fb0ca..92407d771845 100644 --- a/packages/pretty-format/src/plugins/asymmetric_matcher.js +++ b/packages/pretty-format/src/plugins/asymmetric_matcher.js @@ -47,7 +47,6 @@ export const serialize = ( SPACE + '{' + printObjectProperties( - Object.keys(val.sample).sort(), val.sample, config, indentation,