Skip to content

Commit 783b640

Browse files
feat: Support enhancing React component serializer
snapshot-diff provides a convenience serializer for React components which renders them and serializes them before diffing. Support is already in place for allowing custom serializers to be added which will work at the root level; serializing unknown types (e.g. React component rendered with Enzyme). However, there is no ability to update the serializers used within the React component serializer. The use case for this need to when a CSS-in-JS solution, such as Emotion, is used and custom serilization is required to support outputting the styles attached. Emotion provides this serializer, but adding at the root level then only outputs the styles and does not output the component. To support this, the same `defaultSerializers` and `setSerializers` API as provided by `snapshot-diff` for adding root level serializers has been applied to the React component serializer to allow "sub-serializers" to be added which will then be passed into `pretty-format`. This then provides an API which will allow the Emotion serializer to work as expected with the rest of the React component serialization process. Fixes #162
1 parent 2c32732 commit 783b640

8 files changed

+369
-16
lines changed

README.md

+44-9
Original file line numberDiff line numberDiff line change
@@ -95,31 +95,37 @@ exports[`snapshot difference between 2 React components state 1`] = `
9595

9696
## Custom serializers
9797

98-
By default, `snapshot-diff` uses a built in React serializer based on `react-test-renderer`. The
99-
[serializers](https://jestjs.io/docs/en/configuration#snapshotserializers-array-string) used can be set by calling
100-
`setSerializers` with an array of serializers to use. The order of serializers in this array may be important to you as
101-
serializers are tested in order until a match is found.
98+
By default, `snapshot-diff` uses a built in React component serializer based on `react-test-renderer`. The serializers
99+
used can be set by calling `setSerializers` with an array of serializers to use. The order of serializers in this array
100+
may be important to you as serializers are tested in order until a match is found.
102101

103-
`setSerializers` can be used to add new serializers for unsupported data types, or to set a different serializer
104-
for React components. If you want to keep the default React serializer in place, don't forget to add the default
102+
`setSerializers` can be used to add new serializers for unsupported data types, or to set a different serializer for
103+
React components. If you want to keep the default React component serializer in place, don't forget to add the default
105104
serializers to your list of serializers!
106105

106+
ℹ️ **Note:** Serializers are independent; once a serializer is matched no further serializers will be run for that
107+
input. This would be expected when adding a different serializer for React components (e.g. enzyme's serializer instead
108+
of the built in React component serializer) or adding a new serializer for an unsupported data types. It may not be as
109+
expected when you need serializers to work together (e.g. rendering a React component which makes use of CSS-in-JS like
110+
Emotion). If you need a serializer to work with the existing React component serializer, see "_Enhancing the React
111+
component serializer_" section below.
112+
107113
### Adding a new custom serializer
108114

109115
```js
110116
const snapshotDiff = require('snapshot-diff');
111117
const myCustomSerializer = require('./my-custom-serializer');
112118

