Skip to content

Commit 6afc4a0

Browse files
feat: Allow custom serializers through setSerializers
Provides new setSerializers function to set the serializer objects to be used before diffing. React component serializer implementation using `react-test-renderer` has been extracted out. Fixes #18 Fixes #30 Fixes #31
1 parent 8cc8f70 commit 6afc4a0

File tree

8 files changed

+859
-44
lines changed

8 files changed

+859
-44
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,45 @@ exports[`snapshot difference between 2 React components state 1`] = `
9393
`;
9494
```
9595

96+
## Custom serializers
97+
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.
102+
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
105+
serializers to your list of serializers!
106+
107+
### Adding a new custom serializer
108+
109+
```js
110+
const snapshotDiff = require('snapshot-diff');
111+
const myCustomSerializer = require('./my-custom-serializer');
112+
113+
snapshotDiff.setSerializers([
114+
...snapshotDiff.defaultSerializers, // use default React serializer - add this if you want to serialise React components!
115+
myCustomSerializer
116+
]);
117+
```
118+
119+
### Serializing React components with a different serializer
120+
121+
You can replace the default React serializer by omitting it from the serializer list. The following uses enzymes to-json
122+
serializer instead:
123+
124+
```js
125+
const snapshotDiff = require('snapshot-diff');
126+
const enzymeToJson = require('enzyme-to-json/serializer');
127+
const myCustomSerializer = require('./my-custom-serializer');
128+
129+
snapshotDiff.setSerializers([
130+
enzymeToJson, // using enzymes serializer instead
131+
myCustomSerializer
132+
]);
133+
```
134+
96135
## Snapshot serializer
97136

98137
By default Jest adds extra quotes around strings so it makes diff snapshots of objects too noisy.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`default rendered components can use contextLines 1`] = `
4+
"Snapshot Diff:
5+
- <NestedComponent test=\\"say\\" />
6+
+ <NestedComponent test=\\"my name\\" />
7+
8+
@@ -4,1 +4,1 @@
9+
- say
10+
+ my name"
11+
`;
12+
13+
exports[`default rendered components diffs components 1`] = `
14+
"Snapshot Diff:
15+
- <NestedComponent test=\\"say\\" />
16+
+ <NestedComponent test=\\"my name\\" />
17+
18+
@@ -1,9 +1,9 @@
19+
<div>
20+
<span>
21+
Hello World -
22+
- say
23+
+ my name
24+
</span>
25+
<div>
26+
I have value
27+
1234
28+
</div>"
29+
`;
30+
31+
exports[`enzyme shallow rendered components can use contextLines 1`] = `
32+
"Snapshot Diff:
33+
- First value
34+
+ Second value
35+
36+
@@ -4,1 +4,1 @@
37+
- say
38+
+ my name"
39+
`;
40+
41+
exports[`enzyme shallow rendered components diffs components 1`] = `
42+
"Snapshot Diff:
43+
- First value
44+
+ Second value
45+
46+
<div>
47+
<span>
48+
Hello World -
49+
- say
50+
+ my name
51+
</span>
52+
<Component
53+
value={1234}
54+
/>
55+
</div>"
56+
`;
57+
58+
exports[`react test-renderer shallow rendered components can use contextLines 1`] = `
59+
"Snapshot Diff:
60+
- <div><span>Hello World - say</span><Component value={1234} /></div>
61+
+ <div><span>Hello World - my name</span><Component value={1234} /></div>
62+
63+
@@ -4,1 +4,1 @@
64+
- say
65+
+ my name"
66+
`;
67+
68+
exports[`react test-renderer shallow rendered components diffs components 1`] = `
69+
"Snapshot Diff:
70+
- <div><span>Hello World - say</span><Component value={1234} /></div>
71+
+ <div><span>Hello World - my name</span><Component value={1234} /></div>
72+
73+
@@ -1,9 +1,9 @@
74+
<div>
75+
<span>
76+
Hello World -
77+
- say
78+
+ my name
79+
</span>
80+
<div>
81+
I have value
82+
1234
83+
</div>"
84+
`;
85+
86+
exports[`values which are not components can use contextLines 1`] = `
87+
"Snapshot Diff:
88+
- First value
89+
+ Second value
90+
91+
@@ -3,1 +3,1 @@
92+
- \\"hello\\": \\"world\\",
93+
+ \\"hello\\": \\"there\\","
94+
`;
95+
96+
exports[`values which are not components diffs objects 1`] = `
97+
"Snapshot Diff:
98+
- First value
99+
+ Second value
100+
101+
Object {
102+
\\"foo\\": \\"bar\\",
103+
- \\"hello\\": \\"world\\",
104+
+ \\"hello\\": \\"there\\",
105+
\\"testing\\": 123,
106+
}"
107+
`;

__tests__/setSerializers.test.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// @flow
2+
const React = require('react');
3+
const { configure, shallow: enzymeShallow } = require('enzyme');
4+
const ReactShallowRenderer = require('react-test-renderer/shallow');
5+
const Adapter = require('enzyme-adapter-react-16');
6+
const enzymeToJson = require('enzyme-to-json/serializer');
7+
const snapshotDiff = require('../src/index');
8+
9+
configure({ adapter: new Adapter() });
10+
const reactShallow = new ReactShallowRenderer();
11+
12+
snapshotDiff.setSerializers([...snapshotDiff.defaultSerializers, enzymeToJson]);
13+
14+
type Props = {
15+
test: string,
16+
};
17+
18+
const Component = ({ value }) => <div>I have value {value}</div>;
19+
20+
const NestedComponent = (props: Props) => (
21+
<div>
22+
<span>Hello World - {props.test}</span>
23+
<Component value={1234} />
24+
</div>
25+
);
26+
27+
describe('default rendered components', () => {
28+
test('diffs components', () => {
29+
expect(
30+
snapshotDiff(
31+
<NestedComponent test="say" />,
32+
<NestedComponent test="my name" />
33+
)
34+
).toMatchSnapshot();
35+
});
36+
37+
test('can use contextLines', () => {
38+
expect(
39+
snapshotDiff(
40+
<NestedComponent test="say" />,
41+
<NestedComponent test="my name" />,
42+
{
43+
contextLines: 0,
44+
}
45+
)
46+
).toMatchSnapshot();
47+
});
48+
});
49+
50+
describe('enzyme shallow rendered components', () => {
51+
test('diffs components', () => {
52+
expect(
53+
snapshotDiff(
54+
enzymeShallow(<NestedComponent test="say" />),
55+
enzymeShallow(<NestedComponent test="my name" />)
56+
)
57+
).toMatchSnapshot();
58+
});
59+
60+
test('can use contextLines', () => {
61+
expect(
62+
snapshotDiff(
63+
enzymeShallow(<NestedComponent test="say" />),
64+
enzymeShallow(<NestedComponent test="my name" />),
65+
{
66+
contextLines: 0,
67+
}
68+
)
69+
).toMatchSnapshot();
70+
});
71+
});
72+
73+
describe('react test-renderer shallow rendered components', () => {
74+
test('diffs components', () => {
75+
expect(
76+
snapshotDiff(
77+
reactShallow.render(<NestedComponent test="say" />),
78+
reactShallow.render(<NestedComponent test="my name" />)
79+
)
80+
).toMatchSnapshot();
81+
});
82+
83+
test('can use contextLines', () => {
84+
expect(
85+
snapshotDiff(
86+
reactShallow.render(<NestedComponent test="say" />),
87+
reactShallow.render(<NestedComponent test="my name" />),
88+
{
89+
contextLines: 0,
90+
}
91+
)
92+
).toMatchSnapshot();
93+
});
94+
});
95+
96+
describe('values which are not components', () => {
97+
test('diffs objects', () => {
98+
expect(
99+
snapshotDiff(
100+
{ foo: 'bar', hello: 'world', testing: 123 },
101+
{ foo: 'bar', hello: 'there', testing: 123 }
102+
)
103+
).toMatchSnapshot();
104+
});
105+
106+
test('can use contextLines', () => {
107+
expect(
108+
snapshotDiff(
109+
{ foo: 'bar', hello: 'world', testing: 123 },
110+
{ foo: 'bar', hello: 'there', testing: 123 },
111+
{
112+
contextLines: 0,
113+
}
114+
)
115+
).toMatchSnapshot();
116+
});
117+
});

extend-expect.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* global expect */
12
const { toMatchDiffSnapshot } = require('./build/');
23

3-
expect.extend({ toMatchDiffSnapshot });
4+
expect.extend({ toMatchDiffSnapshot });

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@
3030
"@babel/preset-env": "^7.0.0",
3131
"@babel/preset-flow": "^7.0.0",
3232
"@babel/preset-react": "^7.0.0",
33+
"enzyme": "^3.10.0",
34+
"enzyme-adapter-react-16": "^1.14.0",
35+
"enzyme-to-json": "^3.4.0",
3336
"eslint": "^5.15.3",
3437
"eslint-config-callstack-io": "^1.1.1",
3538
"flow-bin": "^0.102.0",
3639
"jest": "^24.0.0",
3740
"react": "^16.7.0",
41+
"react-dom": "16.7.0",
3842
"react-test-renderer": "^16.7.0"
3943
}
4044
}

src/index.js

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
const diff = require('jest-diff');
66
const snapshot = require('jest-snapshot');
7-
const prettyFormat = require('pretty-format');
8-
9-
const { ReactElement } = prettyFormat.plugins;
10-
const reactElement = Symbol.for('react.element');
7+
const reactSerializer = require('./react-serializer');
118

129
type Options = {|
1310
expand?: boolean,
@@ -29,12 +26,27 @@ const defaultOptions = {
2926

3027
const SNAPSHOT_TITLE = 'Snapshot Diff:\n';
3128

29+
const identity = value => value;
30+
const defaultSerializers = [reactSerializer];
31+
let serializers = defaultSerializers;
32+
3233
const snapshotDiff = (valueA: any, valueB: any, options?: Options): string => {
3334
let difference;
3435
const mergedOptions = { ...defaultOptions, ...options };
3536

36-
if (isReactComponent(valueA) && isReactComponent(valueB)) {
37-
difference = diffReactComponents(valueA, valueB, mergedOptions);
37+
const matchingSerializer = serializers.find(
38+
({ test }) => test(valueA) && test(valueB)
39+
);
40+
41+
if (matchingSerializer) {
42+
const { print, diffOptions } = matchingSerializer;
43+
const serializerOptions = diffOptions
44+
? diffOptions(valueA, valueB) || {}
45+
: {};
46+
difference = diffStrings(print(valueA, identity), print(valueB, identity), {
47+
...mergedOptions,
48+
...serializerOptions,
49+
});
3850
} else {
3951
difference = diffStrings(valueA, valueB, mergedOptions);
4052
}
@@ -55,9 +67,6 @@ const snapshotDiff = (valueA: any, valueB: any, options?: Options): string => {
5567
return SNAPSHOT_TITLE + difference;
5668
};
5769

58-
const isReactComponent = (value: any) =>
59-
value && value.$$typeof === reactElement;
60-
6170
function diffStrings(valueA: any, valueB: any, options: Options) {
6271
return diff(valueA, valueB, {
6372
expand: options.expand,
@@ -67,36 +76,6 @@ function diffStrings(valueA: any, valueB: any, options: Options) {
6776
});
6877
}
6978

70-
function requireReactTestRenderer() {
71-
try {
72-
return require('react-test-renderer'); // eslint-disable-line import/no-extraneous-dependencies
73-
} catch (error) {
74-
if (error.code === 'MODULE_NOT_FOUND') {
75-
throw new Error(
76-
`Failed to load optional module "react-test-renderer". ` +
77-
`If you need to compare React elements, please add "react-test-renderer" to your ` +
78-
`project's dependencies.\n` +
79-
`${error.message}`
80-
);
81-
}
82-
throw error;
83-
}
84-
}
85-
86-
function diffReactComponents(valueA: any, valueB: any, options: Options) {
87-
const renderer = requireReactTestRenderer();
88-
const reactValueA = renderer.create(valueA).toJSON();
89-
const reactValueB = renderer.create(valueB).toJSON();
90-
const prettyFormatOptions = { plugins: [ReactElement], min: true };
91-
92-
return diff(reactValueA, reactValueB, {
93-
expand: options.expand,
94-
contextLines: options.contextLines,
95-
aAnnotation: prettyFormat(valueA, prettyFormatOptions),
96-
bAnnotation: prettyFormat(valueB, prettyFormatOptions),
97-
});
98-
}
99-
10079
function toMatchDiffSnapshot(
10180
valueA: any,
10281
valueB: any,
@@ -119,7 +98,13 @@ function getSnapshotDiffSerializer() {
11998
};
12099
}
121100

101+
function setSerializers(customSerializers) {
102+
serializers = customSerializers;
103+
}
104+
122105
module.exports = snapshotDiff;
123106
module.exports.snapshotDiff = snapshotDiff;
124107
module.exports.toMatchDiffSnapshot = toMatchDiffSnapshot;
125108
module.exports.getSnapshotDiffSerializer = getSnapshotDiffSerializer;
109+
module.exports.setSerializers = setSerializers;
110+
module.exports.defaultSerializers = defaultSerializers;

0 commit comments

Comments
 (0)