The Smart™ React tree differ.
diff-react
provides a single function for shallow-rendering and diffing two React components. The idea is that basic snapshotting, while great for small components that don't change frequently, doesn't scale well to large components or components that change frequently (since the snapshots tend to be brittle, breaking due to unrelated changes somewhere else in the component). Shallow rendering helps this somewhat but doesn't obviate the problem entirely. Instead, it would be better to create snapshots that only contain DOM changes relative to some baseline rendering.
For example, if you wanted to test that <MyButton disabled={true}/>
properly grays out the text and icon inside, with Jest you could do something like:
expect(diffReact(
<MyButton />,
<MyButton disabled={true} />,
)).toMatchSnapshot();
This would then create a minimal diff snapshot that you could test against, highlighting only the relevant changes in your custom component and their context:
<Button …=…
- disabled={false}
+ disabled={true}
>
<Image …=…
style={
Object {
- "tintColor": "#eee",
+ "tintColor": "#666",
}
}
/>
<Text …=…
style={
Object {
- "color": "#eee",
+ "color": "#666",
}
}
>…</Text>
</Button>
Well, from a high-level perspective:
diffReact()
receives two React elements and an optional canonicalization function.- Using Jest's shallow renderer, it renders the elements one-level deep.
virtualizeTree()
then takes each shallowly-rendered tree and recursively converts each node intovirtual-dom
VNode
,VText
orSENTINEL_NULL_VNODE
. As we traverse each node, we also canonicalize the props to ignore certain irrelevant changes later (e.g. non-renderingon*
handler props).virtual-dom
'sdiff()
then performs a diff on the two canonicalized,VNode
-based trees, returning basically a POJO that maps numeric in-order traversal node indices to patches (e.g. insert/remove/replace/change props/reorder operations).serializePatchesRecursively()
recurses over the original in-order building a serialized diff as it goes based on the originalVNode
tree and the patch map:- For
VTEXT
andVNODE
patches (which represent node replacements),serializeTree()
, a simplified version ofserializePatchesRecursively)
that takes aVNode
tree (without patches), an indent level, and a modification prefix (e.g.+
/-
) and returns a the tree rendered with those params, is used to render the before node (with-
) and the after node (with+
). - For
PROPS
patches (which represent props changes), iterate over the list of combined props from the originalVNode
and the patch, and serialize each one:- For props that are equal, combine them all into a single
…=…
prop. - For primitives that differ, print the before prop with a
-
prefix before each line and the after prop with a+
prefix. - For arrays, objects, and React trees (all of which can be complex), serialize the values with with
prettyFormat()
and run a text diff on them. In order to provide shorter diffs than just entire before prop and the entire after prop,formatArrayOrObjectPropDiff()
collapses equal chunks of text to…
.
- For props that are equal, combine them all into a single
- For
INSERT
patches, serialize thepatch.patch
withserializeTree()
and add it to the end of the children array with+
prefixes. If the node was actually inserted in the middle of a list, we should expect anORDER
patch to follow, which will move it into the right location. - For
REMOVE
patches, serialize the node in-place usingserializeTree()
and-
prefixes. - For
ORDER
patches (which represent children being reordered)... it's probably best explained by looking at the code directly.
- For
This module also does some things like collapse all the children to …
if they're all equal and try to keep props on the same line as the start tag in serializeTree()
if possible, but that covers the vast majority of this module's complexity.