diff --git a/index.js b/index.js index 30053ed..295ab7a 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ exports.serializerVersion = 2 exports.theme = { react: { functionType: '\u235F', + memoizedType: `\u235D`, openTag: { start: '<', end: '>', diff --git a/lib/elementFactory.js b/lib/elementFactory.js index 2697f87..43b1ef3 100644 --- a/lib/elementFactory.js +++ b/lib/elementFactory.js @@ -5,6 +5,7 @@ const diffShallow = require('./diffShallow') const escapeText = require('./escapeText') const FRAGMENT_NAME = Symbol.for('react.fragment') +const MEMO_TYPE = Symbol.for('react.memo') function factory (api, reactTags) { const tag = Symbol('@concordance/react.ElementValue') @@ -62,7 +63,11 @@ function factory (api, reactTags) { function describe (props) { const element = props.value - const type = element.type + let type = element.type + const hasMemoizedType = type.$$typeof === MEMO_TYPE + // Dereference underlying type if memoized. + if (hasMemoizedType) type = type.type + const hasTypeFn = typeof type === 'function' const typeFn = hasTypeFn ? type : null const name = hasTypeFn ? type.displayName || type.name : type @@ -78,6 +83,7 @@ function factory (api, reactTags) { return new DescribedElementValue(Object.assign({ children, + hasMemoizedType, hasProperties, hasTypeFn, name, @@ -96,6 +102,7 @@ function factory (api, reactTags) { super(props) this.isFragment = props.name === FRAGMENT_NAME this.name = props.name + this.hasMemoizedType = props.hasMemoizedType this.hasProperties = props.hasProperties this.hasTypeFn = props.hasTypeFn @@ -110,13 +117,15 @@ function factory (api, reactTags) { formatName (theme) { const formatted = api.wrapFromTheme(theme.react.tagName, this.isFragment ? 'React.Fragment' : this.name) - return this.hasTypeFn - ? formatted + theme.react.functionType - : formatted + if (this.hasMemoizedType) return formatted + theme.react.memoizedType + if (this.hasTypeFn) return formatted + theme.react.functionType + return formatted } compareNames (expected) { - return this.name === expected.name && this.hasTypeFn === expected.hasTypeFn + return this.name === expected.name && + this.hasMemoizedType === expected.hasMemoizedType && + this.hasTypeFn === expected.hasTypeFn } formatShallow (theme, indent) { @@ -216,7 +225,15 @@ function factory (api, reactTags) { } serialize () { - return [this.isFragment, this.isFragment ? null : this.name, this.hasProperties, this.hasTypeFn, super.serialize()] + // TODO: Reorder hasMemoizedType before next major release. + return [ + this.isFragment, + this.isFragment ? null : this.name, + this.hasProperties, + this.hasTypeFn, + super.serialize(), + this.hasMemoizedType + ] } } Object.defineProperty(ElementValue.prototype, 'tag', {value: tag}) @@ -328,6 +345,7 @@ function factory (api, reactTags) { super(state[4], recursor) this.isFragment = state[0] this.name = this.isFragment ? FRAGMENT_NAME : state[1] + this.hasMemoizedType = state.length === 5 ? false : state[5] this.hasProperties = state[2] this.hasTypeFn = state[3] } diff --git a/test/backcompat.js b/test/backcompat.js new file mode 100644 index 0000000..242b6e5 --- /dev/null +++ b/test/backcompat.js @@ -0,0 +1,24 @@ +import test from 'ava' +import {compareDescriptors, describe, deserialize} from 'concordance' + +import React from 'react' + +import plugin from '..' +import HelloMessage from './fixtures/react/HelloMessage' + +const plugins = [plugin] + +const equalsSerialization = (t, buffer, getValue) => { + const expected = describe(getValue(), {plugins}) + + const deserialized = deserialize(buffer, {plugins}) + t.true( + compareDescriptors(deserialized, expected), + 'the deserialized descriptor equals the expected value') +} + +test('element serialization before React.memo support was added', + equalsSerialization, + Buffer.from('AwAfAAAAAQERARJAY29uY29yZGFuY2UvcmVhY3QBAgEBAQEBAlwAAABiAAAAEwEFEBEBDEhlbGxvTWVzc2FnZQ8PEwEGEQEGT2JqZWN0AQERAQZPYmplY3QQEBAAAQ0AAQEAAQ8AEwECFAEDAAEFEQEEbmFtZRQBAwABBREBBEpvaG4=', 'base64'), // eslint-disable-line max-len + () => +) diff --git a/test/compare.js b/test/compare.js index 91c863f..7e15bd5 100644 --- a/test/compare.js +++ b/test/compare.js @@ -4,7 +4,7 @@ import React from 'react' import renderer from 'react-test-renderer' import plugin from '..' -import HelloMessage from './fixtures/react/HelloMessage' +import HelloMessage, {MemoizedHelloMessage} from './fixtures/react/HelloMessage' const plugins = [plugin] const render = value => renderer.create(value).toJSON() @@ -30,6 +30,10 @@ test('react elements', macros, () => React.createElement('Foo'), () => React.createElement('Bar')) +test('memoized elements', macros, + () => , + () => ) + test('fragments', macros, () => , () => ) diff --git a/test/diff.js b/test/diff.js index 5b02113..5c06c60 100644 --- a/test/diff.js +++ b/test/diff.js @@ -4,7 +4,7 @@ import React from 'react' import renderer from 'react-test-renderer' import plugin from '..' -import HelloMessage from './fixtures/react/HelloMessage' +import HelloMessage, {MemoizedHelloMessage} from './fixtures/react/HelloMessage' const plugins = [plugin] @@ -28,6 +28,14 @@ test('react elements', macros, () => arm, () => arm) +test('memoized elements', macros, + () => , + () => ) + +test('memoized elements against non-memoized elements', macros, + () => , + () => ) + test('fragments', macros, () => , () => ) diff --git a/test/fixtures/react/HelloMessage.jsx b/test/fixtures/react/HelloMessage.jsx index 2606758..a129e80 100644 --- a/test/fixtures/react/HelloMessage.jsx +++ b/test/fixtures/react/HelloMessage.jsx @@ -11,3 +11,5 @@ export default class HelloMessage extends React.Component { return
Hello
} } + +export const MemoizedHelloMessage = React.memo(HelloMessage) diff --git a/test/format.js b/test/format.js index 1c4f404..ce31ce7 100644 --- a/test/format.js +++ b/test/format.js @@ -4,7 +4,7 @@ import React from 'react' import renderer from 'react-test-renderer' import plugin from '..' -import HelloMessage from './fixtures/react/HelloMessage' +import HelloMessage, {MemoizedHelloMessage} from './fixtures/react/HelloMessage' const plugins = [plugin] const format = (value, options) => concordance.format(value, Object.assign({plugins}, options)) @@ -22,6 +22,7 @@ snapshotRendered.title = prefix => `formats rendered ${prefix}` const macros = [snapshot, snapshotRendered] test('react elements', macros, () => ) +test('memoized elements', macros, () => ) test('fragments', macros, () => ) test('object properties', macros, () => { return React.createElement('Foo', {object: {baz: 'thud'}}) diff --git a/test/serialize-and-encode.js b/test/serialize-and-encode.js index ead3b46..03bbc1f 100644 --- a/test/serialize-and-encode.js +++ b/test/serialize-and-encode.js @@ -5,7 +5,7 @@ import React from 'react' import renderer from 'react-test-renderer' import plugin from '..' -import HelloMessage from './fixtures/react/HelloMessage' +import HelloMessage, {MemoizedHelloMessage} from './fixtures/react/HelloMessage' const plugins = [plugin] @@ -46,15 +46,7 @@ useDeserializedRendered.title = prefix => `deserialized rendered ${prefix} is eq const macros = [useDeserialized, useDeserializedRendered] test('react elements', macros, () => ) -// TODO: Combine next two tests with `macros` array -test.failing('memoized react elements', useDeserialized, () => { - const MemoizedHelloMessage = React.memo(HelloMessage) - return -}) -test('memoized react elements', useDeserializedRendered, () => { - const MemoizedHelloMessage = React.memo(HelloMessage) - return -}) +test('memoized elements', macros, () => ) test('fragments', macros, () => ) test('object properties', macros, () => { return React.createElement('Foo', {object: {baz: 'thud'}}) diff --git a/test/snapshots/diff.js.md b/test/snapshots/diff.js.md index 7581e05..cededdb 100644 --- a/test/snapshots/diff.js.md +++ b/test/snapshots/diff.js.md @@ -181,6 +181,24 @@ Generated by [AVA](https://ava.li). ␊ ` +## diffs memoized elements + +> Snapshot 1 + + ` ` + +## diffs memoized elements against non-memoized elements + +> Snapshot 1 + + `- ` + ## diffs multiline string properties > Snapshot 1 @@ -447,6 +465,29 @@ Generated by [AVA](https://ava.li). ␊ ` +## diffs rendered memoized elements + +> Snapshot 1 + + `
␊ + Hello ␊ + ␊ + - John␊ + + Olivia␊ + ␊ +
` + +## diffs rendered memoized elements against non-memoized elements + +> Snapshot 1 + + `
␊ + Hello ␊ + ␊ + John␊ + ␊ +
` + ## diffs rendered multiline string properties > Snapshot 1 diff --git a/test/snapshots/diff.js.snap b/test/snapshots/diff.js.snap index 085e199..7da6f68 100644 Binary files a/test/snapshots/diff.js.snap and b/test/snapshots/diff.js.snap differ diff --git a/test/snapshots/format.js.md b/test/snapshots/format.js.md index 272a383..3bfc8fe 100644 --- a/test/snapshots/format.js.md +++ b/test/snapshots/format.js.md @@ -66,6 +66,14 @@ Generated by [AVA](https://ava.li). ␊ ` +## formats memoized elements + +> Snapshot 1 + + `` + ## formats multiline string properties > Snapshot 1 @@ -184,6 +192,17 @@ Generated by [AVA](https://ava.li). ␊ ` +## formats rendered memoized elements + +> Snapshot 1 + + `
␊ + Hello ␊ + ␊ + John␊ + ␊ +
` + ## formats rendered multiline string properties > Snapshot 1 diff --git a/test/snapshots/format.js.snap b/test/snapshots/format.js.snap index 99684ba..f0c9f53 100644 Binary files a/test/snapshots/format.js.snap and b/test/snapshots/format.js.snap differ