1
1
import { act , renderHook } from "@testing-library/react-hooks" ;
2
2
import { atom , useAtom } from "jotai" ;
3
3
import React , { ReactNode , useContext , useRef , useState } from "react" ;
4
- import { ComponentScope , createScope , molecule } from "../vanilla" ;
4
+ import { createLifecycleUtils } from "../shared/testing/lifecycle" ;
5
+ import { ComponentScope , createScope , molecule , use } from "../vanilla" ;
5
6
import { ScopeProvider } from "./ScopeProvider" ;
6
7
import { ScopeContext } from "./contexts/ScopeContext" ;
7
8
import { strictModeSuite } from "./testing/strictModeSuite" ;
18
19
19
20
const AtomScope = createScope ( atom ( "[email protected] " ) ) ;
20
21
21
- export const UserMolecule = molecule ( ( _ , getScope ) => {
22
- const userId = getScope ( UserScope ) ;
22
+ const userLifecycle = createLifecycleUtils ( ) ;
23
+ export const UserMolecule = molecule ( ( ) => {
24
+ const userId = use ( UserScope ) ;
23
25
26
+ userLifecycle . connect ( userId ) ;
24
27
return {
25
28
example : Math . random ( ) ,
26
29
userId,
27
30
} ;
28
31
} ) ;
29
32
30
- const AtomMolecule = molecule ( ( _ , getScope ) => {
31
- const userAtom = getScope ( AtomScope ) ;
33
+ const atomLifecycle = createLifecycleUtils ( ) ;
34
+ const AtomMolecule = molecule ( ( ) => {
35
+ const userAtom = use ( AtomScope ) ;
32
36
33
37
const userNameAtom = atom ( ( get ) => get ( userAtom ) + " name" ) ;
38
+ atomLifecycle . connect ( userAtom ) ;
34
39
return {
35
40
example : Math . random ( ) ,
36
41
userIdAtom : userAtom ,
@@ -109,28 +114,41 @@ strictModeSuite(({ wrapper: Outer }) => {
109
114
context : useContext ( ScopeContext ) ,
110
115
} ;
111
116
} ;
112
- const { result : result1 } = renderHook ( useUserMolecule , {
117
+ const { result : result1 , ... rest1 } = renderHook ( useUserMolecule , {
113
118
wrapper : Wrapper1 ,
114
119
} ) ;
115
- const { result : result2 } = renderHook ( useUserMolecule , {
120
+
121
+ expect ( userLifecycle . mounts ) . toHaveBeenLastCalledWith ( "[email protected] " ) ;
122
+
123
+ const { result : result2 , ...rest2 } = renderHook ( useUserMolecule , {
116
124
wrapper : Wrapper2 ,
117
125
} ) ;
126
+ expect ( userLifecycle . mounts ) . toHaveBeenLastCalledWith (
127
+
128
+ ) ;
118
129
119
130
expect ( result1 . current . context ) . toStrictEqual ( [
120
131
121
132
] ) ;
122
133
expect ( result1 . current . molecule . userId ) . toBe ( "[email protected] " ) ;
123
134
expect ( result2 . current . molecule . userId ) . toBe ( "[email protected] " ) ;
135
+
136
+ rest1 . unmount ( ) ;
137
+ expect ( userLifecycle . unmounts ) . toHaveBeenLastCalledWith ( "[email protected] " ) ;
138
+
139
+ rest2 . unmount ( ) ;
140
+ expect ( userLifecycle . unmounts ) . toHaveBeenLastCalledWith (
141
+
142
+ ) ;
143
+
144
+ userLifecycle . expectToHaveBeenCalledTimes ( 2 ) ;
145
+ userLifecycle . expectToMatchCalls (
146
+
147
+
148
+ ) ;
124
149
} ) ;
125
150
126
151
describe ( "String scopes" , ( ) => {
127
- const useUserMolecule = ( ) => {
128
- return {
129
- molecule : useMolecule ( UserMolecule ) ,
130
- context : useContext ( ScopeContext ) ,
131
- } ;
132
- } ;
133
-
134
152
test ( "String scope values are cleaned up at the right time (not too soon, not too late)" , async ( ) => {
135
153
const StringScopeTestContext = React . createContext <
136
154
ReturnType < typeof useTextHook >
@@ -142,7 +160,7 @@ strictModeSuite(({ wrapper: Outer }) => {
142
160
const props = { mountA, mountB, setMountA, setMountB, insideValue } ;
143
161
return props ;
144
162
} ;
145
- const sharedKey = "[email protected] " ;
163
+ const sharedAtExample = "[email protected] " ;
146
164
const TestStuffProvider : React . FC < { children : ReactNode } > = ( {
147
165
children,
148
166
} ) => {
@@ -161,69 +179,96 @@ strictModeSuite(({ wrapper: Outer }) => {
161
179
( [ scope ] ) => scope !== ComponentScope ,
162
180
) ;
163
181
const context = useContext ( StringScopeTestContext ) ;
182
+ useMolecule ( UserMolecule ) ;
164
183
context . insideValue . current = scopes ;
165
184
return < div > Bad</ div > ;
166
185
} ;
167
186
const Controller = ( props : any ) => {
168
187
return (
169
188
< >
170
189
{ props . mountA && (
171
- < ScopeProvider scope = { UserScope } value = { sharedKey } >
190
+ < ScopeProvider scope = { UserScope } value = { sharedAtExample } >
172
191
< Child />
173
192
</ ScopeProvider >
174
193
) }
175
194
{ props . mountB && (
176
- < ScopeProvider scope = { UserScope } value = { sharedKey } >
195
+ < ScopeProvider scope = { UserScope } value = { sharedAtExample } >
177
196
< Child />
178
197
</ ScopeProvider >
179
198
) }
180
199
</ >
181
200
) ;
182
201
} ;
183
202
184
- const { result } = renderHook ( ( ) => useContext ( StringScopeTestContext ) , {
185
- wrapper : TestStuffProvider ,
186
- } ) ;
203
+ userLifecycle . expectUncalled ( ) ;
204
+
205
+ // When the component is initially mounted
206
+ const { result, ...rest } = renderHook (
207
+ ( ) => useContext ( StringScopeTestContext ) ,
208
+ {
209
+ wrapper : TestStuffProvider ,
210
+ } ,
211
+ ) ;
187
212
188
213
const { insideValue } = result . current ;
189
- const initialScopes = insideValue . current ;
190
- expect ( initialScopes ) . not . toBeUndefined ( ) ;
191
- expect ( initialScopes . length ) . toBe ( 1 ) ;
192
214
193
- const userScopeTuple = initialScopes [ 0 ] ;
194
- if ( true ) {
195
- const [ scopeKey , scopeValue ] = userScopeTuple ;
196
- expect ( scopeKey ) . toBe ( UserScope ) ;
197
- expect ( scopeValue ) . toBe ( sharedKey ) ;
198
- }
215
+ // Then the lifecycle events are called
216
+ expect ( userLifecycle . mounts ) . toHaveBeenCalledWith ( sharedAtExample ) ;
217
+ // Then the scopes matches the initial value
218
+ expect ( insideValue . current ) . toStrictEqual ( [ [ UserScope , sharedAtExample ] ] ) ;
219
+
220
+ const userScopeTuple = insideValue . current [ 0 ] ;
199
221
200
- await act ( ( ) => {
222
+ // When A is unmounted
223
+ act ( ( ) => {
201
224
result . current . setMountA ( false ) ;
202
225
} ) ;
203
226
227
+ // Then the molecule is still mounted
228
+ userLifecycle . expectActivelyMounted ( ) ;
229
+
204
230
const afterUnmountCache = insideValue . current ;
205
231
232
+ // Then the scope tuple is unchanged
206
233
expect ( afterUnmountCache [ 0 ] ) . toBe ( userScopeTuple ) ;
207
234
235
+ // When B is unmounted
208
236
act ( ( ) => {
209
237
result . current . setMountB ( false ) ;
210
238
} ) ;
211
239
240
+ // Then the molecule is unmounted
241
+ userLifecycle . expectToMatchCalls ( [ sharedAtExample ] ) ;
242
+
243
+ // Then the scope tuple is unchanged
212
244
const finalTuples = insideValue . current ;
213
245
expect ( finalTuples [ 0 ] ) . toBe ( userScopeTuple ) ;
214
246
247
+ // When B is re-mounted
215
248
act ( ( ) => {
216
249
result . current . setMountB ( true ) ;
217
250
} ) ;
218
251
252
+ // Then a fresh tuple is created
219
253
const freshTuples = insideValue . current ;
220
254
const [ freshTuple ] = freshTuples ;
255
+
256
+ // And it does not match the original
221
257
expect ( freshTuple ) . not . toBe ( userScopeTuple ) ;
222
258
if ( true ) {
223
259
const [ scopeKey , scopeValue ] = freshTuple ;
224
260
expect ( scopeKey ) . toBe ( UserScope ) ;
225
- expect ( scopeValue ) . toBe ( sharedKey ) ;
261
+ expect ( scopeValue ) . toBe ( sharedAtExample ) ;
226
262
}
263
+
264
+ // When the component is unmounted
265
+ rest . unmount ( ) ;
266
+
267
+ // Then the user molecule lifecycle has been completed 2
268
+ userLifecycle . expectToHaveBeenCalledTimes ( 2 ) ;
269
+
270
+ // And it has been called with the same value twice, across 2 leases
271
+ userLifecycle . expectToMatchCalls ( [ sharedAtExample ] , [ sharedAtExample ] ) ;
227
272
} ) ;
228
273
} ) ;
229
274
@@ -241,8 +286,10 @@ strictModeSuite(({ wrapper: Outer }) => {
241
286
</ Outer >
242
287
) ;
243
288
244
- const voidMolecule = molecule ( ( _ , getScope ) => {
245
- getScope ( VoidScope ) ;
289
+ const voidLifecycle = createLifecycleUtils ( ) ;
290
+ const voidMolecule = molecule ( ( ) => {
291
+ use ( VoidScope ) ;
292
+ voidLifecycle . connect ( ) ;
246
293
return {
247
294
example : Math . random ( ) ,
248
295
} ;
@@ -253,14 +300,21 @@ strictModeSuite(({ wrapper: Outer }) => {
253
300
context : useContext ( ScopeContext ) ,
254
301
} ;
255
302
} ;
256
- const { result : result1 } = renderHook ( useVoidMolecule , {
303
+
304
+ voidLifecycle . expectUncalled ( ) ;
305
+ const { result : result1 , ...rest1 } = renderHook ( useVoidMolecule , {
257
306
wrapper : Wrapper1 ,
258
307
} ) ;
259
- const { result : result2 } = renderHook ( useVoidMolecule , {
308
+ const { result : result2 , ... rest2 } = renderHook ( useVoidMolecule , {
260
309
wrapper : Wrapper2 ,
261
310
} ) ;
262
311
263
312
expect ( result1 . current . molecule ) . not . toBe ( result2 . current . molecule ) ;
313
+
314
+ rest1 . unmount ( ) ;
315
+ rest2 . unmount ( ) ;
316
+
317
+ voidLifecycle . expectToHaveBeenCalledTimes ( 2 ) ;
264
318
} ) ;
265
319
266
320
test ( "Object scope values are shared across providers" , ( ) => {
@@ -284,16 +338,24 @@ strictModeSuite(({ wrapper: Outer }) => {
284
338
userId,
285
339
} ;
286
340
} ;
287
- const { result : result1 } = renderHook ( useUserMolecule , {
341
+ atomLifecycle . expectUncalled ( ) ;
342
+ const { result : result1 , ...rest1 } = renderHook ( useUserMolecule , {
288
343
wrapper : Wrapper1 ,
289
344
} ) ;
290
- const { result : result2 } = renderHook ( useUserMolecule , {
345
+ atomLifecycle . expectActivelyMounted ( ) ;
346
+ const { result : result2 , ...rest2 } = renderHook ( useUserMolecule , {
291
347
wrapper : Wrapper2 ,
292
348
} ) ;
293
-
349
+ atomLifecycle . expectActivelyMounted ( ) ;
294
350
expect ( result1 . current . molecule ) . toBe ( result2 . current . molecule ) ;
295
351
expect ( result1 . current . userId ) . toBe ( "[email protected] " ) ;
296
352
expect ( result2 . current . userId ) . toBe ( "[email protected] " ) ;
353
+
354
+ rest1 . unmount ( ) ;
355
+ atomLifecycle . expectActivelyMounted ( ) ;
356
+ rest2 . unmount ( ) ;
357
+
358
+ atomLifecycle . expectToMatchCalls ( [ childAtom ] ) ;
297
359
} ) ;
298
360
299
361
test ( "Use molecule should will use the nested scope" , ( ) => {
@@ -307,19 +369,25 @@ strictModeSuite(({ wrapper: Outer }) => {
307
369
</ Outer >
308
370
) ;
309
371
372
+ userLifecycle . expectUncalled ( ) ;
310
373
const useUserMolecule = ( ) => {
311
374
return {
312
375
molecule : useMolecule ( UserMolecule ) ,
313
376
context : useContext ( ScopeContext ) ,
314
377
} ;
315
378
} ;
316
- const { result } = renderHook ( useUserMolecule , {
379
+ const { result, ... rest } = renderHook ( useUserMolecule , {
317
380
wrapper : Wrapper ,
318
381
} ) ;
382
+ userLifecycle . expectActivelyMounted ( ) ;
319
383
320
384
expect ( result . current . context ) . toStrictEqual ( [
321
385
322
386
] ) ;
323
387
expect ( result . current . molecule . userId ) . toBe ( "[email protected] " ) ;
388
+
389
+ rest . unmount ( ) ;
390
+
391
+ userLifecycle . expectToMatchCalls ( [ "[email protected] " ] ) ;
324
392
} ) ;
325
393
} ) ;
0 commit comments