import { retry } from 'next-test-utils'
import { isNextDev, isNextStart, nextTestSetup } from 'e2e-utils'
// | | Pages Client | Pages Server (SSR,RSC) | API Routes/Middleware/Metadata |
// |---------|-------------------------|-------------------------|--------------------------------|
// | new URL | /_next/static/media/... | /_next/static/media/... | /server/assets/... |
// | import | /_next/static/media/... | /_next/static/media/... | /_next/static/media/... |
// |---------|-------------------------|-------------------------|--------------------------------|
//
// Webpack has
// - a bug where App Router API routes (and Metadata) return client assets for `new URL`s.
// - a bug where Edge Page routes return client assets for `new URL`s.
describe(`Handle new URL asset references`, () => {
const { next, skipped, isTurbopack } = nextTestSetup({
files: __dirname,
env: {
// rely on skew protection when deployed
NEXT_DEPLOYMENT_ID: isNextStart ? 'test-deployment-id' : undefined,
__NEXT_SUPPORTS_IMMUTABLE_ASSETS: isNextStart ? '1' : undefined,
},
skipDeployment: true,
})
if (skipped) {
return
}
const serverFileRegex = expect.stringMatching(
/file:.*\/.next(\/dev)?\/server\/.*\/vercel.HASH.png$/
)
const serverEdgeUrl = isTurbopack
? `blob:server/edge/assets/vercel.HASH.png`
: `blob:vercel.HASH.png`
let clientUrl: string
const expectedPageContent = (count: number) =>
'Hello ' + Array(count).fill(clientUrl).join('+')
beforeAll(() => {
let expectedToken: string | undefined
if (isNextDev || !isTurbopack) {
expectedToken = undefined
} else {
expectedToken = next.assetToken
}
clientUrl =
isTurbopack && !isNextDev
? `/_next/static/immutable/media/vercel.HASH.png`
: `/_next/static/media/vercel.HASH.png${expectedToken ? `?dpl=${expectedToken}` : ''}`
})
it('should respond on middleware api', async () => {
const data = await next
.fetch('/middleware')
.then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
imported: clientUrl,
url: serverEdgeUrl,
})
})
describe('app router', () => {
it('should respond on webmanifest', async () => {
const data = await next
.fetch('/manifest.webmanifest')
.then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
short_name: 'Next.js',
name: 'Next.js',
icons: [
{
src: clientUrl,
type: 'image/png',
sizes: '512x512',
},
],
// TODO Webpack bug?
description: isTurbopack ? serverFileRegex : clientUrl,
})
})
it('should respond on opengraph-image', async () => {
const data = await next
.fetch('/opengraph-image')
.then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
imported: clientUrl,
// TODO Webpack bug?
url: isTurbopack ? serverFileRegex : clientUrl,
})
})
for (const page of ['/rsc', '/rsc-edge', '/client', '/client-edge']) {
// TODO Webpack bug?
let shouldSkip = isTurbopack ? false : page.includes('edge')
;(shouldSkip ? it.skip : it)(
`should render the ${page} page`,
async () => {
const $ = await next.render$(page)
// eslint-disable-next-line jest/no-standalone-expect
expect(stripVercelPngHash($('main').text())).toEqual(
expectedPageContent(2)
)
}
)
;(shouldSkip ? it.skip : it)(
`should client-render the ${page} page`,
async () => {
const browser = await next.browser(page)
await retry(async () =>
expect(
stripVercelPngHash(await browser.elementByCss('main').text())
).toEqual(expectedPageContent(2))
)
}
)
}
it('should respond on API', async () => {
const data = await next.fetch('/api').then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
imported: clientUrl,
// TODO Webpack bug?
url: isTurbopack ? serverFileRegex : clientUrl,
size: isTurbopack ? 30079 : expect.toBeString(),
})
})
})
describe('pages router', () => {
for (const [page, count] of [
['/pages/static', 2],
['/pages/ssr', 3],
['/pages/ssg', 3],
['/pages-edge/static', 2],
['/pages-edge/ssr', 3],
] as const) {
// TODO Webpack bug?
let shouldSkip = isTurbopack ? false : page.includes('edge')
;(shouldSkip ? it.skip : it)(
`should render the ${page} page`,
async () => {
const $ = await next.render$(page)
// eslint-disable-next-line jest/no-standalone-expect
expect(stripVercelPngHash($('main').text())).toEqual(
expectedPageContent(count)
)
}
)
;(shouldSkip ? it.skip : it)(
`should client-render the ${page} page`,
async () => {
const browser = await next.browser(page)
await retry(async () =>
expect(
stripVercelPngHash(await browser.elementByCss('main').text())
).toEqual(expectedPageContent(count))
)
}
)
}
it('should respond on API', async () => {
const data = await next
.fetch('/api/pages/')
.then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
imported: clientUrl,
url: serverFileRegex,
size: 30079,
})
})
it('should respond on edge API', async () => {
const data = await next
.fetch('/api/pages-edge/')
.then((res) => res.ok && res.text())
const json = JSON.parse(stripVercelPngHash(data))
expect(json).toEqual({
imported: clientUrl,
url: serverEdgeUrl,
})
})
})
})
function stripVercelPngHash(text: string) {
return text.replace(/vercel\.[0-9a-z_-]{4,}\.png/g, 'vercel.HASH.png')
}