Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update React examples to match with the new React guidelines #12217

Merged
merged 15 commits into from Feb 11, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -22,6 +22,7 @@
- `[*]` [**BREAKING**] Drop support for Node v10 and v15 and target first LTS `16.13.0` ([#12220](https://github.com/facebook/jest/pull/12220))
- `[*]` [**BREAKING**] Drop support for `typescript@3.8`, minimum version is now `4.2` ([#11142](https://github.com/facebook/jest/pull/11142))
- `[*]` Bundle all `.d.ts` files into a single `index.d.ts` per module ([#12345](https://github.com/facebook/jest/pull/12345))
- `[docs, examples]` Update React examples to match with the new React guidelines for code examples ([#12217](https://github.com/facebook/jest/pull/12217))
- `[expect]` [**BREAKING**] Remove support for importing `build/utils` ([#12323](https://github.com/facebook/jest/pull/12323))
- `[expect]` [**BREAKING**] Migrate to ESM ([#12344](https://github.com/facebook/jest/pull/12344))
- `[jest-cli]` Update `yargs` to v17 ([#12357](https://github.com/facebook/jest/pull/12357))
Expand Down
1 change: 0 additions & 1 deletion docs/SnapshotTesting.md
Expand Up @@ -12,7 +12,6 @@ A typical snapshot test case renders a UI component, takes a snapshot, then comp
A similar approach can be taken when it comes to testing your React components. Instead of rendering the graphical UI, which would require building the entire app, you can use a test renderer to quickly generate a serializable value for your React tree. Consider this [example test](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/link.test.js) for a [Link component](https://github.com/facebook/jest/blob/main/examples/snapshot/Link.js):
charpeni marked this conversation as resolved.
Show resolved Hide resolved

```tsx
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link';

Expand Down
61 changes: 34 additions & 27 deletions docs/TutorialReact.md
Expand Up @@ -48,7 +48,10 @@ Your `package.json` should look something like this (where `<current-version>` i

```js title="babel.config.js"
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
```

Expand All @@ -59,14 +62,14 @@ module.exports = {
Let's create a [snapshot test](SnapshotTesting.md) for a Link component that renders hyperlinks:

```tsx title="Link.js"
import React, {useState} from 'react';
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

const Link = ({page, children}) => {
export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);

const onMouseEnter = () => {
Expand All @@ -87,35 +90,36 @@ const Link = ({page, children}) => {
{children}
</a>
);
};

export default Link;
}
```

> Note: Examples are using Function components, but Class components can be tested in the same way. See [React: Function and Class Components](https://reactjs.org/docs/components-and-props.html#function-and-class-components). **Reminders** that with Class components, we expect Jest to be used to test props and not methods directly.

Now let's use React's test renderer and Jest's snapshot feature to interact with the component and capture the rendered output and create a snapshot file:

```tsx title="Link.react.test.js"
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

test('Link changes the class when hovered', () => {
it('changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
tree.props.onMouseEnter();
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
tree.props.onMouseLeave();
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
Expand All @@ -125,32 +129,35 @@ test('Link changes the class when hovered', () => {
When you run `yarn test` or `jest`, this will produce an output file like this:

```javascript title="__tests__/__snapshots__/Link.react.test.js.snap"
exports[`Link changes the class when hovered 1`] = `
exports[`changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`Link changes the class when hovered 2`] = `
exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`Link changes the class when hovered 3`] = `
exports[`changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;
Expand All @@ -160,7 +167,7 @@ The next time you run the tests, the rendered output will be compared to the pre

The code for this example is available at [examples/snapshot](https://github.com/facebook/jest/tree/main/examples/snapshot).

#### Snapshot Testing with Mocks, Enzyme and React 16
#### Snapshot Testing with Mocks, Enzyme and React 16+

There's a caveat around snapshot testing when using Enzyme and React 16+. If you mock out a module using the following style:

Expand Down Expand Up @@ -205,9 +212,9 @@ You have to run `yarn add --dev @testing-library/react` to use react-testing-lib
Let's implement a checkbox which swaps between two labels:

```tsx title="CheckboxWithLabel.js"
import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelOn, labelOff}) => {
export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -220,13 +227,10 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
```

```tsx title="__tests__/CheckboxWithLabel-test.js"
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

Expand Down Expand Up @@ -256,11 +260,14 @@ You have to run `yarn add --dev enzyme` to use Enzyme. If you are using a React
Let's rewrite the test from above using Enzyme instead of react-testing-library. We use Enzyme's [shallow renderer](http://airbnb.io/enzyme/docs/api/shallow.html) in this example.

```tsx title="__tests__/CheckboxWithLabel-test.js"
import React from 'react';
import {shallow} from 'enzyme';
import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({adapter: new Adapter()});

import CheckboxWithLabel from '../CheckboxWithLabel';

test('CheckboxWithLabel changes the text after click', () => {
it('CheckboxWithLabel changes the text after click', () => {
// Render a checkbox with label in the document
const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);

Expand Down
6 changes: 4 additions & 2 deletions examples/enzyme/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties']
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
40 changes: 21 additions & 19 deletions examples/enzyme/CheckboxWithLabel.js
@@ -1,26 +1,28 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import {useState} from 'react';

export default class CheckboxWithLabel extends React.Component {
state = {
isChecked: false,
};
export default function CheckboxWithLabel({
labelRef,
inputRef,
labelOn,
labelOff,
}) {
const [isChecked, setIsChecked] = useState(false);

onChange = () => {
this.setState({isChecked: !this.state.isChecked});
const onChange = () => {
setIsChecked(!isChecked);
};

render() {
return (
<label>
<input
type="checkbox"
checked={this.state.isChecked}
onChange={this.onChange}
/>
{this.state.isChecked ? this.props.labelOn : this.props.labelOff}
</label>
);
}
return (
<label ref={labelRef}>
<input
ref={inputRef}
type="checkbox"
checked={isChecked}
onChange={onChange}
/>
{isChecked ? labelOn : labelOff}
</label>
);
}
1 change: 0 additions & 1 deletion examples/enzyme/__tests__/CheckboxWithLabel-test.js
@@ -1,6 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Expand Down
6 changes: 4 additions & 2 deletions examples/react-testing-library/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
8 changes: 3 additions & 5 deletions examples/react-testing-library/CheckboxWithLabel.js
@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelOn, labelOff}) => {
export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -15,6 +15,4 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
@@ -1,6 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

Expand Down
5 changes: 4 additions & 1 deletion examples/react/.babelrc.js
@@ -1,5 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
13 changes: 8 additions & 5 deletions examples/react/CheckboxWithLabel.js
@@ -1,8 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => {
export default function CheckboxWithLabel({
labelRef,
inputRef,
labelOn,
labelOff,
}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -20,6 +25,4 @@ const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
2 changes: 1 addition & 1 deletion examples/react/__tests__/CheckboxWithLabel-test.js
@@ -1,6 +1,6 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {createRef} from 'react';
import {createRef} from 'react';

import * as TestUtils from 'react-dom/test-utils';
import CheckboxWithLabel from '../CheckboxWithLabel';
Expand Down
6 changes: 4 additions & 2 deletions examples/snapshot/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
8 changes: 3 additions & 5 deletions examples/snapshot/Clock.js
@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useEffect, useState} from 'react';
import {useEffect, useState} from 'react';

const Clock = () => {
export default function Clock() {
const [seconds, setSeconds] = useState(Date.now() / 1000);

const tick = () => {
Expand All @@ -16,6 +16,4 @@ const Clock = () => {
}, []);

return <p>{seconds} seconds have elapsed since the UNIX epoch.</p>;
};

export default Clock;
}
8 changes: 3 additions & 5 deletions examples/snapshot/Link.js
@@ -1,13 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useState} from 'react';
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

const Link = ({page, children}) => {
export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);

const onMouseEnter = () => {
Expand All @@ -28,6 +28,4 @@ const Link = ({page, children}) => {
{children}
</a>
);
};

export default Link;
}
1 change: 0 additions & 1 deletion examples/snapshot/__tests__/clock.test.js
Expand Up @@ -2,7 +2,6 @@

'use strict';

import React from 'react';
import Clock from '../Clock';
import renderer from 'react-test-renderer';

Expand Down