@@ -13,6 +13,8 @@ import {createEventTarget} from 'dom-event-testing-library';
13
13
14
14
let React ;
15
15
let ReactFeatureFlags ;
16
+ let ReactDOMServer ;
17
+ let Scheduler ;
16
18
17
19
describe ( 'ReactScope' , ( ) => {
18
20
beforeEach ( ( ) => {
@@ -21,6 +23,7 @@ describe('ReactScope', () => {
21
23
ReactFeatureFlags . enableScopeAPI = true ;
22
24
ReactFeatureFlags . enableDeprecatedFlareAPI = true ;
23
25
React = require ( 'react' ) ;
26
+ Scheduler = require ( 'scheduler' ) ;
24
27
} ) ;
25
28
26
29
if ( ! __EXPERIMENTAL__ ) {
@@ -34,6 +37,7 @@ describe('ReactScope', () => {
34
37
35
38
beforeEach ( ( ) => {
36
39
ReactDOM = require ( 'react-dom' ) ;
40
+ ReactDOMServer = require ( 'react-dom/server' ) ;
37
41
container = document . createElement ( 'div' ) ;
38
42
document . body . appendChild ( container ) ;
39
43
} ) ;
@@ -208,7 +212,6 @@ describe('ReactScope', () => {
208
212
209
213
it ( 'scopes support server-side rendering and hydration' , ( ) => {
210
214
const TestScope = React . unstable_createScope ( ) ;
211
- const ReactDOMServer = require ( 'react-dom/server' ) ;
212
215
const scopeRef = React . createRef ( ) ;
213
216
const divRef = React . createRef ( ) ;
214
217
const spanRef = React . createRef ( ) ;
@@ -306,6 +309,72 @@ describe('ReactScope', () => {
306
309
ReactDOM . render ( null , container ) ;
307
310
expect ( scopeRef . current ) . toBe ( null ) ;
308
311
} ) ;
312
+
313
+ it ( 'correctly works with suspended boundaries that are hydrated' , async ( ) => {
314
+ let suspend = false ;
315
+ let resolve ;
316
+ const promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
317
+ const ref = React . createRef ( ) ;
318
+ const TestScope = React . unstable_createScope ( ) ;
319
+ const scopeRef = React . createRef ( ) ;
320
+ const testScopeQuery = ( type , props ) => true ;
321
+
322
+ function Child ( ) {
323
+ if ( suspend ) {
324
+ throw promise ;
325
+ } else {
326
+ return 'Hello' ;
327
+ }
328
+ }
329
+
330
+ function App ( ) {
331
+ return (
332
+ < div >
333
+ < TestScope ref = { scopeRef } >
334
+ < React . Suspense fallback = "Loading..." >
335
+ < span ref = { ref } >
336
+ < Child />
337
+ </ span >
338
+ </ React . Suspense >
339
+ </ TestScope >
340
+ </ div >
341
+ ) ;
342
+ }
343
+
344
+ // First we render the final HTML. With the streaming renderer
345
+ // this may have suspense points on the server but here we want
346
+ // to test the completed HTML. Don't suspend on the server.
347
+ suspend = false ;
348
+ let finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
349
+
350
+ let container2 = document . createElement ( 'div' ) ;
351
+ container2 . innerHTML = finalHTML ;
352
+
353
+ let span = container2 . getElementsByTagName ( 'span' ) [ 0 ] ;
354
+
355
+ // On the client we don't have all data yet but we want to start
356
+ // hydrating anyway.
357
+ suspend = true ;
358
+ let root = ReactDOM . createRoot ( container2 , { hydrate : true } ) ;
359
+ root . render ( < App /> ) ;
360
+ Scheduler . unstable_flushAll ( ) ;
361
+ jest . runAllTimers ( ) ;
362
+
363
+ // This should not cause a runtime exception, see:
364
+ // https://github.com/facebook/react/pull/18184
365
+ scopeRef . current . DO_NOT_USE_queryAllNodes ( testScopeQuery ) ;
366
+ expect ( ref . current ) . toBe ( null ) ;
367
+
368
+ // Resolving the promise should continue hydration
369
+ suspend = false ;
370
+ resolve ( ) ;
371
+ await promise ;
372
+ Scheduler . unstable_flushAll ( ) ;
373
+ jest . runAllTimers ( ) ;
374
+
375
+ // We should now have hydrated with a ref on the existing span.
376
+ expect ( ref . current ) . toBe ( span ) ;
377
+ } ) ;
309
378
} ) ;
310
379
311
380
describe ( 'ReactTestRenderer' , ( ) => {
0 commit comments