Files
vercel__next.js/test/development/acceptance/ReactRefreshLogBox.test.ts
Jiwon Choi d7aa66c345 Remove generated error codes (#97687)
### Why?

Should come up with better solution that does not block PRs with git
conflict

x-ref:
https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH

### How?

- Delete `errors.json`, the error-code SWC plugin, generated WASM, merge
driver, and validation/build tooling.
- Stop attaching error codes to server-rendering digests, redboxes, and
telemetry; native `Error.code` and `Error.name` remain available where
applicable.
- Remove the development-overlay error feedback UI, middleware, and
telemetry event that depended on stable codes.
- Update fixtures, snapshots, and guidance for code-free errors and
numeric-only digests.

<!-- NEXT_JS_LLM -->
2026-08-21 22:45:12 +02:00

1419 lines
38 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.
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, isReact18, nextTestSetup } from 'e2e-utils'
import {
getRedboxTotalErrorCount,
getRedboxCallStack,
retry,
toggleCollapseCallStackFrames,
} from 'next-test-utils'
import path from 'path'
import { outdent } from 'outdent'
describe('ReactRefreshLogBox', () => {
const { isTurbopack, next, isRspack } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
test('should strip whitespace correctly with newline', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
export default function Page() {
return (
<>
<p>index page</p>
<a onClick={() => {
throw new Error('idk')
}}>
click me
</a>
</>
)
}
`
)
await browser.elementByCss('a').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "idk",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (8:27) @ onClick
> 8 | throw new Error('idk')
| ^",
"stack": [
"onClick index.js (8:27)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "idk",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (8:27) @ onClick
> 8 | throw new Error('idk')
| ^",
"stack": [
"onClick index.js (8:27)",
],
}
`)
}
})
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807
test('module init error not shown', async () => {
// Start here:
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
// We start here.
await session.patch(
'index.js',
outdent`
import * as React from 'react';
class ClassDefault extends React.Component {
render() {
return <h1>Default Export</h1>;
}
}
export default ClassDefault;
`
)
expect(
await session.evaluate(() => document.querySelector('h1').textContent)
).toBe('Default Export')
// Add a throw in module init phase:
await session.patch(
'index.js',
outdent`
// top offset for snapshot
import * as React from 'react';
throw new Error('no')
class ClassDefault extends React.Component {
render() {
return <h1>Default Export</h1>;
}
}
export default ClassDefault;
`
)
if (isReact18) {
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ module evaluation
> 3 | throw new Error('no')
| ^",
"stack": [
"module evaluation index.js (3:7)",
"module evaluation pages/index.js (1:1)",
"module evaluation index.js (9:16)",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ eval
> 3 | throw new Error('no')
| ^",
"stack": [
"eval index.js (3:7)",
"<FIXME-next-dist-dir>",
"eval ./pages/index.js",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
}
} else {
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ module evaluation
> 3 | throw new Error('no')
| ^",
"stack": [
"module evaluation index.js (3:7)",
"module evaluation pages/index.js (1:1)",
"module evaluation index.js (9:16)",
"<FIXME-next-dist-dir>",
],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ eval
> 3 | throw new Error('no')
| ^",
"stack": [
"eval index.js (3:7)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"eval ./pages/index.js",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ eval
> 3 | throw new Error('no')
| ^",
"stack": [
"eval index.js (3:7)",
"<FIXME-next-dist-dir>",
"eval ./pages/index.js",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
}
}
})
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127
it('boundaries', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.write(
'FunctionDefault.js',
outdent`
export default function FunctionDefault() {
return <h2>hello</h2>
}
`
)
await session.patch(
'index.js',
outdent`
import FunctionDefault from './FunctionDefault.js'
import * as React from 'react'
class ErrorBoundary extends React.Component {
constructor() {
super()
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return {
hasError: true,
error
};
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallback={<h2>error</h2>}>
<FunctionDefault />
</ErrorBoundary>
);
}
export default App;
`
)
expect(
await session.evaluate(() => document.querySelector('h2').textContent)
).toBe('hello')
await session.write(
'FunctionDefault.js',
`export default function FunctionDefault() { throw new Error('no'); }`
)
if (isReact18 && isTurbopack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
],
},
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
],
},
]
`)
} else if (isReact18 && isRspack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
],
},
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
],
},
]
`)
} else {
if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "FunctionDefault.js (1:51) @ FunctionDefault
> 1 | export default function FunctionDefault() { throw new Error('no'); }
| ^",
"stack": [
"FunctionDefault FunctionDefault.js (1:51)",
],
}
`)
}
}
})
// TODO: investigate why this fails when running outside of the Next.js
// monorepo e.g. fails when using pnpm create next-app
// https://github.com/vercel/next.js/pull/23203
test.skip('internal package errors', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
// Make a react build-time error.
await session.patch(
'index.js',
outdent`
export default function FunctionNamed() {
return <div>{{}}</div>
}`
)
if (isReact18) {
await expect(browser).toDisplayRedbox()
} else {
await expect(browser).toDisplayRedbox()
}
})
test('unterminated JSX', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
export default () => {
return (
<div>
<p>lol</p>
</div>
)
}
`
)
await session.waitForNoRedbox()
await session.patch(
'index.js',
outdent`
export default () => {
return (
<div>
<p>lol</p>
div
)
}
`
)
if (process.env.IS_TURBOPACK_TEST) {
await expect(browser).toDisplayRedbox(`
{
"description": "Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (7:1)
Error: Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?
> 7 | }
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?
│ ,-[7:1]
│ 4 | <p>lol</p>
│ 5 | div
│ 6 | )
│ 7 | }
│ : ^
\`----
│ x Expected '</', got '<eof>'
│ ,-[7:1]
│ 4 | <p>lol</p>
│ 5 | div
│ 6 | )
│ 7 | }
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Unexpected token. Did you mean \`{'}'}\` or \`&rbrace;\`?
,-[7:1]
4 | <p>lol</p>
5 | div
6 | )
7 | }
: ^
\`----
x Expected '</', got '<eof>'
,-[7:1]
4 | <p>lol</p>
5 | div
6 | )
7 | }
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
})
it('conversion to class component (1)', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.write(
'Child.js',
outdent`
export default function ClickCount() {
return <p>hello</p>
}
`
)
await session.patch(
'index.js',
outdent`
import Child from './Child';
export default function Home() {
return (
<div>
<Child />
</div>
)
}
`
)
await session.waitForNoRedbox()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('hello')
await session.patch(
'Child.js',
outdent`
import { Component } from 'react';
export default class ClickCount extends Component {
render() {
throw new Error()
}
}
`
)
if (isReact18 && isTurbopack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
],
},
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
],
},
]
`)
} else if (isReact18 && isRspack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
],
},
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
],
},
]
`)
} else {
if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "",
"environmentLabel": null,
"label": "Runtime Error",
"source": "Child.js (4:11) @ ClickCount.render
> 4 | throw new Error()
| ^",
"stack": [
"ClickCount.render Child.js (4:11)",
],
}
`)
}
}
await session.patch(
'Child.js',
outdent`
import { Component } from 'react';
export default class ClickCount extends Component {
render() {
return <p>hello new</p>
}
}
`
)
await session.waitForNoRedbox()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('hello new')
})
test('css syntax errors', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.write('index.module.css', `.button {}`)
await session.patch(
'index.js',
outdent`
import './index.module.css';
export default () => {
return (
<div>
<p>lol</p>
</div>
)
}
`
)
await session.waitForNoRedbox()
// Syntax error
await session.patch('index.module.css', `.button`)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Parsing CSS source code failed",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css (1:8)
Error: Parsing CSS source code failed
> 1 | .button
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × SyntaxError",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css
╰─▶ × SyntaxError
│ (1:1) <FIXME-project-root>/index.module.css Unknown word .button
│ > 1 | .button
│ | ^
Import trace for requested module:
./index.module.css
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": "Syntax error: <FIXME-project-root>/index.module.css Unknown word .button",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css (1:1)
Syntax error: <FIXME-project-root>/index.module.css Unknown word .button
> 1 | .button
| ^",
"stack": [],
}
`)
}
// Checks for selectors that can't be prefixed.
// Selector "button" is not pure (pure selectors must contain at least one local class or id)
await session.patch('index.module.css', `button {}`)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Transforming CSS failed",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css
Error: Transforming CSS failed
Selector "button" is not pure. Pure selectors must contain at least one local class or id.
Import traces:
Browser:
./index.module.css
./index.js
./pages/index.js
SSR:
./index.module.css
./index.js
./pages/index.js",
"stack": [],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": " ╰─▶ × CssSyntaxError",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css
╰─▶ × CssSyntaxError
│ (1:1) Selector "button" is not pure (pure selectors must contain at least one local class or id)
│ > 1 | button {}
│ | ^
Import trace for requested module:
./index.module.css
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Syntax error: Selector "button" is not pure (pure selectors must contain at least one local class or id)",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.module.css (1:1)
Syntax error: Selector "button" is not pure (pure selectors must contain at least one local class or id)
> 1 | button {}
| ^",
"stack": [],
}
`)
}
})
test('logbox: anchors links in error messages', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
import { useCallback } from 'react'
export default function Index() {
const boom = useCallback(() => {
throw new Error('end https://nextjs.org')
}, [])
return (
<main>
<button onClick={boom}>Boom!</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
await browser.elementByCss('button').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "end https://nextjs.org",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('end https://nextjs.org')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "end https://nextjs.org",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('end https://nextjs.org')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
}
await session.patch(
'index.js',
outdent`
import { useCallback } from 'react'
export default function Index() {
const boom = useCallback(() => {
throw new Error('https://nextjs.org start')
}, [])
return (
<main>
<button onClick={boom}>Boom!</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
await browser.elementByCss('button').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "https://nextjs.org start",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('https://nextjs.org start')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "https://nextjs.org start",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('https://nextjs.org start')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
}
await session.patch(
'index.js',
outdent`
import { useCallback } from 'react'
export default function Index() {
const boom = useCallback(() => {
throw new Error('middle https://nextjs.org end')
}, [])
return (
<main>
<button onClick={boom}>Boom!</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
await browser.elementByCss('button').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "middle https://nextjs.org end",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('middle https://nextjs.org end')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "middle https://nextjs.org end",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('middle https://nextjs.org end')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
}
await session.patch(
'index.js',
outdent`
import { useCallback } from 'react'
export default function Index() {
const boom = useCallback(() => {
throw new Error('multiple https://nextjs.org links http://example.com')
}, [])
return (
<main>
<button onClick={boom}>Boom!</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
await browser.elementByCss('button').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "multiple https://nextjs.org links http://example.com",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('multiple https://nextjs.org links http://example.com')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "multiple https://nextjs.org links http://example.com",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('multiple https://nextjs.org links http://example.com')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
}
await session.patch(
'index.js',
outdent`
import { useCallback } from 'react'
export default function Index() {
const boom = useCallback(() => {
throw new Error('multiple https://nextjs.org links (http://example.com)')
}, [])
return (
<main>
<button onClick={boom}>Boom!</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
await browser.elementByCss('button').click()
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "multiple https://nextjs.org links (http://example.com)",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('multiple https://nextjs.org links (http://example.com)')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "multiple https://nextjs.org links (http://example.com)",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ Index.useCallback[boom]
> 5 | throw new Error('multiple https://nextjs.org links (http://example.com)')
| ^",
"stack": [
"Index.useCallback[boom] index.js (5:11)",
],
}
`)
}
})
test('non-Error errors are handled properly', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
export default () => {
throw {'a': 1, 'b': 'x'};
return (
<div>hello</div>
)
}
`
)
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "{"a":1,"b":"x"}",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "{"a":1,"b":"x"}",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
}
// fix previous error
await session.patch(
'index.js',
outdent`
export default () => {
return (
<div>hello</div>
)
}
`
)
await session.waitForNoRedbox()
await session.patch(
'index.js',
outdent`
class Hello {}
export default () => {
throw Hello
return (
<div>hello</div>
)
}
`
)
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "class Hello {
}",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "class Hello {
}",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
}
// fix previous error
await session.patch(
'index.js',
outdent`
export default () => {
return (
<div>hello</div>
)
}
`
)
await session.waitForNoRedbox()
await session.patch(
'index.js',
outdent`
export default () => {
throw "string error"
return (
<div>hello</div>
)
}
`
)
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "string error",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "string error",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
}
// fix previous error
await session.patch(
'index.js',
outdent`
export default () => {
return (
<div>hello</div>
)
}
`
)
await session.waitForNoRedbox()
await session.patch(
'index.js',
outdent`
export default () => {
throw null
return (
<div>hello</div>
)
}
`
)
if (isReact18) {
await expect(browser).toDisplayRedbox(`
{
"description": "A null error was thrown, see here for more info: https://nextjs.org/docs/messages/threw-undefined",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "A null error was thrown, see here for more info: https://nextjs.org/docs/messages/threw-undefined",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
}
})
it('Call stack count is correct for pages error', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/index.js',
outdent`
export default function Page() {
if (typeof window !== 'undefined') {
throw new Error('Client error')
}
return null
}
`,
],
])
)
const { browser } = sandbox
if (isReact18) {
// Wait for the error to reach the correct count
await retry(async () => {
expect(await getRedboxTotalErrorCount(browser)).toBe(3)
})
await expect(browser).toDisplayRedbox(`
[
{
"description": "Client error",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (3:11) @ Page
> 3 | throw new Error('Client error')
| ^",
"stack": [
"Page pages/index.js (3:11)",
],
},
{
"description": "Client error",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (3:11) @ Page
> 3 | throw new Error('Client error')
| ^",
"stack": [
"Page pages/index.js (3:11)",
],
},
{
"description": "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.",
"environmentLabel": null,
"label": "Recoverable Error",
"source": null,
"stack": [],
},
]
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Client error",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (3:11) @ Page
> 3 | throw new Error('Client error')
| ^",
"stack": [
"Page pages/index.js (3:11)",
],
}
`)
}
await toggleCollapseCallStackFrames(browser)
// Expect more than the default amount of frames
// The default stackTraceLimit results in max 9 [data-nextjs-call-stack-frame] elements
const callStackFrames = await browser.elementsByCss(
'[data-nextjs-call-stack-frame]'
)
expect(callStackFrames.length).toBeGreaterThan(9)
})
// TODO: hide the anonymous frames between 2 ignored frames
test('should show anonymous frames from stack trace', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/index.js',
outdent`
export default function Page() {
[1, 2, 3].map(() => {
throw new Error("anonymous error!");
})
}`,
],
])
)
const { browser } = sandbox
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "anonymous error!",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (3:11) @ <unknown>
> 3 | throw new Error("anonymous error!");
| ^",
"stack": [
"<unknown> pages/index.js (3:11)",
"Array.map <anonymous>",
"Page pages/index.js (2:13)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "anonymous error!",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (3:11) @ eval
> 3 | throw new Error("anonymous error!");
| ^",
"stack": [
"eval pages/index.js (3:11)",
"Array.map <anonymous>",
"Page pages/index.js (2:13)",
],
}
`)
}
})
test('should collapse nodejs internal stack frames from stack trace by default', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/index.js',
outdent`
export default function Page() {}
function createURL() {
new URL("/", "invalid")
}
export function getServerSideProps() {
createURL()
return { props: {} }
}`,
],
])
)
const { browser } = sandbox
await expect(browser).toDisplayRedbox(`
{
"description": "Invalid URL",
"environmentLabel": null,
"label": "Runtime TypeError",
"source": "pages/index.js (4:3) @ createURL
> 4 | new URL("/", "invalid")
| ^",
"stack": [
"createURL pages/index.js (4:3)",
"getServerSideProps pages/index.js (8:3)",
],
}
`)
await toggleCollapseCallStackFrames(browser)
const stackExpanded = await getRedboxCallStack(browser)
expect(stackExpanded).toEqual(
expect.arrayContaining([
expect.stringContaining(
// ignore exact location.
// If this breaks, choose a different error that contains Node.js internals in its stack.
'new URL node:internal/url ('
),
])
)
})
})