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 #162
  • Loading branch information
alistairjcbrown committed Jul 18, 2020
1 parent 965daff commit 8fa0af8
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 17 deletions.
59 changes: 49 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,43 +95,82 @@ 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 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 the "_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!
// Use the default React component serializer. Don't forget to add this if you
...snapshotDiff.defaultSerializers, // 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');
const enzymeToJson = require('enzyme-to-json/serializer');
const myCustomSerializer = require('./my-custom-serializer');

snapshotDiff.setSerializers([
enzymeToJson, // using enzymes serializer instead
// Use Enzyme's React component serializer. Add this instead of the default React
enzymeToJson, // component serializer if you want to replace how React components are serialized
myCustomSerializer
]);
```

## Enhancing the React component serializer

`snapshot-diff` uses a built in React component serializer based on `react-test-renderer`. Internally, this makes use of
the default Jest serializers which are passed to `pretty-format`. However, you may wish to use a different configuration
of internal serializers when serializing a React component, e.g. Adding a new internal serializer to deal with using a
CSS-in-JS solution, such as Emotion.

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

The API for adding new internal 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 to
change the internal serializers used for serializing a React component. If you want to keep using the default internal
serializers, don't forget to add them too!

ℹ️ **Note:** Internal serializers added to the React component serializer are only used by the React component
serializer. i.e.
- `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.129.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 8fa0af8

Please sign in to comment.