@@ -3,22 +3,22 @@ import {
3
3
of ,
4
4
defer ,
5
5
concat ,
6
- BehaviorSubject ,
7
6
throwError ,
8
7
Observable ,
9
8
Subject ,
9
+ merge ,
10
10
} from "rxjs"
11
11
import { renderHook , act as actHook } from "@testing-library/react-hooks"
12
- import { switchMap , delay , take , catchError , map } from "rxjs/operators"
13
- import { FC , Suspense , useState } from "react"
12
+ import { delay , take , catchError , map , switchMapTo } from "rxjs/operators"
13
+ import { FC , useState } from "react"
14
14
import React from "react"
15
15
import {
16
16
act as componentAct ,
17
17
fireEvent ,
18
18
screen ,
19
19
render ,
20
20
} from "@testing-library/react"
21
- import { bind } from "../"
21
+ import { bind , Subscribe } from "../"
22
22
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
23
23
24
24
const wait = ( ms : number ) => new Promise ( ( res ) => setTimeout ( res , ms ) )
@@ -42,8 +42,8 @@ describe("connectFactoryObservable", () => {
42
42
} )
43
43
describe ( "hook" , ( ) => {
44
44
it ( "returns the latest emitted value" , async ( ) => {
45
- const valueStream = new BehaviorSubject ( 1 )
46
- const [ useNumber ] = bind ( ( ) => valueStream )
45
+ const valueStream = new Subject < number > ( )
46
+ const [ useNumber ] = bind ( ( ) => valueStream , 1 )
47
47
const { result } = renderHook ( ( ) => useNumber ( ) )
48
48
expect ( result . current ) . toBe ( 1 )
49
49
@@ -56,13 +56,15 @@ describe("connectFactoryObservable", () => {
56
56
it ( "suspends the component when the observable hasn't emitted yet." , async ( ) => {
57
57
const source$ = of ( 1 ) . pipe ( delay ( 100 ) )
58
58
const [ useDelayedNumber , getDelayedNumber$ ] = bind ( ( ) => source$ )
59
- const subs = getDelayedNumber$ ( ) . subscribe ( )
60
59
const Result : React . FC = ( ) => < div > Result { useDelayedNumber ( ) } </ div >
61
60
const TestSuspense : React . FC = ( ) => {
62
61
return (
63
- < Suspense fallback = { < span > Waiting</ span > } >
62
+ < Subscribe
63
+ source$ = { getDelayedNumber$ ( ) }
64
+ fallback = { < span > Waiting</ span > }
65
+ >
64
66
< Result />
65
- </ Suspense >
67
+ </ Subscribe >
66
68
)
67
69
}
68
70
@@ -75,7 +77,51 @@ describe("connectFactoryObservable", () => {
75
77
76
78
expect ( screen . queryByText ( "Result 1" ) ) . not . toBeNull ( )
77
79
expect ( screen . queryByText ( "Waiting" ) ) . toBeNull ( )
78
- subs . unsubscribe ( )
80
+ } )
81
+
82
+ it ( "synchronously mounts the emitted value if the observable emits synchronously" , ( ) => {
83
+ const source$ = of ( 1 )
84
+ const [ useDelayedNumber , getDelayedNumber$ ] = bind ( ( ) => source$ )
85
+ const Result : React . FC = ( ) => < div > Result { useDelayedNumber ( ) } </ div >
86
+ const TestSuspense : React . FC = ( ) => {
87
+ return (
88
+ < Subscribe
89
+ source$ = { getDelayedNumber$ ( ) }
90
+ fallback = { < span > Waiting</ span > }
91
+ >
92
+ < Result />
93
+ </ Subscribe >
94
+ )
95
+ }
96
+
97
+ render ( < TestSuspense /> )
98
+
99
+ expect ( screen . queryByText ( "Result 1" ) ) . not . toBeNull ( )
100
+ expect ( screen . queryByText ( "Waiting" ) ) . toBeNull ( )
101
+ } )
102
+
103
+ it ( "doesn't mount the fallback element if the subscription is already active" , ( ) => {
104
+ const source$ = new Subject < number > ( )
105
+ const [ useDelayedNumber , getDelayedNumber$ ] = bind ( ( ) => source$ )
106
+ const Result : React . FC = ( ) => < div > Result { useDelayedNumber ( ) } </ div >
107
+ const TestSuspense : React . FC = ( ) => {
108
+ return (
109
+ < Subscribe
110
+ source$ = { getDelayedNumber$ ( ) }
111
+ fallback = { < span > Waiting</ span > }
112
+ >
113
+ < Result />
114
+ </ Subscribe >
115
+ )
116
+ }
117
+
118
+ const subscription = getDelayedNumber$ ( ) . subscribe ( )
119
+ source$ . next ( 1 )
120
+ render ( < TestSuspense /> )
121
+
122
+ expect ( screen . queryByText ( "Result 1" ) ) . not . toBeNull ( )
123
+ expect ( screen . queryByText ( "Waiting" ) ) . toBeNull ( )
124
+ subscription . unsubscribe ( )
79
125
} )
80
126
81
127
it ( "shares the multicasted subscription with all of the components that use the same parameters" , async ( ) => {
@@ -114,7 +160,12 @@ describe("connectFactoryObservable", () => {
114
160
} )
115
161
116
162
it ( "returns the value of next new Observable when the arguments change" , ( ) => {
117
- const [ useNumber ] = bind ( ( x : number ) => of ( x ) )
163
+ const [ useNumber , getNumber$ ] = bind ( ( x : number ) => of ( x ) )
164
+ const subs = merge (
165
+ getNumber$ ( 0 ) ,
166
+ getNumber$ ( 1 ) ,
167
+ getNumber$ ( 2 ) ,
168
+ ) . subscribe ( )
118
169
const { result, rerender } = renderHook ( ( { input } ) => useNumber ( input ) , {
119
170
initialProps : { input : 0 } ,
120
171
} )
@@ -129,6 +180,7 @@ describe("connectFactoryObservable", () => {
129
180
rerender ( { input : 2 } )
130
181
} )
131
182
expect ( result . current ) . toBe ( 2 )
183
+ subs . unsubscribe ( )
132
184
} )
133
185
134
186
it ( "handles optional args correctly" , ( ) => {
@@ -149,9 +201,12 @@ describe("connectFactoryObservable", () => {
149
201
const [ input , setInput ] = useState ( 0 )
150
202
return (
151
203
< >
152
- < Suspense fallback = { < span > Waiting</ span > } >
204
+ < Subscribe
205
+ source$ = { getDelayedNumber$ ( input ) }
206
+ fallback = { < span > Waiting</ span > }
207
+ >
153
208
< Result input = { input } />
154
- </ Suspense >
209
+ </ Subscribe >
155
210
< button onClick = { ( ) => setInput ( ( x ) => x + 1 ) } > increase</ button >
156
211
</ >
157
212
)
@@ -223,8 +278,8 @@ describe("connectFactoryObservable", () => {
223
278
} )
224
279
225
280
it ( "allows errors to be caught in error boundaries" , ( ) => {
226
- const errStream = new BehaviorSubject ( 1 )
227
- const [ useError ] = bind ( ( ) => errStream )
281
+ const errStream = new Subject ( )
282
+ const [ useError ] = bind ( ( ) => errStream , 1 )
228
283
229
284
const ErrorComponent = ( ) => {
230
285
const value = useError ( )
@@ -253,7 +308,7 @@ describe("connectFactoryObservable", () => {
253
308
const errStream = new Observable ( ( observer ) =>
254
309
observer . error ( "controlled error" ) ,
255
310
)
256
- const [ useError ] = bind ( ( _ : string ) => errStream )
311
+ const [ useError , getErrStream$ ] = bind ( ( _ : string ) => errStream )
257
312
258
313
const ErrorComponent = ( ) => {
259
314
const value = useError ( "foo" )
@@ -264,9 +319,12 @@ describe("connectFactoryObservable", () => {
264
319
const errorCallback = jest . fn ( )
265
320
const { unmount } = render (
266
321
< TestErrorBoundary onError = { errorCallback } >
267
- < Suspense fallback = { < div > Loading...</ div > } >
322
+ < Subscribe
323
+ source$ = { getErrStream$ ( "foo" ) }
324
+ fallback = { < div > Loading...</ div > }
325
+ >
268
326
< ErrorComponent />
269
- </ Suspense >
327
+ </ Subscribe >
270
328
</ TestErrorBoundary > ,
271
329
)
272
330
@@ -279,7 +337,7 @@ describe("connectFactoryObservable", () => {
279
337
280
338
it ( "allows async errors to be caught in error boundaries with suspense" , async ( ) => {
281
339
const errStream = new Subject ( )
282
- const [ useError ] = bind ( ( _ : string ) => errStream )
340
+ const [ useError , getErrStream$ ] = bind ( ( _ : string ) => errStream )
283
341
284
342
const ErrorComponent = ( ) => {
285
343
const value = useError ( "foo" )
@@ -290,9 +348,12 @@ describe("connectFactoryObservable", () => {
290
348
const errorCallback = jest . fn ( )
291
349
const { unmount } = render (
292
350
< TestErrorBoundary onError = { errorCallback } >
293
- < Suspense fallback = { < div > Loading...</ div > } >
351
+ < Subscribe
352
+ source$ = { getErrStream$ ( "foo" ) }
353
+ fallback = { < div > Loading...</ div > }
354
+ >
294
355
< ErrorComponent />
295
- </ Suspense >
356
+ </ Subscribe >
296
357
</ TestErrorBoundary > ,
297
358
)
298
359
@@ -325,19 +386,24 @@ describe("connectFactoryObservable", () => {
325
386
. pipe ( catchError ( ( ) => [ ] ) )
326
387
. subscribe ( )
327
388
389
+ const Ok : React . FC < { ok : boolean } > = ( { ok } ) => < > { useOkKo ( ok ) } </ >
390
+
328
391
const ErrorComponent = ( ) => {
329
392
const [ ok , setOk ] = useState ( true )
330
- const value = useOkKo ( ok )
331
393
332
- return < span onClick = { ( ) => setOk ( false ) } > { value } </ span >
394
+ return (
395
+ < Subscribe source$ = { getObs$ ( ok ) } fallback = { < div > Loading...</ div > } >
396
+ < span onClick = { ( ) => setOk ( false ) } >
397
+ < Ok ok = { ok } />
398
+ </ span >
399
+ </ Subscribe >
400
+ )
333
401
}
334
402
335
403
const errorCallback = jest . fn ( )
336
404
const { unmount } = render (
337
405
< TestErrorBoundary onError = { errorCallback } >
338
- < Suspense fallback = { < div > Loading...</ div > } >
339
- < ErrorComponent />
340
- </ Suspense >
406
+ < ErrorComponent />
341
407
</ TestErrorBoundary > ,
342
408
)
343
409
@@ -367,12 +433,11 @@ describe("connectFactoryObservable", () => {
367
433
)
368
434
369
435
it ( "doesn't throw errors on components that will get unmounted on the next cycle" , ( ) => {
370
- const valueStream = new BehaviorSubject ( 1 )
371
- const [ useValue , value$ ] = bind ( ( ) => valueStream )
372
- const [ useError ] = bind ( ( ) =>
373
- value$ ( ) . pipe (
374
- switchMap ( ( v ) => ( v === 1 ? of ( v ) : throwError ( "error" ) ) ) ,
375
- ) ,
436
+ const valueStream = new Subject < number > ( )
437
+ const [ useValue , value$ ] = bind ( ( ) => valueStream , 1 )
438
+ const [ useError ] = bind (
439
+ ( ) => value$ ( ) . pipe ( switchMapTo ( throwError ( "error" ) ) ) ,
440
+ 1 ,
376
441
)
377
442
378
443
const ErrorComponent : FC = ( ) => {
@@ -403,30 +468,6 @@ describe("connectFactoryObservable", () => {
403
468
expect ( errorCallback ) . not . toHaveBeenCalled ( )
404
469
} )
405
470
406
- it ( "does not resubscribe to an observable that emits synchronously and that does not have a top-level subscription after a re-render" , ( ) => {
407
- let nTopSubscriptions = 0
408
-
409
- const [ useNTopSubscriptions ] = bind ( ( id : number ) =>
410
- defer ( ( ) => {
411
- return of ( ++ nTopSubscriptions + id )
412
- } ) ,
413
- )
414
-
415
- const { result, rerender, unmount } = renderHook ( ( ) =>
416
- useNTopSubscriptions ( 0 ) ,
417
- )
418
-
419
- expect ( result . current ) . toBe ( 2 )
420
-
421
- actHook ( ( ) => {
422
- rerender ( )
423
- } )
424
- expect ( result . current ) . toBe ( 2 )
425
- expect ( nTopSubscriptions ) . toBe ( 2 )
426
-
427
- unmount ( )
428
- } )
429
-
430
471
it ( "if the observable hasn't emitted and a defaultValue is provided, it does not start suspense" , ( ) => {
431
472
const number$ = new Subject < number > ( )
432
473
const [ useNumber ] = bind (
0 commit comments