Files
vercel__next.js/test/e2e/app-dir/cache-components-errors/http-access-fallback-prerender.test.ts
Josh Story e0966df114 test: migrate caching deploy exclusions to force gates (#98154)
## Summary

- migrate Cache Components, PPR, prefetching, prerendering,
revalidation, and SSG deployment exclusions to `@force-gate`
- remove the corresponding `skipDeployment` options and `skipped`
control flow while preserving each suite's rationale

This keeps caching-related exclusions together so their deployed
semantics can be reviewed by the same owners.

## Verification

- verified on the combined top-of-stack tree with `pnpm typescript`
- compared ordinary-mode collection before and after: all 4,670 existing
test names matched
- collected all 510 affected files in deploy mode: 4,578 skipped tests,
zero failures

<!-- NEXT_JS_LLM -->
2026-09-09 08:25:36 -07:00

320 lines
9.9 KiB
TypeScript

import { isNextDev, nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import { getPrerenderOutput } from './utils'
// TODO(deploy-test-completion): Re-enable this suite in deploy mode.
// It likely asserts local CLI or runtime output that deploy tests do not expose.
// @force-gate !deploy
describe('Cache Components HTTP Access Fallback Prerender', () => {
const { next, isNextStart } = nextTestSetup({
files: __dirname + '/fixtures/http-access-fallback-prerender',
skipStart: !isNextDev,
})
let cliOutputLength: number
beforeEach(() => {
cliOutputLength = next.cliOutput.length
})
afterEach(async () => {
if (isNextStart) {
await next.stop()
}
})
const testCases: { isDebugPrerender: boolean; name: string }[] = []
if (isNextDev) {
testCases.push({ isDebugPrerender: false, name: 'Dev' })
} else {
const prerenderMode = process.env.NEXT_TEST_DEBUG_PRERENDER
if (!prerenderMode || prerenderMode === 'true') {
testCases.push({
isDebugPrerender: true,
name: 'Build With --debug-prerender',
})
}
if (!prerenderMode || prerenderMode === 'false') {
testCases.push({
isDebugPrerender: false,
name: 'Build Without --debug-prerender',
})
}
}
describe.each(testCases)('$name', ({ isDebugPrerender }) => {
beforeAll(async () => {
if (isNextStart) {
const args = ['--experimental-build-mode', 'compile']
if (isDebugPrerender) {
args.push('--debug-prerender')
}
await next.build({ args })
}
})
const prerender = async (pathname: string) => {
const args = [
'--experimental-build-mode',
'generate',
'--debug-build-paths',
`app${pathname}/page.tsx`,
]
if (isDebugPrerender) {
args.push('--debug-prerender')
}
await next.build({ args })
}
const buildPath = async (pathname: string) => {
const args = ['--debug-build-paths', `app${pathname}/page.tsx`]
if (isDebugPrerender) {
args.push('--debug-prerender')
}
await next.build({ args })
}
const expectStaticRouteArtifacts = async (route: string, status = 404) => {
const meta = JSON.parse(
await next.readFile(`.next/server/app/${route}.meta`)
)
expect(await next.readFile(`.next/server/app/${route}.html`)).toEqual(
expect.any(String)
)
expect(meta.status).toBe(status)
expect(meta.postponed).toBeUndefined()
expect(meta.segmentPaths).toContain('/_tree')
expect(
await next.readFile(
`.next/server/app/${route}.segments/_tree.segment.rsc`
)
).toEqual(expect.any(String))
}
const expectPartiallyStaticErrorArtifacts = async (
route: string,
status = 404
) => {
const meta = JSON.parse(
await next.readFile(`.next/server/app/${route}.meta`)
)
expect(await next.readFile(`.next/server/app/${route}.html`)).toEqual(
expect.any(String)
)
expect(meta.status).toBe(status)
expect(meta.postponed).toEqual(expect.any(String))
expect(meta.segmentPaths).toContain('/_tree')
expect(
await next.readFile(
`.next/server/app/${route}.segments/_tree.segment.rsc`
)
).toEqual(expect.any(String))
}
describe('notFound()', () => {
const pagePath = '/not-found/[slug]'
const visitUrl = '/not-found/not-found'
if (isNextDev) {
it('should show a collapsed redbox when not-found.tsx uses useSearchParams without Suspense', async () => {
const browser = await next.browser(visitUrl)
await expect(browser).toDisplayCollapsedRedbox(
`"Redbox did not open."`
)
})
} else {
it('should skip static shell validation during fallback recovery', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectStaticRouteArtifacts('not-found/not-found')
})
}
})
describe('notFound() with root client params during recovery', () => {
const pagePath = '/not-found-use-params/[slug]'
if (!isNextDev) {
it('should fall through from the resume-abort recovery and emit static artifacts', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectStaticRouteArtifacts('not-found-use-params/not-found')
})
}
})
describe('notFound() with dynamic metadata and viewport', () => {
const pagePath = '/not-found-dynamic-head/[slug]'
if (!isNextDev) {
it('should allow fallback recovery when the route opts out of static shell validation', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectPartiallyStaticErrorArtifacts(
'not-found-dynamic-head/not-found'
)
const prerenderedHtml = await next.readFile(
'.next/server/app/not-found-dynamic-head/not-found.html'
)
expect(prerenderedHtml).not.toContain('not-found metadata marker')
expect(prerenderedHtml).not.toContain('metadata from not-found.tsx')
expect(prerenderedHtml).not.toContain('#123456')
await next.start({ skipBuild: true })
const browser = await next.browser(
'/not-found-dynamic-head/not-found'
)
await retry(async () => {
const head = await browser.eval(() => {
return {
title: document.title,
description: document
.querySelector('meta[name="description"]')
?.getAttribute('content'),
themeColors: Array.from(
document.querySelectorAll('meta[name="theme-color"]')
).map((meta) => meta.getAttribute('content')),
}
})
expect(head.title).toBe('not-found metadata marker')
expect(head.description).toBe('metadata from not-found.tsx')
expect(head.themeColors).toContain('#123456')
expect(head.themeColors).not.toContain('black')
})
})
}
})
describe('notFound() with static RSC data', () => {
const pagePath = '/not-found-static-flight/[slug]'
if (!isNextDev) {
it('should preserve the original static Flight data during recovery', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectStaticRouteArtifacts('not-found-static-flight/not-found')
})
}
})
describe('notFound() with dynamic RSC data', () => {
const pagePath = '/not-found-dynamic-flight/[slug]'
if (!isNextDev) {
it('should preserve the original dynamic Flight data during recovery', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectPartiallyStaticErrorArtifacts(
'not-found-dynamic-flight/not-found'
)
})
}
})
describe('forbidden()', () => {
const pagePath = '/forbidden/[slug]'
const visitUrl = '/forbidden/forbidden'
if (isNextDev) {
it('should show a collapsed redbox when forbidden.tsx uses useSearchParams without Suspense', async () => {
const browser = await next.browser(visitUrl)
await expect(browser).toDisplayCollapsedRedbox(
`"Redbox did not open."`
)
})
} else {
it('should skip static shell validation during fallback recovery', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectStaticRouteArtifacts('forbidden/forbidden', 403)
})
}
})
describe('unauthorized()', () => {
const pagePath = '/unauthorized/[slug]'
const visitUrl = '/unauthorized/unauthorized'
if (isNextDev) {
it('should show a collapsed redbox when unauthorized.tsx uses useSearchParams without Suspense', async () => {
const browser = await next.browser(visitUrl)
await expect(browser).toDisplayCollapsedRedbox(
`"Redbox did not open."`
)
})
} else {
it('should skip static shell validation during fallback recovery', async () => {
await prerender(pagePath)
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
expect(output).toMatchInlineSnapshot(`""`)
await expectStaticRouteArtifacts('unauthorized/unauthorized', 401)
})
}
})
describe('notFound() above the matching not-found boundary', () => {
if (!isNextDev) {
it('should emit static artifacts', async () => {
await buildPath('/not-found-above-boundary/child')
await expectStaticRouteArtifacts('not-found-above-boundary/child')
})
}
})
})
})