mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
prefetching is now controlled with the prefetch export and this option is inert on the instant export. this removes the prefetch option as a valid property on the instant config type. also updates the ts plugin for this export and adds the definition for the previously landed prefetch export
226 lines
7.1 KiB
TypeScript
226 lines
7.1 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from '../../../lib/next-test-utils'
|
|
|
|
describe('instant validation causes', () => {
|
|
const { next, skipped, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
skipDeployment: true,
|
|
env: {
|
|
NEXT_TEST_LOG_VALIDATION: '1',
|
|
},
|
|
})
|
|
if (skipped) return
|
|
if (!isNextDev) {
|
|
it.skip('Only implemented in dev', () => {})
|
|
return
|
|
}
|
|
|
|
let currentCliOutputIndex = 0
|
|
beforeEach(() => {
|
|
currentCliOutputIndex = next.cliOutput.length
|
|
})
|
|
|
|
function getCliOutputSinceMark(): string {
|
|
if (next.cliOutput.length < currentCliOutputIndex) {
|
|
currentCliOutputIndex = 0
|
|
}
|
|
return next.cliOutput.slice(currentCliOutputIndex)
|
|
}
|
|
|
|
type ValidationEvent =
|
|
| { type: 'validation_start'; requestId: string; url: string }
|
|
| { type: 'validation_end'; requestId: string; url: string }
|
|
|
|
function parseValidationMessages(output: string): ValidationEvent[] {
|
|
const messageRe = /<VALIDATION_MESSAGE>(.*?)<\/VALIDATION_MESSAGE>/g
|
|
const events: ValidationEvent[] = []
|
|
let match: RegExpExecArray | null
|
|
while ((match = messageRe.exec(output)) !== null) {
|
|
try {
|
|
events.push(JSON.parse(match[1]))
|
|
} catch (err) {
|
|
throw new Error(`Failed to parse message '${match[1]}'`, {
|
|
cause: err,
|
|
})
|
|
}
|
|
}
|
|
return events
|
|
}
|
|
|
|
function normalizeValidationUrl(url: string): string {
|
|
const parsed = new URL(url, 'http://n')
|
|
parsed.searchParams.delete('_rsc')
|
|
return parsed.pathname + parsed.search + parsed.hash
|
|
}
|
|
|
|
async function waitForValidation(targetUrl: string) {
|
|
const parsedTargetUrl = new URL(targetUrl)
|
|
const relativeTargetUrl =
|
|
parsedTargetUrl.pathname + parsedTargetUrl.search + parsedTargetUrl.hash
|
|
|
|
const requestId = await retry(
|
|
async () => {
|
|
const events = parseValidationMessages(getCliOutputSinceMark())
|
|
const start = events.find(
|
|
(e) =>
|
|
e.type === 'validation_start' &&
|
|
normalizeValidationUrl(e.url) === relativeTargetUrl
|
|
)
|
|
expect(start).toBeDefined()
|
|
return start!.requestId
|
|
},
|
|
undefined,
|
|
undefined,
|
|
`wait for validation of '${relativeTargetUrl}' to start`
|
|
)
|
|
|
|
await retry(
|
|
async () => {
|
|
const events = parseValidationMessages(getCliOutputSinceMark())
|
|
const end = events.find(
|
|
(e) => e.type === 'validation_end' && e.requestId === requestId
|
|
)
|
|
expect(end).toBeDefined()
|
|
},
|
|
undefined,
|
|
undefined,
|
|
'wait for validation to end'
|
|
)
|
|
}
|
|
|
|
it('named export - export { unstable_instant }', async () => {
|
|
const browser = await next.browser('/named-export')
|
|
await waitForValidation(await browser.url())
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/named-export/page.tsx (3:26) @ unstable_instant
|
|
> 3 | const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/named-export/page.tsx (3:26)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/named-export/page.tsx (7:16) @ Page
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/named-export/page.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('aliased export - export { instant as unstable_instant }', async () => {
|
|
const browser = await next.browser('/aliased-export')
|
|
await waitForValidation(await browser.url())
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/aliased-export/page.tsx (3:17) @ unstable_instant
|
|
> 3 | const instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/aliased-export/page.tsx (3:17)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/aliased-export/page.tsx (7:16) @ Page
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/aliased-export/page.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('re-export - export { unstable_instant } from "./config"', async () => {
|
|
const browser = await next.browser('/reexport')
|
|
await waitForValidation(await browser.url())
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/reexport/page.tsx (3:10) @ unstable_instant
|
|
> 3 | export { unstable_instant } from './config'
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/reexport/page.tsx (3:10)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/reexport/page.tsx (6:16) @ Page
|
|
> 6 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/reexport/page.tsx (6:16)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('indirect export - const instant = _instant; export { instant as unstable_instant }', async () => {
|
|
const browser = await next.browser('/indirect-export')
|
|
await waitForValidation(await browser.url())
|
|
// Ideally we'd be pointing at the original value declaration.
|
|
// We're not following declarations recursively mostly to keep the implementation simpler
|
|
// presuming that almost all configs are just `export const instant = ...`
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/indirect-export/page.tsx (4:17) @ unstable_instant
|
|
> 4 | const instant = _instant
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/indirect-export/page.tsx (4:17)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/indirect-export/page.tsx (8:16) @ Page
|
|
> 8 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/indirect-export/page.tsx (8:16)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('does not add an instant stack for random unstable_instant exports', async () => {
|
|
const browser = await next.browser('/not-actual-instant')
|
|
const config = await browser.waitForElementByCss('[data-testid="config"]')
|
|
expect(await config.innerText()).toBe(
|
|
JSON.stringify({ unstable_instant: false })
|
|
)
|
|
})
|
|
})
|