@@ -17,6 +17,7 @@ let Scheduler;
17
17
let ReactFeatureFlags ;
18
18
let Suspense ;
19
19
let SuspenseList ;
20
+ let Offscreen ;
20
21
let act ;
21
22
let IdleEventPriority ;
22
23
@@ -106,6 +107,7 @@ describe('ReactDOMServerPartialHydration', () => {
106
107
ReactDOMServer = require ( 'react-dom/server' ) ;
107
108
Scheduler = require ( 'scheduler' ) ;
108
109
Suspense = React . Suspense ;
110
+ Offscreen = React . unstable_Offscreen ;
109
111
if ( gate ( flags => flags . enableSuspenseList ) ) {
110
112
SuspenseList = React . SuspenseList ;
111
113
}
@@ -3283,6 +3285,103 @@ describe('ReactDOMServerPartialHydration', () => {
3283
3285
expect ( ref . current . innerHTML ) . toBe ( 'Hidden child' ) ;
3284
3286
} ) ;
3285
3287
3288
+ // @gate enableOffscreen
3289
+ it ( 'a visible Offscreen component acts like a fragment' , async ( ) => {
3290
+ const ref = React . createRef ( ) ;
3291
+
3292
+ function App ( ) {
3293
+ return (
3294
+ < Offscreen mode = "visible" >
3295
+ < span ref = { ref } > Child</ span >
3296
+ </ Offscreen >
3297
+ ) ;
3298
+ }
3299
+
3300
+ const finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
3301
+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
3302
+
3303
+ const container = document . createElement ( 'div' ) ;
3304
+ container . innerHTML = finalHTML ;
3305
+
3306
+ // Visible Offscreen boundaries behave exactly like fragments: a
3307
+ // pure indirection.
3308
+ expect ( container ) . toMatchInlineSnapshot ( `
3309
+ <div>
3310
+ <span>
3311
+ Child
3312
+ </span>
3313
+ </div>
3314
+ ` ) ;
3315
+
3316
+ const span = container . getElementsByTagName ( 'span' ) [ 0 ] ;
3317
+
3318
+ // The tree successfully hydrates
3319
+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
3320
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
3321
+ expect ( ref . current ) . toBe ( span ) ;
3322
+ } ) ;
3323
+
3324
+ // @gate enableOffscreen
3325
+ it ( 'a hidden Offscreen component is skipped over during server rendering' , async ( ) => {
3326
+ const visibleRef = React . createRef ( ) ;
3327
+
3328
+ function HiddenChild ( ) {
3329
+ Scheduler . unstable_yieldValue ( 'HiddenChild' ) ;
3330
+ return < span > Hidden</ span > ;
3331
+ }
3332
+
3333
+ function App ( ) {
3334
+ Scheduler . unstable_yieldValue ( 'App' ) ;
3335
+ return (
3336
+ < >
3337
+ < span ref = { visibleRef } > Visible</ span >
3338
+ < Offscreen mode = "hidden" >
3339
+ < HiddenChild />
3340
+ </ Offscreen >
3341
+ </ >
3342
+ ) ;
3343
+ }
3344
+
3345
+ // During server rendering, the Child component should not be evaluated,
3346
+ // because it's inside a hidden tree.
3347
+ const finalHTML = ReactDOMServer . renderToString ( < App /> ) ;
3348
+ expect ( Scheduler ) . toHaveYielded ( [ 'App' ] ) ;
3349
+
3350
+ const container = document . createElement ( 'div' ) ;
3351
+ container . innerHTML = finalHTML ;
3352
+
3353
+ // The hidden child is not part of the server rendered HTML
3354
+ expect ( container ) . toMatchInlineSnapshot ( `
3355
+ <div>
3356
+ <span>
3357
+ Visible
3358
+ </span>
3359
+ </div>
3360
+ ` ) ;
3361
+
3362
+ const visibleSpan = container . getElementsByTagName ( 'span' ) [ 0 ] ;
3363
+
3364
+ // The visible span successfully hydrates
3365
+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
3366
+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ 'App' ] ) ;
3367
+ expect ( visibleRef . current ) . toBe ( visibleSpan ) ;
3368
+
3369
+ // Subsequently, the hidden child is prerendered on the client
3370
+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ 'HiddenChild' ] ) ;
3371
+ expect ( container ) . toMatchInlineSnapshot ( `
3372
+ <div>
3373
+ <span>
3374
+ Visible
3375
+ </span>
3376
+ <span
3377
+ style="display: none;"
3378
+ >
3379
+ Hidden
3380
+ </span>
3381
+ </div>
3382
+ ` ) ;
3383
+ } ) ;
3384
+
3286
3385
function itHydratesWithoutMismatch ( msg , App ) {
3287
3386
it ( 'hydrates without mismatch ' + msg , ( ) => {
3288
3387
const container = document . createElement ( 'div' ) ;
0 commit comments