1
1
/*eslint-disable react/prop-types*/
2
2
3
3
import React , { Component } from 'react'
4
+ import ReactDOM from "react-dom"
4
5
import { createStore } from 'redux'
5
6
import { Provider , connect } from '../../src/index.js'
6
- import Context from ' ../../src/components/Context.js'
7
+ import { ReactReduxContext } from " ../../src/components/Context"
7
8
import * as rtl from 'react-testing-library'
8
9
import 'jest-dom/extend-expect'
9
10
10
- const Consumer = Context . Consumer
11
+ const createExampleTextReducer = ( ) => ( state = "example text" ) => state ;
11
12
12
13
describe ( 'React' , ( ) => {
13
14
describe ( 'Provider' , ( ) => {
@@ -17,20 +18,11 @@ describe('React', () => {
17
18
class Child extends Component {
18
19
render ( ) {
19
20
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 >
32
24
} }
33
- </ Consumer >
25
+ </ ReactReduxContext . Consumer >
34
26
)
35
27
}
36
28
}
@@ -69,8 +61,8 @@ describe('React', () => {
69
61
Provider . propTypes = propTypes
70
62
} )
71
63
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 ( ) )
74
66
75
67
const spy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } )
76
68
const tester = rtl . render (
@@ -81,25 +73,9 @@ describe('React', () => {
81
73
expect ( spy ) . toHaveBeenCalledTimes ( 0 )
82
74
spy . mockRestore ( )
83
75
84
- expect ( tester . getByTestId ( 'store' ) ) . toHaveTextContent ( 'store - hi ' )
76
+ expect ( tester . getByTestId ( 'store' ) ) . toHaveTextContent ( 'store - example text ' )
85
77
} )
86
78
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
- } )
103
79
104
80
it ( 'accepts new store in props' , ( ) => {
105
81
const store1 = createStore ( ( state = 10 ) => state + 1 )
@@ -124,25 +100,25 @@ describe('React', () => {
124
100
}
125
101
126
102
const tester = rtl . render ( < ProviderContainer /> )
127
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 11' )
103
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 11' )
128
104
store1 . dispatch ( { type : 'hi' } )
129
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 12' )
105
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 12' )
130
106
131
107
externalSetState ( { store : store2 } )
132
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 20' )
108
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 20' )
133
109
store1 . dispatch ( { type : 'hi' } )
134
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 20' )
110
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 20' )
135
111
store2 . dispatch ( { type : 'hi' } )
136
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 40' )
112
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 40' )
137
113
138
114
externalSetState ( { store : store3 } )
139
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 101' )
115
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 101' )
140
116
store1 . dispatch ( { type : 'hi' } )
141
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 101' )
117
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 101' )
142
118
store2 . dispatch ( { type : 'hi' } )
143
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 101' )
119
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 101' )
144
120
store3 . dispatch ( { type : 'hi' } )
145
- expect ( tester . getByTestId ( 'value ' ) ) . toHaveTextContent ( 'value: 10202' )
121
+ expect ( tester . getByTestId ( 'store ' ) ) . toHaveTextContent ( 'store - 10202' )
146
122
} )
147
123
148
124
it ( 'should handle subscriptions correctly when there is nested Providers' , ( ) => {
@@ -167,98 +143,129 @@ describe('React', () => {
167
143
innerStore . dispatch ( { type : 'INC' } )
168
144
expect ( innerMapStateToProps ) . toHaveBeenCalledTimes ( 2 )
169
145
} )
170
- } )
171
146
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
- }
178
147
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 )
180
156
181
- store . dispatch ( { type : 'APPEND' , body : 'a' } )
182
- let childMapStateInvokes = 0
157
+ store . dispatch ( { type : 'APPEND' , body : 'a' } )
158
+ let childMapStateInvokes = 0
183
159
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
+ }
188
174
}
189
175
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
+ }
197
187
}
198
- }
199
188
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 )
207
219
} )
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
211
225
}
212
- }
226
+ const spy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } )
227
+ const store = createStore ( ( ) => ( { } ) )
213
228
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
+ } )
246
239
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 ( ( ) => ( { } ) )
253
240
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 (
256
257
< Provider store = { store } >
257
258
< div />
258
- </ Provider >
259
- </ React . StrictMode >
260
- )
259
+ </ Provider > ,
260
+ div
261
+ )
261
262
262
- expect ( spy ) . not . toHaveBeenCalled ( )
263
+ expect ( spy ) . toHaveBeenCalledTimes ( 0 )
264
+ ReactDOM . unmountComponentAtNode ( div )
265
+ expect ( spy ) . toHaveBeenCalledTimes ( 1 )
266
+ } )
263
267
} )
268
+
269
+
270
+
264
271
} )
0 commit comments