113119
snapshotDiff.setSerializers([
114-
...snapshotDiff.defaultSerializers, // use default React serializer - add this if you want to serialise React components!
120+
...snapshotDiff.defaultSerializers, // use the default React component serializer - add this if you want to continue to serialize React components!
115121
myCustomSerializer
116122
]);
117123
```
118124

119125
### Serializing React components with a different serializer
120126

121-
You can replace the default React serializer by omitting it from the serializer list. The following uses enzymes to-json
122-
serializer instead:
127+
You can replace the default React component serializer by omitting it from the serializer list. The following uses
128+
Enzyme's `to-json` serializer instead:
123129

124130
```js
125131
const snapshotDiff = require('snapshot-diff');
@@ -132,6 +138,35 @@ snapshotDiff.setSerializers([
132138
]);
133139
```
134140

141+
## Enhancing the React component serializer
142+
143+
`snapshot-diff` uses a built in React component serializer based on `react-test-renderer`. This makes use of the default
144+
Jest serializers passed to `pretty-format`. However, you may wish to add additional serializers to be passed to
145+
`pretty-format` when serializing a React component, e.g. Using a CSS-in-JS solution such as Emotion.
146+
147+
The React component serializer is exposed at `snapshotDiff.reactSerializer`
148+
149+
The API for adding new "sub-serializers" to the React component serializer is similar to how top level serializers are
150+
added to `snapshot-diff`. The React component serializer has a `setSerializers` function which can be used an be used
151+
to change the serializers used for serializing a React component. Again, if you want to keep the default serializers in
152+
place, don't forget to add the default serializers too!
153+
154+
ℹ️ **Note:** Serializers added to the React component serializer are only used by the React component serializer
155+
- `snapshotDiff.setSerializers` is not the same as `snapshotDiff.reactSerializer.setSerializers`
156+
- `snapshotDiff.defaultSerializers` is not the same as `snapshotDiff.reactSerializer.defaultSerializers`
157+
158+
### Adding a new serializer
159+
160+
```js
161+
const snapshotDiff = require('snapshot-diff');
162+
const emotionSerializer = require('jest-emotion');
163+
164+
snapshotDiff.reactSerializer.setSerializers([
165+
emotionSerializer,
166+
...snapshotDiff.reactSerializer.defaultSerializers
167+
]);
168+
```
169+
135170
## Snapshot serializer
136171

137172
By default Jest adds extra quotes around strings so it makes diff snapshots of objects too noisy.

__tests__/__snapshots__/setSerializers.test.js.snap

+38
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`components using CSS-in-JS can use contextLines 1`] = `
4+
"Snapshot Diff:
5+
- <NestedComponent css=\\"unknown styles\\" test=\\"say\\" />
6+
+ <NestedComponent css=\\"unknown styles\\" test=\\"my name\\" />
7+
8+
@@ -2,1 +2,1 @@
9+
- color: green;
10+
+ color: red;
11+
@@ -10,1 +10,1 @@
12+
- say
13+
+ my name"
14+
`;
15+
16+
exports[`components using CSS-in-JS diffs components 1`] = `
17+
"Snapshot Diff:
18+
- <NestedComponent css=\\"unknown styles\\" test=\\"say\\" />
19+
+ <NestedComponent css=\\"unknown styles\\" test=\\"my name\\" />
20+
21+
@@ -1,15 +1,15 @@
22+
.emotion-0 {
23+
- color: green;
24+
+ color: red;
25+
}
26+
27+
<div
28+
className=\\"emotion-0\\"
29+
>
30+
<span>
31+
Hello World -
32+
- say
33+
+ my name
34+
</span>
35+
<div>
36+
I have value
37+
1234
38+
</div>"
39+
`;
40+
341
exports[`default rendered components can use contextLines 1`] = `
442
"Snapshot Diff:
543
- <NestedComponent test=\\"say\\" />

__tests__/setSerializers.test.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// @flow
22
const React = require('react');
3+
const { jsx, css } = require('@emotion/core');
34
const { configure, shallow: enzymeShallow } = require('enzyme');
45
const ReactShallowRenderer = require('react-test-renderer/shallow');
56
const Adapter = require('enzyme-adapter-react-16');
67
const enzymeToJson = require('enzyme-to-json/serializer');
8+
const emotionSerializer = require('jest-emotion');
79
const snapshotDiff = require('../src/index');
810

