mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
The `hmr-intercept-routes` test created the `@intercept` parallel-route slot files before editing the root layout to render the slot. Adding a new parallel-route slot is a structural change that forces a full page reload, and that reload raced with the Fast Refresh for the layout edit. Under load the client's `reloading` guard could drop the in-flight refresh, so the new slot never reached the browser and the test timed out waiting for `#default-intercept`, [failing 30-40% of the time in CI](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22hmr-intercept-routes%20should%20update%20intercept%20routes%20via%20HMR%22%20%40test.status%3A%28fail%20OR%20pass%29&agg_m=count&agg_m_source=base&agg_t=count¤tTab=overview&eventStack=&fromUser=false&index=citest&start=1781719381980&end=1782324181980&paused=false). <img width="428" height="176" alt="Screenshot 2026-06-24 at 20 04 01" src="https://github.com/user-attachments/assets/0272886e-dd75-4dbd-bd8c-b26ee8f40082" /> This change edits the layout first, so the `intercept` slot is already rendered as an undefined, no-op prop before the slot files exist. Creating the slot files afterward triggers the structural reload, and because the layout already renders the slot, that reload serves the complete updated route tree in a single fetch with no competing Fast Refresh. The server always resolved the route correctly; only the browser-side delivery was racy.
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
|
|
// This only works for Turbopack HMR builds
|
|
;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)(
|
|
'hmr-intercept-routes',
|
|
() => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
it('should update intercept routes via HMR', async () => {
|
|
const browser = await next.browser('/')
|
|
expect(await browser.elementByCss('h1').text()).toBe('Main Page')
|
|
|
|
const parallelDefaultContent = await next.readFile(
|
|
'fixtures/@intercept/default.js'
|
|
)
|
|
|
|
const parallelInterceptContent = await next.readFile(
|
|
'fixtures/@intercept/(.)intercept/page.js'
|
|
)
|
|
|
|
// Read the original code of the root layout page
|
|
const rootLayoutContent = await next.readFile('app/layout.js')
|
|
const fixtureLayoutContent = await next.readFile('fixtures/layout.js')
|
|
|
|
// Render the `intercept` parallel-route slot from the root layout first.
|
|
// No `@intercept` directory exists yet, so the slot prop is undefined and
|
|
// nothing renders for it. This is a plain Fast Refresh that does not
|
|
// force a page reload.
|
|
await next.patchFile('app/layout.js', fixtureLayoutContent)
|
|
|
|
// Now create the slot. Adding a new parallel-route slot is a structural
|
|
// change that forces a full page reload, and because the layout already
|
|
// renders the slot, that reload serves the complete updated route tree.
|
|
//
|
|
// Writing the slot files before the layout edit (the reverse order) is
|
|
// flaky: the structural reload races with the layout's Fast Refresh, and
|
|
// under load the client's `reloading` guard can drop the in-flight
|
|
// refresh, leaving the new slot absent until the next change.
|
|
await next.patchFile('app/@intercept/default.js', parallelDefaultContent)
|
|
await next.patchFile(
|
|
'app/@intercept/(.)intercept/page.js',
|
|
parallelInterceptContent
|
|
)
|
|
|
|
// Check to make sure that the main page now has the correct layout changes
|
|
await browser.waitForElementByCss('#default-intercept')
|
|
expect(await browser.elementById('default-intercept').text()).toBe(
|
|
"I'm the default intercept"
|
|
)
|
|
|
|
// Go to the intercept route and check that the intercept worked correctly
|
|
await browser.elementById('to-intercept').click()
|
|
await browser.waitForElementByCss('#intercept')
|
|
expect(await browser.elementById('intercept').text()).toBe(
|
|
"I'm the intercept"
|
|
)
|
|
|
|
// Reset the file statuses
|
|
await next.patchFile('app/layout.js', rootLayoutContent)
|
|
await next.deleteFile('app/@intercept')
|
|
})
|
|
}
|
|
)
|