@@ -114,4 +114,180 @@ describe('ReactDOMServerSelectiveHydration', () => {
114
114
115
115
document . body . removeChild ( container ) ;
116
116
} ) ;
117
+
118
+ it ( 'hydrates at higher pri if sync did not work first time' , async ( ) => {
119
+ let suspend = false ;
120
+ let resolve ;
121
+ let promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
122
+
123
+ function Child ( { text} ) {
124
+ if ( ( text === 'A' || text === 'D' ) && suspend ) {
125
+ throw promise ;
126
+ }
127
+ Scheduler . unstable_yieldValue ( text ) ;
128
+ return (
129
+ < span
130
+ onClick = { e => {
131
+ e . preventDefault ( ) ;
132
+ Scheduler . unstable_yieldValue ( 'Clicked ' + text ) ;
133
+ } } >
134
+ { text }
135
+ </ span >
136
+ ) ;
137
+ }
138
+
139
+ function App ( ) {
140
+ Scheduler . unstable_yieldValue ( 'App' ) ;
141
+ return (
142
+ < div >
143
+ < Suspense fallback = "Loading..." >
144
+ < Child text = "A" />
145
+ </ Suspense >
146
+ < Suspense fallback = "Loading..." >
147
+ < Child text = "B" />
148
+ </ Suspense >
149
+ < Suspense fallback = "Loading..." >
150
+ < Child text = "C" />
151
+ </ Suspense >
152
+ < Suspense fallback = "Loading..." >
153
+ < Child text = "D" />
154
+ </ Suspense >
155
+ </ div >
156
+ ) ;
157
+ }
158
+
159
+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
160
+
161
+ expect ( Scheduler ) . toHaveYielded ( [ 'App' , 'A' , 'B' , 'C' , 'D' ] ) ;
162
+
163
+ let container = document . createElement ( 'div' ) ;
164
+ // We need this to be in the document since we'll dispatch events on it.
165
+ document . body . appendChild ( container ) ;
166
+
167
+ container . innerHTML = finalHTML ;
168
+
169
+ let spanD = container . getElementsByTagName ( 'span' ) [ 3 ] ;
170
+
171
+ suspend = true ;
172
+
173
+ // A and D will be suspended. We'll click on D which should take
174
+ // priority, after we unsuspend.
175
+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
176
+ root . render ( < App /> ) ;
177
+
178
+ // Nothing has been hydrated so far.
179
+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
180
+
181
+ // This click target cannot be hydrated yet because it's suspended.
182
+ let result = dispatchClickEvent ( spanD ) ;
183
+
184
+ expect ( Scheduler ) . toHaveYielded ( [ 'App' ] ) ;
185
+
186
+ expect ( result ) . toBe ( true ) ;
187
+
188
+ // Continuing rendering will render B next.
189
+ expect ( Scheduler ) . toFlushAndYield ( [ 'B' , 'C' ] ) ;
190
+
191
+ suspend = false ;
192
+ resolve ( ) ;
193
+ await promise ;
194
+
195
+ // After the click, we should prioritize D and the Click first,
196
+ // and only after that render A and C.
197
+ expect ( Scheduler ) . toFlushAndYield ( [ 'D' , 'Clicked D' , 'A' ] ) ;
198
+
199
+ document . body . removeChild ( container ) ;
200
+ } ) ;
201
+
202
+ it ( 'hydrates at higher pri for secondary discrete events' , async ( ) => {
203
+ let suspend = false ;
204
+ let resolve ;
205
+ let promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
206
+
207
+ function Child ( { text} ) {
208
+ if ( ( text === 'A' || text === 'D' ) && suspend ) {
209
+ throw promise ;
210
+ }
211
+ Scheduler . unstable_yieldValue ( text ) ;
212
+ return (
213
+ < span
214
+ onClick = { e => {
215
+ e . preventDefault ( ) ;
216
+ Scheduler . unstable_yieldValue ( 'Clicked ' + text ) ;
217
+ } } >
218
+ { text }
219
+ </ span >
220
+ ) ;
221
+ }
222
+
223
+ function App ( ) {
224
+ Scheduler . unstable_yieldValue ( 'App' ) ;
225
+ return (
226
+ < div >
227
+ < Suspense fallback = "Loading..." >
228
+ < Child text = "A" />
229
+ </ Suspense >
230
+ < Suspense fallback = "Loading..." >
231
+ < Child text = "B" />
232
+ </ Suspense >
233
+ < Suspense fallback = "Loading..." >
234
+ < Child text = "C" />
235
+ </ Suspense >
236
+ < Suspense fallback = "Loading..." >
237
+ < Child text = "D" />
238
+ </ Suspense >
239
+ </ div >
240
+ ) ;
241
+ }
242
+
243
+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
244
+
245
+ expect ( Scheduler ) . toHaveYielded ( [ 'App' , 'A' , 'B' , 'C' , 'D' ] ) ;
246
+
247
+ let container = document . createElement ( 'div' ) ;
248
+ // We need this to be in the document since we'll dispatch events on it.
249
+ document . body . appendChild ( container ) ;
250
+
251
+ container . innerHTML = finalHTML ;
252
+
253
+ let spanA = container . getElementsByTagName ( 'span' ) [ 0 ] ;
254
+ let spanC = container . getElementsByTagName ( 'span' ) [ 2 ] ;
255
+ let spanD = container . getElementsByTagName ( 'span' ) [ 3 ] ;
256
+
257
+ suspend = true ;
258
+
259
+ // A and D will be suspended. We'll click on D which should take
260
+ // priority, after we unsuspend.
261
+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
262
+ root . render ( < App /> ) ;
263
+
264
+ // Nothing has been hydrated so far.
265
+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
266
+
267
+ // This click target cannot be hydrated yet because the first is Suspended.
268
+ dispatchClickEvent ( spanA ) ;
269
+ dispatchClickEvent ( spanC ) ;
270
+ dispatchClickEvent ( spanD ) ;
271
+
272
+ expect ( Scheduler ) . toHaveYielded ( [ 'App' ] ) ;
273
+
274
+ suspend = false ;
275
+ resolve ( ) ;
276
+ await promise ;
277
+
278
+ // We should prioritize hydrating A, C and D first since we clicked in
279
+ // them. Only after they're done will we hydrate B.
280
+ expect ( Scheduler ) . toFlushAndYield ( [
281
+ 'A' ,
282
+ 'Clicked A' ,
283
+ 'C' ,
284
+ 'Clicked C' ,
285
+ 'D' ,
286
+ 'Clicked D' ,
287
+ // B should render last since it wasn't clicked.
288
+ 'B' ,
289
+ ] ) ;
290
+
291
+ document . body . removeChild ( container ) ;
292
+ } ) ;
117
293
} ) ;
0 commit comments