mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
When a route with params specifies `generateStaticParams`, the meaningful outputs are the sub-paths contained within. The top level route just corresponds with the file path, which isn't representative of a static or partially prerendered output. Previously it'd just inherit one of the sub-path values (ie if one path was PPRed, it'd show PPR, even if some were fully static). This removes the symbol from those types of outputs, since #92518 moved those symbols to be within the subpaths. For truly dynamic param cases (ie, no subpaths), the rendering symbol will still appear.
201 lines
6.4 KiB
TypeScript
201 lines
6.4 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry, findAllTelemetryEvents } from 'next-test-utils'
|
|
import stripAnsi from 'strip-ansi'
|
|
|
|
// TODO(NAR-423): Migrate to Cache Components.
|
|
describe.skip('ppr', () => {
|
|
const { next, isNextDev, isNextStart } = nextTestSetup({
|
|
files: __dirname,
|
|
env: {
|
|
NEXT_TELEMETRY_DEBUG: '1',
|
|
},
|
|
})
|
|
|
|
it('should indicate the feature is experimental', async () => {
|
|
await retry(() => {
|
|
expect(next.cliOutput).toContain('Experiments (use with caution)')
|
|
expect(stripAnsi(next.cliOutput)).toContain('✓ ppr')
|
|
})
|
|
})
|
|
if (isNextStart) {
|
|
describe('build output', () => {
|
|
it('correctly marks pages as being partially prerendered in the build output', () => {
|
|
expect(next.cliOutput).toContain('◐ /loading/nested/[slug]')
|
|
expect(next.cliOutput).toContain('◐ /suspense/node')
|
|
expect(next.cliOutput).toContain(' /suspense/node/gsp/[slug]')
|
|
expect(next.cliOutput).toContain(' /suspense/node/nested/[slug]')
|
|
})
|
|
})
|
|
|
|
describe('telemetry', () => {
|
|
it('should send ppr feature usage event', async () => {
|
|
const events = findAllTelemetryEvents(
|
|
next.cliOutput,
|
|
'NEXT_BUILD_FEATURE_USAGE'
|
|
)
|
|
expect(events).toContainEqual({
|
|
featureName: 'experimental/ppr',
|
|
invocationCount: 1,
|
|
})
|
|
})
|
|
})
|
|
}
|
|
describe.each([
|
|
{ pathname: '/suspense/node' },
|
|
{ pathname: '/suspense/node/nested/1' },
|
|
{ pathname: '/suspense/node/nested/2' },
|
|
{ pathname: '/suspense/node/nested/3' },
|
|
{ pathname: '/loading/nested/1' },
|
|
{ pathname: '/loading/nested/2' },
|
|
{ pathname: '/loading/nested/3' },
|
|
])('for $pathname', ({ pathname }) => {
|
|
// When we're partially pre-rendering, we should get the static parts
|
|
// immediately, and the dynamic parts after the page loads. So we should
|
|
// see the static part in the output, but the dynamic part should be
|
|
// missing.
|
|
it('should serve the static part', async () => {
|
|
const $ = await next.render$(pathname)
|
|
expect($('#page').length).toBe(1)
|
|
})
|
|
|
|
if (isNextDev) {
|
|
it('should have the dynamic part', async () => {
|
|
let $ = await next.render$(pathname)
|
|
// asserting this flushes in a single pass in dev is not reliable
|
|
// so we just assert that the nodes are somewhere in the document
|
|
// even if they aren't in position
|
|
let dynamic = $('#dynamic > #state')
|
|
|
|
expect(dynamic.length).toBe(1)
|
|
expect(dynamic.text()).toBe('Not Signed In')
|
|
|
|
$ = await next.render$(
|
|
pathname,
|
|
{},
|
|
{
|
|
headers: {
|
|
cookie: 'session=1',
|
|
},
|
|
}
|
|
)
|
|
dynamic = $('#dynamic > #state')
|
|
expect(dynamic.length).toBe(1)
|
|
expect(dynamic.text()).toBe('Signed In')
|
|
})
|
|
} else {
|
|
it('should not have the dynamic part', async () => {
|
|
const $ = await next.render$(pathname)
|
|
expect($('#container > #dynamic > #state').length).toBe(0)
|
|
})
|
|
}
|
|
})
|
|
|
|
describe.each([
|
|
{ pathname: '/suspense/node' },
|
|
{ pathname: '/suspense/edge' },
|
|
])('with suspense for $pathname', ({ pathname }) => {
|
|
// When the browser loads the page, we expect that the dynamic part will
|
|
// be rendered.
|
|
it('should eventually render the dynamic part', async () => {
|
|
const browser = await next.browser(pathname)
|
|
|
|
try {
|
|
// Wait for the page part to load.
|
|
await browser.waitForElementByCss('#page')
|
|
await browser.waitForIdleNetwork()
|
|
|
|
// Wait for the dynamic part to load.
|
|
await browser.waitForElementByCss('#container > #dynamic > #state')
|
|
|
|
// Ensure we've got the right dynamic part.
|
|
let dynamic = await browser
|
|
.elementByCss('#container > #dynamic > #state')
|
|
.text()
|
|
|
|
expect(dynamic).toBe('Not Signed In')
|
|
|
|
// Re-visit the page with the cookie.
|
|
await browser.addCookie({ name: 'session', value: '1' }).refresh()
|
|
|
|
// Wait for the page part to load.
|
|
await browser.waitForElementByCss('#page')
|
|
await browser.waitForIdleNetwork()
|
|
|
|
// Wait for the dynamic part to load.
|
|
await browser.waitForElementByCss('#container > #dynamic > #state')
|
|
|
|
// Ensure we've got the right dynamic part.
|
|
dynamic = await browser
|
|
.elementByCss('#container > #dynamic > #state')
|
|
.text()
|
|
|
|
expect(dynamic).toBe('Signed In')
|
|
} finally {
|
|
await browser.deleteCookies()
|
|
await browser.close()
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('search parameters', () => {
|
|
it('should render the page with the search parameters', async () => {
|
|
const expected = `${Date.now()}:${Math.random()}`
|
|
const res = await next.fetch(
|
|
`/search?query=${encodeURIComponent(expected)}`
|
|
)
|
|
expect(res.status).toBe(200)
|
|
|
|
const html = await res.text()
|
|
expect(html).toContain(expected)
|
|
})
|
|
})
|
|
|
|
describe.each([{ pathname: '/no-suspense' }])(
|
|
'without suspense for $pathname',
|
|
({ pathname }) => {
|
|
// When the browser loads the page, we expect that the dynamic part will
|
|
// be rendered.
|
|
it('should immediately render the dynamic part', async () => {
|
|
let $ = await next.render$(pathname)
|
|
|
|
let dynamic = $('#container > #dynamic > #state').text()
|
|
expect(dynamic).toBe('Not Signed In')
|
|
|
|
// Re-visit the page with the cookie.
|
|
$ = await next.render$(
|
|
pathname,
|
|
{},
|
|
{
|
|
headers: {
|
|
cookie: 'session=1',
|
|
},
|
|
}
|
|
)
|
|
|
|
dynamic = $('#container > #dynamic > #state').text()
|
|
expect(dynamic).toBe('Signed In')
|
|
})
|
|
}
|
|
)
|
|
|
|
describe('/no-suspense/node/gsp/[slug]', () => {
|
|
it('should serve the static & dynamic parts', async () => {
|
|
const $ = await next.render$('/no-suspense/node/gsp/foo')
|
|
expect($('#page').length).toBe(1)
|
|
expect($('#container > #dynamic > #state').length).toBe(1)
|
|
})
|
|
})
|
|
|
|
describe('/suspense/node/gsp/[slug]', () => {
|
|
it('should serve the static part first', async () => {
|
|
const $ = await next.render$('/suspense/node/gsp/foo')
|
|
expect($('#page').length).toBe(1)
|
|
})
|
|
|
|
it('should not have the dynamic part', async () => {
|
|
const $ = await next.render$('/suspense/node/gsp/foo')
|
|
expect($('#container > #dynamic > #state').length).toBe(0)
|
|
})
|
|
})
|
|
})
|