mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### Depends on - #60577 --- A popstate navigation reads data from the local cache. It does not issue new network requests (unless the cache entries have been evicted). So, when navigating with back/forward, we should not switch back to the PPR loading state. We should render the full, cached dynamic data immediately. To implement this, on a popstate navigation, we update the cache to drop the prefetch data for any segment whose dynamic data was already received. We clone the entire cache node tree and set the `prefetchRsc` field to `null` to prevent it from being rendered. (We can't mutate the node in place because Cache Node is a concurrent data structure.) Technically, what we're actually checking is whether the dynamic network response was received. But since it's a streaming response, this does not mean that all the dynamic data has fully streamed in. It just means that _some_ of the dynamic data was received. But as a heuristic, we assume that the rest dynamic data will stream in quickly, so it's still better to skip the prefetch state. Closes NEXT-2084
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
import { createNext } from 'e2e-utils'
|
|
import { findPort } from 'next-test-utils'
|
|
import { createTestDataServer } from 'test-data-service/writer'
|
|
import { createTestLog } from 'test-log'
|
|
|
|
describe('avoid-popstate-flash', () => {
|
|
if ((global as any).isNextDev) {
|
|
test('ppr is disabled in dev', () => {})
|
|
return
|
|
}
|
|
|
|
let server
|
|
let next
|
|
afterEach(async () => {
|
|
await next?.destroy()
|
|
server?.close()
|
|
})
|
|
|
|
test('does not flash back to partial PPR data during back/forward navigation', async () => {
|
|
const TestLog = createTestLog()
|
|
let autoresolveRequests = true
|
|
let pendingRequests = new Map()
|
|
server = createTestDataServer(async (key, res) => {
|
|
TestLog.log('REQUEST: ' + key)
|
|
if (autoresolveRequests) {
|
|
res.resolve()
|
|
return
|
|
}
|
|
if (pendingRequests.has(key)) {
|
|
throw new Error('Request already pending for ' + key)
|
|
}
|
|
pendingRequests.set(key, res)
|
|
})
|
|
const port = await findPort()
|
|
server.listen(port)
|
|
next = await createNext({
|
|
files: __dirname,
|
|
env: { TEST_DATA_SERVICE_URL: `http://localhost:${port}` },
|
|
})
|
|
TestLog.assert(['REQUEST: Static'])
|
|
autoresolveRequests = false
|
|
|
|
const browser = await next.browser('/')
|
|
|
|
// Navigate to the target page.
|
|
const link = await browser.elementByCss('a[href="/some-page"]')
|
|
await link.click()
|
|
|
|
// The static UI appears immediately because it was prerendered at
|
|
// build time.
|
|
const staticContainer = await browser.elementById('static')
|
|
expect(await staticContainer.innerText()).toBe('Static')
|
|
|
|
await TestLog.waitFor(['REQUEST: Dynamic'])
|
|
pendingRequests.get('Dynamic').resolve()
|
|
|
|
// Now the dynamic data appears.
|
|
const dynamic = await browser.elementById('dynamic')
|
|
expect(await dynamic.innerText()).toBe('Dynamic')
|
|
|
|
// At this point all the data has been loaded into the cache. We're going
|
|
// to test what happens during a back/forward navigation.
|
|
|
|
// Set a global state that causes Suspense fallbacks to throw.
|
|
const checkbox = await browser.elementById('should-fallback-throw')
|
|
await checkbox.click()
|
|
const checked = await checkbox.getProperty('checked')
|
|
expect(await checked.jsonValue()).toBe(true)
|
|
|
|
// Navigate using back/forward using the browser's history stack. This
|
|
// should not trigger a fresh navigation, nor any network requests. We
|
|
// should read the data from the cache. And we should not render the
|
|
// partially complete PPR data that was used during the initial navigation.
|
|
//
|
|
// If the data is not read from cache, or if partial data is shown, it will
|
|
// trigger a fallback, which will throw an error because of the state we
|
|
// set above.
|
|
await browser.back()
|
|
await browser.forward()
|
|
|
|
// Confirm that the dynamic data is visible. This implies that the fallback
|
|
// did not throw.
|
|
const dynamic2 = await browser.elementById('dynamic')
|
|
expect(await dynamic2.innerText()).toBe('Dynamic')
|
|
|
|
// There should have been no additional requests.
|
|
TestLog.assert([])
|
|
})
|
|
})
|