mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
This API has experimental modes for build validation but the dev validation is going to be stable in the next release.
788 lines
29 KiB
TypeScript
788 lines
29 KiB
TypeScript
import { nextTestSetup, type Playwright as NextBrowser } from 'e2e-utils'
|
|
import type * as Playwright from 'playwright'
|
|
import { createRouterAct } from 'router-act'
|
|
|
|
// TODO: Skipped due to flakiness
|
|
describe.skip('layout sharing in non-static prefetches', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
if (isNextDev) {
|
|
it('disabled in development', () => {})
|
|
return
|
|
}
|
|
|
|
// Glossary:
|
|
//
|
|
// - A "full prefetch" is `<Link prefetch="true">`
|
|
// It includes cached and uncached IO.
|
|
|
|
// - A "runtime prefetch" is the new `instant` segment config (only available in cacheComponents mode).
|
|
// It includes cached IO, and allows access to cookies/params/searchParams/"use cache: private", but excludes uncached IO.
|
|
|
|
// TODO (runtime-prefetching): link-level opt-in has been removed. These tests need to be updated to use the segment configuration.
|
|
it.skip('runtime prefetches should omit layouts that were already prefetched with a runtime prefetch', async () => {
|
|
// Prefetches should re-use results from previous prefetches with the same fetch strategy.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/shared-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// should prefetch page one, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
// Should not prefetch any dynamic content
|
|
{
|
|
includes: 'Dynamic content',
|
|
block: 'reject',
|
|
},
|
|
])
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/shared-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should not prefetch the shared layout, because we already have it in the cache
|
|
{
|
|
includes: 'Cookie from layout: testValue',
|
|
block: 'reject',
|
|
},
|
|
// should prefetch page two, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
// Should not prefetch the dynamic content from either of them
|
|
{
|
|
includes: 'Dynamic content',
|
|
block: 'reject',
|
|
},
|
|
])
|
|
|
|
// Navigate to page two
|
|
await act(async () => {
|
|
await act(
|
|
async () => {
|
|
await browser.elementByCss(`a[href="/shared-layout/two"]`).click()
|
|
},
|
|
{
|
|
// Temporarily block the navigation request.
|
|
// The runtime-prefetched parts of the tree should be visible before it finishes.
|
|
includes: 'Dynamic content',
|
|
block: true,
|
|
}
|
|
)
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
})
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-layout').text()).toEqual(
|
|
'Dynamic content from layout'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
it('full prefetches should omit layouts that were already prefetched with a full prefetch', async () => {
|
|
// Prefetches should re-use results from previous prefetches with the same fetch strategy.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a full prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/shared-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should prefetch the dynamic content
|
|
{
|
|
includes: 'Dynamic content from page one',
|
|
},
|
|
])
|
|
|
|
// Reveal the link to trigger a full prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/shared-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should not prefetch the shared layout, because we already have it in the cache
|
|
{
|
|
includes: 'Dynamic content from layout',
|
|
block: 'reject',
|
|
},
|
|
// Should prefetch the dynamic content
|
|
{
|
|
includes: 'Dynamic content from page two',
|
|
},
|
|
])
|
|
|
|
// Navigate to page two. We have everything in the cache, so we shouldn't issue any new requests
|
|
await act(async () => {
|
|
await browser.elementByCss(`a[href="/shared-layout/two"]`).click()
|
|
}, 'no-requests')
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('dynamic-content-layout').text()).toEqual(
|
|
'Dynamic content from layout'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
it('navigations should omit layouts that were already prefetched with a full prefetch', async () => {
|
|
// A navigation is mostly equivalent to a full prefetch, so it should re-use results from full prefetches.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a full prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/shared-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should prefetch the dynamic content
|
|
{
|
|
includes: 'Dynamic content from page one',
|
|
},
|
|
])
|
|
|
|
// Reveal the link to trigger an auto prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="auto"][data-link-accordion="/shared-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
})
|
|
|
|
// Navigate to page two. We have everything in the cache, so we shouldn't issue any new requests
|
|
await act(async () => {
|
|
await browser.elementByCss(`a[href="/shared-layout/two"]`).click()
|
|
}, [
|
|
// Should not fetch the shared layout, because we already have it in the cache
|
|
{
|
|
includes: 'Dynamic content from layout',
|
|
block: 'reject',
|
|
},
|
|
// Should fetch the dynamic content
|
|
{
|
|
includes: 'Dynamic content from page two',
|
|
},
|
|
])
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('dynamic-content-layout').text()).toEqual(
|
|
'Dynamic content from layout'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
// TODO (runtime-prefetching): link-level opt-in has been removed. These tests need to be updated to use the segment configuration.
|
|
it.skip('runtime prefetches should omit layouts that were already prefetched with a full prefetch', async () => {
|
|
// A prefetch should re-use layouts from past prefetches with more specific fetch strategies.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a full prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/shared-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should prefetch the dynamic content
|
|
{
|
|
includes: 'Dynamic content from page one',
|
|
},
|
|
])
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/shared-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should not prefetch the shared layout, because we already have it in the cache
|
|
{
|
|
includes: 'Cookie from layout: testValue',
|
|
block: 'reject',
|
|
},
|
|
// should prefetch page two, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
// Should not prefetch any dynamic content
|
|
{
|
|
includes: 'Dynamic content',
|
|
block: 'reject',
|
|
},
|
|
])
|
|
|
|
// Navigate to page two
|
|
await act(async () => {
|
|
await act(async () => {
|
|
await browser.elementByCss(`a[href="/shared-layout/two"]`).click()
|
|
}, [
|
|
// Should not fetch the shared layout, because we already have a full prefetch of it
|
|
{
|
|
includes: 'Cookie from layout: testValue',
|
|
block: 'reject',
|
|
},
|
|
{
|
|
// Temporarily block the navigation request.
|
|
// The runtime-prefetched parts of the tree should be visible before it finishes.
|
|
includes: 'Dynamic content',
|
|
block: true,
|
|
},
|
|
])
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
})
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-layout').text()).toEqual(
|
|
'Dynamic content from layout'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
// TODO (runtime-prefetching): link-level opt-in has been removed. These tests need to be updated to use the segment configuration.
|
|
it.skip('full prefetches should include layouts that were only prefetched with a runtime prefetch', async () => {
|
|
// A prefetch should NOT re-use layouts from past prefetches if they used a less specific fetch strategy.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/shared-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// should prefetch page one, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
// Should not prefetch any dynamic content
|
|
{
|
|
includes: 'Dynamic content',
|
|
block: 'reject',
|
|
},
|
|
])
|
|
|
|
// Reveal the link to trigger a full prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/shared-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should prefetch the shared layout, because we didn't prefetch it fully
|
|
{
|
|
includes: 'Dynamic content from layout',
|
|
},
|
|
])
|
|
|
|
// Navigate to page two. We have everything in the cache, so we shouldn't issue any new requests
|
|
await act(async () => {
|
|
await browser.elementByCss(`a[href="/shared-layout/two"]`).click()
|
|
}, 'no-requests')
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-layout').text()).toEqual(
|
|
'Dynamic content from layout'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
// TODO (runtime-prefetching): link-level opt-in has been removed. These tests need to be updated to use the segment configuration.
|
|
it.skip('full prefetches should omit layouts that were prefetched with a runtime prefetch and had no dynamic holes', async () => {
|
|
// If a runtime prefetch gave us a complete segment with no dynamic holes left, then it's equivalent to a full prefetch.
|
|
//
|
|
// TODO: This doesn't work in all cases -- if any segment in a runtime prefetch was partial, we'll mark all of them as partial,
|
|
// which means they can't be reused in a full prefetch or a navigation. So this only works if the dynaimic prefetch has no holes at all.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/runtime-prefetchable-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// should prefetch page one, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
])
|
|
|
|
// Navigate to page one. It should have been completely prefetched by the runtime prefetch.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(`a[href="/runtime-prefetchable-layout/one"]`)
|
|
.click()
|
|
}, 'no-requests')
|
|
|
|
await browser.back()
|
|
|
|
// Reveal the link to trigger a full prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="full"][data-link-accordion="/runtime-prefetchable-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should not prefetch the shared layout, because we already got a complete result for it.
|
|
{
|
|
includes: 'Cookie from layout',
|
|
block: 'reject',
|
|
},
|
|
// Should fully prefetch the page, which we haven't prefetched before.
|
|
{
|
|
includes: 'Dynamic content from page two',
|
|
},
|
|
])
|
|
|
|
// Navigate to page two. We have everything in the cache, so we shouldn't issue any new requests
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(`a[href="/runtime-prefetchable-layout/two"]`)
|
|
.click()
|
|
}, 'no-requests')
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
// TODO (runtime-prefetching): link-level opt-in has been removed. These tests need to be updated to use the segment configuration.
|
|
it.skip('navigations should omit layouts that were prefetched with a runtime prefetch and had no dynamic holes', async () => {
|
|
// If a runtime prefetch gave us a complete segment with no dynamic holes left, then it's equivalent to a full prefetch.
|
|
//
|
|
// TODO: This doesn't work in all cases -- if any segment in a runtime prefetch was partial, we'll mark all of them as partial,
|
|
// which means they can't be reused in a full prefetch or a navigation. So this only works if the dynaimic prefetch has no holes at all.
|
|
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
// Clear cookies after the test. This currently doesn't happen automatically.
|
|
await using _ = defer(() => browser.deleteCookies())
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
await browser.addCookie({ name: 'testCookie', value: 'testValue' })
|
|
|
|
// Reveal the link to trigger a runtime prefetch for page one
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="runtime"][data-link-accordion="/runtime-prefetchable-layout/one"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// should prefetch page one, and allow reading cookies
|
|
{
|
|
includes: 'Cookie from page: testValue',
|
|
},
|
|
])
|
|
|
|
// Navigate to page one. It should have been completely prefetched by the runtime prefetch.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(`a[href="/runtime-prefetchable-layout/one"]`)
|
|
.click()
|
|
}, 'no-requests')
|
|
|
|
await browser.back()
|
|
|
|
// Reveal the link to trigger an auto prefetch for page two
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-prefetch="auto"][data-link-accordion="/runtime-prefetchable-layout/two"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
// Should not fetch the shared layout, because that was already prefetched
|
|
{
|
|
includes: 'Shared layout',
|
|
block: 'reject',
|
|
},
|
|
// Should fetch the static part of page two
|
|
{ includes: 'Page two' },
|
|
])
|
|
|
|
// Navigate to page two. We need to request the page segment dynamically, but the shared layout should be cached.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(`a[href="/runtime-prefetchable-layout/two"]`)
|
|
.click()
|
|
}, [
|
|
// Should not fetch the shared layout, because we already got a complete result for it.
|
|
{
|
|
includes: 'Cookie from layout',
|
|
block: 'reject',
|
|
},
|
|
// Should fetch the page, which we haven't prefetched before.
|
|
{
|
|
includes: 'Dynamic content from page two',
|
|
},
|
|
])
|
|
|
|
// After navigating, we should see both the parts that we prefetched and dynamic content.
|
|
expect(await browser.elementByCss('h2').text()).toEqual('Shared layout')
|
|
expect(await browser.elementByCss('h1').text()).toEqual('Page two')
|
|
expect(await browser.elementById('cookie-value-layout').text()).toEqual(
|
|
'Cookie from layout: testValue'
|
|
)
|
|
expect(await browser.elementById('cookie-value-page').text()).toEqual(
|
|
'Cookie from page: testValue'
|
|
)
|
|
expect(await browser.elementById('dynamic-content-page').text()).toEqual(
|
|
'Dynamic content from page two'
|
|
)
|
|
})
|
|
|
|
describe('segment-level prefetch config', () => {
|
|
const clientNavigateToSegmentConfigPage = async (
|
|
browser: NextBrowser,
|
|
act: ReturnType<typeof createRouterAct>
|
|
) => {
|
|
// Reveal the link to trigger a (automatic) runtime prefetch for the segment-config entrypoint page
|
|
await act(async () => {
|
|
const linkToggle = await browser.elementByCss(
|
|
`input[data-link-accordion="/segment-config/runtime-prefetchable"]`
|
|
)
|
|
await linkToggle.click()
|
|
}, [
|
|
{
|
|
includes: 'runtime-prefetchable-content-layout',
|
|
},
|
|
])
|
|
|
|
// Navigate to the segment-config entrypoint page.
|
|
// The layout is configured as runtime prefetchable, but the page is not.
|
|
// Both contain runtime-prefetchable content,
|
|
// but nothing dynamic, so we shouldn't need any extra requests.
|
|
//
|
|
// Note that the page itself doesn't specify that it should use a runtime prefetch,
|
|
// but we'll currently automatically include it in the runtime prefetch request
|
|
// that we're doing because of the layout's config.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(`a[href="/segment-config/runtime-prefetchable"]`)
|
|
.click()
|
|
}, 'no-requests')
|
|
}
|
|
|
|
it('does not unnecessarily use a runtime prefetch for sub-pages of runtime-prefetchable layouts', async () => {
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
// First, we move from the starting page to another page that has a runtime-prefetchable layout.
|
|
await clientNavigateToSegmentConfigPage(browser, act)
|
|
|
|
// Now we're on a page that uses the runtime-prefetchable layout,
|
|
// sub-pages of the same layout that are configured as static shouldn't automatically issue a runtime prefetch.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(
|
|
`input[data-link-accordion="/segment-config/runtime-prefetchable/configured-as-static"]`
|
|
)
|
|
.click()
|
|
}, [
|
|
// We should not prefetch anything from the parent layout again.
|
|
{ includes: 'static-content-layout', block: 'reject' },
|
|
{ includes: 'runtime-prefetchable-content-layout', block: 'reject' },
|
|
|
|
// We should prefetch the static content for the page, but nothing more.
|
|
{ includes: 'static-content-page' },
|
|
{ includes: 'dynamic-content-page', block: 'reject' },
|
|
{ includes: 'runtime-prefetchable-content-page', block: 'reject' },
|
|
])
|
|
})
|
|
|
|
it('statically prefetches a fully-static page segment if all its runtime-prefetchable parents are available', async () => {
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
// First, we move from the starting page to another page that has a runtime-prefetchable layout.
|
|
await clientNavigateToSegmentConfigPage(browser, act)
|
|
|
|
// Now we're on a page that uses the runtime-prefetchable layout,
|
|
// sub-pages of the same layout that are configured as static shouldn't automatically issue a runtime prefetch.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(
|
|
`input[data-link-accordion="/segment-config/runtime-prefetchable/fully-static"]`
|
|
)
|
|
.click()
|
|
}, [
|
|
// We should not prefetch anything from the parent layout again.
|
|
{ includes: 'static-content-layout', block: 'reject' },
|
|
{ includes: 'runtime-prefetchable-content-layout', block: 'reject' },
|
|
|
|
// We should prefetch the static content for the page, but nothing more.
|
|
{ includes: 'static-content-page' },
|
|
])
|
|
|
|
// The page segment is fully static, so we shouldn't need any extra requests to navigate to it.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(
|
|
`a[href="/segment-config/runtime-prefetchable/fully-static"]`
|
|
)
|
|
.click()
|
|
}, 'no-requests')
|
|
|
|
expect(
|
|
await (await browser.elementById('static-content-page')).isVisible()
|
|
).toBeTrue()
|
|
})
|
|
|
|
it('uses a runtime prefetch for sub-pages of runtime-prefetchable layouts if requested', async () => {
|
|
let page: Playwright.Page
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(p: Playwright.Page) {
|
|
page = p
|
|
},
|
|
})
|
|
|
|
const act = createRouterAct(page)
|
|
|
|
// First, we move from the starting page to another page that has a runtime-prefetchable layout.
|
|
await clientNavigateToSegmentConfigPage(browser, act)
|
|
|
|
// Sub-pages of this layout that are configured as runtime-prefetchable should be prefetched as such.
|
|
// However, if the page also has a sub-layout (not shared with the current page)
|
|
// that is configured as static, that part shouldn't be runtime-prefetched.
|
|
//
|
|
// Note that this is a sub-optimal configuration -- in a real app, we'd likely want the sub-layout
|
|
// to be runtime-prefetched as well, because it contains some runtime-prefetchable content.
|
|
// However, this is deliberately set up this way to assert that we don't do a runtime prefetch unless requested.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(
|
|
`input[data-link-accordion="/segment-config/runtime-prefetchable/configured-as-runtime"]`
|
|
)
|
|
.click()
|
|
}, [
|
|
// We should not prefetch anything from the parent layout again.
|
|
{ includes: 'static-content-layout', block: 'reject' },
|
|
{ includes: 'runtime-prefetchable-content-layout', block: 'reject' },
|
|
|
|
{
|
|
// We should *not* prefetch the runtime parts of the sub-layout,
|
|
// because it's not configured as runtime-prefetchable.
|
|
includes: 'runtime-prefetchable-content-sub-layout',
|
|
block: 'reject',
|
|
},
|
|
|
|
// ...but we should prefetch the runtime parts of the page.
|
|
{ includes: 'runtime-prefetchable-content-page' },
|
|
{ includes: 'dynamic-content-page', block: 'reject' },
|
|
])
|
|
|
|
// Navigate to the runtime-prefetchable sub-page.
|
|
await act(async () => {
|
|
// We should be able to display what we've prefetched before the navigation request resolves.
|
|
await act(async () => {
|
|
await browser
|
|
.elementByCss(
|
|
`a[href="/segment-config/runtime-prefetchable/configured-as-runtime"]`
|
|
)
|
|
.click()
|
|
}, 'block')
|
|
|
|
// The sub-layout should show static content, but not runtime-prefetchable content.
|
|
expect(
|
|
await (
|
|
await browser.elementById('static-content-sub-layout')
|
|
).isVisible()
|
|
).toBeTrue()
|
|
expect(
|
|
await (
|
|
await browser.elementById(
|
|
'runtime-prefetchable-fallback-sub-layout'
|
|
)
|
|
).isVisible()
|
|
).toBeTrue()
|
|
|
|
// The sub-page should show static/runtime-prefetchable content.
|
|
expect(
|
|
await (await browser.elementById('static-content-page')).isVisible()
|
|
).toBeTrue()
|
|
expect(
|
|
await (
|
|
await browser.elementById('runtime-prefetchable-content-page')
|
|
).isVisible()
|
|
).toBeTrue()
|
|
})
|
|
|
|
// After the navigation, we should see the content that wasn't prefetched.
|
|
// (because the sub-layout was configured as static)
|
|
expect(
|
|
await (
|
|
await browser.elementById('runtime-prefetchable-content-sub-layout')
|
|
).isVisible()
|
|
).toBeTrue()
|
|
})
|
|
})
|
|
})
|
|
|
|
function defer(callback: () => Promise<void>) {
|
|
return {
|
|
[Symbol.asyncDispose]: callback,
|
|
}
|
|
}
|