Skip to content

Commit

Permalink
Convert tests to use react-testing-library instead of Enzyme (#998)
Browse files Browse the repository at this point in the history
* Update React to 16.4.2

* Restore other test changes from 5.1.0-test commit

* Fix tests that fail due to use of a string ref

* Disable <StrictMode> tests for now

* Add react-testing-library

* Convert Provider tests to RTL

* Convert Connect tests to RTL

* Remove uses of Enzyme and getTestDeps

* Remove Enzyme and react-test-renderer packages

* Fix a test lint error

* Disable lint warnings about componentWillReceiveProps
  • Loading branch information
markerikson committed Aug 14, 2018
1 parent 73691e5 commit 47facdb
Show file tree
Hide file tree
Showing 17 changed files with 1,314 additions and 2,664 deletions.
615 changes: 223 additions & 392 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"build:umd:min": "cross-env BABEL_ENV=rollup NODE_ENV=production rollup -c -o dist/react-redux.min.js",
"build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
"clean": "rimraf lib dist es coverage",
"lint": "eslint src test/utils test/components test/getTestDeps.js",
"lint": "eslint src test/utils test/components",
"prepare": "npm run clean && npm run build",
"test": "node ./test/run-tests.js",
"coverage": "codecov"
Expand Down Expand Up @@ -81,18 +81,17 @@
"create-react-class": "^15.6.3",
"cross-env": "^5.2.0",
"cross-spawn": "^6.0.5",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"es3ify": "^0.2.0",
"eslint": "^4.19.1",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react": "^7.9.1",
"glob": "^7.1.1",
"jest": "^23.4.1",
"jest-dom": "^1.12.0",
"npm-run": "^5.0.1",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-test-renderer": "^16.3.2",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-testing-library": "^5.0.0",
"redux": "^4.0.0",
"rimraf": "^2.6.2",
"rollup": "^0.61.1",
Expand Down
5 changes: 5 additions & 0 deletions src/components/connectAdvanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export default function connectAdvanced(
WrappedComponent
}

// TODO Actually fix our use of componentWillReceiveProps
/* eslint-disable react/no-deprecated */

class Connect extends Component {
constructor(props, context) {
super(props, context)
Expand Down Expand Up @@ -258,6 +261,8 @@ export default function connectAdvanced(
}
}

/* eslint-enable react/no-deprecated */

Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Expand Down
82 changes: 50 additions & 32 deletions test/components/Provider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@ import PropTypes from 'prop-types'
import semver from 'semver'
import { createStore } from 'redux'
import { Provider, createProvider, connect } from '../../src/index.js'
import { TestRenderer, enzyme } from '../getTestDeps.js'
import * as rtl from 'react-testing-library'
import 'jest-dom/extend-expect'

const createExampleTextReducer = () => (state = "example text") => state;

describe('React', () => {
describe('Provider', () => {
const createChild = (storeKey = 'store') => {
class Child extends Component {
render() {
return <div />
afterEach(() => rtl.cleanup())
const createChild = (storeKey = 'store') => {
class Child extends Component {
render() {
const store = this.context[storeKey];

let text = '';

if(store) {
text = store.getState().toString()
}

return (
<div data-testid="store">
{storeKey} - {text}
</div>
)
}
}


Child.contextTypes = {
[storeKey]: PropTypes.object.isRequired
Expand All @@ -34,33 +51,33 @@ describe('React', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})

try {
expect(() => enzyme.mount(
expect(() => rtl.render(
<Provider store={store}>
<div />
</Provider>
)).not.toThrow()

if (semver.lt(React.version, '15.0.0')) {
expect(() => enzyme.mount(
expect(() => rtl.render(
<Provider store={store}>
</Provider>
)).toThrow(/children with exactly one child/)
} else {
expect(() => enzyme.mount(
expect(() => rtl.render(
<Provider store={store}>
</Provider>
)).toThrow(/a single React element child/)
}

if (semver.lt(React.version, '15.0.0')) {
expect(() => enzyme.mount(
expect(() => rtl.render(
<Provider store={store}>
<div />
<div />
</Provider>
)).toThrow(/children with exactly one child/)
} else {
expect(() => enzyme.mount(
expect(() => rtl.render(
<Provider store={store}>
<div />
<div />
Expand All @@ -74,48 +91,48 @@ describe('React', () => {
})

it('should add the store to the child context', () => {
const store = createStore(() => ({}))
const store = createStore(createExampleTextReducer())

const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
const testRenderer = enzyme.mount(
const tester = rtl.render(
<Provider store={store}>
<Child />
</Provider>
)
expect(spy).toHaveBeenCalledTimes(0)
spy.mockRestore()

const child = testRenderer.find(Child).instance()
expect(child.context.store).toBe(store)
expect(tester.getByTestId('store')).toHaveTextContent('store - example text')
})

it('should add the store to the child context using a custom store key', () => {
const store = createStore(() => ({}))
const store = createStore(createExampleTextReducer())
const CustomProvider = createProvider('customStoreKey');
const CustomChild = createChild('customStoreKey');

const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
const testRenderer = enzyme.mount(
const tester = rtl.render(
<CustomProvider store={store}>
<CustomChild />
</CustomProvider>
)
expect(spy).toHaveBeenCalledTimes(0)
spy.mockRestore()

const child = testRenderer.find(CustomChild).instance()
expect(child.context.customStoreKey).toBe(store)
expect(tester.getByTestId('store')).toHaveTextContent('customStoreKey - example text')
})

it('should warn once when receiving a new store in props', () => {
const store1 = createStore((state = 10) => state + 1)
const store2 = createStore((state = 10) => state * 2)
const store3 = createStore((state = 10) => state * state)

let externalSetState
class ProviderContainer extends Component {
constructor() {
super()
this.state = { store: store1 }
externalSetState = this.setState.bind(this)
}
render() {
return (
Expand All @@ -126,14 +143,13 @@ describe('React', () => {
}
}

const testRenderer = enzyme.mount(<ProviderContainer />)
const child = testRenderer.find(Child).instance()
expect(child.context.store.getState()).toEqual(11)
const tester = rtl.render(<ProviderContainer />)
expect(tester.getByTestId('store')).toHaveTextContent('store - 11')

let spy = jest.spyOn(console, 'error').mockImplementation(() => {})
testRenderer.setState({ store: store2 })
externalSetState({ store: store2 })

expect(child.context.store.getState()).toEqual(11)
expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
expect(spy).toHaveBeenCalledTimes(1)
expect(spy.mock.calls[0][0]).toBe(
'<Provider> does not support changing `store` on the fly. ' +
Expand All @@ -145,9 +161,9 @@ describe('React', () => {
spy.mockRestore()

spy = jest.spyOn(console, 'error').mockImplementation(() => {})
testRenderer.setState({ store: store3 })
externalSetState({ store: store3 })

expect(child.context.store.getState()).toEqual(11)
expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
expect(spy).toHaveBeenCalledTimes(0)
spy.mockRestore()
})
Expand All @@ -168,7 +184,7 @@ describe('React', () => {
render() { return <Provider store={innerStore}><Inner /></Provider> }
}

enzyme.mount(<Provider store={outerStore}><Outer /></Provider>)
rtl.render(<Provider store={outerStore}><Outer /></Provider>)
expect(innerMapStateToProps).toHaveBeenCalledTimes(1)

innerStore.dispatch({ type: 'INC'})
Expand Down Expand Up @@ -197,7 +213,7 @@ describe('React', () => {
render() {
return (
<div>
<button ref="button" onClick={this.emitChange.bind(this)}>change</button>
<button onClick={this.emitChange.bind(this)}>change</button>
<ChildContainer parentState={this.props.state} />
</div>
)
Expand All @@ -216,7 +232,7 @@ describe('React', () => {
}
}

const testRenderer = enzyme.mount(
const tester = rtl.render(
<Provider store={store}>
<Container />
</Provider>
Expand All @@ -229,23 +245,24 @@ describe('React', () => {
expect(childMapStateInvokes).toBe(2)

// setState calls DOM handlers are batched
const button = testRenderer.find('button')
button.prop('onClick')()
const button = tester.getByText('change')
rtl.fireEvent.click(button)
expect(childMapStateInvokes).toBe(3)

// Provider uses unstable_batchedUpdates() under the hood
store.dispatch({ type: 'APPEND', body: 'd' })
expect(childMapStateInvokes).toBe(4)
})

it('works in <StrictMode> without warnings (React 16.3+)', () => {

it.skip('works in <StrictMode> without warnings (React 16.3+)', () => {
if (!React.StrictMode) {
return
}
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
const store = createStore(() => ({}))

TestRenderer.create(
rtl.render(
<React.StrictMode>
<Provider store={store}>
<div />
Expand All @@ -255,4 +272,5 @@ describe('React', () => {

expect(spy).not.toHaveBeenCalled()
})

})
Loading

0 comments on commit 47facdb

Please sign in to comment.