911
configure({ adapter: new Adapter() });
@@ -18,7 +20,7 @@ type Props = {
1820
const Component = ({ value }) => <div>I have value {value}</div>;
1921

2022
const NestedComponent = (props: Props) => (
21-
<div>
23+
<div className={props.className}>
2224
<span>Hello World - {props.test}</span>
2325
<Component value={1234} />
2426
</div>
@@ -115,3 +117,53 @@ describe('values which are not components', () => {
115117
).toMatchSnapshot();
116118
});
117119
});
120+
121+
describe('components using CSS-in-JS', () => {
122+
beforeEach(() => {
123+
snapshotDiff.reactSerializer.setSerializers([
124+
emotionSerializer,
125+
...snapshotDiff.reactSerializer.defaultSerializers,
126+
]);
127+
});
128+
129+
test('diffs components', () => {
130+
expect(
131+
snapshotDiff(
132+
jsx(NestedComponent, {
133+
css: css`
134+
color: green;
135+
`,
136+
test: 'say',
137+
}),
138+
jsx(NestedComponent, {
139+
css: css`
140+
color: red;
141+
`,
142+
test: 'my name',
143+
})
144+
)
145+
).toMatchSnapshot();
146+
});
147+
148+
test('can use contextLines', () => {
149+
expect(
150+
snapshotDiff(
151+
jsx(NestedComponent, {
152+
css: css`
153+
color: green;
154+
`,
155+
test: 'say',
156+
}),
157+
jsx(NestedComponent, {
158+
css: css`
159+
color: red;
160+
`,
161+
test: 'my name',
162+
}),
163+
{
164+
contextLines: 0,
165+
}
166+
)
167+
).toMatchSnapshot();
168+
});
169+
});

index.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ interface Serializer {
2727
test: (value: any) => boolean;
2828
print: (value: any, _serializer?: any) => any;
2929
diffOptions?: (valueA: any, valueB: any) => DiffOptions;
30+
setSerializers?: (serializers: Array<Serializer>) => void;
31+
defaultSerializers?: Array<Serializer>;
3032
}
3133

3234
declare module 'snapshot-diff' {
@@ -54,6 +56,7 @@ declare module 'snapshot-diff' {
5456
*/
5557
setSerializers: (serializers: Array<Serializer>) => void;
5658
defaultSerializers: Array<Serializer>;
59+
reactSerializer: Serializer;
5760
}
5861
const diff: SnapshotDiff;
5962
export = diff;

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@
3333
"@babel/preset-flow": "^7.0.0",
3434
"@babel/preset-react": "^7.7.0",
3535
"@callstack/eslint-config": "^10.0.0",
36+
"@emotion/core": "^10.0.28",
3637
"enzyme": "^3.10.0",
3738
"enzyme-adapter-react-16": "^1.14.0",
3839
"enzyme-to-json": "^3.4.0",
3940
"eslint": "^7.0.0",
4041
"flow-bin": "^0.127.0",
4142
"jest": "^26.0.1",
43+
"jest-emotion": "^10.0.32",
4244
"react": "^16.13.1",
4345
"react-dom": "16.13.1",
4446
"react-test-renderer": "^16.13.1"

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,4 @@ module.exports.toMatchDiffSnapshot = toMatchDiffSnapshot;
112112
module.exports.getSnapshotDiffSerializer = getSnapshotDiffSerializer;
113113
module.exports.setSerializers = setSerializers;
114114
module.exports.defaultSerializers = defaultSerializers;
115+
module.exports.reactSerializer = reactSerializer;

src/react-serializer.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
const prettyFormat = require('pretty-format');
66
const snapshot = require('jest-snapshot');
77

8-
const serializers = snapshot.getSerializers();
8+
const defaultSerializers = snapshot.getSerializers();
9+
let serializers = defaultSerializers;
910

1011
const reactElement = Symbol.for('react.element');
1112

@@ -25,7 +26,11 @@ function getReactComponentSerializer() {
2526
throw error;
2627
}
2728
return (value) =>
28-
prettyFormat(renderer.create(value), { plugins: serializers });
29+
prettyFormat(renderer.create(value).toJSON(), { plugins: serializers });
30+
}
31+
32+
function setSerializers(customSerializers) {
33+
serializers = customSerializers;
2934
}
3035

3136
const reactSerializer = {
@@ -41,6 +46,8 @@ const reactSerializer = {
4146
bAnnotation: prettyFormat(valueB, prettyFormatOptions),
4247
};
4348
},
49+
setSerializers,
50+
defaultSerializers,
4451
};
4552

4653
module.exports = reactSerializer;

0 commit comments

Comments
 (0)