mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### What?
Converts every test under `test/integration/` to an isolated test
running through `nextTestSetup` (under `test/e2e/`, `test/production/`,
`test/development/`, or `test/unit/`), then deletes `test/integration/`
along with the legacy CI orchestration that was specific to it.
- `test/integration/` removed entirely (~327 test suites)
- New isolated suites added across the existing folders:
- `test/e2e/` — 175
- `test/production/` — 130
- `test/development/` — 43
- `test/unit/` — 1
- `.github/workflows/build_and_test.yml` and `run-tests.js` no longer
have any `integration` branches
- `nextTestSetup` gained a `baseUrl` option on `next.browser()` so a
small number of tests that drive their own proxy/static-export server
can keep using `next.browser(...)` instead of importing `next-webdriver`
directly
### Why?
`test/integration/` predated `nextTestSetup` and ran tests directly
against the source checkout via custom helpers (`launchApp`,
`nextBuild`, `nextStart`, `runNextCommand`, `webdriver`, `fetchViaHTTP`,
…). Each suite hand-rolled its own dev/start/build orchestration,
fixture mutation, and process management.
The isolated test model used by the rest of the repo gives each suite an
isolated working directory containing a packed `next.tgz` install, a
uniform `next.start()` / `next.build()` / `next.fetch()` /
`next.browser()` API, and the same lifecycle for dev, start, and deploy
modes — so a single set of assertions covers all three. Deploy-mode
skips and per-feature gates are expressed declaratively
(`skipDeployment`, `disableAutoSkewProtection`, `if (skipped) return`)
instead of branching on `process.env`.
Removing `test/integration/` lets us:
- Delete the bespoke orchestration code in the CI workflow and
`run-tests.js`
- Run every converted suite consistently in dev, start, and deploy modes
(where applicable)
- Reproduce every test locally with the same `pnpm
test-{dev,start}-{turbo,webpack}` commands; no separate `integration`
path
- Open the door to running `test/production` against deployments in the
future (the converted suites already declare `skipDeployment` so they
can be flipped on)
### How?
Mechanical conversion per suite, with targeted clean-ups:
1. **Per-suite conversion.** Each
`test/integration/<name>/test/index.test.{js,ts}` was rewritten into a
single `<name>.test.ts` under the right folder based on what the
original exercised:
- `launchApp` / dev-only assertions → `test/development/`
- `nextBuild` + `nextStart` / start-only assertions → `test/production/`
- Both → `test/e2e/`
- The one pure jsdom render check (`link-without-router`) → `test/unit/`
2. **API mapping.** Custom helpers were replaced by `nextTestSetup`
equivalents: `launchApp` → `next.start()`, `nextBuild` → `next.build()`,
`runNextCommand` → `next.runCommand`, `fetchViaHTTP` → `next.fetch`,
`webdriver(...)` → `next.browser(...)`. Fixture mutations switched from
raw `fs.writeFile`/`fs.rename` to `next.patchFile` (with the 3-arg
`runWithTempContent` callback when the change has a defined scope) and
`next.deleteFile`.
3. **Deploy-mode handling.** Suites that can't run in deploy mode (use
`patchFile` / `next.build()` / depend on local CLI output) declare
`skipDeployment: true` and early-return on the `skipped` boolean. Suites
where Vercel's edge mutates URLs (`&dpl=`, immutable assets) declare
`disableAutoSkewProtection: true`.
4. **`next.browser({ baseUrl })`.** A handful of tests
(`prerender-export`, `cdn-cache-busting`, `preload-viewport`, both
`react-virtualized` suites) need to drive a separate server (a
static-export server or an `http-proxy` instance) rather than the
Next.js process. Instead of importing `next-webdriver` directly, those
tests now pass `{ baseUrl: <port|url> }` to `next.browser()`. For the
proxy cases, the proxy was moved into `server.js` inside the fixture and
`http-proxy` declared via the `dependencies` option of `nextTestSetup`,
so the test runs with a fully isolated dependency graph.
5. **CI clean-up.** With `test/integration` gone, the `test
integration*` jobs and `integration-tests-manifest`-related logic in
`.github/workflows/build_and_test.yml` were removed, and `run-tests.js`
no longer has the `integration` test-folder branch.
6. **Validation.** The PR was iterated against multiple full CI runs;
the remaining failures on the latest run are pre-existing flakes
(segment-cache 60s `act` timeouts in turbopack-prod) or transient
infrastructure issues unrelated to the conversion.
711 lines
24 KiB
TypeScript
711 lines
24 KiB
TypeScript
import { nextTestSetup, isNextDev, isNextStart } from 'e2e-utils'
|
|
import { shouldUseTurbopack } from 'next-test-utils'
|
|
import { retry } from 'next-test-utils'
|
|
|
|
const TELEMETRY_EVENT_NAME = 'NEXT_EDGE_ALLOW_DYNAMIC_USED'
|
|
const LIB_PATH = 'node_modules/lib/index.js'
|
|
|
|
// Production-mode tests run a full `next build` followed by `next start`,
|
|
// which on webpack regularly takes 30-60s per test case (see the original
|
|
// `test/e2e/edge-runtime-configurable-guards` which also used
|
|
// `jest.setTimeout(1000 * 60 * 2)`). The default 60s-per-test timeout is
|
|
// too tight for webpack here; bump it so slower runs do not cascade into
|
|
// "server is running" errors on subsequent tests.
|
|
jest.setTimeout(120 * 1000)
|
|
|
|
describe('Edge runtime configurable guards', () => {
|
|
;(isNextDev ? describe : describe.skip)('development mode', () => {
|
|
const { next, isTurbopack, skipped } = nextTestSetup({
|
|
files: __dirname,
|
|
skipDeployment: true,
|
|
})
|
|
if (skipped) return
|
|
|
|
let originalApiRoute: string
|
|
let originalMiddleware: string
|
|
let originalLib: string
|
|
|
|
beforeAll(async () => {
|
|
originalApiRoute = await next.readFile('pages/api/route.js')
|
|
originalMiddleware = await next.readFile('middleware.js')
|
|
// Handle lib file which might not exist or be empty
|
|
try {
|
|
originalLib = await next.readFile(LIB_PATH)
|
|
} catch (e) {
|
|
// File doesn't exist, use default content
|
|
originalLib = '// populated by tests\n'
|
|
}
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await next.patchFile('pages/api/route.js', originalApiRoute)
|
|
await next.patchFile('middleware.js', originalMiddleware)
|
|
await next.patchFile(LIB_PATH, originalLib)
|
|
})
|
|
|
|
// Webpack treats `node_modules` as a "managed path" in its snapshot
|
|
// config (see packages/next/src/build/webpack-config.ts), meaning it
|
|
// assumes the contents of any file under `node_modules` are immutable
|
|
// per package version. When a test patches
|
|
// `node_modules/lib/index.js`, webpack's dev server keeps serving the
|
|
// originally-cached (empty) lib module, so imports like
|
|
// `import { hasDynamic } from 'lib'` resolve to an object without
|
|
// `hasDynamic`. Restarting the dev server forces a fresh read of
|
|
// `node_modules/lib/index.js`. Turbopack watches node_modules
|
|
// correctly and does not need this workaround.
|
|
async function restartForWebpackIfLibPatched(libPatched: boolean) {
|
|
if (!libPatched || isTurbopack) return
|
|
await next.stop()
|
|
await next.start()
|
|
}
|
|
|
|
describe('Multiple functions with different configurations', () => {
|
|
async function patchMultipleFunctions() {
|
|
await next.patchFile(
|
|
'middleware.js',
|
|
`
|
|
import { NextResponse } from 'next/server'
|
|
|
|
export default () => {
|
|
eval('100')
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '/middleware.js'
|
|
}
|
|
`
|
|
)
|
|
await next.patchFile(
|
|
'pages/api/route.js',
|
|
`
|
|
export default async function handler(request) {
|
|
eval('100')
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`
|
|
)
|
|
}
|
|
|
|
it('warns in dev for allowed code', async () => {
|
|
await patchMultipleFunctions()
|
|
const outputIndex = next.cliOutput.length
|
|
await retry(async () => {
|
|
const res = await next.fetch('/')
|
|
expect(res.status).toBe(200)
|
|
expect(next.cliOutput.slice(outputIndex)).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
})
|
|
})
|
|
|
|
it('warns in dev for unallowed code', async () => {
|
|
await patchMultipleFunctions()
|
|
const outputIndex = next.cliOutput.length
|
|
await retry(async () => {
|
|
const res = await next.fetch('/api/route')
|
|
expect(res.status).toBe(200)
|
|
expect(next.cliOutput.slice(outputIndex)).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
export default async function handler(request) {
|
|
eval('100')
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: null as string | null,
|
|
skip: false,
|
|
},
|
|
{
|
|
title: 'Middleware',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
|
|
export default () => {
|
|
eval('100')
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '**'
|
|
}
|
|
`,
|
|
libContent: null as string | null,
|
|
skip: false,
|
|
},
|
|
{
|
|
title: 'Edge API using lib',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
import { hasDynamic } from 'lib'
|
|
export default async function handler(request) {
|
|
await hasDynamic()
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
skip: false,
|
|
},
|
|
{
|
|
title: 'Middleware using lib',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { hasDynamic } from 'lib'
|
|
|
|
// populated with tests
|
|
export default async function () {
|
|
await hasDynamic()
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
// TODO: Re-enable when Turbopack applies the middleware dynamic code
|
|
// evaluation transforms also to code in node_modules.
|
|
skip: isTurbopack,
|
|
},
|
|
])(
|
|
'$title with allowed, used dynamic code',
|
|
({ url, apiContent, middlewareContent, libContent, skip }) => {
|
|
;(skip ? it.skip : it)('still warns in dev at runtime', async () => {
|
|
if (apiContent) await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
if (libContent) await next.patchFile(LIB_PATH, libContent)
|
|
await restartForWebpackIfLibPatched(libContent !== null)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await retry(async () => {
|
|
const res = await next.fetch(url)
|
|
|
|
expect(res.status).toBe(200)
|
|
|
|
expect(next.cliOutput.slice(outputIndex)).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
})
|
|
})
|
|
}
|
|
)
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API using lib',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
import { hasDynamic } from 'lib'
|
|
export default async function handler(request) {
|
|
await hasDynamic()
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '/pages/**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
// TODO: Re-enable when Turbopack applies the edge runtime transforms also
|
|
// to code in node_modules.
|
|
skip: isTurbopack,
|
|
},
|
|
{
|
|
title: 'Middleware using lib',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { hasDynamic } from 'lib'
|
|
export default async function () {
|
|
await hasDynamic()
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '/pages/**'
|
|
}
|
|
`,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
// TODO: Re-enable when Turbopack applies the middleware dynamic code
|
|
// evaluation transforms also to code in node_modules.
|
|
skip: isTurbopack,
|
|
},
|
|
])(
|
|
'$title with unallowed, used dynamic code',
|
|
({ url, apiContent, middlewareContent, libContent, skip }) => {
|
|
;(skip ? it.skip : it)('warns in dev at runtime', async () => {
|
|
if (apiContent) await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
if (libContent) await next.patchFile(LIB_PATH, libContent)
|
|
await restartForWebpackIfLibPatched(libContent !== null)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await retry(async () => {
|
|
const res = await next.fetch(url)
|
|
|
|
expect(res.status).toBe(200)
|
|
|
|
expect(next.cliOutput.slice(outputIndex)).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
})
|
|
})
|
|
}
|
|
)
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
export default async function handler(request) {
|
|
return Response.json({ result: (() => {}) instanceof Function })
|
|
}
|
|
export const config = { runtime: 'edge' }
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
},
|
|
{
|
|
title: 'Middleware',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { returnTrue } from 'lib'
|
|
export default async function () {
|
|
(() => {}) instanceof Function
|
|
return NextResponse.next()
|
|
}
|
|
`,
|
|
},
|
|
])(
|
|
'$title with use of Function as a type',
|
|
({ url, apiContent, middlewareContent }) => {
|
|
it('does not warn in dev at runtime', async () => {
|
|
if (apiContent) await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await retry(async () => {
|
|
const res = await next.fetch(url)
|
|
expect(res.status).toBe(200)
|
|
})
|
|
expect(next.cliOutput.slice(outputIndex)).not.toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
})
|
|
}
|
|
)
|
|
})
|
|
;(isNextStart ? describe : describe.skip)('production mode', () => {
|
|
const { next, isTurbopack, skipped } = nextTestSetup({
|
|
files: __dirname,
|
|
skipStart: true,
|
|
env: shouldUseTurbopack() ? {} : { NEXT_TELEMETRY_DEBUG: '1' },
|
|
skipDeployment: true,
|
|
})
|
|
if (skipped) return
|
|
|
|
let originalApiRoute: string
|
|
let originalMiddleware: string
|
|
let originalLib: string
|
|
|
|
beforeAll(async () => {
|
|
originalApiRoute = await next.readFile('pages/api/route.js')
|
|
originalMiddleware = await next.readFile('middleware.js')
|
|
// Handle lib file which might not exist or be empty
|
|
try {
|
|
originalLib = await next.readFile(LIB_PATH)
|
|
} catch (e) {
|
|
// File doesn't exist, use default content
|
|
originalLib = '// populated by tests\n'
|
|
}
|
|
})
|
|
|
|
afterEach(async () => {
|
|
// Production-mode tests that reach `next.start()` normally call
|
|
// `next.stop()` themselves, but if a test times out or throws before
|
|
// that we would otherwise leave the server running and every later
|
|
// `next.build()` call would fail with
|
|
// "can not run export while server is running". Stopping here is a
|
|
// no-op when the server is already stopped.
|
|
try {
|
|
await next.stop()
|
|
} catch {}
|
|
await next.patchFile('pages/api/route.js', originalApiRoute)
|
|
await next.patchFile('middleware.js', originalMiddleware)
|
|
await next.patchFile(LIB_PATH, originalLib)
|
|
// Webpack treats `node_modules` as a "managed path" in its snapshot
|
|
// config, so changes to `node_modules/lib/index.js` between test
|
|
// cases are not invalidated from webpack's persistent build cache
|
|
// at `.next/cache/webpack`. That causes later builds to re-use a
|
|
// stale compiled `lib` module from a previous test (e.g. still
|
|
// containing `hasUnusedDynamic`), which can trip the
|
|
// `unstable_allowDynamic` analyzer. Drop the entire `.next` dir
|
|
// between cases so each build reads `lib` fresh. Turbopack does
|
|
// not share this cache and does not need the workaround.
|
|
if (!isTurbopack) {
|
|
await next.deleteFile('.next')
|
|
}
|
|
})
|
|
|
|
// eslint-disable-next-line jest/no-identical-title
|
|
describe('Multiple functions with different configurations', () => {
|
|
it('fails to build because of unallowed code', async () => {
|
|
await next.patchFile(
|
|
'middleware.js',
|
|
`
|
|
import { NextResponse } from 'next/server'
|
|
|
|
export default () => {
|
|
eval('100')
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '/middleware.js'
|
|
}
|
|
`
|
|
)
|
|
await next.patchFile(
|
|
'pages/api/route.js',
|
|
`
|
|
export default async function handler(request) {
|
|
eval('100')
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`
|
|
)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
const { exitCode } = await next.build()
|
|
const buildOutput = next.cliOutput.slice(outputIndex)
|
|
|
|
expect(exitCode).toBe(1)
|
|
if (!isTurbopack) {
|
|
expect(buildOutput).toContain(`./pages/api/route.js`)
|
|
}
|
|
expect(buildOutput).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime`
|
|
)
|
|
if (!isTurbopack) {
|
|
expect(buildOutput).toContain(`Used by default`)
|
|
expect(buildOutput).toContain(TELEMETRY_EVENT_NAME)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
export default async function handler(request) {
|
|
if ((() => false)()) {
|
|
eval('100')
|
|
}
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: null as string | null,
|
|
},
|
|
{
|
|
title: 'Middleware',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
// populated with tests
|
|
export default () => {
|
|
if ((() => false)()) {
|
|
eval('100')
|
|
}
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '**'
|
|
}
|
|
`,
|
|
libContent: null as string | null,
|
|
},
|
|
{
|
|
title: 'Edge API using lib',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
import { hasUnusedDynamic } from 'lib'
|
|
export default async function handler(request) {
|
|
await hasUnusedDynamic()
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: `
|
|
export async function hasUnusedDynamic() {
|
|
if ((() => false)()) {
|
|
eval('100')
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
title: 'Middleware using lib',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { hasUnusedDynamic } from 'lib'
|
|
// populated with tests
|
|
export default async function () {
|
|
await hasUnusedDynamic()
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '**/node_modules/lib/**'
|
|
}
|
|
`,
|
|
libContent: `
|
|
export async function hasUnusedDynamic() {
|
|
if ((() => false)()) {
|
|
eval('100')
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
])(
|
|
'$title with allowed, unused dynamic code',
|
|
({ url, apiContent, middlewareContent, libContent }) => {
|
|
// unstable_allowDynamic configuration is not supported in Turbopack.
|
|
;(isTurbopack ? it.skip : it)(
|
|
'build and does not warn at runtime',
|
|
async () => {
|
|
if (apiContent)
|
|
await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
if (libContent) await next.patchFile(LIB_PATH, libContent)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await next.build()
|
|
const buildOutput = next.cliOutput.slice(outputIndex)
|
|
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(buildOutput).not.toContain(`Build failed`)
|
|
if (!isTurbopack) {
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(buildOutput).toContain(TELEMETRY_EVENT_NAME)
|
|
}
|
|
|
|
await next.start()
|
|
const startIndex = next.cliOutput.length
|
|
|
|
const res = await next.fetch(url)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(res.status).toBe(200)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(next.cliOutput.slice(startIndex)).not.toContain(`warn`)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(next.cliOutput.slice(startIndex)).not.toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
await next.stop()
|
|
}
|
|
)
|
|
}
|
|
)
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API using lib',
|
|
apiContent: `
|
|
import { hasDynamic } from 'lib'
|
|
export default async function handler(request) {
|
|
await hasDynamic()
|
|
return Response.json({ result: true })
|
|
}
|
|
export const config = {
|
|
runtime: 'edge',
|
|
unstable_allowDynamic: '/pages/**'
|
|
}
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
// TODO: Re-enable when Turbopack applies the edge runtime transforms also
|
|
// to code in node_modules.
|
|
skip: isTurbopack,
|
|
},
|
|
{
|
|
title: 'Middleware using lib',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { hasDynamic } from 'lib'
|
|
export default async function () {
|
|
await hasDynamic()
|
|
return NextResponse.next()
|
|
}
|
|
export const config = {
|
|
unstable_allowDynamic: '/pages/**'
|
|
}
|
|
`,
|
|
libContent: `
|
|
export async function hasDynamic() {
|
|
eval('100')
|
|
}
|
|
`,
|
|
// TODO: Re-enable when Turbopack applies the middleware dynamic code
|
|
// evaluation transforms also to code in node_modules.
|
|
skip: isTurbopack,
|
|
},
|
|
])(
|
|
'$title with unallowed, used dynamic code',
|
|
({ apiContent, middlewareContent, libContent, skip }) => {
|
|
;(skip ? it.skip : it)(
|
|
'fails to build because of dynamic code evaluation',
|
|
async () => {
|
|
if (apiContent)
|
|
await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
if (libContent) await next.patchFile(LIB_PATH, libContent)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await next.build()
|
|
const buildOutput = next.cliOutput.slice(outputIndex)
|
|
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(buildOutput).toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime`
|
|
)
|
|
if (!isTurbopack) {
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(buildOutput).toContain(TELEMETRY_EVENT_NAME)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
)
|
|
|
|
describe.each([
|
|
{
|
|
title: 'Edge API',
|
|
url: '/api/route',
|
|
apiContent: `
|
|
export default async function handler(request) {
|
|
return Response.json({ result: (() => {}) instanceof Function })
|
|
}
|
|
export const config = { runtime: 'edge' }
|
|
`,
|
|
middlewareContent: null as string | null,
|
|
},
|
|
{
|
|
title: 'Middleware',
|
|
url: '/',
|
|
apiContent: null as string | null,
|
|
middlewareContent: `
|
|
import { NextResponse } from 'next/server'
|
|
import { returnTrue } from 'lib'
|
|
export default async function () {
|
|
(() => {}) instanceof Function
|
|
return NextResponse.next()
|
|
}
|
|
`,
|
|
},
|
|
])(
|
|
'$title with use of Function as a type',
|
|
({ url, apiContent, middlewareContent }) => {
|
|
// unstable_allowDynamic configuration is not supported in Turbopack.
|
|
;(isTurbopack ? it.skip : it)(
|
|
'build and does not warn at runtime',
|
|
async () => {
|
|
if (apiContent)
|
|
await next.patchFile('pages/api/route.js', apiContent)
|
|
if (middlewareContent)
|
|
await next.patchFile('middleware.js', middlewareContent)
|
|
|
|
const outputIndex = next.cliOutput.length
|
|
await next.build()
|
|
const buildOutput = next.cliOutput.slice(outputIndex)
|
|
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(buildOutput).not.toContain(`Build failed`)
|
|
|
|
await next.start()
|
|
const startIndex = next.cliOutput.length
|
|
|
|
const res = await next.fetch(url)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(res.status).toBe(200)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(next.cliOutput.slice(startIndex)).not.toContain(`warn`)
|
|
// eslint-disable-next-line jest/no-standalone-expect
|
|
expect(next.cliOutput.slice(startIndex)).not.toContain(
|
|
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
|
|
)
|
|
await next.stop()
|
|
}
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|