Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
1802 lines
48 KiB
TypeScript
1802 lines
48 KiB
TypeScript
/* eslint-env jest */
|
||
import { createSandbox } from 'development-sandbox'
|
||
import { FileRef, nextTestSetup } from 'e2e-utils'
|
||
import { getToastErrorCount, retry } from 'next-test-utils'
|
||
import path from 'path'
|
||
import { outdent } from 'outdent'
|
||
|
||
describe('ReactRefreshLogBox app', () => {
|
||
const { next, isTurbopack, isRspack } = nextTestSetup({
|
||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||
skipStart: true,
|
||
patchFileDelay: 1000,
|
||
})
|
||
|
||
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 (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(
|
||
`"Expected Redbox but found no visible one."`
|
||
)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(
|
||
`"Expected Redbox but found no visible one."`
|
||
)
|
||
}
|
||
})
|
||
|
||
// 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 (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 app/page.js (2:1)",
|
||
],
|
||
}
|
||
`)
|
||
} 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>",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/page.js",
|
||
"<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 ./app/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
},
|
||
{
|
||
"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 ./app/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
}
|
||
})
|
||
|
||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127
|
||
test('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 (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(
|
||
`"Expected Redbox but found no visible one."`
|
||
)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(
|
||
`"Expected Redbox but found no visible one."`
|
||
)
|
||
}
|
||
})
|
||
|
||
// 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>
|
||
}
|
||
`
|
||
)
|
||
|
||
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 (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Unexpected token. Did you mean \`{'}'}\` or \`}\`?",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./index.js (7:1)
|
||
Unexpected token. Did you mean \`{'}'}\` or \`}\`?
|
||
> 7 | }
|
||
| ^",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"description": " ╰─▶ × Error: x Unexpected token. Did you mean \`{'}'}\` or \`}\`?",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./index.js
|
||
╰─▶ × Error: x Unexpected token. Did you mean \`{'}'}\` or \`}\`?
|
||
│ ,-[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
|
||
./app/page.js",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": " x Unexpected token. Did you mean \`{'}'}\` or \`}\`?",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./index.js
|
||
Error: x Unexpected token. Did you mean \`{'}'}\` or \`}\`?
|
||
,-[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
|
||
./app/page.js",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
// Module trace is only available with webpack 5
|
||
test('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 (isTurbopack) {
|
||
// TODO(veil): Possibly https://linear.app/vercel/issue/NEXT-4411
|
||
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)",
|
||
"Home index.js (6:7)",
|
||
"<FIXME-file-protocol>",
|
||
],
|
||
}
|
||
`)
|
||
} 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)",
|
||
"Home index.js (6:7)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
|
||
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()
|
||
|
||
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)
|
||
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
|
||
│
|
||
│ > 1 | .button
|
||
│ | ^
|
||
│
|
||
Import trace for requested module:
|
||
./index.module.css
|
||
./index.js
|
||
./app/page.js",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"description": "Syntax error: <FIXME-project-root>/index.module.css Unknown word",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./index.module.css (1:1)
|
||
Syntax error: <FIXME-project-root>/index.module.css Unknown word
|
||
> 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
|
||
Transforming CSS failed
|
||
Selector "button" is not pure. Pure selectors must contain at least one local class or id.
|
||
Import traces:
|
||
Client Component Browser:
|
||
./index.module.css [Client Component Browser]
|
||
./index.js [Client Component Browser]
|
||
./app/page.js [Client Component Browser]
|
||
./app/page.js [Server Component]
|
||
Client Component SSR:
|
||
./index.module.css [Client Component SSR]
|
||
./index.js [Client Component SSR]
|
||
./app/page.js [Client Component SSR]
|
||
./app/page.js [Server Component]",
|
||
"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
|
||
./app/page.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": [],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
it('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 browser.elementByCss('button').click()
|
||
|
||
// TODO(veil): Why Owner Stack location different?
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page index.js (9:30)",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||
.length
|
||
)
|
||
).toBe(1)
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
(
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelector(
|
||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||
) as any
|
||
).href
|
||
)
|
||
).toMatchInlineSnapshot(`"https://nextjs.org/"`)
|
||
|
||
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 browser.elementByCss('button').click()
|
||
|
||
// TODO(veil): Why Owner Stack location different?
|
||
if (isTurbopack) {
|
||
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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page index.js (9:30)",
|
||
],
|
||
}
|
||
`)
|
||
} 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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||
.length
|
||
)
|
||
).toBe(1)
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
(
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelector(
|
||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||
) as any
|
||
).href
|
||
)
|
||
).toMatchInlineSnapshot(`"https://nextjs.org/"`)
|
||
|
||
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 browser.elementByCss('button').click()
|
||
|
||
// TODO(veil): Why Owner Stack location different?
|
||
if (isTurbopack) {
|
||
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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page index.js (9:30)",
|
||
],
|
||
}
|
||
`)
|
||
} 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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||
.length
|
||
)
|
||
).toBe(1)
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
(
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelector(
|
||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||
) as any
|
||
).href
|
||
)
|
||
).toMatchInlineSnapshot(`"https://nextjs.org/"`)
|
||
|
||
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 browser.elementByCss('button').click()
|
||
|
||
// TODO(veil): Why Owner Stack location different?
|
||
if (isTurbopack) {
|
||
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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page index.js (9:30)",
|
||
],
|
||
}
|
||
`)
|
||
} 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)",
|
||
"button <anonymous>",
|
||
"Index index.js (9:7)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||
.length
|
||
)
|
||
).toBe(2)
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
(
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelector(
|
||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||
) as any
|
||
).href
|
||
)
|
||
).toMatchInlineSnapshot(`"https://nextjs.org/"`)
|
||
expect(
|
||
await session.evaluate(
|
||
() =>
|
||
(
|
||
document
|
||
.querySelector('body nextjs-portal')
|
||
.shadowRoot.querySelector(
|
||
'#nextjs__container_errors_desc a:nth-of-type(2)'
|
||
) as any
|
||
).href
|
||
)
|
||
).toBe('http://example.com/')
|
||
})
|
||
|
||
// TODO-APP: Catch errors that happen before useEffect
|
||
test.skip('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>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
await expect(browser).toDisplayRedbox()
|
||
|
||
// 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>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
await expect(browser).toDisplayRedbox()
|
||
|
||
// 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>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
await expect(browser).toDisplayRedbox()
|
||
|
||
// 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>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
await expect(browser).toDisplayRedbox()
|
||
})
|
||
|
||
test('Should not show __webpack_exports__ when exporting anonymous arrow function', async () => {
|
||
await using sandbox = await createSandbox(next)
|
||
const { browser, session } = sandbox
|
||
|
||
await session.patch(
|
||
'index.js',
|
||
outdent`
|
||
export default () => {
|
||
if (typeof window !== 'undefined') {
|
||
throw new Error('test')
|
||
}
|
||
|
||
return null
|
||
}
|
||
`
|
||
)
|
||
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "test",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (3:11) @ {default export}
|
||
> 3 | throw new Error('test')
|
||
| ^",
|
||
"stack": [
|
||
"{default export} index.js (3:11)",
|
||
"Page app/page.js (2:1)",
|
||
],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "test",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (3:11) @ __rspack_default_export
|
||
> 3 | throw new Error('test')
|
||
| ^",
|
||
"stack": [
|
||
"__rspack_default_export index.js (3:11)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "test",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (3:11) @ default
|
||
> 3 | throw new Error('test')
|
||
| ^",
|
||
"stack": [
|
||
"default index.js (3:11)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
test('Unhandled errors and rejections opens up in the minimized state', async () => {
|
||
await using sandbox = await createSandbox(next)
|
||
const { session, browser } = sandbox
|
||
|
||
const file = outdent`
|
||
export default function Index() {
|
||
//
|
||
setTimeout(() => {
|
||
throw new Error('Unhandled error')
|
||
}, 0)
|
||
setTimeout(() => {
|
||
Promise.reject(new Error('Undhandled rejection'))
|
||
}, 0)
|
||
return (
|
||
<>
|
||
<button
|
||
id="unhandled-error"
|
||
onClick={() => {
|
||
throw new Error('Unhandled error')
|
||
}}
|
||
>
|
||
Unhandled error
|
||
</button>
|
||
<button
|
||
id="unhandled-rejection"
|
||
onClick={() => {
|
||
Promise.reject(new Error('Undhandled rejection'))
|
||
}}
|
||
>
|
||
Unhandled rejection
|
||
</button>
|
||
</>
|
||
)
|
||
}
|
||
`
|
||
|
||
await session.patch('index.js', file)
|
||
|
||
// Unhandled error and rejection in setTimeout
|
||
await retry(async () => {
|
||
expect(await getToastErrorCount(browser)).toBe(2)
|
||
})
|
||
|
||
// Unhandled error in event handler
|
||
await browser.elementById('unhandled-error').click()
|
||
await retry(async () => {
|
||
expect(await getToastErrorCount(browser)).toBe(3)
|
||
})
|
||
|
||
// Unhandled rejection in event handler
|
||
await browser.elementById('unhandled-rejection').click()
|
||
await retry(async () => {
|
||
expect(await getToastErrorCount(browser)).toBe(4)
|
||
})
|
||
await session.waitForNoRedbox()
|
||
|
||
// Add Component error
|
||
await session.patch(
|
||
'index.js',
|
||
file.replace(
|
||
'//',
|
||
"if (typeof window !== 'undefined') throw new Error('Component error')"
|
||
)
|
||
)
|
||
|
||
// Render error should "win" and show up in fullscreen
|
||
// TODO(veil): Why Owner Stack location different?
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Component error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (2:44) @ Index
|
||
> 2 | if (typeof window !== 'undefined') throw new Error('Component error')
|
||
| ^",
|
||
"stack": [
|
||
"Index index.js (2:44)",
|
||
"Page index.js (16:8)",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Component error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (2:44) @ Index
|
||
> 2 | if (typeof window !== 'undefined') throw new Error('Component error')
|
||
| ^",
|
||
"stack": [
|
||
"Index index.js (2:44)",
|
||
"Page app/page.js (4:10)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
test('Call stack for client error', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/page.js',
|
||
outdent`
|
||
'use client'
|
||
export default function Page() {
|
||
if (typeof window !== 'undefined') {
|
||
throw new Error('Client error')
|
||
}
|
||
return null
|
||
}
|
||
`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Client error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/page.js (4:11) @ Page
|
||
> 4 | throw new Error('Client error')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/page.js (4:11)",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
test('Call stack for server error', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/page.js',
|
||
outdent`
|
||
export default function Page() {
|
||
throw new Error('Server error')
|
||
}
|
||
`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Server error",
|
||
"environmentLabel": "Server",
|
||
"label": "Runtime Error",
|
||
"source": "app/page.js (2:9) @ Page
|
||
> 2 | throw new Error('Server error')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/page.js (2:9)",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
test('should hide unrelated frames in stack trace with unknown anonymous calls', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/page.js',
|
||
outdent`
|
||
export default function Page() {
|
||
try {
|
||
(function() {
|
||
throw new Error("This is an error from an anonymous function");
|
||
})();
|
||
} catch (e) {
|
||
throw e
|
||
}
|
||
}
|
||
`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
|
||
// TODO(veil): Turbopack uses correct name
|
||
// TODO(veil): Column of 2nd frame should be 7
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "This is an error from an anonymous function",
|
||
"environmentLabel": "Server",
|
||
"label": "Runtime Error",
|
||
"source": "app/page.js (4:13) @ ${isTurbopack ? '<anonymous>' : 'eval'}
|
||
> 4 | throw new Error("This is an error from an anonymous function");
|
||
| ^",
|
||
"stack": [
|
||
"${isTurbopack ? '<anonymous>' : 'eval'} app/page.js (4:13)",
|
||
"Page app/page.js (5:${isTurbopack ? '6' : '5'})",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
it('should hide unrelated frames in stack trace with nodejs internal calls', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/page.js',
|
||
// Node.js will throw an error about the invalid URL since this is a server component
|
||
outdent`
|
||
export default function Page() {
|
||
new URL("/", "invalid");
|
||
}`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Invalid URL",
|
||
"environmentLabel": "Server",
|
||
"label": "Runtime TypeError",
|
||
"source": "app/page.js (2:3) @ Page
|
||
> 2 | new URL("/", "invalid");
|
||
| ^",
|
||
"stack": [
|
||
"Page app/page.js (2:3)",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
test('Server component errors should open up in fullscreen', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
// Start with error
|
||
[
|
||
'app/page.js',
|
||
outdent`
|
||
export default function Page() {
|
||
throw new Error('Server component error')
|
||
return <p id="text">Hello world</p>
|
||
}
|
||
`,
|
||
],
|
||
])
|
||
)
|
||
const { session, browser } = sandbox
|
||
|
||
await expect(browser).toDisplayRedbox(
|
||
`
|
||
{
|
||
"description": "Server component error",
|
||
"environmentLabel": "Server",
|
||
"label": "<FIXME-excluded-label>",
|
||
"source": "app/page.js (2:9) @ Page
|
||
> 2 | throw new Error('Server component error')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/page.js (2:9)",
|
||
],
|
||
}
|
||
`,
|
||
|
||
// FIXME: `label` is flaking between "Runtime Error" and "Recoverable Error"
|
||
{ label: false }
|
||
)
|
||
|
||
// Remove error
|
||
await session.patch(
|
||
'app/page.js',
|
||
outdent`
|
||
export default function Page() {
|
||
return <p id="text">Hello world</p>
|
||
}
|
||
`
|
||
)
|
||
expect(await browser.waitForElementByCss('#text').text()).toBe(
|
||
'Hello world'
|
||
)
|
||
await session.waitForNoRedbox()
|
||
|
||
// Re-add error
|
||
await session.patch(
|
||
'app/page.js',
|
||
outdent`
|
||
export default function Page() {
|
||
throw new Error('Server component error!')
|
||
return <p id="text">Hello world</p>
|
||
}
|
||
`
|
||
)
|
||
|
||
await expect(browser).toDisplayRedbox(
|
||
`
|
||
{
|
||
"description": "Server component error!",
|
||
"environmentLabel": "Server",
|
||
"label": "<FIXME-excluded-label>",
|
||
"source": "app/page.js (2:9) @ Page
|
||
> 2 | throw new Error('Server component error!')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/page.js (2:9)",
|
||
],
|
||
}
|
||
`,
|
||
|
||
// FIXME: `label` is flaking between "Runtime Error" and "Recoverable Error"
|
||
{ label: false }
|
||
)
|
||
})
|
||
|
||
test('Import trace when module not found in layout', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
|
||
new Map([['app/module.js', `import "non-existing-module"`]])
|
||
)
|
||
const { browser, session } = sandbox
|
||
|
||
await session.patch(
|
||
'app/layout.js',
|
||
outdent`
|
||
import "./module"
|
||
|
||
export default function RootLayout({ children }) {
|
||
return (
|
||
<html>
|
||
<head></head>
|
||
<body>{children}</body>
|
||
</html>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Module not found: Can't resolve 'non-existing-module'",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./app/module.js (1:1)
|
||
Module not found: Can't resolve 'non-existing-module'
|
||
> 1 | import "non-existing-module"
|
||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"description": " × Module not found: Can't resolve 'non-existing-module' in '<FIXME-project-root>/app'",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./app/module.js
|
||
× Module not found: Can't resolve 'non-existing-module' in '<FIXME-project-root>/app'
|
||
╭────
|
||
1 │ import "non-existing-module";
|
||
· ─────────────────────────────
|
||
╰────
|
||
Import trace for requested module:
|
||
./app/module.js
|
||
./app/layout.js",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Module not found: Can't resolve 'non-existing-module'",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./app/module.js (1:1)
|
||
Module not found: Can't resolve 'non-existing-module'
|
||
> 1 | import "non-existing-module"
|
||
| ^",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
test("Can't resolve @import in CSS file", async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
['app/styles1.css', '@import "./styles2.css"'],
|
||
['app/styles2.css', '@import "./boom.css"'],
|
||
])
|
||
)
|
||
const { browser, session } = sandbox
|
||
await session.patch(
|
||
'app/layout.js',
|
||
outdent`
|
||
import "./styles1.css"
|
||
|
||
export default function RootLayout({ children }) {
|
||
return (
|
||
<html>
|
||
<head></head>
|
||
<body>{children}</body>
|
||
</html>
|
||
)
|
||
}
|
||
`
|
||
)
|
||
|
||
// Wait for patch to apply and new error to show.
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Module not found: Can't resolve './boom.css'",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./app/styles2.css (1:1)
|
||
Module not found: Can't resolve './boom.css'
|
||
> 1 | @import "./boom.css"
|
||
| ^",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"description": "Failed to compile",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "╰─▶ × Error: RspackResolver(NotFound("./boom.css"))",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "Module not found: Can't resolve './boom.css'",
|
||
"environmentLabel": null,
|
||
"label": "Build Error",
|
||
"source": "./app/styles2.css
|
||
Module not found: Can't resolve './boom.css'
|
||
https://nextjs.org/docs/messages/module-not-found
|
||
Import trace for requested module:
|
||
./app/styles1.css",
|
||
"stack": [],
|
||
}
|
||
`)
|
||
}
|
||
})
|
||
|
||
// TODO: The error overlay is not closed when restoring the working code.
|
||
for (const type of ['server' /* , 'client' */]) {
|
||
test(`${type} component can recover from error thrown in the module`, async () => {
|
||
await using sandbox = await createSandbox(next, undefined, '/' + type)
|
||
const { browser, session } = sandbox
|
||
|
||
await next.patchFile('index.js', "throw new Error('module error')")
|
||
|
||
await retry(async () => {
|
||
// Should use `await expect(browser).toDisplayRedbox()`
|
||
await session.waitForRedbox()
|
||
})
|
||
|
||
if (isRspack) {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"code": "E394",
|
||
"description": "module error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (1:7) @ eval
|
||
> 1 | throw new Error('module error')
|
||
| ^",
|
||
"stack": [
|
||
"eval index.js (1:7)",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/server/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
}
|
||
`)
|
||
} else if (!isTurbopack) {
|
||
await expect({ browser, next }).toDisplayRedbox(`
|
||
{
|
||
"code": "E394",
|
||
"description": "module error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "index.js (1:7) @ eval
|
||
> 1 | throw new Error('module error')
|
||
| ^",
|
||
"stack": [
|
||
"eval index.js (1:7)",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/server/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
|
||
await next.patchFile(
|
||
'index.js',
|
||
'export default function Page() {return <p>hello world</p>}'
|
||
)
|
||
await session.waitForNoRedbox()
|
||
})
|
||
}
|
||
|
||
test('Should show error location for server actions in client component', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/actions.ts',
|
||
`"use server";
|
||
|
||
export async function serverAction(a) {
|
||
throw new Error("server action was here");
|
||
}`,
|
||
],
|
||
[
|
||
'app/page.js',
|
||
`"use client";
|
||
import { serverAction } from "./actions";
|
||
|
||
export default function Home() {
|
||
return (
|
||
<>
|
||
<form action={serverAction}>
|
||
<button id="trigger-action">Submit</button>
|
||
</form>
|
||
</>
|
||
);
|
||
}`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
await browser.elementByCss('#trigger-action').click()
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "server action was here",
|
||
"environmentLabel": "Server",
|
||
"label": "Runtime Error",
|
||
"source": "app/actions.ts (4:9) @ serverAction
|
||
> 4 | throw new Error("server action was here");
|
||
| ^",
|
||
"stack": [
|
||
"serverAction app/actions.ts (4:9)",
|
||
"form <anonymous>",
|
||
"Home app/page.js (7:7)",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
test('Should show error location for server actions in server component', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/actions.ts',
|
||
`"use server";
|
||
|
||
export async function serverAction(a) {
|
||
throw new Error("server action was here");
|
||
}`,
|
||
],
|
||
[
|
||
'app/page.js',
|
||
`import { serverAction } from "./actions";
|
||
|
||
export default function Home() {
|
||
return (
|
||
<>
|
||
<form action={serverAction}>
|
||
<button id="trigger-action">Submit</button>
|
||
</form>
|
||
</>
|
||
);
|
||
}`,
|
||
],
|
||
])
|
||
)
|
||
const { browser } = sandbox
|
||
await browser.elementByCss('#trigger-action').click()
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "server action was here",
|
||
"environmentLabel": "Server",
|
||
"label": "Runtime Error",
|
||
"source": "app/actions.ts (4:9) @ serverAction
|
||
> 4 | throw new Error("server action was here");
|
||
| ^",
|
||
"stack": [
|
||
"serverAction app/actions.ts (4:9)",
|
||
"form <anonymous>",
|
||
"Home app/page.js (6:7)",
|
||
],
|
||
}
|
||
`)
|
||
})
|
||
|
||
test('should collapse bundler internal stack frames', async () => {
|
||
await using sandbox = await createSandbox(
|
||
next,
|
||
new Map([
|
||
[
|
||
'app/utils.ts',
|
||
`throw new Error('utils error')
|
||
export function foo(){}
|
||
`,
|
||
],
|
||
[
|
||
'app/page.js',
|
||
`"use client";
|
||
import { foo } from "./utils";
|
||
|
||
export default function Home() {
|
||
foo();
|
||
return "hello";
|
||
}`,
|
||
],
|
||
])
|
||
)
|
||
|
||
const { browser } = sandbox
|
||
|
||
if (isTurbopack) {
|
||
// FIXME: display the sourcemapped stack frames
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "utils error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/utils.ts (1:7) @ module evaluation
|
||
> 1 | throw new Error('utils error')
|
||
| ^",
|
||
"stack": [
|
||
"module evaluation app/utils.ts (1:7)",
|
||
"module evaluation app/page.js (2:1)",
|
||
],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "utils error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/utils.ts (1:7) @ eval
|
||
> 1 | throw new Error('utils error')
|
||
| ^",
|
||
"stack": [
|
||
"eval app/utils.ts (1:7)",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
// FIXME: Webpack stack frames are not source mapped
|
||
await expect(browser).toDisplayRedbox(`
|
||
[
|
||
{
|
||
"description": "utils error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/utils.ts (1:7) @ eval
|
||
> 1 | throw new Error('utils error')
|
||
| ^",
|
||
"stack": [
|
||
"eval app/utils.ts (1:7)",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
},
|
||
{
|
||
"description": "utils error",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/utils.ts (1:7) @ eval
|
||
> 1 | throw new Error('utils error')
|
||
| ^",
|
||
"stack": [
|
||
"eval app/utils.ts (1:7)",
|
||
"<FIXME-next-dist-dir>",
|
||
"eval ./app/page.js",
|
||
"<FIXME-next-dist-dir>",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
}
|
||
})
|
||
})
|