Skip to content

Commit 8fa0af8

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 965daff commit 8fa0af8

8 files changed

+374
-17
lines changed

README.md

+49-10
Original file line numberDiff line numberDiff line change
@@ -95,43 +95,82 @@ 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 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 the "_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+
// Use the default React component serializer. Don't forget to add this if you
121+
...snapshotDiff.defaultSerializers, // want to continue to serialize React components
115122
myCustomSerializer
116123
]);
117124
```
118125

119126
### Serializing React components with a different serializer
120127

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

124131
```js
125132
const snapshotDiff = require('snapshot-diff');
126133
const enzymeToJson = require('enzyme-to-json/serializer');
127134
const myCustomSerializer = require('./my-custom-serializer');
128135

129136
snapshotDiff.setSerializers([
130-
enzymeToJson, // using enzymes serializer instead
137+
// Use Enzyme's React component serializer. Add this instead of the default React
138+
enzymeToJson, // component serializer if you want to replace how React components are serialized
131139
myCustomSerializer
132140
]);
133141
```
134142

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

137176
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.129.0",
4142
"jest": "^26.1.0",
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)