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