Skip to content

Commit 106a7e1

Browse files
committed
Rework Provider tests
1 parent 93fb69b commit 106a7e1

File tree

1 file changed

+127
-120
lines changed

1 file changed

+127
-120
lines changed

test/components/Provider.spec.js

Lines changed: 127 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/*eslint-disable react/prop-types*/
22

33
import React, { Component } from 'react'
4+
import ReactDOM from "react-dom"
45
import { createStore } from 'redux'
56
import { Provider, connect } from '../../src/index.js'
6-
import Context from '../../src/components/Context.js'
7+
import {ReactReduxContext} from "../../src/components/Context"
78
import * as rtl from 'react-testing-library'
89
import 'jest-dom/extend-expect'
910

10-
const Consumer = Context.Consumer
11+
const createExampleTextReducer = () => (state = "example text") => state;
1112

1213
describe('React', () => {
1314
describe('Provider', () => {
@@ -17,20 +18,11 @@ describe('React', () => {
1718
class Child extends Component {
1819
render() {
1920
return (
20-
<Consumer>
21-
{(value) => {
22-
return (
23-
<div>
24-
<div data-testid="store">
25-
{storeKey} - {value && value.store ? value.store.getState().mine : ''}
26-
</div>
27-
<div data-testid="value">
28-
value: {JSON.stringify(value.state)}
29-
</div>
30-
</div>
31-
)
21+
<ReactReduxContext.Consumer>
22+
{({storeState}) => {
23+
return <div data-testid="store">{`${storeKey} - ${storeState}`}</div>
3224
}}
33-
</Consumer>
25+
</ReactReduxContext.Consumer>
3426
)
3527
}
3628
}
@@ -69,8 +61,8 @@ describe('React', () => {
6961
Provider.propTypes = propTypes
7062
})
7163

72-
it('should add the store to the child context', () => {
73-
const store = createStore(() => ({ mine: 'hi'}))
64+
it('should add the store state to context', () => {
65+
const store = createStore(createExampleTextReducer())
7466

7567
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
7668
const tester = rtl.render(
@@ -81,25 +73,9 @@ describe('React', () => {
8173
expect(spy).toHaveBeenCalledTimes(0)
8274
spy.mockRestore()
8375

84-
expect(tester.getByTestId('store')).toHaveTextContent('store - hi')
76+
expect(tester.getByTestId('store')).toHaveTextContent('store - example text')
8577
})
8678

87-
it('should add the store to the child context using a custom store key', () => {
88-
const store = createStore(() => ({ mine: 'hi' }))
89-
const CustomProvider = Provider;
90-
const CustomChild = createChild('customStoreKey');
91-
92-
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
93-
const tester = rtl.render(
94-
<CustomProvider store={store}>
95-
<CustomChild />
96-
</CustomProvider>
97-
)
98-
expect(spy).toHaveBeenCalledTimes(0)
99-
spy.mockRestore()
100-
101-
expect(tester.getByTestId('store')).toHaveTextContent('customStoreKey - hi')
102-
})
10379

10480
it('accepts new store in props', () => {
10581
const store1 = createStore((state = 10) => state + 1)
@@ -124,25 +100,25 @@ describe('React', () => {
124100
}
125101

126102
const tester = rtl.render(<ProviderContainer />)
127-
expect(tester.getByTestId('value')).toHaveTextContent('value: 11')
103+
expect(tester.getByTestId('store')).toHaveTextContent('store - 11')
128104
store1.dispatch({ type: 'hi' })
129-
expect(tester.getByTestId('value')).toHaveTextContent('value: 12')
105+
expect(tester.getByTestId('store')).toHaveTextContent('store - 12')
130106

131107
externalSetState({ store: store2 })
132-
expect(tester.getByTestId('value')).toHaveTextContent('value: 20')
108+
expect(tester.getByTestId('store')).toHaveTextContent('store - 20')
133109
store1.dispatch({ type: 'hi' })
134-
expect(tester.getByTestId('value')).toHaveTextContent('value: 20')
110+
expect(tester.getByTestId('store')).toHaveTextContent('store - 20')
135111
store2.dispatch({ type: 'hi' })
136-
expect(tester.getByTestId('value')).toHaveTextContent('value: 40')
112+
expect(tester.getByTestId('store')).toHaveTextContent('store - 40')
137113

138114
externalSetState({ store: store3 })
139-
expect(tester.getByTestId('value')).toHaveTextContent('value: 101')
115+
expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
140116
store1.dispatch({ type: 'hi' })
141-
expect(tester.getByTestId('value')).toHaveTextContent('value: 101')
117+
expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
142118
store2.dispatch({ type: 'hi' })
143-
expect(tester.getByTestId('value')).toHaveTextContent('value: 101')
119+
expect(tester.getByTestId('store')).toHaveTextContent('store - 101')
144120
store3.dispatch({ type: 'hi' })
145-
expect(tester.getByTestId('value')).toHaveTextContent('value: 10202')
121+
expect(tester.getByTestId('store')).toHaveTextContent('store - 10202')
146122
})
147123

148124
it('should handle subscriptions correctly when there is nested Providers', () => {
@@ -167,98 +143,129 @@ describe('React', () => {
167143
innerStore.dispatch({ type: 'INC'})
168144
expect(innerMapStateToProps).toHaveBeenCalledTimes(2)
169145
})
170-
})
171146

172-
it('should pass state consistently to mapState', () => {
173-
function stringBuilder(prev = '', action) {
174-
return action.type === 'APPEND'
175-
? prev + action.body
176-
: prev
177-
}
178147

179-
const store = createStore(stringBuilder)
148+
it('should pass state consistently to mapState', () => {
149+
function stringBuilder(prev = '', action) {
150+
return action.type === 'APPEND'
151+
? prev + action.body
152+
: prev
153+
}
154+
155+
const store = createStore(stringBuilder)
180156

181-
store.dispatch({ type: 'APPEND', body: 'a' })
182-
let childMapStateInvokes = 0
157+
store.dispatch({ type: 'APPEND', body: 'a' })
158+
let childMapStateInvokes = 0
183159

184-
@connect(state => ({ state }), null, null)
185-
class Container extends Component {
186-
emitChange() {
187-
store.dispatch({ type: 'APPEND', body: 'b' })
160+
@connect(state => ({ state }))
161+
class Container extends Component {
162+
emitChange() {
163+
store.dispatch({ type: 'APPEND', body: 'b' })
164+
}
165+
166+
render() {
167+
return (
168+
<div>
169+
<button onClick={this.emitChange.bind(this)}>change</button>
170+
<ChildContainer parentState={this.props.state} />
171+
</div>
172+
)
173+
}
188174
}
189175

190-
render() {
191-
return (
192-
<div>
193-
<button onClick={this.emitChange.bind(this)}>change</button>
194-
<ChildContainer parentState={this.props.state} />
195-
</div>
196-
)
176+
const childCalls = []
177+
@connect((state, parentProps) => {
178+
childMapStateInvokes++
179+
childCalls.push([state, parentProps.parentState])
180+
// The state from parent props should always be consistent with the current state
181+
return {}
182+
})
183+
class ChildContainer extends Component {
184+
render() {
185+
return <div />
186+
}
197187
}
198-
}
199188

200-
const childCalls = []
201-
@connect((state, parentProps) => {
202-
childMapStateInvokes++
203-
childCalls.push([state, parentProps.parentState])
204-
// The state from parent props should always be consistent with the current state
205-
//expect(state).toEqual(parentProps.parentState)
206-
return {}
189+
const tester = rtl.render(
190+
<Provider store={store}>
191+
<Container />
192+
</Provider>
193+
)
194+
195+
expect(childMapStateInvokes).toBe(1)
196+
197+
// The store state stays consistent when setState calls are batched
198+
store.dispatch({ type: 'APPEND', body: 'c' })
199+
expect(childMapStateInvokes).toBe(2)
200+
expect(childCalls).toEqual([
201+
['a', 'a'],
202+
['ac', 'ac']
203+
])
204+
205+
// setState calls DOM handlers are batched
206+
const button = tester.getByText('change')
207+
rtl.fireEvent.click(button)
208+
expect(childMapStateInvokes).toBe(3)
209+
210+
// Provider uses unstable_batchedUpdates() under the hood
211+
store.dispatch({ type: 'APPEND', body: 'd' })
212+
expect(childCalls).toEqual([
213+
['a', 'a'],
214+
['ac', 'ac'], // then store update is processed
215+
['acb', 'acb'], // then store update is processed
216+
['acbd', 'acbd'], // then store update is processed
217+
])
218+
expect(childMapStateInvokes).toBe(4)
207219
})
208-
class ChildContainer extends Component {
209-
render() {
210-
return <div />
220+
221+
222+
it('works in <StrictMode> without warnings (React 16.3+)', () => {
223+
if (!React.StrictMode) {
224+
return
211225
}
212-
}
226+
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
227+
const store = createStore(() => ({}))
213228

214-
const tester = rtl.render(
215-
<Provider store={store}>
216-
<Container />
217-
</Provider>
218-
)
219-
220-
expect(childMapStateInvokes).toBe(1)
221-
222-
// The store state stays consistent when setState calls are batched
223-
store.dispatch({ type: 'APPEND', body: 'c' })
224-
expect(childMapStateInvokes).toBe(2)
225-
expect(childCalls).toEqual([
226-
['a', 'a'],
227-
['ac', 'ac']
228-
])
229-
230-
// setState calls DOM handlers are batched
231-
232-
const button = tester.getByText('change')
233-
rtl.fireEvent.click(button)
234-
expect(childMapStateInvokes).toBe(3)
235-
236-
// Provider uses unstable_batchedUpdates() under the hood
237-
store.dispatch({ type: 'APPEND', body: 'd' })
238-
expect(childCalls).toEqual([
239-
['a', 'a'],
240-
['ac', 'ac'], // then store update is processed
241-
['acb', 'acb'], // then store update is processed
242-
['acbd', 'acbd'], // then store update is processed
243-
])
244-
expect(childMapStateInvokes).toBe(4)
245-
})
229+
rtl.render(
230+
<React.StrictMode>
231+
<Provider store={store}>
232+
<div />
233+
</Provider>
234+
</React.StrictMode>
235+
)
236+
237+
expect(spy).not.toHaveBeenCalled()
238+
})
246239

247-
it('works in <StrictMode> without warnings (React 16.3+)', () => {
248-
if (!React.StrictMode) {
249-
return
250-
}
251-
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
252-
const store = createStore(() => ({}))
253240

254-
rtl.render(
255-
<React.StrictMode>
241+
it('should unsubscribe before unmounting', () => {
242+
const store = createStore(createExampleTextReducer())
243+
const subscribe = store.subscribe
244+
245+
// Keep track of unsubscribe by wrapping subscribe()
246+
const spy = jest.fn(() => ({}))
247+
store.subscribe = (listener) => {
248+
const unsubscribe = subscribe(listener)
249+
return () => {
250+
spy()
251+
return unsubscribe()
252+
}
253+
}
254+
255+
const div = document.createElement('div')
256+
ReactDOM.render(
256257
<Provider store={store}>
257258
<div />
258-
</Provider>
259-
</React.StrictMode>
260-
)
259+
</Provider>,
260+
div
261+
)
261262

262-
expect(spy).not.toHaveBeenCalled()
263+
expect(spy).toHaveBeenCalledTimes(0)
264+
ReactDOM.unmountComponentAtNode(div)
265+
expect(spy).toHaveBeenCalledTimes(1)
266+
})
263267
})
268+
269+
270+
264271
})

0 commit comments

Comments
 (0)