Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
451 lines
15 KiB
TypeScript
451 lines
15 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { createRouterAct } from 'router-act'
|
|
import { waitFor } from 'next-test-utils'
|
|
|
|
describe('segment cache (basic tests)', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
if (isNextDev) {
|
|
test('ppr is disabled', () => {})
|
|
return
|
|
}
|
|
|
|
it('navigate before any data has loaded into the prefetch cache', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
await act(
|
|
async () => {
|
|
// Reveal the link to trigger a prefetch, but block the responses.
|
|
const link = await act(async () => {
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
await reveal.click()
|
|
return await browser.elementByCss('a')
|
|
}, 'block')
|
|
|
|
// While the prefetches are blocked, navigate to the test page.
|
|
await act(
|
|
async () => {
|
|
// Navigate to the test page
|
|
await link.click()
|
|
},
|
|
{
|
|
includes: 'Dynamic in nav',
|
|
}
|
|
)
|
|
|
|
// The static and dynamic content appears simultaneously because everything
|
|
// was fetched as part of the same navigation request.
|
|
const nav = await browser.elementById('nav')
|
|
expect(await nav.innerHTML()).toMatchInlineSnapshot(
|
|
`"<div><div data-streaming-text-static="Static in nav">Static in nav</div><div data-streaming-text-dynamic="Dynamic in nav">Dynamic in nav</div></div>"`
|
|
)
|
|
},
|
|
// Although the blocked prefetches are allowed to continue when we exit
|
|
// the outer `act` scope, they were canceled when we navigated to the new
|
|
// page. So there should be no additional requests in the outer
|
|
// `act` scope.
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('navigate with prefetched data', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch, but block the responses.
|
|
const link = await act(async () => {
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
await reveal.click()
|
|
return await browser.elementByCss('a')
|
|
})
|
|
|
|
// Navigate to the test page
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// Because we haven't exited the `act` scope yet, no new data has been
|
|
// received, but we're still able to immediately render the static
|
|
// content because it was prefetched.
|
|
const nav = await browser.elementById('nav')
|
|
expect(await nav.innerHTML()).toMatchInlineSnapshot(
|
|
`"<div><div data-streaming-text-static="Static in nav">Static in nav</div><div>Loading... [Dynamic in nav]</div></div>"`
|
|
)
|
|
},
|
|
// The dynamic data streams in after the loading state
|
|
{ includes: 'Dynamic in nav' }
|
|
)
|
|
|
|
const nav = await browser.elementById('nav')
|
|
await browser.elementByCss('[data-streaming-text-dynamic="Dynamic in nav"]')
|
|
expect(await nav.innerHTML()).toMatchInlineSnapshot(
|
|
`"<div><div data-streaming-text-static="Static in nav">Static in nav</div><div data-streaming-text-dynamic="Dynamic in nav">Dynamic in nav</div></div>"`
|
|
)
|
|
})
|
|
|
|
// TODO(cache-components): With `cacheComponents` enabled, this test is outdated, because
|
|
// we no longer put the param values in the prefetched RSC response. You'd have to opt into runtime
|
|
// prefetching for this test to pass until we ship the optimization that would mark this as fully static
|
|
// if you don't reference any dynamic params in the server components.
|
|
it.skip('navigate to page with lazily-generated (not at build time) static param', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/lazily-generated-params', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss('a')
|
|
},
|
|
{ includes: 'target-page-with-lazily-generated-param' }
|
|
)
|
|
|
|
// Navigate to the test page
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// We should be able to render the page with the dynamic param, because
|
|
// it is lazily generated
|
|
const target = await browser.elementById(
|
|
'target-page-with-lazily-generated-param'
|
|
)
|
|
expect(await target.innerHTML()).toMatchInlineSnapshot(
|
|
`"Param: some-param-value"`
|
|
)
|
|
},
|
|
// No additional requests were required, because everything was prefetched
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('prefetch interception route', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/interception/feed', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss('a')
|
|
},
|
|
{ includes: 'intercepted-photo-page' }
|
|
)
|
|
|
|
// Navigate to the test page
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// The page should render immediately because it was prefetched
|
|
const div = await browser.elementById('intercepted-photo-page')
|
|
expect(await div.innerHTML()).toBe('Intercepted photo page')
|
|
},
|
|
// No additional requests were required, because everything was prefetched
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('prefetch interception route with params', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/interception-with-params/feed', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss('a')
|
|
},
|
|
{ includes: 'intercepted-photo-page' }
|
|
)
|
|
|
|
// Navigate to the test page
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// The page should render immediately because it was prefetched
|
|
const div = await browser.elementById('intercepted-photo-page')
|
|
expect(await div.innerHTML()).toBe('Intercepted photo page for id "1"')
|
|
},
|
|
// No additional requests were required, because everything was prefetched
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('skips dynamic request if prefetched data is fully static', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/fully-static', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss('a[href="/fully-static/target-page"]')
|
|
},
|
|
{ includes: 'Target' }
|
|
)
|
|
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// The page should render immediately because it was prefetched.
|
|
const div = await browser.elementById('target-page')
|
|
expect(await div.innerHTML()).toBe('Target')
|
|
},
|
|
// No additional requests were required, because everything was prefetched
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('skips static layouts during partially static navigation', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/partially-static', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
const layoutMarkerId = 'static-layout'
|
|
const layoutMarkerContent = 'Static layout'
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss(
|
|
'a[href="/partially-static/target-page"]'
|
|
)
|
|
},
|
|
// The static layout should not be included in the dynamic response,
|
|
// because it was already prefetched.
|
|
{ includes: layoutMarkerContent }
|
|
)
|
|
|
|
await act(async () => {
|
|
await link.click()
|
|
|
|
// The static layout and the loading state of the dynamic page should
|
|
// render immediately because they were prefetched.
|
|
const layoutMarker = await browser.elementById(layoutMarkerId)
|
|
expect(await layoutMarker.innerHTML()).toBe('Static layout')
|
|
const dynamicDiv = await browser.elementById('dynamic-page')
|
|
expect(await dynamicDiv.innerHTML()).toBe('Loading...')
|
|
}, [
|
|
// The dynamic page is included in the dynamic response.
|
|
{ includes: 'Dynamic page' },
|
|
|
|
// The static layout should not be included in the dynamic response,
|
|
// because it was already prefetched.
|
|
{ includes: layoutMarkerContent, block: 'reject' },
|
|
])
|
|
|
|
// The dynamic content has streamed in.
|
|
const dynamicDiv = await browser.elementById('dynamic-page')
|
|
expect(await dynamicDiv.innerHTML()).toBe('Dynamic page')
|
|
})
|
|
|
|
it('refreshes page segments when navigating to the exact same URL as the current location', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/same-page-nav', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
const linkWithNoHash = await browser.elementByCss(
|
|
'a[href="/same-page-nav"]'
|
|
)
|
|
const linkWithHashA = await browser.elementByCss(
|
|
'a[href="/same-page-nav#hash-a"]'
|
|
)
|
|
const linkWithHashB = await browser.elementByCss(
|
|
'a[href="/same-page-nav#hash-b"]'
|
|
)
|
|
|
|
async function readRandomNumberFromPage() {
|
|
const randomNumber = await browser.elementById('random-number')
|
|
return await randomNumber.textContent()
|
|
}
|
|
|
|
// Navigating to the same URL should refresh the page
|
|
const randomNumber = await readRandomNumberFromPage()
|
|
await act(async () => {
|
|
await linkWithNoHash.click()
|
|
}, [
|
|
{
|
|
includes: 'random-number',
|
|
},
|
|
{
|
|
// Only the page segments should be refreshed, not the layouts.
|
|
// TODO: We plan to change this in the future.
|
|
block: 'reject',
|
|
includes: 'same-page-nav-layout',
|
|
},
|
|
])
|
|
const randomNumber2 = await readRandomNumberFromPage()
|
|
expect(randomNumber2).not.toBe(randomNumber)
|
|
|
|
// Navigating to a different hash should *not* refresh the page
|
|
await act(async () => {
|
|
await linkWithHashA.click()
|
|
}, 'no-requests')
|
|
expect(await readRandomNumberFromPage()).toBe(randomNumber2)
|
|
|
|
// Navigating to the same hash again should refresh the page
|
|
await act(
|
|
async () => {
|
|
await linkWithHashA.click()
|
|
},
|
|
{
|
|
includes: 'random-number',
|
|
}
|
|
)
|
|
const randomNumber3 = await readRandomNumberFromPage()
|
|
expect(randomNumber3).not.toBe(randomNumber2)
|
|
|
|
// Navigating to a different hash should *not* refresh the page
|
|
await act(async () => {
|
|
await linkWithHashB.click()
|
|
}, 'no-requests')
|
|
expect(await readRandomNumberFromPage()).toBe(randomNumber3)
|
|
})
|
|
|
|
it('does not throw an error when navigating to a page with a server action', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/with-server-action', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
const link = await act(
|
|
async () => {
|
|
await reveal.click()
|
|
return await browser.elementByCss(
|
|
'a[href="/with-server-action/target-page"]'
|
|
)
|
|
},
|
|
{ includes: 'Target' }
|
|
)
|
|
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// The page should render immediately because it was prefetched, and it
|
|
// should not throw an error.
|
|
const form = await browser.elementById('target-page')
|
|
expect(await form.innerHTML()).toBe('Target')
|
|
},
|
|
// No additional requests were required, because everything was prefetched
|
|
'no-requests'
|
|
)
|
|
})
|
|
|
|
it('does not cause infinite loop with cacheLife("seconds")', async () => {
|
|
let requestCount = 0
|
|
|
|
const browser = await next.browser('/cache-life-seconds-test', {
|
|
beforePageLoad(page) {
|
|
page.on('request', (request) => {
|
|
const url = request.url()
|
|
if (url.includes('/cache-life-seconds') && url.includes('_rsc')) {
|
|
requestCount++
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch
|
|
const reveal = await browser.elementByCss('input[type="checkbox"]')
|
|
await reveal.click()
|
|
|
|
// Wait for the link to appear
|
|
const link = await browser.elementByCss('a[href="/cache-life-seconds"]')
|
|
|
|
// Give the prefetch a moment to potentially start looping
|
|
await waitFor(500)
|
|
|
|
// Check that we haven't made excessive requests during prefetch
|
|
expect(requestCount).toBeLessThan(10)
|
|
|
|
// Now navigate to the page to ensure it works correctly
|
|
await link.click()
|
|
|
|
// Wait for the page to load
|
|
const page = await browser.elementById('cache-life-seconds-page')
|
|
const content = await page.textContent()
|
|
expect(content).toContain('Cache Life Seconds Page')
|
|
})
|
|
|
|
it('can handle circular references in client component props', async () => {
|
|
let act: ReturnType<typeof createRouterAct>
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(page) {
|
|
act = createRouterAct(page)
|
|
},
|
|
})
|
|
|
|
// Reveal the link to trigger a prefetch.
|
|
const link = await act(
|
|
async () => {
|
|
await browser
|
|
.elementByCss('input[data-link-accordion="/cycle"]')
|
|
.click()
|
|
return browser.elementByCss('a[href="/cycle"]')
|
|
},
|
|
{ includes: 'testProp' }
|
|
)
|
|
|
|
await act(
|
|
async () => {
|
|
await link.click()
|
|
|
|
// The page should render immediately because it was prefetched, and it
|
|
// should show the resolved cycle text.
|
|
expect(await browser.elementById('cycle-check').text()).toBe(
|
|
'Cycle resolved'
|
|
)
|
|
},
|
|
// No additional requests were required, because everything was
|
|
// prefetched.
|
|
'no-requests'
|
|
)
|
|
})
|
|
})
|