Files
vercel__next.js/test/development/acceptance-app/rsc-build-errors.test.ts
Benjamin Woodruff a87692e17c [ci] Split up large rsc-build-errors development test (#95624)
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
2026-07-08 19:56:27 -07:00

288 lines
9.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createSandbox } from 'development-sandbox'
import { outdent } from 'outdent'
import { runRscBuildErrorsTests } from './rsc-build-errors.util'
runRscBuildErrorsTests(({ next }) => {
it('should throw an error when getServerSideProps is used', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/client-with-errors/get-server-side-props'
)
const { session } = sandbox
const pageFile = 'app/client-with-errors/get-server-side-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getServerSideProps',
'export function getServerSideProps'
)
await session.patch(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'"getServerSideProps" is not supported in app/'
)
})
it('should throw an error when metadata export is used in client components', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/client-with-errors/metadata-export'
)
const { session } = sandbox
const pageFile = 'app/client-with-errors/metadata-export/page.js'
const content = await next.readFile(pageFile)
// Add `metadata` error
let uncomment = content.replace(
'// export const metadata',
'export const metadata'
)
await session.patch(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'You are attempting to export "metadata" from a component marked with "use client", which is disallowed.'
)
// Restore file
await session.patch(pageFile, content)
await session.waitForNoRedbox()
// Add `generateMetadata` error
uncomment = content.replace(
'// export async function generateMetadata',
'export async function generateMetadata'
)
await session.patch(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'You are attempting to export "generateMetadata" from a component marked with "use client", which is disallowed.'
)
// Fix the error again to test error overlay works with hmr rebuild
await session.patch(pageFile, content)
await session.waitForNoRedbox()
})
it('should throw an error when metadata exports are used together in server components', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/server-with-errors/metadata-export'
)
const { session } = sandbox
const pageFile = 'app/server-with-errors/metadata-export/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export async function generateMetadata',
'export async function generateMetadata'
)
await session.patch(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'"metadata" and "generateMetadata" cannot be exported at the same time, please keep one of them.'
)
})
// TODO: investigate flakey test case
it.skip('should throw an error when getStaticProps is used', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/client-with-errors/get-static-props'
)
const { session } = sandbox
const pageFile = 'app/client-with-errors/get-static-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getStaticProps',
'export function getStaticProps'
)
await session.patch(pageFile, uncomment)
await next.patchFile(pageFile, content)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'"getStaticProps" is not supported in app/'
)
})
it('should throw an error when "use client" is on the top level but after other expressions', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/swc/use-client'
)
const { session } = sandbox
const pageFile = 'app/swc/use-client/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace("// 'use client'", "'use client'")
await next.patchFile(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
'directive must be placed before other expressions'
)
})
it('should throw an error when "Component" is imported in server components', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/server-with-errors/class-component'
)
const { session } = sandbox
const pageFile = 'app/server-with-errors/class-component/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
"// import { Component } from 'react'",
"import { Component } from 'react'"
)
await session.patch(pageFile, uncomment)
await session.waitForRedbox()
expect(await session.getRedboxSource()).toInclude(
`You’re importing a class component. It only works in a Client Component`
)
})
it('should error for invalid undefined module retuning from next dynamic', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/client-with-errors/dynamic'
)
const { session } = sandbox
const file = 'app/client-with-errors/dynamic/page.js'
const content = await next.readFile(file)
await session.patch(
file,
content.replace('() => <p>hello dynamic world</p>', 'undefined')
)
await session.waitForRedbox()
expect(await session.getRedboxDescription()).toInclude(
`Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function.`
)
})
it('should throw an error when error file is a server component', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/server-with-errors/error-file'
)
const { session } = sandbox
// Remove "use client"
await session.patch(
'app/server-with-errors/error-file/error.js',
'export default function Error() {}'
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/must be a Client \n| Component/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./app/server-with-errors/error-file/error.js (1:1)
Error: app/server-with-errors/error-file/error.js must be a Client Component. Add the "use client" directive the top of the file to resolve this issue.
Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
> 1 | export default function Error() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ecmascript file had an error"
`)
} else {
await expect(session.getRedboxSource()).resolves.toMatch(
/Add the "use client"/
)
// TODO: investigate flakey snapshot due to spacing below
// expect(next.normalizeTestDirContent(await session.getRedboxSource()))
// .toMatchInlineSnapshot(`
// "./app/server-with-errors/error-file/error.js
// Error: x TEST_DIR/app/server-with-errors/error-file/error.js must be a Client
// | Component. Add the "use client" directive the top of the file to resolve this issue.
// | Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
// |
// |
// ,----
// 1 | export default function Error() {}
// : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// \`----
// Import trace for requested module:
// ./app/server-with-errors/error-file/error.js"
// `)
}
})
it('should throw an error when error file is a server component with empty error file', async () => {
await using sandbox = await createSandbox(
next,
undefined,
'/server-with-errors/error-file'
)
const { session } = sandbox
// Empty file
await session.patch('app/server-with-errors/error-file/error.js', '')
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/Add the "use client"/
)
// TODO: investigate flakey snapshot due to spacing below
// expect(next.normalizeTestDirContent(await session.getRedboxSource()))
// .toMatchInlineSnapshot(n`
// "./app/server-with-errors/error-file/error.js
// ReactServerComponentsError:
// ./app/server-with-errors/error-file/error.js must be a Client Component. Add the "use client" directive the top of the file to resolve this issue.
// ,-[TEST_DIR/app/server-with-errors/error-file/error.js:1:1]
// 1 |
// : ^
// \`----
// Import path:
// ./app/server-with-errors/error-file/error.js"
// `)
})
it('should freeze parent resolved metadata to avoid mutating in generateMetadata', async () => {
const pagePath = 'app/metadata/mutate/page.js'
const content = outdent`
export default function page(props) {
return <p>mutate</p>
}
export async function generateMetadata(props, parent) {
const parentMetadata = await parent
parentMetadata.x = 1
return {
...parentMetadata,
}
}
`
await using sandbox = await createSandbox(
next,
undefined,
'/metadata/mutate'
)
const { session } = sandbox
await session.patch(pagePath, content)
await session.waitForRedbox()
expect(await session.getRedboxDescription()).toContain(
'Cannot add property x, object is not extensible'
)
})
})