@@ -562,6 +562,86 @@ describe('ReactDOMServerPartialHydration', () => {
562562 expect ( container . textContent ) . toBe ( 'Hi Hi' ) ;
563563 } ) ;
564564
565+ it ( 'hydrates first if props changed but we are able to resolve within a timeout' , async ( ) => {
566+ let suspend = false ;
567+ let resolve ;
568+ let promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
569+ let ref = React . createRef ( ) ;
570+
571+ function Child ( { text} ) {
572+ if ( suspend ) {
573+ throw promise ;
574+ } else {
575+ return text ;
576+ }
577+ }
578+
579+ function App ( { text, className} ) {
580+ return (
581+ < div >
582+ < Suspense fallback = "Loading..." >
583+ < span ref = { ref } className = { className } >
584+ < Child text = { text } />
585+ </ span >
586+ </ Suspense >
587+ </ div >
588+ ) ;
589+ }
590+
591+ suspend = false ;
592+ let finalHTML = ReactDOMServer . renderToString (
593+ < App text = "Hello" className = "hello" /> ,
594+ ) ;
595+ let container = document . createElement ( 'div' ) ;
596+ container . innerHTML = finalHTML ;
597+
598+ let span = container . getElementsByTagName ( 'span' ) [ 0 ] ;
599+
600+ // On the client we don't have all data yet but we want to start
601+ // hydrating anyway.
602+ suspend = true ;
603+ let root = ReactDOM . unstable_createRoot ( container , { hydrate : true } ) ;
604+ root . render ( < App text = "Hello" className = "hello" /> ) ;
605+ Scheduler . unstable_flushAll ( ) ;
606+ jest . runAllTimers ( ) ;
607+
608+ expect ( ref . current ) . toBe ( null ) ;
609+ expect ( container . textContent ) . toBe ( 'Hello' ) ;
610+
611+ // Render an update with a long timeout.
612+ React . unstable_withSuspenseConfig (
613+ ( ) => root . render ( < App text = "Hi" className = "hi" /> ) ,
614+ { timeoutMs : 5000 } ,
615+ ) ;
616+
617+ // This shouldn't force the fallback yet.
618+ Scheduler . unstable_flushAll ( ) ;
619+
620+ expect ( ref . current ) . toBe ( null ) ;
621+ expect ( container . textContent ) . toBe ( 'Hello' ) ;
622+
623+ // Resolving the promise so that rendering can complete.
624+ suspend = false ;
625+ resolve ( ) ;
626+ await promise ;
627+
628+ // This should first complete the hydration and then flush the update onto the hydrated state.
629+ Scheduler . unstable_flushAll ( ) ;
630+ jest . runAllTimers ( ) ;
631+
632+ // The new span should be the same since we should have successfully hydrated
633+ // before changing it.
634+ let newSpan = container . getElementsByTagName ( 'span' ) [ 0 ] ;
635+ expect ( span ) . toBe ( newSpan ) ;
636+
637+ // We should now have fully rendered with a ref on the new span.
638+ expect ( ref . current ) . toBe ( span ) ;
639+ expect ( container . textContent ) . toBe ( 'Hi' ) ;
640+ // If we ended up hydrating the existing content, we won't have properly
641+ // patched up the tree, which might mean we haven't patched the className.
642+ expect ( span . className ) . toBe ( 'hi' ) ;
643+ } ) ;
644+
565645 it ( 'blocks the update to hydrate first if context has changed' , async ( ) => {
566646 let suspend = false ;
567647 let resolve ;
0 commit comments