diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap index 75a482ba8acf..7d1c41967f82 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -20,7 +20,7 @@ exports[`lastCalledWith works with Immutable.js objects 1`] = ` "expect(jest.fn()).not.lastCalledWith(expected) Expected mock function to not have been last called with: - [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" + [Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}, Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}]" `; exports[`lastCalledWith works with Map 1`] = ` @@ -239,7 +239,7 @@ exports[`toHaveBeenCalledWith works with Immutable.js objects 1`] = ` "expect(jest.fn()).not.toHaveBeenCalledWith(expected) Expected mock function not to have been called with: - [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" + [Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}, Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}]" `; exports[`toHaveBeenCalledWith works with Map 1`] = ` @@ -322,7 +322,7 @@ exports[`toHaveBeenLastCalledWith works with Immutable.js objects 1`] = ` "expect(jest.fn()).not.toHaveBeenLastCalledWith(expected) Expected mock function to not have been last called with: - [Immutable.Map {a: {\\"b\\": \\"c\\"}}, Immutable.Map {a: {\\"b\\": \\"c\\"}}]" + [Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}, Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}]" `; exports[`toHaveBeenLastCalledWith works with Map 1`] = ` diff --git a/packages/jest-snapshot/src/__tests__/plugins.test.js b/packages/jest-snapshot/src/__tests__/plugins.test.js index ed463a0aa5f5..bde7bfaad3af 100644 --- a/packages/jest-snapshot/src/__tests__/plugins.test.js +++ b/packages/jest-snapshot/src/__tests__/plugins.test.js @@ -29,7 +29,7 @@ const testPath = names => { it('gets plugins', () => { const {getSerializers} = require('../plugins'); const plugins = getSerializers(); - expect(plugins.length).toBe(10); + expect(plugins.length).toBe(4); }); it('adds plugins from an empty array', () => testPath([])); diff --git a/packages/pretty-format/src/__tests__/expect_util.js b/packages/pretty-format/src/__tests__/expect_util.js index 5cb623fa163b..f99f06ea4b6d 100644 --- a/packages/pretty-format/src/__tests__/expect_util.js +++ b/packages/pretty-format/src/__tests__/expect_util.js @@ -10,21 +10,21 @@ 'use strict'; -import type {Plugins} from 'types/PrettyFormat'; +import type {OptionsReceived, Plugins} from 'types/PrettyFormat'; const diff = require('jest-diff'); const prettyFormat = require('../'); module.exports = { getPrettyPrint: (plugins: Plugins) => - function(received: any, expected: any, opts: any) { + function(received: any, expected: any, options?: OptionsReceived) { const prettyFormatted = prettyFormat( received, Object.assign( - { + ({ plugins, - }, - opts, + }: OptionsReceived), + options, ), ); const pass = prettyFormatted === expected; diff --git a/packages/pretty-format/src/__tests__/immutable.test.js b/packages/pretty-format/src/__tests__/immutable.test.js index 54b88a0bd70b..f0e45b913f3f 100644 --- a/packages/pretty-format/src/__tests__/immutable.test.js +++ b/packages/pretty-format/src/__tests__/immutable.test.js @@ -12,19 +12,18 @@ import React from 'react'; import Immutable from 'immutable'; -import ReactElementPlugin from '../plugins/react_element'; -import ReactTestComponentPlugin from '../plugins/react_test_component'; -import ImmutablePlugins from '../plugins/immutable_plugins'; +import prettyFormat from '../'; +const {Immutable: ImmutablePlugins, ReactElement} = prettyFormat.plugins; import expectUtil from './expect_util'; const toPrettyPrintTo = expectUtil.getPrettyPrint( - [ReactElementPlugin, ReactTestComponentPlugin].concat(ImmutablePlugins), + [ReactElement].concat(ImmutablePlugins), ); const expect = global.expect; expect.extend({toPrettyPrintTo}); -describe('Immutable.OrderedSet plugin', () => { +describe('Immutable.OrderedSet', () => { it('supports an empty collection {min: true}', () => { expect( Immutable.OrderedSet([]), @@ -97,19 +96,19 @@ describe('Immutable.OrderedSet plugin', () => { ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.OrderedSet([reactComponent, reactComponent]), + Immutable.OrderedSet([reactElement, reactElement]), ).toPrettyPrintTo('Immutable.OrderedSet [Hello World]', { min: true, }); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.OrderedSet([reactComponent, reactComponent]), + Immutable.OrderedSet([reactElement, reactElement]), ).toPrettyPrintTo( 'Immutable.OrderedSet [\n \n Hello World\n ,\n]', {min: false}, @@ -117,7 +116,7 @@ describe('Immutable.OrderedSet plugin', () => { }); }); -describe('Immutable.List plugin', () => { +describe('Immutable.List', () => { it('supports an empty collection {min: true}', () => { expect(Immutable.List([])).toPrettyPrintTo('Immutable.List []', { min: true, @@ -180,25 +179,25 @@ describe('Immutable.List plugin', () => { ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.List([reactComponent, reactComponent]), + Immutable.List([reactElement, reactElement]), ).toPrettyPrintTo( 'Immutable.List [Hello World, Hello World]', {min: true}, ); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); - expect(Immutable.List([reactComponent, reactComponent])).toPrettyPrintTo( + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); + expect(Immutable.List([reactElement, reactElement])).toPrettyPrintTo( 'Immutable.List [\n \n Hello World\n ,\n \n Hello World\n ,\n]', ); }); }); -describe('Immutable.Stack plugin', () => { +describe('Immutable.Stack', () => { it('supports an empty collection {min: true}', () => { expect(Immutable.Stack([])).toPrettyPrintTo('Immutable.Stack []', { min: true, @@ -263,25 +262,25 @@ describe('Immutable.Stack plugin', () => { ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.Stack([reactComponent, reactComponent]), + Immutable.Stack([reactElement, reactElement]), ).toPrettyPrintTo( 'Immutable.Stack [Hello World, Hello World]', {min: true}, ); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); - expect(Immutable.Stack([reactComponent, reactComponent])).toPrettyPrintTo( + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); + expect(Immutable.Stack([reactElement, reactElement])).toPrettyPrintTo( 'Immutable.Stack [\n \n Hello World\n ,\n \n Hello World\n ,\n]', ); }); }); -describe('Immutable.Set plugin', () => { +describe('Immutable.Set', () => { it('supports an empty collection {min: true}', () => { expect(Immutable.Set([])).toPrettyPrintTo('Immutable.Set []', {min: true}); }); @@ -342,24 +341,24 @@ describe('Immutable.Set plugin', () => { ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.Set([reactComponent, reactComponent]), + Immutable.Set([reactElement, reactElement]), ).toPrettyPrintTo('Immutable.Set [Hello World]', { min: true, }); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); - expect(Immutable.Set([reactComponent, reactComponent])).toPrettyPrintTo( + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); + expect(Immutable.Set([reactElement, reactElement])).toPrettyPrintTo( 'Immutable.Set [\n \n Hello World\n ,\n]', ); }); }); -describe('Immutable.Map plugin', () => { +describe('Immutable.Map', () => { it('supports an empty collection {min: true}', () => { expect(Immutable.Map({})).toPrettyPrintTo('Immutable.Map {}', {min: true}); }); @@ -371,7 +370,7 @@ describe('Immutable.Map plugin', () => { }); it('supports an object with single key', () => { - expect(Immutable.Map({a: 1})).toPrettyPrintTo('Immutable.Map {a: 1}', { + expect(Immutable.Map({a: 1})).toPrettyPrintTo('Immutable.Map {"a": 1}', { min: true, }); }); @@ -379,50 +378,48 @@ describe('Immutable.Map plugin', () => { it('supports an object with multiple keys {min: true}', () => { expect( Immutable.Map({a: 1, b: 2, c: 3}), - ).toPrettyPrintTo('Immutable.Map {a: 1, b: 2, c: 3}', {min: true}); + ).toPrettyPrintTo('Immutable.Map {"a": 1, "b": 2, "c": 3}', {min: true}); }); it('supports an object with multiple keys {min: false}', () => { expect(Immutable.Map({a: 1, b: 2, c: 3})).toPrettyPrintTo( - 'Immutable.Map {\n a: 1,\n b: 2,\n c: 3,\n}', + 'Immutable.Map {\n "a": 1,\n "b": 2,\n "c": 3,\n}', ); }); it('supports object elements {min: true}', () => { expect( Immutable.Map({key: {a: 1, b: 2, c: 3}}), - ).toPrettyPrintTo('Immutable.Map {key: {"a": 1, "b": 2, "c": 3}}', { + ).toPrettyPrintTo('Immutable.Map {"key": {"a": 1, "b": 2, "c": 3}}', { min: true, }); }); it('supports object elements {min: false}', () => { expect(Immutable.Map({key: {a: 1, b: 2, c: 3}})).toPrettyPrintTo( - 'Immutable.Map {\n key: Object {\n "a": 1,\n "b": 2,\n "c": 3,\n },\n}', + 'Immutable.Map {\n "key": Object {\n "a": 1,\n "b": 2,\n "c": 3,\n },\n}', ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.Map({a: reactComponent, b: reactComponent}), + Immutable.Map({a: reactElement, b: reactElement}), ).toPrettyPrintTo( - 'Immutable.Map {a: Hello World, b: Hello World}', + 'Immutable.Map {"a": Hello World, "b": Hello World}', {min: true}, ); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); - expect( - Immutable.Map({a: reactComponent, b: reactComponent}), - ).toPrettyPrintTo( - 'Immutable.Map {\n a: \n Hello World\n ,\n b: \n Hello World\n ,\n}', + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); + expect(Immutable.Map({a: reactElement, b: reactElement})).toPrettyPrintTo( + 'Immutable.Map {\n "a": \n Hello World\n ,\n "b": \n Hello World\n ,\n}', ); }); }); -describe('Immutable.OrderedMap plugin', () => { +describe('Immutable.OrderedMap', () => { it('supports an empty collection {min: true}', () => { expect( Immutable.OrderedMap({}), @@ -438,56 +435,104 @@ describe('Immutable.OrderedMap plugin', () => { it('supports an object with single key', () => { expect( Immutable.OrderedMap({a: 1}), - ).toPrettyPrintTo('Immutable.OrderedMap {a: 1}', {min: true}); + ).toPrettyPrintTo('Immutable.OrderedMap {"a": 1}', {min: true}); }); it('supports an object with multiple keys {min: true}', () => { expect( Immutable.OrderedMap({a: 1, b: 2, c: 3}), - ).toPrettyPrintTo('Immutable.OrderedMap {a: 1, b: 2, c: 3}', {min: true}); + ).toPrettyPrintTo('Immutable.OrderedMap {"a": 1, "b": 2, "c": 3}', { + min: true, + }); }); it('supports an object with multiple keys {min: false}', () => { expect(Immutable.OrderedMap({a: 1, b: 2, c: 3})).toPrettyPrintTo( - 'Immutable.OrderedMap {\n a: 1,\n b: 2,\n c: 3,\n}', + 'Immutable.OrderedMap {\n "a": 1,\n "b": 2,\n "c": 3,\n}', ); }); it('supports object elements {min: true}', () => { expect( Immutable.OrderedMap({key: {a: 1, b: 2, c: 3}}), - ).toPrettyPrintTo('Immutable.OrderedMap {key: {"a": 1, "b": 2, "c": 3}}', { - min: true, - }); + ).toPrettyPrintTo( + 'Immutable.OrderedMap {"key": {"a": 1, "b": 2, "c": 3}}', + { + min: true, + }, + ); }); it('supports object elements {min: false}', () => { expect(Immutable.OrderedMap({key: {a: 1, b: 2, c: 3}})).toPrettyPrintTo( - 'Immutable.OrderedMap {\n key: Object {\n "a": 1,\n "b": 2,\n "c": 3,\n },\n}', + 'Immutable.OrderedMap {\n "key": Object {\n "a": 1,\n "b": 2,\n "c": 3,\n },\n}', ); }); - it('supports React components {min: true}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: true}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.OrderedMap({a: reactComponent, b: reactComponent}), + Immutable.OrderedMap({a: reactElement, b: reactElement}), ).toPrettyPrintTo( - 'Immutable.OrderedMap {a: Hello World, b: Hello World}', + 'Immutable.OrderedMap {"a": Hello World, "b": Hello World}', {min: true}, ); }); - it('supports React components {min: false}', () => { - const reactComponent = React.createElement('Mouse', null, 'Hello World'); + it('supports React elements {min: false}', () => { + const reactElement = React.createElement('Mouse', null, 'Hello World'); expect( - Immutable.OrderedMap({a: reactComponent, b: reactComponent}), + Immutable.OrderedMap({a: reactElement, b: reactElement}), ).toPrettyPrintTo( - 'Immutable.OrderedMap {\n a: \n Hello World\n ,\n b: \n Hello World\n ,\n}', + 'Immutable.OrderedMap {\n "a": \n Hello World\n ,\n "b": \n Hello World\n ,\n}', ); }); + + it('supports non-string keys', () => { + const val = Immutable.OrderedMap([ + [false, 'boolean'], + ['false', 'string'], + [0, 'number'], + ['0', 'string'], + [null, 'null'], + ['null', 'string'], + [undefined, 'undefined'], + ['undefined', 'string'], + [Symbol('description'), 'symbol'], + ['Symbol(description)', 'string'], + [['array', 'key'], 'array'], + [{key: 'value'}, 'object'], + [Immutable.Map({key: 'value'}), 'immutable map'], + ]); + const expected = [ + 'Immutable.OrderedMap {', + ' false: "boolean",', + ' "false": "string",', + ' 0: "number",', + ' "0": "string",', + ' null: "null",', + ' "null": "string",', + ' undefined: "undefined",', + ' "undefined": "string",', + ' Symbol(description): "symbol",', + ' "Symbol(description)": "string",', + ' Array [', + ' "array",', + ' "key",', + ' ]: "array",', + ' Object {', + ' "key": "value",', + ' }: "object",', + ' Immutable.Map {', + ' "key": "value",', + ' }: "immutable map",', + '}', + ].join('\n'); + expect(val).toPrettyPrintTo(expected); + }); }); -describe('Immutable.Record plugin', () => { +describe('Immutable.Record', () => { it('supports an empty record {min: true}', () => { const ABRecord = Immutable.Record({}, 'ABRecord'); @@ -507,7 +552,7 @@ describe('Immutable.Record plugin', () => { it('supports a record with descriptive name', () => { const ABRecord = Immutable.Record({a: 1, b: 2}, 'ABRecord'); - expect(ABRecord()).toPrettyPrintTo('Immutable.ABRecord {a: 1, b: 2}', { + expect(ABRecord()).toPrettyPrintTo('Immutable.ABRecord {"a": 1, "b": 2}', { min: true, }); }); @@ -515,7 +560,7 @@ describe('Immutable.Record plugin', () => { it('supports a record without descriptive name', () => { const ABRecord = Immutable.Record({a: 1, b: 2}); - expect(ABRecord()).toPrettyPrintTo('Immutable.Record {a: 1, b: 2}', { + expect(ABRecord()).toPrettyPrintTo('Immutable.Record {"a": 1, "b": 2}', { min: true, }); }); @@ -525,14 +570,14 @@ describe('Immutable.Record plugin', () => { expect( ABRecord({a: 3, b: 4}), - ).toPrettyPrintTo('Immutable.ABRecord {a: 3, b: 4}', {min: true}); + ).toPrettyPrintTo('Immutable.ABRecord {"a": 3, "b": 4}', {min: true}); }); it('supports a record with values {min: false}', () => { const ABRecord = Immutable.Record({a: 1, b: 2}, 'ABRecord'); expect(ABRecord({a: 3, b: 4})).toPrettyPrintTo( - 'Immutable.ABRecord {\n a: 3,\n b: 4,\n}', + 'Immutable.ABRecord {\n "a": 3,\n "b": 4,\n}', ); }); @@ -544,9 +589,12 @@ describe('Immutable.Record plugin', () => { expect( ABRecord(), - ).toPrettyPrintTo('Immutable.ABRecord {a: Immutable.Map {c: 1}, b: 2}', { - min: true, - }); + ).toPrettyPrintTo( + 'Immutable.ABRecord {"a": Immutable.Map {"c": 1}, "b": 2}', + { + min: true, + }, + ); }); it('supports a record with Map value {min: false}', () => { @@ -556,7 +604,7 @@ describe('Immutable.Record plugin', () => { ); expect(ABRecord()).toPrettyPrintTo( - 'Immutable.ABRecord {\n a: Immutable.Map {\n c: 1,\n },\n b: 2,\n}', + 'Immutable.ABRecord {\n "a": Immutable.Map {\n "c": 1,\n },\n "b": 2,\n}', ); }); @@ -567,7 +615,7 @@ describe('Immutable.Record plugin', () => { expect( ABRecord(), ).toPrettyPrintTo( - 'Immutable.ABRecord {a: Immutable.CDRecord {c: 3, d: 4}, b: 2}', + 'Immutable.ABRecord {"a": Immutable.CDRecord {"c": 3, "d": 4}, "b": 2}', {min: true}, ); }); @@ -577,7 +625,238 @@ describe('Immutable.Record plugin', () => { const ABRecord = Immutable.Record({a: CDRecord(), b: 2}, 'ABRecord'); expect(ABRecord()).toPrettyPrintTo( - 'Immutable.ABRecord {\n a: Immutable.CDRecord {\n c: 3,\n d: 4,\n },\n b: 2,\n}', + 'Immutable.ABRecord {\n "a": Immutable.CDRecord {\n "c": 3,\n "d": 4,\n },\n "b": 2,\n}', ); }); }); + +describe('indentation of heterogeneous collections', () => { + // Don’t interpret tests that pretty-format and plugins are compatible + // as recommendation to compose immutable and non-immutable collections. + test('empty Immutable.List as child of Object', () => { + const val = { + filter: 'all', + todos: Immutable.List([]), + }; + expect(val).toPrettyPrintTo( + [ + 'Object {', + ' "filter": "all",', + ' "todos": Immutable.List [', + ' ],', + '}', + ].join('\n'), + ); + }); + test('empty Immutable.Map as child of Array', () => { + const val = [Immutable.Map({})]; + expect(val).toPrettyPrintTo( + ['Array [', ' Immutable.Map {', ' },', ']'].join('\n'), + ); + }); + + test('non-empty Array as child of Immutable.Map', () => { + const val = Immutable.Map({ + filter: 'completed', + todos: [ + Immutable.Map({ + completed: true, + text: 'Replace print with serialize', + }), + ], + }); + expect(val).toPrettyPrintTo( + [ + 'Immutable.Map {', + ' "filter": "completed",', + ' "todos": Array [', + ' Immutable.Map {', + ' "completed": true,', + ' "text": "Replace print with serialize",', + ' },', + ' ],', + '}', + ].join('\n'), + ); + }); + test('non-empty Object as child of Immutable.List', () => { + const val = Immutable.List([ + { + completed: true, + text: 'Replace print with serialize', + }, + ]); + expect(val).toPrettyPrintTo( + [ + 'Immutable.List [', + ' Object {', + ' "completed": true,', + ' "text": "Replace print with serialize",', + ' },', + ']', + ].join('\n'), + ); + }); +}); + +describe('indent option', () => { + const val = Immutable.Map({ + filter: 'completed', + todos: Immutable.List([ + Immutable.Map({ + completed: true, + text: 'Replace print with serialize', + }), + Immutable.Map({ + completed: false, + text: 'Return if depth exceeds max', + }), + ]), + }); + const expected = [ + 'Immutable.Map {', + ' "filter": "completed",', + ' "todos": Immutable.List [', + ' Immutable.Map {', + ' "completed": true,', + ' "text": "Replace print with serialize",', + ' },', + ' Immutable.Map {', + ' "completed": false,', + ' "text": "Return if depth exceeds max",', + ' },', + ' ],', + '}', + ].join('\n'); + test('default implicit: 2 spaces', () => { + expect(val).toPrettyPrintTo(expected); + }); + test('default explicit: 2 spaces', () => { + expect(val).toPrettyPrintTo(expected, {indent: 2}); + }); + + // Tests assume that no strings in val contain multiple adjacent spaces! + test('non-default: 0 spaces', () => { + const indent = 0; + expect(val).toPrettyPrintTo(expected.replace(/ {2}/g, ' '.repeat(indent)), { + indent, + }); + }); + test('non-default: 4 spaces', () => { + const indent = 4; + expect(val).toPrettyPrintTo(expected.replace(/ {2}/g, ' '.repeat(indent)), { + indent, + }); + }); +}); + +describe('maxDepth option', () => { + // Don’t interpret tests that pretty-format and plugins are compatible + // as recommendation to compose immutable and non-immutable collections. + test('Immutable.List as child of Object', () => { + const val = { + // ++depth === 1 + filter: 'all', + todos: Immutable.List([ + Immutable.Map({ + completed: true, + text: 'Return if depth exceeds max', + }), + ]), + }; + const expected = [ + 'Object {', + ' "filter": "all",', + ' "todos": [Immutable.List],', + '}', + ].join('\n'); + expect(val).toPrettyPrintTo(expected, {maxDepth: 1}); + }); + test('Immutable.Map as child of Array', () => { + const val = [ + // ++depth === 1 + Immutable.Map({ + completed: false, + text: 'Return if depth exceeds max', + }), + ]; + const expected = ['Array [', ' [Immutable.Map],', ']'].join('\n'); + expect(val).toPrettyPrintTo(expected, {maxDepth: 1}); + }); + + test('Immutable.Map as descendants in immutable collection', () => { + const val = Immutable.Map({ + // ++depth === 1 + filter: 'uncompleted', + todos: Immutable.List([ + // ++depth === 2 + Immutable.Map({ + // ++depth === 3 + completed: true, + text: 'Replace print with serialize', + }), + Immutable.Map({ + // ++depth === 3 + completed: true, + text: 'Return if depth exceeds max', + }), + ]), + }); + const expected = [ + 'Immutable.Map {', + ' "filter": "uncompleted",', + ' "todos": Immutable.List [', + ' [Immutable.Map],', + ' [Immutable.Map],', + ' ],', + '}', + ].join('\n'); + expect(val).toPrettyPrintTo(expected, {maxDepth: 2}); + }); +}); + +describe('Immutable.Seq', () => { + const expected = '[Immutable.Seq]'; + it('supports an empty sequence from array {min: true}', () => { + expect(Immutable.Seq([])).toPrettyPrintTo(expected, {min: true}); + }); + it('supports an empty sequence from array {min: false}', () => { + expect(Immutable.Seq([])).toPrettyPrintTo(expected, {min: false}); + }); + it('supports a non-empty sequence from array {min: true}', () => { + expect(Immutable.Seq([0, 1, 2])).toPrettyPrintTo(expected, {min: true}); + }); + it('supports a non-empty sequence from array {min: false}', () => { + expect(Immutable.Seq([0, 1, 2])).toPrettyPrintTo(expected, {min: false}); + }); + it('supports an empty sequence from object {min: true}', () => { + expect(Immutable.Seq({})).toPrettyPrintTo(expected, {min: true}); + }); + it('supports an empty sequence from object {min: false}', () => { + expect(Immutable.Seq({})).toPrettyPrintTo(expected, {min: false}); + }); + it('supports a non-empty sequence from object {min: true}', () => { + expect(Immutable.Seq({key: 'value'})).toPrettyPrintTo(expected, { + min: true, + }); + }); + it('supports a non-empty sequence from object {min: false}', () => { + expect(Immutable.Seq({key: 'value'})).toPrettyPrintTo(expected, { + min: false, + }); + }); + it('supports a sequence from Immutable.Map', () => { + expect(Immutable.Seq(Immutable.Map({key: 'value'}))).toPrettyPrintTo( + expected, + ); + }); + it('supports a sequence from Immutable.List', () => { + expect(Immutable.Seq(Immutable.List([0, 1, 2]))).toPrettyPrintTo(expected); + }); + it('supports a sequence from Immutable.Set', () => { + expect(Immutable.Seq(Immutable.Set([0, 1, 2]))).toPrettyPrintTo(expected); + }); + it('supports a sequence from Immutable.Stack', () => { + expect(Immutable.Seq(Immutable.Stack([0, 1, 2]))).toPrettyPrintTo(expected); + }); +}); diff --git a/packages/pretty-format/src/__tests__/pretty_format.test.js b/packages/pretty-format/src/__tests__/pretty_format.test.js index 0cd01cd16772..d5faa065aa94 100644 --- a/packages/pretty-format/src/__tests__/pretty_format.test.js +++ b/packages/pretty-format/src/__tests__/pretty_format.test.js @@ -162,11 +162,42 @@ describe('prettyFormat()', () => { }); it('prints a map with non-string keys', () => { - const val = new Map(); - val.set({prop: 'value'}, {prop: 'value'}); - expect(prettyFormat(val)).toEqual( - 'Map {\n Object {\n "prop": "value",\n } => Object {\n "prop": "value",\n },\n}', - ); + const val = new Map([ + [false, 'boolean'], + ['false', 'string'], + [0, 'number'], + ['0', 'string'], + [null, 'null'], + ['null', 'string'], + [undefined, 'undefined'], + ['undefined', 'string'], + [Symbol('description'), 'symbol'], + ['Symbol(description)', 'string'], + [['array', 'key'], 'array'], + [{key: 'value'}, 'object'], + ]); + const expected = [ + 'Map {', + ' false => "boolean",', + ' "false" => "string",', + ' 0 => "number",', + ' "0" => "string",', + ' null => "null",', + ' "null" => "string",', + ' undefined => "undefined",', + ' "undefined" => "string",', + ' Symbol(description) => "symbol",', + ' "Symbol(description)" => "string",', + ' Array [', + ' "array",', + ' "key",', + ' ] => "array",', + ' Object {', + ' "key": "value",', + ' } => "object",', + '}', + ].join('\n'); + expect(prettyFormat(val)).toEqual(expected); }); it('prints NaN', () => { diff --git a/packages/pretty-format/src/collections.js b/packages/pretty-format/src/collections.js index 82fa62fecb1a..c3f7980930b6 100644 --- a/packages/pretty-format/src/collections.js +++ b/packages/pretty-format/src/collections.js @@ -20,7 +20,10 @@ const isSymbol = key => // with spacing, indentation, and comma // without surrounding punctuation (for example, braces) export function printIteratorEntries( - iterator: Iterator<[any, any]>, + // Flow 0.51.0: property `@@iterator` of $Iterator not found in Object + // To allow simplistic getRecordIterator in immutable.js + // replaced Iterator<[any, any]> with any + iterator: any, config: Config, indentation: string, depth: number, diff --git a/packages/pretty-format/src/index.js b/packages/pretty-format/src/index.js index d4b6647f5367..ade05a5211c0 100644 --- a/packages/pretty-format/src/index.js +++ b/packages/pretty-format/src/index.js @@ -32,7 +32,7 @@ import { import AsymmetricMatcher from './plugins/asymmetric_matcher'; import ConvertAnsi from './plugins/convert_ansi'; import HTMLElement from './plugins/html_element'; -import Immutable from './plugins/immutable_plugins'; +import Immutable from './plugins/immutable'; import ReactElement from './plugins/react_element'; import ReactTestComponent from './plugins/react_test_component'; diff --git a/packages/pretty-format/src/plugins/immutable.js b/packages/pretty-format/src/plugins/immutable.js new file mode 100644 index 000000000000..e6f6ac85e0b0 --- /dev/null +++ b/packages/pretty-format/src/plugins/immutable.js @@ -0,0 +1,191 @@ +/** + * 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, NewPlugin, Refs} from 'types/PrettyFormat'; +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_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@'; +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 SPACE = ' '; + +const printImmutableEntries = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + type: string, +): string => + ++depth > config.maxDepth + ? '[' + getImmutableName(type) + ']' + : getImmutableName(type) + + SPACE + + '{' + + (val.size !== 0 + ? printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + ) + : config.spacingOuter + indentation) + + '}'; + +// Return an iterator for Immutable Record in v4 or later. +const getRecordEntries = val => { + let i = 0; + return { + next() { + if (i < val._keys.length) { + const key = val._keys[i++]; + return {done: false, value: [key, val.get(key)]}; + } + return {done: true}; + }, + }; +}; + +const printImmutableRecord = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string => { + // _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 + return ++depth > config.maxDepth + ? '[' + name + ']' + : name + + SPACE + + '{' + + (size !== 0 + ? printIteratorEntries( + entries, + config, + indentation, + depth, + refs, + printer, + ) + : config.spacingOuter + indentation) + + '}'; +}; + +const printImmutableValues = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + type: string, +): string => + ++depth > config.maxDepth + ? '[' + getImmutableName(type) + ']' + : getImmutableName(type) + + SPACE + + '[' + + (val.size !== 0 + ? printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + ) + : config.spacingOuter + indentation) + + ']'; + +export const serialize = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string => { + if (val[IS_MAP_SENTINEL]) { + return printImmutableEntries( + val, + config, + indentation, + depth, + refs, + printer, + val[IS_ORDERED_SENTINEL] ? 'OrderedMap' : 'Map', + ); + } + + if (val[IS_LIST_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + 'List', + ); + } + if (val[IS_SET_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + val[IS_ORDERED_SENTINEL] ? 'OrderedSet' : 'Set', + ); + } + if (val[IS_STACK_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + 'Stack', + ); + } + + if (val[IS_SEQ_SENTINEL]) { + return '[' + getImmutableName('Seq') + ']'; + } + + // For compatibility with immutable v3 and v4, let record be the default. + return printImmutableRecord(val, config, indentation, depth, refs, printer); +}; + +export const test = (val: any) => + val && (val[IS_ITERABLE_SENTINEL] || val[IS_RECORD_SENTINEL]); + +export default ({serialize, test}: NewPlugin); diff --git a/packages/pretty-format/src/plugins/immutable_list.js b/packages/pretty-format/src/plugins/immutable_list.js deleted file mode 100644 index e4d9cd0c41c3..000000000000 --- a/packages/pretty-format/src/plugins/immutable_list.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_LIST = '@@__IMMUTABLE_LIST__@@'; -export const test = (maybeList: any) => !!(maybeList && maybeList[IS_LIST]); - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'List', false); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_map.js b/packages/pretty-format/src/plugins/immutable_map.js deleted file mode 100644 index 7a9ebfb85e62..000000000000 --- a/packages/pretty-format/src/plugins/immutable_map.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_MAP = '@@__IMMUTABLE_MAP__@@'; -const IS_ORDERED = '@@__IMMUTABLE_ORDERED__@@'; -export const test = (maybeMap: any) => - !!(maybeMap && maybeMap[IS_MAP] && !maybeMap[IS_ORDERED]); - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'Map', true); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_ordered_map.js b/packages/pretty-format/src/plugins/immutable_ordered_map.js deleted file mode 100644 index 212e0eba5889..000000000000 --- a/packages/pretty-format/src/plugins/immutable_ordered_map.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_MAP = '@@__IMMUTABLE_MAP__@@'; -const IS_ORDERED = '@@__IMMUTABLE_ORDERED__@@'; -export const test = (maybeOrderedMap: any) => - maybeOrderedMap && maybeOrderedMap[IS_MAP] && maybeOrderedMap[IS_ORDERED]; - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'OrderedMap', true); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_ordered_set.js b/packages/pretty-format/src/plugins/immutable_ordered_set.js deleted file mode 100644 index cddc9d77b991..000000000000 --- a/packages/pretty-format/src/plugins/immutable_ordered_set.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_SET = '@@__IMMUTABLE_SET__@@'; -const IS_ORDERED = '@@__IMMUTABLE_ORDERED__@@'; -export const test = (maybeOrderedSet: any) => - maybeOrderedSet && maybeOrderedSet[IS_SET] && maybeOrderedSet[IS_ORDERED]; - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'OrderedSet', false); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_plugins.js b/packages/pretty-format/src/plugins/immutable_plugins.js deleted file mode 100644 index 29a074e2a004..000000000000 --- a/packages/pretty-format/src/plugins/immutable_plugins.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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 ImmutableList from './immutable_list'; -import ImmutableSet from './immutable_set'; -import ImmutableMap from './immutable_map'; -import ImmutableStack from './immutable_stack'; -import ImmutableOrderedSet from './immutable_ordered_set'; -import ImmutableOrderedMap from './immutable_ordered_map'; -import ImmutableRecord from './immutable_record'; - -export default [ - ImmutableList, - ImmutableSet, - ImmutableMap, - ImmutableStack, - ImmutableOrderedSet, - ImmutableOrderedMap, - ImmutableRecord, -]; diff --git a/packages/pretty-format/src/plugins/immutable_record.js b/packages/pretty-format/src/plugins/immutable_record.js deleted file mode 100644 index 8a4de33e57ed..000000000000 --- a/packages/pretty-format/src/plugins/immutable_record.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_RECORD = '@@__IMMUTABLE_RECORD__@@'; -export const test = (maybeRecord: any) => - !!(maybeRecord && maybeRecord[IS_RECORD]); - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'Record', true); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_set.js b/packages/pretty-format/src/plugins/immutable_set.js deleted file mode 100644 index c5c66d94cc7d..000000000000 --- a/packages/pretty-format/src/plugins/immutable_set.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_SET = '@@__IMMUTABLE_SET__@@'; -const IS_ORDERED = '@@__IMMUTABLE_ORDERED__@@'; -export const test = (maybeSet: any) => - !!(maybeSet && maybeSet[IS_SET] && !maybeSet[IS_ORDERED]); - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'Set', false); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/immutable_stack.js b/packages/pretty-format/src/plugins/immutable_stack.js deleted file mode 100644 index 00aed6010ce0..000000000000 --- a/packages/pretty-format/src/plugins/immutable_stack.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 { - Colors, - Indent, - PluginOptions, - Print, - Plugin, -} from 'types/PrettyFormat'; - -import printImmutable from './lib/print_immutable'; - -const IS_STACK = '@@__IMMUTABLE_STACK__@@'; -export const test = (maybeStack: any) => !!(maybeStack && maybeStack[IS_STACK]); - -export const print = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, -) => printImmutable(val, print, indent, opts, colors, 'Stack', false); - -export default ({print, test}: Plugin); diff --git a/packages/pretty-format/src/plugins/lib/print_immutable.js b/packages/pretty-format/src/plugins/lib/print_immutable.js deleted file mode 100644 index 26f62ecfb3c7..000000000000 --- a/packages/pretty-format/src/plugins/lib/print_immutable.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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 {Colors, Indent, PluginOptions, Print} from 'types/PrettyFormat'; - -const IMMUTABLE_NAMESPACE = 'Immutable.'; -const SPACE = ' '; - -const addKey = (isMap: boolean, key: any) => (isMap ? key + ': ' : ''); - -const addFinalEdgeSpacing = (length: number, edgeSpacing: string) => - length !== 0 ? edgeSpacing : ''; - -const printImmutable = ( - val: any, - print: Print, - indent: Indent, - opts: PluginOptions, - colors: Colors, - immutableDataStructureName: string, - isMap: boolean, -): string => { - const [openTag, closeTag] = isMap ? ['{', '}'] : ['[', ']']; - const fullStructureName = val._name || immutableDataStructureName; - - let result = - IMMUTABLE_NAMESPACE + - fullStructureName + - SPACE + - openTag + - opts.edgeSpacing; - - const immutableArray = []; - - const pushToImmutableArray = (item: any, key: string) => { - immutableArray.push(indent(addKey(isMap, key) + print(item))); - }; - - if (Array.isArray(val._keys)) { - // if we have a record, we can not iterate on it directly - val._keys.forEach(key => pushToImmutableArray(val.get(key), key)); - } else { - val.forEach((item, key) => pushToImmutableArray(item, key)); - } - - result += immutableArray.join(',' + opts.spacing); - if (!opts.min && immutableArray.length !== 0) { - result += ','; - } - - return ( - result + - addFinalEdgeSpacing(immutableArray.length, opts.edgeSpacing) + - closeTag - ); -}; - -export default printImmutable;