From c61165ed9ab17c9e63e824b2f81b4615cca69dbb Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 14 Aug 2017 03:11:12 -0400 Subject: [PATCH] Improve Seq and remove newline from non-min empty in Immutable plugin (#4241) * Improve Seq and remove newline from non-min empty in Immutable plugin * Edit comment * Add tests for arguments and iterator * Add assertions size toBeUndefined for lazy Seq --- .../src/__tests__/immutable.test.js | 166 +++++++++++++++--- .../pretty-format/src/plugins/immutable.js | 110 ++++++++---- 2 files changed, 216 insertions(+), 60 deletions(-) diff --git a/packages/pretty-format/src/__tests__/immutable.test.js b/packages/pretty-format/src/__tests__/immutable.test.js index 059f4e52a5e6..20867fd464ac 100644 --- a/packages/pretty-format/src/__tests__/immutable.test.js +++ b/packages/pretty-format/src/__tests__/immutable.test.js @@ -34,7 +34,7 @@ describe('Immutable.OrderedSet', () => { it('supports an empty collection {min: false}', () => { expect( Immutable.OrderedSet([]), - ).toPrettyPrintTo('Immutable.OrderedSet [\n]', {min: false}); + ).toPrettyPrintTo('Immutable.OrderedSet []', {min: false}); }); it('supports a single string element', () => { @@ -125,7 +125,7 @@ describe('Immutable.List', () => { }); it('supports an empty collection {min: false}', () => { - expect(Immutable.List([])).toPrettyPrintTo('Immutable.List [\n]', { + expect(Immutable.List([])).toPrettyPrintTo('Immutable.List []', { min: false, }); }); @@ -206,7 +206,7 @@ describe('Immutable.Stack', () => { }); it('supports an empty collection {min: false}', () => { - expect(Immutable.Stack([])).toPrettyPrintTo('Immutable.Stack [\n]', { + expect(Immutable.Stack([])).toPrettyPrintTo('Immutable.Stack []', { min: false, }); }); @@ -287,7 +287,7 @@ describe('Immutable.Set', () => { }); it('supports an empty collection {min: false}', () => { - expect(Immutable.Set([])).toPrettyPrintTo('Immutable.Set [\n]', { + expect(Immutable.Set([])).toPrettyPrintTo('Immutable.Set []', { min: false, }); }); @@ -365,7 +365,7 @@ describe('Immutable.Map', () => { }); it('supports an empty collection {min: false}', () => { - expect(Immutable.Map({})).toPrettyPrintTo('Immutable.Map {\n}', { + expect(Immutable.Map({})).toPrettyPrintTo('Immutable.Map {}', { min: false, }); }); @@ -430,7 +430,7 @@ describe('Immutable.OrderedMap', () => { it('supports an empty collection {min: false}', () => { expect( Immutable.OrderedMap({}), - ).toPrettyPrintTo('Immutable.OrderedMap {\n}', {min: false}); + ).toPrettyPrintTo('Immutable.OrderedMap {}', {min: false}); }); it('supports an object with single key', () => { @@ -545,7 +545,7 @@ describe('Immutable.Record', () => { it('supports an empty record {min: false}', () => { const ABRecord = Immutable.Record({}, 'ABRecord'); - expect(ABRecord()).toPrettyPrintTo('Immutable.ABRecord {\n}', { + expect(ABRecord()).toPrettyPrintTo('Immutable.ABRecord {}', { min: false, }); }); @@ -643,8 +643,7 @@ describe('indentation of heterogeneous collections', () => { [ 'Object {', ' "filter": "all",', - ' "todos": Immutable.List [', - ' ],', + ' "todos": Immutable.List [],', '}', ].join('\n'), ); @@ -652,7 +651,7 @@ describe('indentation of heterogeneous collections', () => { test('empty Immutable.Map as child of Array', () => { const val = [Immutable.Map({})]; expect(val).toPrettyPrintTo( - ['Array [', ' Immutable.Map {', ' },', ']'].join('\n'), + ['Array [', ' Immutable.Map {},', ']'].join('\n'), ); }); @@ -785,6 +784,27 @@ describe('maxDepth option', () => { expect(val).toPrettyPrintTo(expected, {maxDepth: 1}); }); + test('Immutable.Seq as child of Immutable.Map', () => { + const val = { + // ++depth === 1 + filter: 'all', + todos: Immutable.Seq( + Immutable.List([ + Immutable.Map({ + completed: true, + text: 'Return if depth exceeds max', + }), + ]), + ), + }; + const expected = [ + 'Object {', + ' "filter": "all",', + ' "todos": [Immutable.Seq],', + '}', + ].join('\n'); + expect(val).toPrettyPrintTo(expected, {maxDepth: 1}); + }); test('Immutable.Map as descendants in immutable collection', () => { const val = Immutable.Map({ // ++depth === 1 @@ -817,47 +837,139 @@ describe('maxDepth option', () => { }); describe('Immutable.Seq', () => { - const expected = '[Immutable.Seq]'; it('supports an empty sequence from array {min: true}', () => { - expect(Immutable.Seq([])).toPrettyPrintTo(expected, {min: true}); + expect(Immutable.Seq([])).toPrettyPrintTo('Immutable.Seq []', {min: true}); }); it('supports an empty sequence from array {min: false}', () => { - expect(Immutable.Seq([])).toPrettyPrintTo(expected, {min: false}); + expect(Immutable.Seq([])).toPrettyPrintTo('Immutable.Seq []', {min: false}); }); it('supports a non-empty sequence from array {min: true}', () => { - expect(Immutable.Seq([0, 1, 2])).toPrettyPrintTo(expected, {min: true}); + expect( + Immutable.Seq([0, 1, 2]), + ).toPrettyPrintTo('Immutable.Seq [0, 1, 2]', {min: true}); }); it('supports a non-empty sequence from array {min: false}', () => { - expect(Immutable.Seq([0, 1, 2])).toPrettyPrintTo(expected, {min: false}); + expect( + Immutable.Seq([0, 1, 2]), + ).toPrettyPrintTo('Immutable.Seq [\n 0,\n 1,\n 2,\n]', {min: false}); + }); + + it('supports a non-empty sequence from arguments', () => { + function returnArguments(...args) { + return arguments; + } + expect(Immutable.Seq(returnArguments(0, 1, 2))).toPrettyPrintTo( + 'Immutable.Seq [\n 0,\n 1,\n 2,\n]', + ); }); + it('supports an empty sequence from object {min: true}', () => { - expect(Immutable.Seq({})).toPrettyPrintTo(expected, {min: true}); + expect(Immutable.Seq({})).toPrettyPrintTo('Immutable.Seq {}', {min: true}); }); it('supports an empty sequence from object {min: false}', () => { - expect(Immutable.Seq({})).toPrettyPrintTo(expected, {min: false}); + expect(Immutable.Seq({})).toPrettyPrintTo('Immutable.Seq {}', {min: false}); }); it('supports a non-empty sequence from object {min: true}', () => { - expect(Immutable.Seq({key: 'value'})).toPrettyPrintTo(expected, { + expect( + Immutable.Seq({key: 'value'}), + ).toPrettyPrintTo('Immutable.Seq {"key": "value"}', { min: true, }); }); it('supports a non-empty sequence from object {min: false}', () => { - expect(Immutable.Seq({key: 'value'})).toPrettyPrintTo(expected, { + expect( + Immutable.Seq({key: 'value'}), + ).toPrettyPrintTo('Immutable.Seq {\n "key": "value",\n}', { min: false, }); }); - it('supports a sequence from Immutable.Map', () => { + + it('supports a sequence of entries from Immutable.Map', () => { expect(Immutable.Seq(Immutable.Map({key: 'value'}))).toPrettyPrintTo( - expected, + 'Immutable.Seq {\n "key": "value",\n}', ); }); - it('supports a sequence from Immutable.List', () => { - expect(Immutable.Seq(Immutable.List([0, 1, 2]))).toPrettyPrintTo(expected); + + it('supports a sequence of values from ECMAScript Set', () => { + expect(Immutable.Seq(new Set([0, 1, 2]))).toPrettyPrintTo( + 'Immutable.Seq [\n 0,\n 1,\n 2,\n]', + ); }); - it('supports a sequence from Immutable.Set', () => { - expect(Immutable.Seq(Immutable.Set([0, 1, 2]))).toPrettyPrintTo(expected); + it('supports a sequence of values from Immutable.List', () => { + expect(Immutable.Seq(Immutable.List([0, 1, 2]))).toPrettyPrintTo( + 'Immutable.Seq [\n 0,\n 1,\n 2,\n]', + ); + }); + it('supports a sequence of values from Immutable.Set', () => { + expect(Immutable.Seq(Immutable.Set([0, 1, 2]))).toPrettyPrintTo( + 'Immutable.Seq [\n 0,\n 1,\n 2,\n]', + ); }); - it('supports a sequence from Immutable.Stack', () => { - expect(Immutable.Seq(Immutable.Stack([0, 1, 2]))).toPrettyPrintTo(expected); + it('supports a sequence of values from Immutable.Stack', () => { + expect(Immutable.Seq(Immutable.Stack([0, 1, 2]))).toPrettyPrintTo( + 'Immutable.Seq [\n 0,\n 1,\n 2,\n]', + ); + }); +}); + +describe('Immutable.Seq lazy entries', () => { + const expected = 'Immutable.Seq {…}'; + const object = {key0: '', key1: '1'}; + const filterer = value => value.length !== 0; + + // undefined size confirms correct criteria for lazy Seq + test('from object properties', () => { + const val = Immutable.Seq(object).filter(filterer); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); + }); + test('from Immutable.Map entries', () => { + const val = Immutable.Seq(Immutable.Map(object)).filter(filterer); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); + }); +}); + +describe('Immutable.Seq lazy values', () => { + const expected = 'Immutable.Seq […]'; + const array = ['', '1', '22']; + const filterer = item => item.length !== 0; + + test('from Immutable.Range', () => { + const val = Immutable.Range(1, Infinity); + expect(val.size).toBe(Infinity); + expect(val).toPrettyPrintTo(expected); + }); + + // undefined size confirms correct criteria for lazy Seq + test('from iterator', () => { + function returnIterator(values) { + let i = 0; + return { + next() { + return i < values.length + ? {done: false, value: values[i++]} + : {done: true}; + }, + }; + } + const val = Immutable.Seq(returnIterator(array)); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); + }); + test('from array items', () => { + const val = Immutable.Seq(array).filter(filterer); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); + }); + test('from Immutable.List values', () => { + const val = Immutable.Seq(Immutable.List(array)).filter(filterer); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); + }); + test('from ECMAScript Set values', () => { + const val = Immutable.Seq(new Set(array)).filter(filterer); + expect(val.size).toBeUndefined(); + expect(val).toPrettyPrintTo(expected); }); }); diff --git a/packages/pretty-format/src/plugins/immutable.js b/packages/pretty-format/src/plugins/immutable.js index e6f6ac85e0b0..cfd38dc3a2e7 100644 --- a/packages/pretty-format/src/plugins/immutable.js +++ b/packages/pretty-format/src/plugins/immutable.js @@ -13,16 +13,19 @@ import {printIteratorEntries, printIteratorValues} from '../collections'; // SENTINEL constants are from https://github.com/facebook/immutable-js const IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@'; -const IS_RECORD_SENTINEL = '@@__IMMUTABLE_RECORD__@@'; // v4 or later const IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@'; +const IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@'; const IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@'; +const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@'; +const IS_RECORD_SENTINEL = '@@__IMMUTABLE_RECORD__@@'; // immutable v4 const IS_SEQ_SENTINEL = '@@__IMMUTABLE_SEQ__@@'; const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@'; const IS_STACK_SENTINEL = '@@__IMMUTABLE_STACK__@@'; -const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@'; const getImmutableName = name => 'Immutable.' + name; +const printAsLeaf = name => '[' + name + ']'; const SPACE = ' '; +const LAZY = '…'; // Seq is lazy if it calls a method like filter const printImmutableEntries = ( val: any, @@ -34,20 +37,18 @@ const printImmutableEntries = ( type: string, ): string => ++depth > config.maxDepth - ? '[' + getImmutableName(type) + ']' + ? printAsLeaf(getImmutableName(type)) : getImmutableName(type) + SPACE + '{' + - (val.size !== 0 - ? printIteratorEntries( - val.entries(), - config, - indentation, - depth, - refs, - printer, - ) - : config.spacingOuter + indentation) + + printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + ) + '}'; // Return an iterator for Immutable Record in v4 or later. @@ -75,26 +76,71 @@ const printImmutableRecord = ( // _name property is defined only for an Immutable Record instance // which was constructed with a second optional descriptive name arg const name = getImmutableName(val._name || 'Record'); - const size = Array.isArray(val._keys) ? val._keys.length : val.size; const entries = typeof Array.isArray(val._keys) - ? getRecordEntries(val) // v4 or later - : val.entries(); // v3 or earlier + ? getRecordEntries(val) // immutable v4 + : val.entries(); // Record is a collection in immutable v3 return ++depth > config.maxDepth - ? '[' + name + ']' + ? printAsLeaf(name) : name + SPACE + '{' + - (size !== 0 + printIteratorEntries(entries, config, indentation, depth, refs, printer) + + '}'; +}; + +const printImmutableSeq = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string => { + const name = getImmutableName('Seq'); + + if (++depth > config.maxDepth) { + return printAsLeaf(name); + } + + if (val[IS_KEYED_SENTINEL]) { + return ( + name + + SPACE + + '{' + + // from Immutable collection of entries or from ECMAScript object + (val._iter || val._object ? printIteratorEntries( - entries, + val.entries(), config, indentation, depth, refs, printer, ) - : config.spacingOuter + indentation) + - '}'; + : LAZY) + + '}' + ); + } + + return ( + name + + SPACE + + '[' + + (val._iter || // from Immutable collection of values + val._array || // from ECMAScript array + val._collection || // from ECMAScript collection in immutable v4 + val._iterable // from ECMAScript collection in immutable v3 + ? printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + ) + : LAZY) + + ']' + ); }; const printImmutableValues = ( @@ -107,20 +153,18 @@ const printImmutableValues = ( type: string, ): string => ++depth > config.maxDepth - ? '[' + getImmutableName(type) + ']' + ? printAsLeaf(getImmutableName(type)) : getImmutableName(type) + SPACE + '[' + - (val.size !== 0 - ? printIteratorValues( - val.values(), - config, - indentation, - depth, - refs, - printer, - ) - : config.spacingOuter + indentation) + + printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + ) + ']'; export const serialize = ( @@ -178,7 +222,7 @@ export const serialize = ( } if (val[IS_SEQ_SENTINEL]) { - return '[' + getImmutableName('Seq') + ']'; + return printImmutableSeq(val, config, indentation, depth, refs, printer); } // For compatibility with immutable v3 and v4, let record be the default.