@@ -3,22 +3,22 @@ import {
33 of ,
44 defer ,
55 concat ,
6- BehaviorSubject ,
76 throwError ,
87 Observable ,
98 Subject ,
9+ merge ,
1010} from "rxjs"
1111import { 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"
1414import React from "react"
1515import {
1616 act as componentAct ,
1717 fireEvent ,
1818 screen ,
1919 render ,
2020} from "@testing-library/react"
21- import { bind } from "../"
21+ import { bind , Subscribe } from "../"
2222import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
2323
2424const wait = ( ms : number ) => new Promise ( ( res ) => setTimeout ( res , ms ) )
@@ -42,8 +42,8 @@ describe("connectFactoryObservable", () => {
4242 } )
4343 describe ( "hook" , ( ) => {
4444 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 )
4747 const { result } = renderHook ( ( ) => useNumber ( ) )
4848 expect ( result . current ) . toBe ( 1 )
4949
@@ -56,13 +56,15 @@ describe("connectFactoryObservable", () => {
5656 it ( "suspends the component when the observable hasn't emitted yet." , async ( ) => {
5757 const source$ = of ( 1 ) . pipe ( delay ( 100 ) )
5858 const [ useDelayedNumber , getDelayedNumber$ ] = bind ( ( ) => source$ )
59- const subs = getDelayedNumber$ ( ) . subscribe ( )
6059 const Result : React . FC = ( ) => < div > Result { useDelayedNumber ( ) } </ div >
6160 const TestSuspense : React . FC = ( ) => {
6261 return (
63- < Suspense fallback = { < span > Waiting</ span > } >
62+ < Subscribe
63+ source$ = { getDelayedNumber$ ( ) }
64+ fallback = { < span > Waiting</ span > }
65+ >
6466 < Result />
65- </ Suspense >
67+ </ Subscribe >
6668 )
6769 }
6870
@@ -75,7 +77,51 @@ describe("connectFactoryObservable", () => {
7577
7678 expect ( screen . queryByText ( "Result 1" ) ) . not . toBeNull ( )
7779 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 ( )
79125 } )
80126
81127 it ( "shares the multicasted subscription with all of the components that use the same parameters" , async ( ) => {
@@ -114,7 +160,12 @@ describe("connectFactoryObservable", () => {
114160 } )
115161
116162 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 ( )
118169 const { result, rerender } = renderHook ( ( { input } ) => useNumber ( input ) , {
119170 initialProps : { input : 0 } ,
120171 } )
@@ -129,6 +180,7 @@ describe("connectFactoryObservable", () => {
129180 rerender ( { input : 2 } )
130181 } )
131182 expect ( result . current ) . toBe ( 2 )
183+ subs . unsubscribe ( )
132184 } )
133185
134186 it ( "handles optional args correctly" , ( ) => {
@@ -149,9 +201,12 @@ describe("connectFactoryObservable", () => {
149201 const [ input , setInput ] = useState ( 0 )
150202 return (
151203 < >
152- < Suspense fallback = { < span > Waiting</ span > } >
204+ < Subscribe
205+ source$ = { getDelayedNumber$ ( input ) }
206+ fallback = { < span > Waiting</ span > }
207+ >
153208 < Result input = { input } />
154- </ Suspense >
209+ </ Subscribe >
155210 < button onClick = { ( ) => setInput ( ( x ) => x + 1 ) } > increase</ button >
156211 </ >
157212 )
@@ -223,8 +278,8 @@ describe("connectFactoryObservable", () => {
223278 } )
224279
225280 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 )
228283
229284 const ErrorComponent = ( ) => {
230285 const value = useError ( )
@@ -253,7 +308,7 @@ describe("connectFactoryObservable", () => {
253308 const errStream = new Observable ( ( observer ) =>
254309 observer . error ( "controlled error" ) ,
255310 )
256- const [ useError ] = bind ( ( _ : string ) => errStream )
311+ const [ useError , getErrStream$ ] = bind ( ( _ : string ) => errStream )
257312
258313 const ErrorComponent = ( ) => {
259314 const value = useError ( "foo" )
@@ -264,9 +319,12 @@ describe("connectFactoryObservable", () => {
264319 const errorCallback = jest . fn ( )
265320 const { unmount } = render (
266321 < TestErrorBoundary onError = { errorCallback } >
267- < Suspense fallback = { < div > Loading...</ div > } >
322+ < Subscribe
323+ source$ = { getErrStream$ ( "foo" ) }
324+ fallback = { < div > Loading...</ div > }
325+ >
268326 < ErrorComponent />
269- </ Suspense >
327+ </ Subscribe >
270328 </ TestErrorBoundary > ,
271329 )
272330
@@ -279,7 +337,7 @@ describe("connectFactoryObservable", () => {
279337
280338 it ( "allows async errors to be caught in error boundaries with suspense" , async ( ) => {
281339 const errStream = new Subject ( )
282- const [ useError ] = bind ( ( _ : string ) => errStream )
340+ const [ useError , getErrStream$ ] = bind ( ( _ : string ) => errStream )
283341
284342 const ErrorComponent = ( ) => {
285343 const value = useError ( "foo" )
@@ -290,9 +348,12 @@ describe("connectFactoryObservable", () => {
290348 const errorCallback = jest . fn ( )
291349 const { unmount } = render (
292350 < TestErrorBoundary onError = { errorCallback } >
293- < Suspense fallback = { < div > Loading...</ div > } >
351+ < Subscribe
352+ source$ = { getErrStream$ ( "foo" ) }
353+ fallback = { < div > Loading...</ div > }
354+ >
294355 < ErrorComponent />
295- </ Suspense >
356+ </ Subscribe >
296357 </ TestErrorBoundary > ,
297358 )
298359
@@ -325,19 +386,24 @@ describe("connectFactoryObservable", () => {
325386 . pipe ( catchError ( ( ) => [ ] ) )
326387 . subscribe ( )
327388
389+ const Ok : React . FC < { ok : boolean } > = ( { ok } ) => < > { useOkKo ( ok ) } </ >
390+
328391 const ErrorComponent = ( ) => {
329392 const [ ok , setOk ] = useState ( true )
330- const value = useOkKo ( ok )
331393
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+ )
333401 }
334402
335403 const errorCallback = jest . fn ( )
336404 const { unmount } = render (
337405 < TestErrorBoundary onError = { errorCallback } >
338- < Suspense fallback = { < div > Loading...</ div > } >
339- < ErrorComponent />
340- </ Suspense >
406+ < ErrorComponent />
341407 </ TestErrorBoundary > ,
342408 )
343409
@@ -367,12 +433,11 @@ describe("connectFactoryObservable", () => {
367433 )
368434
369435 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 ,
376441 )
377442
378443 const ErrorComponent : FC = ( ) => {
@@ -403,30 +468,6 @@ describe("connectFactoryObservable", () => {
403468 expect ( errorCallback ) . not . toHaveBeenCalled ( )
404469 } )
405470
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-
430471 it ( "if the observable hasn't emitted and a defaultValue is provided, it does not start suspense" , ( ) => {
431472 const number$ = new Subject < number > ( )
432473 const [ useNumber ] = bind (
0 commit comments