@@ -7,9 +7,9 @@ jest.useFakeTimers();
77
88const testGateReact19 = React . version . startsWith ( '19.' ) ? test : test . skip ;
99
10- function Suspending ( { promise } : { promise : Promise < unknown > } ) {
10+ function Suspending ( { promise, testID } : { promise : Promise < unknown > ; testID : string } ) {
1111 React . use ( promise ) ;
12- return < View testID = "content" /> ;
12+ return < View testID = { testID } /> ;
1313}
1414
1515testGateReact19 ( 'resolves manually-controlled promise' , async ( ) => {
@@ -21,7 +21,7 @@ testGateReact19('resolves manually-controlled promise', async () => {
2121 await renderAsync (
2222 < View >
2323 < React . Suspense fallback = { < Text > Loading...</ Text > } >
24- < Suspending promise = { promise } />
24+ < Suspending promise = { promise } testID = "content" />
2525 < View testID = "sibling" />
2626 </ React . Suspense >
2727 </ View > ,
@@ -45,7 +45,7 @@ testGateReact19('resolves timer-controlled promise', async () => {
4545 await renderAsync (
4646 < View >
4747 < React . Suspense fallback = { < Text > Loading...</ Text > } >
48- < Suspending promise = { promise } />
48+ < Suspending promise = { promise } testID = "content" />
4949 < View testID = "sibling" />
5050 </ React . Suspense >
5151 </ View > ,
@@ -60,48 +60,11 @@ testGateReact19('resolves timer-controlled promise', async () => {
6060 expect ( screen . queryByText ( 'Loading...' ) ) . not . toBeOnTheScreen ( ) ;
6161} ) ;
6262
63- function DelayedSuspending ( { delay, id } : { delay : number ; id : string } ) {
64- let resolvePromise : ( value : string ) => void ;
65- const promise = React . useMemo ( ( ) =>
66- new Promise < string > ( ( resolve ) => {
67- resolvePromise = resolve ;
68- setTimeout ( ( ) => resolve ( `data-${ id } ` ) , delay ) ;
69- } ) , [ delay , id ]
70- ) ;
71-
72- const data = React . use ( promise ) ;
73- return < View testID = { `delayed-content-${ data } ` } /> ;
74- }
75-
76- testGateReact19 ( 'handles timer-based promises with fake timers' , async ( ) => {
77- let resolveManual : ( value : unknown ) => void ;
78- const manualPromise = new Promise ( ( resolve ) => {
79- resolveManual = resolve ;
80- } ) ;
81-
82- await renderAsync (
83- < View >
84- < React . Suspense fallback = { < Text > Manual Loading...</ Text > } >
85- < Suspending promise = { manualPromise } />
86- </ React . Suspense >
87- < View testID = "outside-suspense" />
88- </ View > ,
89- ) ;
90-
91- expect ( screen . getByText ( 'Manual Loading...' ) ) . toBeOnTheScreen ( ) ;
92- expect ( screen . getByTestId ( 'outside-suspense' ) ) . toBeOnTheScreen ( ) ;
93-
94- // eslint-disable-next-line require-await
95- await act ( async ( ) => resolveManual ( null ) ) ;
96- expect ( screen . getByTestId ( 'content' ) ) . toBeOnTheScreen ( ) ;
97- expect ( screen . queryByText ( 'Manual Loading...' ) ) . not . toBeOnTheScreen ( ) ;
98- } ) ;
99-
10063class ErrorBoundary extends React . Component <
101- { children : React . ReactNode ; fallback ? : React . ReactNode } ,
64+ { children : React . ReactNode ; fallback : React . ReactNode } ,
10265 { hasError : boolean }
10366> {
104- constructor ( props : { children : React . ReactNode ; fallback ? : React . ReactNode } ) {
67+ constructor ( props : { children : React . ReactNode ; fallback : React . ReactNode } ) {
10568 super ( props ) ;
10669 this . state = { hasError : false } ;
10770 }
@@ -111,67 +74,98 @@ class ErrorBoundary extends React.Component<
11174 }
11275
11376 render ( ) {
114- if ( this . state . hasError ) {
115- return this . props . fallback || < Text > Something went wrong.</ Text > ;
116- }
117-
118- return this . props . children ;
77+ return this . state . hasError ? this . props . fallback : this . props . children ;
11978 }
12079}
12180
122- testGateReact19 ( 'handles suspense with error boundary in fake timers ' , async ( ) => {
81+ testGateReact19 ( 'handles promise rejection with error boundary' , async ( ) => {
12382 let rejectPromise : ( error : Error ) => void ;
124- const promise = new Promise < unknown > ( ( _ , reject ) => {
125- rejectPromise = reject ;
126- } ) ;
83+ const promise = new Promise < unknown > ( ( _ , reject ) => { rejectPromise = reject ; } ) ;
12784
12885 await renderAsync (
12986 < ErrorBoundary fallback = { < Text > Error occurred</ Text > } >
13087 < React . Suspense fallback = { < Text > Loading...</ Text > } >
131- < Suspending promise = { promise } />
88+ < Suspending promise = { promise } testID = "content" />
13289 </ React . Suspense >
13390 </ ErrorBoundary > ,
13491 ) ;
13592
13693 expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
94+ expect ( screen . queryByTestId ( 'content' ) ) . not . toBeOnTheScreen ( ) ;
13795
13896 // eslint-disable-next-line require-await
13997 await act ( async ( ) => rejectPromise ( new Error ( 'Test error' ) ) ) ;
14098
14199 expect ( screen . getByText ( 'Error occurred' ) ) . toBeOnTheScreen ( ) ;
142100 expect ( screen . queryByText ( 'Loading...' ) ) . not . toBeOnTheScreen ( ) ;
101+ expect ( screen . queryByTestId ( 'error-content' ) ) . not . toBeOnTheScreen ( ) ;
143102} ) ;
144103
145- function MultiComponentSuspense ( ) {
146- let resolveFirst : ( value : unknown ) => void ;
147- let resolveSecond : ( value : unknown ) => void ;
104+ testGateReact19 ( 'handles multiple suspending components' , async ( ) => {
105+ let resolvePromise1 : ( value : unknown ) => void ;
106+ let resolvePromise2 : ( value : unknown ) => void ;
148107
149- const firstPromise = new Promise ( ( resolve ) => {
150- resolveFirst = resolve ;
151- } ) ;
152- const secondPromise = new Promise ( ( resolve ) => {
153- resolveSecond = resolve ;
154- } ) ;
108+ const promise1 = new Promise ( ( resolve ) => { resolvePromise1 = resolve ; } ) ;
109+ const promise2 = new Promise ( ( resolve ) => { resolvePromise2 = resolve ; } ) ;
110+
111+ await renderAsync (
112+ < View >
113+ < React . Suspense fallback = { < Text > Loading...</ Text > } >
114+ < Suspending promise = { promise1 } testID = "content-1" />
115+ < Suspending promise = { promise2 } testID = "content-2" />
116+ </ React . Suspense >
117+ </ View >
118+ ) ;
119+
120+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
121+ expect ( screen . queryByTestId ( 'content-1' ) ) . not . toBeOnTheScreen ( ) ;
122+ expect ( screen . queryByTestId ( 'content-2' ) ) . not . toBeOnTheScreen ( ) ;
123+
124+ // eslint-disable-next-line require-await
125+ await act ( async ( ) => resolvePromise1 ( null ) ) ;
126+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
127+ expect ( screen . queryByTestId ( 'content-1' ) ) . not . toBeOnTheScreen ( ) ;
128+ expect ( screen . queryByTestId ( 'content-2' ) ) . not . toBeOnTheScreen ( ) ;
129+
130+ // eslint-disable-next-line require-await
131+ await act ( async ( ) => resolvePromise2 ( null ) ) ;
132+ expect ( screen . getByTestId ( 'content-1' ) ) . toBeOnTheScreen ( ) ;
133+ expect ( screen . getByTestId ( 'content-2' ) ) . toBeOnTheScreen ( ) ;
134+ expect ( screen . queryByText ( 'Loading...' ) ) . not . toBeOnTheScreen ( ) ;
135+ } ) ;
136+
137+
138+ testGateReact19 ( 'handles multiple suspense boundaries independently' , async ( ) => {
139+ let resolvePromise1 : ( value : unknown ) => void ;
140+ let resolvePromise2 : ( value : unknown ) => void ;
155141
156- return (
142+ const promise1 = new Promise ( ( resolve ) => { resolvePromise1 = resolve ; } ) ;
143+ const promise2 = new Promise ( ( resolve ) => { resolvePromise2 = resolve ; } ) ;
144+
145+ await renderAsync (
157146 < View >
158147 < React . Suspense fallback = { < Text > First Loading...</ Text > } >
159- < Suspending promise = { firstPromise } />
148+ < Suspending promise = { promise1 } testID = "content-1" />
160149 </ React . Suspense >
161150 < React . Suspense fallback = { < Text > Second Loading...</ Text > } >
162- < View testID = "second-boundary" >
163- < Suspending promise = { secondPromise } />
164- </ View >
151+ < Suspending promise = { promise2 } testID = "content-2" />
165152 </ React . Suspense >
166153 </ View >
167154 ) ;
168- }
169155
170- testGateReact19 ( 'handles multiple independent suspense boundaries' , async ( ) => {
171- await renderAsync ( < MultiComponentSuspense /> ) ;
172-
173156 expect ( screen . getByText ( 'First Loading...' ) ) . toBeOnTheScreen ( ) ;
174157 expect ( screen . getByText ( 'Second Loading...' ) ) . toBeOnTheScreen ( ) ;
175- expect ( screen . queryByTestId ( 'content' ) ) . not . toBeOnTheScreen ( ) ;
176- expect ( screen . queryByTestId ( 'second-boundary' ) ) . not . toBeOnTheScreen ( ) ;
158+ expect ( screen . queryByTestId ( 'content-1' ) ) . not . toBeOnTheScreen ( ) ;
159+ expect ( screen . queryByTestId ( 'content-2' ) ) . not . toBeOnTheScreen ( ) ;
160+
161+ // eslint-disable-next-line require-await
162+ await act ( async ( ) => resolvePromise1 ( null ) ) ;
163+ expect ( screen . getByTestId ( 'content-1' ) ) . toBeOnTheScreen ( ) ;
164+ expect ( screen . queryByText ( 'First Loading...' ) ) . not . toBeOnTheScreen ( ) ;
165+ expect ( screen . getByText ( 'Second Loading...' ) ) . toBeOnTheScreen ( ) ;
166+
167+ // eslint-disable-next-line require-await
168+ await act ( async ( ) => resolvePromise2 ( null ) ) ;
169+ expect ( screen . getByTestId ( 'content-2' ) ) . toBeOnTheScreen ( ) ;
170+ expect ( screen . queryByText ( 'Second Loading...' ) ) . not . toBeOnTheScreen ( ) ;
177171} ) ;
0 commit comments