Files
vercel__next.js/test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts
Tobias Koppers c0e749f609 test: stabilize poisoned proxy error overlay (#97609)
### What?

Stabilizes the RSC poisoned-import error-overlay test for `proxy.js`
without weakening or retrying its redbox assertion.

### Why?

An initially broken proxy can emit the expected build error and then
force a startup full reload. That reload can clear the overlay before
the test begins observing it, causing an intermittent `Expected Redbox
but found no visible one` failure even though validation worked.

### How?

The proxy parameter now starts from a valid module and introduces the
poisoned import only after the sandbox page has hydrated. This moves the
assertion onto the live HMR path and removes the startup-reload race.
Middleware and instrumentation retain their initial-compile setup
because their error overlays are reliable there and instrumentation does
not reliably surface this edit through Turbopack HMR.

### Verification

- `pnpm test-dev-webpack
test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts`
- `pnpm test-dev-turbo
test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts`
- Proxy case repeated 5 times with Webpack and 3 times with Turbopack
- Prettier, ESLint, and `git diff --check`

<!-- NEXT_JS_LLM -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-08-21 14:53:12 +02:00

153 lines
5.7 KiB
TypeScript

import { createSandbox } from 'development-sandbox'
import { outdent } from 'outdent'
import { runRscBuildErrorsTests } from './rsc-build-errors.util'
runRscBuildErrorsTests(({ next, isTurbopack }) => {
it('should allow to use and handle rsc poisoning client-only', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/server-with-errors/client-only-in-server'
)
const { session } = sandbox
const file =
'app/server-with-errors/client-only-in-server/client-only-lib.js'
const content = await next.readFile(file)
const uncomment = content.replace(
"// import 'client-only'",
"import 'client-only'"
)
await next.patchFile(file, uncomment)
await session.waitForRedbox()
if (isTurbopack) {
// TODO: fix the issue ordering.
// turbopack emits the resolve issue first instead of the transform issue.
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"./app/server-with-errors/client-only-in-server/client-only-lib.js (1:1)
Error: You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering
> 1 | import 'client-only'
| ^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function ClientOnlyLib() {
4 | return 'client-only-lib'
Ecmascript file had an error
Import trace:
Server Component:
./app/server-with-errors/client-only-in-server/client-only-lib.js
./app/server-with-errors/client-only-in-server/page.js"
`)
} else {
expect(await session.getRedboxSource()).toInclude(
`You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
)
}
})
it('should allow to use and handle rsc poisoning server-only', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/client-with-errors/server-only-in-client'
)
const { session } = sandbox
const file =
'app/client-with-errors/server-only-in-client/server-only-lib.js'
const content = await next.readFile(file)
const uncomment = content.replace(
"// import 'server-only'",
"import 'server-only'"
)
await session.patch(file, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
`You're importing a module that depends on "server-only" into a React Client Component module. This API is only available in Server Components but one of its parents is marked with "use client", so this module is also a Client Component.`
)
})
it('should error when catchError from next/error is used in a server component', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'app/page.js',
outdent`
import { catchError } from 'next/error'
export default function Page() {
return 'Hello world'
}
`,
],
])
)
const { session } = sandbox
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.'
)
})
// In Webpack, proxy runs in the Node.js server compiler, which is also
// invalidated when the app route is added on demand. If the initial proxy
// error reaches the browser before that follow-up build, the HMR client sees
// an update after a runtime error and reloads, clearing the overlay. Defer
// that case until after hydration. Turbopack does not have this compiler
// invalidation race, so its proxy case covers both initial and HMR errors.
test.each([
['middleware.js', 'export function middleware() {}'],
['proxy.js', 'export function proxy() {}'],
['instrumentation.js', 'export function register() {}'],
])(
'should error when catchError from next/error is imported in %s',
async (entryFile, exportCode) => {
const isProxy = entryFile === 'proxy.js'
const deferPoisonedImport = !isTurbopack && isProxy
const entryContent = outdent`
import { catchError } from 'next/error'
${exportCode}
`
await using sandbox = await createSandbox(
next,
new Map([
[
'app/page.js',
outdent`
export default function Page() {
return 'Hello world'
}
`,
],
[entryFile, deferPoisonedImport ? exportCode : entryContent],
])
)
const { session } = sandbox
if (deferPoisonedImport) {
await session.write(entryFile, entryContent)
}
const expectPoisonedImportRedbox = async () => {
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.'
)
}
await expectPoisonedImportRedbox()
if (isTurbopack && isProxy) {
await session.patch(entryFile, exportCode)
await session.waitForNoRedbox()
await session.write(entryFile, entryContent)
await expectPoisonedImportRedbox()
}
}
)
})