@@ -177,4 +177,82 @@ describe('Streaming SSR', () => {
177177 test ( 'should render fallback before final content' , async ( ) => {
178178 await streamingOrderOnServer ( appPort ) ;
179179 } ) ;
180+
181+ test . each ( [
182+ [ 'layout' , '#root-hydration-loading' ] ,
183+ [ 'hydration/layout' , '#nested-hydration-loading' ] ,
184+ ] ) ( 'renders loading while hydrating %s' , async ( routeId , selector ) => {
185+ const hydrationPage = await browser . newPage ( ) ;
186+ const warnings : string [ ] = [ ] ;
187+ hydrationPage . on ( 'console' , message => {
188+ if ( message . text ( ) . includes ( 'No `HydrateFallback`' ) ) {
189+ warnings . push ( message . text ( ) ) ;
190+ }
191+ } ) ;
192+
193+ try {
194+ // Simulate incomplete SSR data before the client creates its router.
195+ await hydrationPage . evaluateOnNewDocument ( id => {
196+ let routerData : { loaderData : Record < string , unknown > } ;
197+ Object . defineProperty ( window , '_ROUTER_DATA' , {
198+ configurable : true ,
199+ get : ( ) => routerData ,
200+ set : value => {
201+ routerData = value ;
202+ delete routerData . loaderData [ id ] ;
203+ } ,
204+ } ) ;
205+ } , routeId ) ;
206+
207+ // Hold the client loader request until the fallback has been observed.
208+ await hydrationPage . setRequestInterception ( true ) ;
209+ hydrationPage . on ( 'request' , request => {
210+ if ( new URL ( request . url ( ) ) . searchParams . get ( '__loader' ) !== routeId ) {
211+ void request . continue ( ) ;
212+ }
213+ } ) ;
214+ const loaderRequest = hydrationPage . waitForRequest (
215+ request =>
216+ new URL ( request . url ( ) ) . searchParams . get ( '__loader' ) === routeId ,
217+ ) ;
218+ await hydrationPage . goto ( `http://localhost:${ appPort } /hydration` , {
219+ waitUntil : 'domcontentloaded' ,
220+ } ) ;
221+ const request = await loaderRequest ;
222+ await hydrationPage . waitForSelector ( selector , { visible : true } ) ;
223+ expect ( await hydrationPage . $ ( '#hydration-page' ) ) . toBeNull ( ) ;
224+ if ( routeId === 'hydration/layout' ) {
225+ await expectPageToMatchTextContent ( hydrationPage , 'Root layout' ) ;
226+ }
227+
228+ await request . continue ( ) ;
229+ await hydrationPage . waitForSelector ( '#hydration-page' , { visible : true } ) ;
230+ await expectPageToMatchTextContent ( hydrationPage , 'Hydrated layout' ) ;
231+ expect ( await hydrationPage . $ ( selector ) ) . toBeNull ( ) ;
232+ expect ( warnings ) . toEqual ( [ ] ) ;
233+ } finally {
234+ await hydrationPage . close ( ) ;
235+ }
236+ } ) ;
237+
238+ test ( 'reuses complete hydration data without rerunning loaders' , async ( ) => {
239+ const hydrationPage = await browser . newPage ( ) ;
240+ const loaderRequests : string [ ] = [ ] ;
241+ hydrationPage . on ( 'request' , request => {
242+ if ( new URL ( request . url ( ) ) . searchParams . has ( '__loader' ) ) {
243+ loaderRequests . push ( request . url ( ) ) ;
244+ }
245+ } ) ;
246+ try {
247+ await hydrationPage . goto ( `http://localhost:${ appPort } /hydration` , {
248+ waitUntil : 'networkidle0' ,
249+ } ) ;
250+ await hydrationPage . waitForSelector ( '#hydration-page' , { visible : true } ) ;
251+ expect ( await hydrationPage . $ ( '#root-hydration-loading' ) ) . toBeNull ( ) ;
252+ expect ( await hydrationPage . $ ( '#nested-hydration-loading' ) ) . toBeNull ( ) ;
253+ expect ( loaderRequests ) . toEqual ( [ ] ) ;
254+ } finally {
255+ await hydrationPage . close ( ) ;
256+ }
257+ } ) ;
180258} ) ;
0 commit comments