@@ -7,9 +7,9 @@ jest.useFakeTimers();
7
7
8
8
const testGateReact19 = React . version . startsWith ( '19.' ) ? test : test . skip ;
9
9
10
- function Suspending ( { promise } : { promise : Promise < unknown > } ) {
10
+ function Suspending ( { promise, testID } : { promise : Promise < unknown > ; testID : string } ) {
11
11
React . use ( promise ) ;
12
- return < View testID = "content" /> ;
12
+ return < View testID = { testID } /> ;
13
13
}
14
14
15
15
testGateReact19 ( 'resolves manually-controlled promise' , async ( ) => {
@@ -21,7 +21,7 @@ testGateReact19('resolves manually-controlled promise', async () => {
21
21
await renderAsync (
22
22
< View >
23
23
< React . Suspense fallback = { < Text > Loading...</ Text > } >
24
- < Suspending promise = { promise } />
24
+ < Suspending promise = { promise } testID = "content" />
25
25
< View testID = "sibling" />
26
26
</ React . Suspense >
27
27
</ View > ,
@@ -45,7 +45,7 @@ testGateReact19('resolves timer-controlled promise', async () => {
45
45
await renderAsync (
46
46
< View >
47
47
< React . Suspense fallback = { < Text > Loading...</ Text > } >
48
- < Suspending promise = { promise } />
48
+ < Suspending promise = { promise } testID = "content" />
49
49
< View testID = "sibling" />
50
50
</ React . Suspense >
51
51
</ View > ,
@@ -60,48 +60,11 @@ testGateReact19('resolves timer-controlled promise', async () => {
60
60
expect ( screen . queryByText ( 'Loading...' ) ) . not . toBeOnTheScreen ( ) ;
61
61
} ) ;
62
62
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
-
100
63
class ErrorBoundary extends React . Component <
101
- { children : React . ReactNode ; fallback ? : React . ReactNode } ,
64
+ { children : React . ReactNode ; fallback : React . ReactNode } ,
102
65
{ hasError : boolean }
103
66
> {
104
- constructor ( props : { children : React . ReactNode ; fallback ? : React . ReactNode } ) {
67
+ constructor ( props : { children : React . ReactNode ; fallback : React . ReactNode } ) {
105
68
super ( props ) ;
106
69
this . state = { hasError : false } ;
107
70
}
@@ -111,67 +74,98 @@ class ErrorBoundary extends React.Component<
111
74
}
112
75
113
76
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 ;
119
78
}
120
79
}
121
80
122
- testGateReact19 ( 'handles suspense with error boundary in fake timers ' , async ( ) => {
81
+ testGateReact19 ( 'handles promise rejection with error boundary' , async ( ) => {
123
82
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 ; } ) ;
127
84
128
85
await renderAsync (
129
86
< ErrorBoundary fallback = { < Text > Error occurred</ Text > } >
130
87
< React . Suspense fallback = { < Text > Loading...</ Text > } >
131
- < Suspending promise = { promise } />
88
+ < Suspending promise = { promise } testID = "content" />
132
89
</ React . Suspense >
133
90
</ ErrorBoundary > ,
134
91
) ;
135
92
136
93
expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
94
+ expect ( screen . queryByTestId ( 'content' ) ) . not . toBeOnTheScreen ( ) ;
137
95
138
96
// eslint-disable-next-line require-await
139
97
await act ( async ( ) => rejectPromise ( new Error ( 'Test error' ) ) ) ;
140
98
141
99
expect ( screen . getByText ( 'Error occurred' ) ) . toBeOnTheScreen ( ) ;
142
100
expect ( screen . queryByText ( 'Loading...' ) ) . not . toBeOnTheScreen ( ) ;
101
+ expect ( screen . queryByTestId ( 'error-content' ) ) . not . toBeOnTheScreen ( ) ;
143
102
} ) ;
144
103
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 ;
148
107
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 ;
155
141
156
- return (
142
+ const promise1 = new Promise ( ( resolve ) => { resolvePromise1 = resolve ; } ) ;
143
+ const promise2 = new Promise ( ( resolve ) => { resolvePromise2 = resolve ; } ) ;
144
+
145
+ await renderAsync (
157
146
< View >
158
147
< React . Suspense fallback = { < Text > First Loading...</ Text > } >
159
- < Suspending promise = { firstPromise } />
148
+ < Suspending promise = { promise1 } testID = "content-1" />
160
149
</ React . Suspense >
161
150
< 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" />
165
152
</ React . Suspense >
166
153
</ View >
167
154
) ;
168
- }
169
155
170
- testGateReact19 ( 'handles multiple independent suspense boundaries' , async ( ) => {
171
- await renderAsync ( < MultiComponentSuspense /> ) ;
172
-
173
156
expect ( screen . getByText ( 'First Loading...' ) ) . toBeOnTheScreen ( ) ;
174
157
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 ( ) ;
177
171
} ) ;
0 commit comments