mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
275 lines
8.1 KiB
TypeScript
275 lines
8.1 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { NextAdapter } from 'next'
|
|
import { retry } from 'next-test-utils'
|
|
import { join } from 'node:path'
|
|
|
|
const immutableAssetsFilter = (p) =>
|
|
p.includes('/_next/static/') &&
|
|
!(
|
|
p.includes('/_buildManifest.js') ||
|
|
p.includes('/_clientMiddlewareManifest.js') ||
|
|
p.includes('/_ssgManifest.js')
|
|
)
|
|
|
|
describe.each([
|
|
['NEXT_DEPLOYMENT_ID', ''],
|
|
['CUSTOM_DEPLOYMENT_ID', ''],
|
|
['NEXT_DEPLOYMENT_ID', ' and runtimeServerDeploymentId'],
|
|
['NEXT_DEPLOYMENT_ID_IMMUTABLE', ''],
|
|
])(
|
|
'deployment-id-handling enabled with %s%s',
|
|
(envKey, runtimeServerDeploymentId) => {
|
|
const usesImmutableAssets = envKey === 'NEXT_DEPLOYMENT_ID_IMMUTABLE'
|
|
|
|
if (usesImmutableAssets && !process.env.IS_TURBOPACK_TEST) {
|
|
it.skip('skip for webpack', () => {})
|
|
return
|
|
}
|
|
|
|
const deploymentId = Date.now() + ''
|
|
const immutableAssetToken = usesImmutableAssets ? '' : deploymentId
|
|
|
|
const { next } = nextTestSetup({
|
|
files: join(__dirname, 'app'),
|
|
env: {
|
|
[envKey]: deploymentId,
|
|
RUNTIME_SERVER_DEPLOYMENT_ID: runtimeServerDeploymentId
|
|
? '1'
|
|
: undefined,
|
|
},
|
|
disableAutoSkewProtection: true,
|
|
})
|
|
|
|
const validateTokenForRequest = (url: string) => {
|
|
const token =
|
|
usesImmutableAssets && immutableAssetsFilter(url)
|
|
? // Turbopack-emitted chunks
|
|
immutableAssetToken
|
|
: // e.g. /_next/static/build-id/_ssgManifest.js
|
|
deploymentId
|
|
if (token) {
|
|
expect(url).toContain('dpl=' + token)
|
|
} else {
|
|
expect(url).not.toContain('dpl=')
|
|
}
|
|
}
|
|
|
|
it.each([
|
|
{ urlPath: '/' },
|
|
{ urlPath: '/pages-edge' },
|
|
{ urlPath: '/from-app' },
|
|
{ urlPath: '/from-app/edge' },
|
|
])(
|
|
'should append dpl query to all assets correctly for $urlPath',
|
|
async ({ urlPath }) => {
|
|
// Validate SSR response
|
|
const $ = await next.render$(urlPath)
|
|
|
|
expect($('#deploymentId').text()).toBe(deploymentId)
|
|
|
|
const scripts = Array.from($('script'))
|
|
expect(scripts.length).toBeGreaterThan(0)
|
|
|
|
for (const script of scripts) {
|
|
if (script.attribs.src) {
|
|
validateTokenForRequest(script.attribs.src)
|
|
}
|
|
}
|
|
|
|
const links = Array.from($('link'))
|
|
expect(links.length).toBeGreaterThan(0)
|
|
|
|
for (const link of links) {
|
|
if (link.attribs.href && link.attribs.rel !== 'expect') {
|
|
validateTokenForRequest(link.attribs.href)
|
|
}
|
|
}
|
|
|
|
// Validate all requests ever performed by a browser
|
|
|
|
const clientRequests = []
|
|
|
|
const browser = await next.browser(urlPath, {
|
|
beforePageLoad(page) {
|
|
page.on('request', async (req) => {
|
|
// TODO this currently exclude _next/image
|
|
if (req.url().includes('/_next/static')) {
|
|
clientRequests.push(req.url())
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
const dynamicImportRequests = []
|
|
browser.on('request', (req) => {
|
|
if (req.url().includes('/_next/static')) {
|
|
dynamicImportRequests.push(req.url())
|
|
}
|
|
})
|
|
await browser.elementByCss('#dynamic-import').click()
|
|
await retry(() => expect(dynamicImportRequests).not.toBeEmpty())
|
|
|
|
try {
|
|
dynamicImportRequests.forEach((item) => validateTokenForRequest(item))
|
|
} finally {
|
|
require('console').error(
|
|
'dynamicImportRequests',
|
|
dynamicImportRequests
|
|
)
|
|
}
|
|
|
|
try {
|
|
clientRequests.forEach((item) => validateTokenForRequest(item))
|
|
} finally {
|
|
require('console').error('clientRequests', clientRequests)
|
|
}
|
|
}
|
|
)
|
|
|
|
it.each([{ pathname: '/api/hello' }, { pathname: '/api/hello-app' }])(
|
|
'should have deployment id env available',
|
|
async ({ pathname }) => {
|
|
const res = await next.fetch(pathname)
|
|
|
|
expect(await res.json()).toEqual({
|
|
deploymentId,
|
|
})
|
|
}
|
|
)
|
|
|
|
it('should contain deployment id in prefetch request', async () => {
|
|
const dataHeaders = []
|
|
const browser = await next.browser('/', {
|
|
beforePageLoad(page) {
|
|
page.on('request', async (req) => {
|
|
const headers = req.headers()
|
|
if (headers['x-nextjs-data']) {
|
|
dataHeaders.push(headers)
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
await browser.elementByCss('#edge-link').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('h1').text()).toBe('hello pages edge')
|
|
expect(await browser.url()).toContain('/pages-edge')
|
|
expect(dataHeaders.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
expect(
|
|
dataHeaders.every(
|
|
(headers) => headers['x-deployment-id'] === deploymentId
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('should contain deployment id in RSC payload request headers', async () => {
|
|
const rscHeaders = []
|
|
const browser = await next.browser('/from-app', {
|
|
beforePageLoad(page) {
|
|
page.on('request', async (req) => {
|
|
const headers = req.headers()
|
|
if (headers['rsc']) {
|
|
rscHeaders.push(headers)
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
await browser.elementByCss('#other-app').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('h1').text()).toBe('other app')
|
|
expect(await browser.url()).toContain('/other-app')
|
|
expect(rscHeaders.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
expect(rscHeaders).toSatisfyAll(
|
|
(headers) => headers['x-deployment-id'] === deploymentId
|
|
)
|
|
})
|
|
|
|
if (usesImmutableAssets) {
|
|
it('should emit hashes to adapter', async () => {
|
|
const { outputs }: Parameters<NextAdapter['onBuildComplete']>[0] =
|
|
await next.readJSON('build-complete.json')
|
|
|
|
const immutableAssets = outputs.staticFiles.filter((p) =>
|
|
immutableAssetsFilter(p.pathname)
|
|
)
|
|
expect(immutableAssets).not.toBeEmpty()
|
|
expect(immutableAssets).toSatisfyAll(
|
|
(f) =>
|
|
// Should be same hash as in the filename, for better build performance.
|
|
// This check also ensure that we don't accidentally forget to content hash sourcemap
|
|
// files (i.e. 0cz1d0mv5g_q7.js is content hashed, but 0cz1d0mv5g_q7.js.map is not a
|
|
// content hash of itself)..
|
|
f.immutableHash &&
|
|
f.pathname.includes(f.immutableHash.slice(0, 13)) &&
|
|
f.pathname.startsWith('/_next/static/immutable')
|
|
)
|
|
})
|
|
}
|
|
}
|
|
)
|
|
|
|
describe('deployment-id-handling disabled', () => {
|
|
const deploymentId = Date.now() + ''
|
|
const { next } = nextTestSetup({
|
|
files: join(__dirname, 'app'),
|
|
disableAutoSkewProtection: true,
|
|
})
|
|
it.each([
|
|
{ urlPath: '/' },
|
|
{ urlPath: '/pages-edge' },
|
|
{ urlPath: '/from-app' },
|
|
{ urlPath: '/from-app/edge' },
|
|
])(
|
|
'should not append dpl query to all assets for $urlPath',
|
|
async ({ urlPath }) => {
|
|
const $ = await next.render$(urlPath)
|
|
|
|
expect($('#deploymentId').text()).not.toBe(deploymentId)
|
|
|
|
const scripts = Array.from($('script'))
|
|
expect(scripts.length).toBeGreaterThan(0)
|
|
|
|
for (const script of scripts) {
|
|
if (script.attribs.src) {
|
|
expect(script.attribs.src).not.toContain('dpl=' + deploymentId)
|
|
}
|
|
}
|
|
|
|
const links = Array.from($('link'))
|
|
expect(links.length).toBeGreaterThan(0)
|
|
|
|
for (const link of links) {
|
|
if (link.attribs.href) {
|
|
expect(link.attribs.href).not.toContain('dpl=' + deploymentId)
|
|
}
|
|
}
|
|
|
|
const browser = await next.browser(urlPath)
|
|
const requests = []
|
|
|
|
browser.on('request', (req) => {
|
|
requests.push(req.url())
|
|
})
|
|
|
|
await browser.elementByCss('#dynamic-import').click()
|
|
|
|
await retry(() => expect(requests).not.toBeEmpty())
|
|
|
|
try {
|
|
expect(requests).toSatisfyAll(
|
|
(item) => !item.includes('dpl=' + deploymentId)
|
|
)
|
|
} finally {
|
|
require('console').error('requests', requests)
|
|
}
|
|
}
|
|
)
|
|
})
|