1- import { useObservable } from '.' ;
2- import { renderHook , act } from '@testing-library/react-hooks' ;
3- import { of , Subject , Observable , observable } from 'rxjs' ;
4- import { delay } from 'rxjs/operators' ;
5- import { render , waitForElement , cleanup } from '@testing-library/react' ;
6- import { ReactFireOptions } from '..' ;
7- import * as React from 'react' ;
81import '@testing-library/jest-dom/extend-expect' ;
2+ import { act , cleanup , render , waitForElement } from '@testing-library/react' ;
3+ import { act as actOnHook , renderHook } from '@testing-library/react-hooks' ;
4+ import * as React from 'react' ;
5+ import { of , Subject } from 'rxjs' ;
6+ import { useObservable } from '.' ;
97
108describe ( 'useObservable' , ( ) => {
119 afterEach ( cleanup ) ;
@@ -32,20 +30,33 @@ describe('useObservable', () => {
3230 expect ( result . current ) . toEqual ( startVal ) ;
3331
3432 // prove that it actually does emit the value from the observable too
35- act ( ( ) => observable$ . next ( observableVal ) ) ;
33+ actOnHook ( ( ) => observable$ . next ( observableVal ) ) ;
3634 expect ( result . current ) . toEqual ( observableVal ) ;
3735 } ) ;
3836
39- it ( 'ignores provided initial value if the observable is ready right away' , ( ) => {
37+ it ( 'returns the provided startWithValue first even if the observable is ready right away' , ( ) => {
38+ // This behavior is a consequense of how observables work. There is
39+ // not a synchronous way to ask an observable if it has a value to emit.
40+
4041 const startVal = 'howdy' ;
4142 const observableVal = "y'all" ;
4243 const observable$ = of ( observableVal ) ;
44+ let hasReturnedStartWithValue = false ;
4345
44- const { result, waitForNextUpdate } = renderHook ( ( ) =>
45- useObservable ( observable$ , 'test' , startVal )
46- ) ;
46+ const Component = ( ) => {
47+ const val = useObservable ( observable$ , 'test' , startVal ) ;
4748
48- expect ( result . current ) . toEqual ( observableVal ) ;
49+ if ( hasReturnedStartWithValue ) {
50+ expect ( val ) . toEqual ( observableVal ) ;
51+ } else {
52+ expect ( val ) . toEqual ( startVal ) ;
53+ hasReturnedStartWithValue = true ;
54+ }
55+
56+ return < h1 > Hello</ h1 > ;
57+ } ;
58+
59+ render ( < Component /> ) ;
4960 } ) ;
5061
5162 it ( 'works with Suspense' , async ( ) => {
@@ -73,7 +84,7 @@ describe('useObservable', () => {
7384 expect ( getByTestId ( fallbackComponentId ) ) . toBeInTheDocument ( ) ;
7485 expect ( queryByTestId ( actualComponentId ) ) . toBeNull ( ) ;
7586
76- act ( ( ) => observable$ . next ( observableFinalVal ) ) ;
87+ actOnHook ( ( ) => observable$ . next ( observableFinalVal ) ) ;
7788 await waitForElement ( ( ) => getByTestId ( actualComponentId ) ) ;
7889
7990 // make sure Suspense correctly renders its child after the observable emits a value
@@ -87,7 +98,6 @@ describe('useObservable', () => {
8798 it ( 'emits new values as the observable changes' , async ( ) => {
8899 const startVal = 'start' ;
89100 const values = [ 'a' , 'b' , 'c' ] ;
90- const observableSecondValue = 'b' ;
91101 const observable$ = new Subject ( ) ;
92102
93103 const { result } = renderHook ( ( ) =>
@@ -97,8 +107,55 @@ describe('useObservable', () => {
97107 expect ( result . current ) . toEqual ( startVal ) ;
98108
99109 values . forEach ( value => {
100- act ( ( ) => observable$ . next ( value ) ) ;
110+ actOnHook ( ( ) => observable$ . next ( value ) ) ;
101111 expect ( result . current ) . toEqual ( value ) ;
102112 } ) ;
103113 } ) ;
114+
115+ it ( 'returns the most recent value of an observable to all subscribers of an observableId' , async ( ) => {
116+ const values = [ 'a' , 'b' , 'c' ] ;
117+ const observable$ = new Subject ( ) ;
118+ const observableId = 'my-observable-id' ;
119+ const firstComponentId = 'first' ;
120+ const secondComponentId = 'second' ;
121+
122+ const ObservableConsumer = props => {
123+ const val = useObservable ( observable$ , observableId ) ;
124+
125+ return < h1 { ...props } > { val } </ h1 > ;
126+ } ;
127+
128+ const Component = ( { renderSecondComponent } ) => {
129+ return (
130+ < React . Suspense fallback = "loading" >
131+ < ObservableConsumer data-testid = { firstComponentId } />
132+ { renderSecondComponent ? (
133+ < ObservableConsumer data-testid = { secondComponentId } />
134+ ) : null }
135+ </ React . Suspense >
136+ ) ;
137+ } ;
138+
139+ const { getByTestId, rerender } = render (
140+ < Component renderSecondComponent = { false } />
141+ ) ;
142+
143+ // emit one value to the first component (second one isn't rendered yet)
144+ act ( ( ) => observable$ . next ( values [ 0 ] ) ) ;
145+ const comp = await waitForElement ( ( ) => getByTestId ( firstComponentId ) ) ;
146+ expect ( comp ) . toHaveTextContent ( values [ 0 ] ) ;
147+
148+ // emit a second value to the first component (second one still isn't rendered)
149+ act ( ( ) => observable$ . next ( values [ 1 ] ) ) ;
150+ expect ( comp ) . toHaveTextContent ( values [ 1 ] ) ;
151+
152+ // keep the original component around, but now render the second one.
153+ // they both use the same observableId
154+ rerender ( < Component renderSecondComponent = { true } /> ) ;
155+
156+ // the second component should start by receiving the latest value
157+ // since the first component has already been subscribed
158+ const comp2 = await waitForElement ( ( ) => getByTestId ( secondComponentId ) ) ;
159+ expect ( comp2 ) . toHaveTextContent ( values [ 1 ] ) ;
160+ } ) ;
104161} ) ;
0 commit comments