Skip to content

Commit

Permalink
feat: Support enhancing React component serializer
Browse files Browse the repository at this point in the history
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 jest-community#162
  • Loading branch information
alistairjcbrown committed Jun 30, 2020
1 parent 1467c8e commit 1cfd88e
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 16 deletions.
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,31 +95,37 @@ exports[`snapshot difference between 2 React components state 1`] = `

## Custom serializers

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

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

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

### Adding a new custom serializer

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

snapshotDiff.setSerializers([
...snapshotDiff.defaultSerializers, // use default React serializer - add this if you want to serialise React components!
...snapshotDiff.defaultSerializers, // use the default React component serializer - add this if you want to continue to serialize React components!
myCustomSerializer
]);
```

### Serializing React components with a different serializer

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

```js
const snapshotDiff = require('snapshot-diff');
Expand All @@ -132,6 +138,35 @@ snapshotDiff.setSerializers([
]);
```

## Enhancing the React component serializer

`snapshot-diff` uses a built in React component serializer based on `react-test-renderer`. This makes use of the default
Jest serializers passed to `pretty-format`. However, you may wish to add additional serializers to be passed to
`pretty-format` when serializing a React component, e.g. Using a CSS-in-JS solution such as Emotion.

The React component serializer is exposed at `snapshotDiff.reactSerializer`

The API for adding new "sub-serializers" to the React component serializer is similar to how top level serializers are
added to `snapshot-diff`. The React component serializer has a `setSerializers` function which can be used an be used
to change the serializers used for serializing a React component. Again, if you want to keep the default serializers in
place, don't forget to add the default serializers too!

ℹ️ **Note:** Serializers added to the React component serializer are only used by the React component serializer
- `snapshotDiff.setSerializers` is not the same as `snapshotDiff.reactSerializer.setSerializers`
- `snapshotDiff.defaultSerializers` is not the same as `snapshotDiff.reactSerializer.defaultSerializers`

### Adding a new serializer

```js
const snapshotDiff = require('snapshot-diff');
const emotionSerializer = require('jest-emotion');

snapshotDiff.reactSerializer.setSerializers([
emotionSerializer,
...snapshotDiff.reactSerializer.defaultSerializers
]);
```

## Snapshot serializer

By default Jest adds extra quotes around strings so it makes diff snapshots of objects too noisy.
Expand Down
38 changes: 38 additions & 0 deletions __tests__/__snapshots__/setSerializers.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`components using CSS-in-JS can use contextLines 1`] = `
"Snapshot Diff:
- <NestedComponent css=\\"unknown styles\\" test=\\"say\\" />
+ <NestedComponent css=\\"unknown styles\\" test=\\"my name\\" />
@@ -2,1 +2,1 @@
- color: green;
+ color: red;
@@ -10,1 +10,1 @@
- say
+ my name"
`;

exports[`components using CSS-in-JS diffs components 1`] = `
"Snapshot Diff:
- <NestedComponent css=\\"unknown styles\\" test=\\"say\\" />
+ <NestedComponent css=\\"unknown styles\\" test=\\"my name\\" />
@@ -1,15 +1,15 @@
.emotion-0 {
- color: green;
+ color: red;
}
<div
className=\\"emotion-0\\"
>
<span>
Hello World -
- say
+ my name
</span>
<div>
I have value
1234
</div>"
`;
exports[`default rendered components can use contextLines 1`] = `
"Snapshot Diff:
- <NestedComponent test=\\"say\\" />
Expand Down
54 changes: 53 additions & 1 deletion __tests__/setSerializers.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// @flow
const React = require('react');
const { jsx, css } = require('@emotion/core');
const { configure, shallow: enzymeShallow } = require('enzyme');
const ReactShallowRenderer = require('react-test-renderer/shallow');
const Adapter = require('enzyme-adapter-react-16');
const enzymeToJson = require('enzyme-to-json/serializer');
const emotionSerializer = require('jest-emotion');
const snapshotDiff = require('../src/index');

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

const NestedComponent = (props: Props) => (
<div>
<div className={props.className}>
<span>Hello World - {props.test}</span>
<Component value={1234} />
</div>
Expand Down Expand Up @@ -115,3 +117,53 @@ describe('values which are not components', () => {
).toMatchSnapshot();
});
});

describe('components using CSS-in-JS', () => {
beforeEach(() => {
snapshotDiff.reactSerializer.setSerializers([
emotionSerializer,
...snapshotDiff.reactSerializer.defaultSerializers,
]);
});

test('diffs components', () => {
expect(
snapshotDiff(
jsx(NestedComponent, {
css: css`
color: green;
`,
test: 'say',
}),
jsx(NestedComponent, {
css: css`
color: red;
`,
test: 'my name',
})
)
).toMatchSnapshot();
});

test('can use contextLines', () => {
expect(
snapshotDiff(
jsx(NestedComponent, {
css: css`
color: green;
`,
test: 'say',
}),
jsx(NestedComponent, {
css: css`
color: red;
`,
test: 'my name',
}),
{
contextLines: 0,
}
)
).toMatchSnapshot();
});
});
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface Serializer {
test: (value: any) => boolean;
print: (value: any, _serializer?: any) => any;
diffOptions?: (valueA: any, valueB: any) => DiffOptions;
setSerializers?: (serializers: Array<Serializer>) => void;
defaultSerializers?: Array<Serializer>;
}

declare module 'snapshot-diff' {
Expand Down Expand Up @@ -54,6 +56,7 @@ declare module 'snapshot-diff' {
*/
setSerializers: (serializers: Array<Serializer>) => void;
defaultSerializers: Array<Serializer>;
reactSerializer: Serializer;
}
const diff: SnapshotDiff;
export = diff;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.7.0",
"@callstack/eslint-config": "^10.0.0",
"@emotion/core": "^10.0.28",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.4.0",
"eslint": "^7.0.0",
"flow-bin": "^0.127.0",
"jest": "^26.1.0",
"jest-emotion": "^10.0.32",
"react": "^16.13.1",
"react-dom": "16.13.1",
"react-test-renderer": "^16.13.1"
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,4 @@ module.exports.toMatchDiffSnapshot = toMatchDiffSnapshot;
module.exports.getSnapshotDiffSerializer = getSnapshotDiffSerializer;
module.exports.setSerializers = setSerializers;
module.exports.defaultSerializers = defaultSerializers;
module.exports.reactSerializer = reactSerializer;
11 changes: 9 additions & 2 deletions src/react-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
const prettyFormat = require('pretty-format');
const snapshot = require('jest-snapshot');

const serializers = snapshot.getSerializers();
const defaultSerializers = snapshot.getSerializers();
let serializers = defaultSerializers;

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

Expand All @@ -25,7 +26,11 @@ function getReactComponentSerializer() {
throw error;
}
return (value) =>
prettyFormat(renderer.create(value), { plugins: serializers });
prettyFormat(renderer.create(value).toJSON(), { plugins: serializers });
}

function setSerializers(customSerializers) {
serializers = customSerializers;
}

const reactSerializer = {
Expand All @@ -41,6 +46,8 @@ const reactSerializer = {
bAnnotation: prettyFormat(valueB, prettyFormatOptions),
};
},
setSerializers,
defaultSerializers,
};

module.exports = reactSerializer;
Loading

0 comments on commit 1cfd88e

Please sign in to comment.