first commit
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

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import path from 'path'
import { outdent } from 'outdent'
describe('ReactRefresh', () => {
const { next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
test('can edit a component without losing state', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 1')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 2')
})
test('cyclic dependencies', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.write(
'NudgeOverview.js',
outdent`
import * as React from 'react';
import { foo } from './routes';
const NudgeOverview = () => {
return <span />;
foo;
};
export default NudgeOverview;
`
)
await session.write(
'SurveyOverview.js',
outdent`
const SurveyOverview = () => {
return 100;
};
export default SurveyOverview;
`
)
await session.write(
'Milestones.js',
outdent`
import React from 'react';
import { fragment } from './DashboardPage';
const Milestones = props => {
return <span />;
fragment;
};
export default Milestones;
`
)
await session.write(
'DashboardPage.js',
outdent`
import React from 'react';
import Milestones from './Milestones';
import SurveyOverview from './SurveyOverview';
import NudgeOverview from './NudgeOverview';
export const fragment = {};
const DashboardPage = () => {
return (
<>
<Milestones />
<SurveyOverview />
<NudgeOverview />
</>
);
};
export default DashboardPage;
`
)
await session.write(
'routes.js',
outdent`
import DashboardPage from './DashboardPage';
export const foo = {};
console.warn('DashboardPage at import time:', DashboardPage);
setTimeout(() => console.warn('DashboardPage after:', DashboardPage), 0);
export default DashboardPage;
`
)
await session.patch(
'index.js',
outdent`
import * as React from 'react';
import DashboardPage from './routes';
const HeroApp = (props) => {
return <p>Hello. {DashboardPage ? <DashboardPage /> : null}</p>;
};
export default HeroApp;
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello. 100')
let didFullRefresh = !(await session.patch(
'SurveyOverview.js',
outdent`
const SurveyOverview = () => {
return 200;
};
export default SurveyOverview;
`
))
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello. 200')
expect(didFullRefresh).toBe(false)
didFullRefresh = !(await session.patch(
'index.js',
outdent`
import * as React from 'react';
import DashboardPage from './routes';
const HeroApp = (props) => {
return <p>Hello: {DashboardPage ? <DashboardPage /> : null}</p>;
};
export default HeroApp;
`
))
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello: 200')
expect(didFullRefresh).toBe(false)
didFullRefresh = !(await session.patch(
'SurveyOverview.js',
outdent`
const SurveyOverview = () => {
return 300;
};
export default SurveyOverview;
`
))
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello: 300')
expect(didFullRefresh).toBe(false)
})
})

View File

@@ -0,0 +1,317 @@
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import { outdent } from 'outdent'
import path from 'path'
const isRspack = process.env.NEXT_RSPACK !== undefined
describe('ReactRefreshLogBox _app _document', () => {
const { isTurbopack, next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
test('empty _app shows logbox', async () => {
await using sandbox = await createSandbox(
next,
new Map([['pages/_app.js', ``]])
)
const { browser, session } = sandbox
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "The default export is not a React Component in page: "/_app"",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
await session.patch(
'pages/_app.js',
outdent`
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp
`
)
await session.waitForNoRedbox()
})
test('empty _document shows logbox', async () => {
await using sandbox = await createSandbox(
next,
new Map([['pages/_document.js', ``]])
)
const { browser, session } = sandbox
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "The default export is not a React Component in page: "/_document"",
"environmentLabel": null,
"label": "Runtime Error",
"source": null,
"stack": [],
}
`)
await session.patch(
'pages/_document.js',
outdent`
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
`
)
await session.waitForNoRedbox()
})
test('_app syntax error shows logbox', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/_app.js',
outdent`
function MyApp({ Component, pageProps }) {
return <<Component {...pageProps} />;
}
export default MyApp
`,
],
])
)
const { browser, session } = sandbox
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Expression expected",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js (2:10)
Expression expected
> 2 | return <<Component {...pageProps} />;
| ^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expression expected",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js
╰─▶ × Error: x Expression expected
│ ,-[2:1]
│ 1 | function MyApp({ Component, pageProps }) {
│ 2 | return <<Component {...pageProps} />;
│ : ^^
│ 3 | }
│ 4 | export default MyApp
\`----
│ x Expected ';', got '{'
│ ,-[2:1]
│ 1 | function MyApp({ Component, pageProps }) {
│ 2 | return <<Component {...pageProps} />;
│ : ^
│ 3 | }
│ 4 | export default MyApp
\`----
│ Caused by:
│ Syntax Error",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Expression expected",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js
Error: x Expression expected
,-[2:1]
1 | function MyApp({ Component, pageProps }) {
2 | return <<Component {...pageProps} />;
: ^^
3 | }
4 | export default MyApp
\`----
x Expected ';', got '{'
,-[2:1]
1 | function MyApp({ Component, pageProps }) {
2 | return <<Component {...pageProps} />;
: ^
3 | }
4 | export default MyApp
\`----
Caused by:
Syntax Error",
"stack": [],
}
`)
}
await session.patch(
'pages/_app.js',
outdent`
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp
`
)
await session.waitForNoRedbox()
})
test('_document syntax error shows logbox', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/_document.js',
outdent`
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {{
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
`,
],
])
)
const { browser, session } = sandbox
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_document.js (3:36)
Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key
> 3 | class MyDocument extends Document {{
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_document.js
╰─▶ × Error: x Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key
│ ,-[3:1]
│ 1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
│ 2 |
│ 3 | class MyDocument extends Document {{
│ : ^
│ 4 | static async getInitialProps(ctx) {
│ 5 | const initialProps = await Document.getInitialProps(ctx)
│ 6 | return { ...initialProps }
\`----
│ Caused by:
│ Syntax Error",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_document.js
Error: x Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key
,-[3:1]
1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | class MyDocument extends Document {{
: ^
4 | static async getInitialProps(ctx) {
5 | const initialProps = await Document.getInitialProps(ctx)
6 | return { ...initialProps }
\`----
Caused by:
Syntax Error",
"stack": [],
}
`)
}
await session.patch(
'pages/_document.js',
outdent`
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
`
)
await session.waitForNoRedbox()
})
})

View File

@@ -0,0 +1,317 @@
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import { outdent } from 'outdent'
import path from 'path'
describe('ReactRefreshLogBox', () => {
const { isTurbopack, next, isRspack } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
// Module trace is only available with webpack 5
test('Node.js builtins', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'node_modules/my-package/index.js',
outdent`
const dns = require('dns')
module.exports = dns
`,
],
[
'node_modules/my-package/package.json',
outdent`
{
"name": "my-package",
"version": "0.0.1"
}
`,
],
])
)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
import pkg from 'my-package'
export default function Hello() {
return (pkg ? <h1>Package loaded</h1> : <h1>Package did not load</h1>)
}
`
)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'dns'",
"environmentLabel": null,
"label": "Build Error",
"source": "./node_modules/my-package/index.js (1:13)
Module not found: Can't resolve 'dns'
> 1 | const dns = require('dns')
| ^^^^^^^^^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " × Module not found: Can't resolve 'dns' in '<FIXME-project-root>/node_modules/my-package'",
"environmentLabel": null,
"label": "Build Error",
"source": "./node_modules/my-package/index.js
× Module not found: Can't resolve 'dns' in '<FIXME-project-root>/node_modules/my-package'
╭─[1:12]
1 │ const dns = require('dns')
· ──────────────
2 │ module.exports = dns
╰────
Import trace for requested module:
./node_modules/my-package/index.js
./index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'dns'",
"environmentLabel": null,
"label": "Build Error",
"source": "./node_modules/my-package/index.js (1:1)
Module not found: Can't resolve 'dns'
> 1 | const dns = require('dns')
| ^",
"stack": [],
}
`)
}
})
test('Module not found', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
import Comp from 'b'
export default function Oops() {
return (
<div>
<Comp>lol</Comp>
</div>
)
}
`
)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'b'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (1:1)
Module not found: Can't resolve 'b'
> 1 | import Comp from 'b'
| ^^^^^^^^^^^^^^^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " × Module not found: Can't resolve 'b' in '<FIXME-project-root>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
× Module not found: Can't resolve 'b' in '<FIXME-project-root>'
╭─[2:0]
1 │ import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
2 │ import Comp from 'b';
· ─────────────────────
3 │ export default function Oops() {
4 │ return /*#__PURE__*/ _jsxDEV("div", {
╰────
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'b'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (1:1)
Module not found: Can't resolve 'b'
> 1 | import Comp from 'b'
| ^",
"stack": [],
}
`)
}
})
test('Module not found (empty import trace)', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'pages/index.js',
outdent`
import Comp from 'b'
export default function Oops() {
return (
<div>
<Comp>lol</Comp>
</div>
)
}
`
)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'b'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/index.js (1:1)
Module not found: Can't resolve 'b'
> 1 | import Comp from 'b'
| ^^^^^^^^^^^^^^^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " × Module not found: Can't resolve 'b' in '<FIXME-project-root>/pages'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/index.js
× Module not found: Can't resolve 'b' in '<FIXME-project-root>/pages'
╭─[2:0]
1 │ import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
2 │ import Comp from 'b';
· ─────────────────────
3 │ export default function Oops() {
4 │ return /*#__PURE__*/ _jsxDEV("div", {
╰────
Import trace for requested module:
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve 'b'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/index.js (1:1)
Module not found: Can't resolve 'b'
> 1 | import Comp from 'b'
| ^",
"stack": [],
}
`)
}
})
test('Module not found (missing global CSS)', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'pages/_app.js',
outdent`
import './non-existent.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
`,
],
[
'pages/index.js',
outdent`
export default function Page(props) {
return <p>index page</p>
}
`,
],
])
)
const { browser, session } = sandbox
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve './non-existent.css'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js (1:1)
Module not found: Can't resolve './non-existent.css'
> 1 | import './non-existent.css'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " × Module not found: Can't resolve './non-existent.css' in '<FIXME-project-root>/pages'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js
× Module not found: Can't resolve './non-existent.css' in '<FIXME-project-root>/pages'
╭─[2:0]
1 │ import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
2 │ import './non-existent.css';
· ────────────────────────────
3 │ export default function App({ Component, pageProps }) {
4 │ return /*#__PURE__*/ _jsxDEV(Component, {
╰────",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "Module not found: Can't resolve './non-existent.css'",
"environmentLabel": null,
"label": "Build Error",
"source": "./pages/_app.js (1:1)
Module not found: Can't resolve './non-existent.css'
> 1 | import './non-existent.css'
| ^",
"stack": [],
}
`)
}
await session.patch(
'pages/_app.js',
outdent`
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
`
)
await session.waitForNoRedbox()
expect(
await session.evaluate(() => document.documentElement.innerHTML)
).toContain('index page')
})
})

View File

@@ -0,0 +1,71 @@
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import path from 'path'
// TODO: figure out why snapshots mismatch on GitHub actions
// specifically but work in docker and locally
describe.skip('ReactRefreshLogBox scss', () => {
const { next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
dependencies: {
sass: 'latest',
},
})
test('scss syntax errors', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.write('index.module.scss', `.button { font-size: 5px; }`)
await session.patch(
'index.js',
`
import './index.module.scss';
export default () => {
return (
<div>
<p>lol</p>
</div>
)
}
`
)
await session.waitForNoRedbox()
// Syntax error
await session.patch('index.module.scss', `.button { font-size: :5px; }`)
await session.waitForRedbox()
const source = await session.getRedboxSource()
expect(source).toMatchSnapshot()
})
test('scss module pure selector error', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.write('index.module.scss', `.button { font-size: 5px; }`)
await session.patch(
'index.js',
`
import './index.module.scss';
export default () => {
return (
<div>
<p>lol</p>
</div>
)
}
`
)
// 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.scss', `button { font-size: 5px; }`)
await session.waitForRedbox()
const source2 = await session.getRedboxSource()
expect(source2).toMatchSnapshot()
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
import { FileRef, nextTestSetup } from 'e2e-utils'
import { createSandbox } from 'development-sandbox'
import path from 'path'
describe('ReactRefreshModule', () => {
const { next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
it('should allow any variable names', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.waitForNoRedbox()
const variables = [
'_a',
'_b',
'currentExports',
'prevExports',
'isNoLongerABoundary',
]
for await (const variable of variables) {
await session.patch(
'pages/index.js',
`import { default as ${variable} } from 'next/link'
console.log({ ${variable} })
export default function Page() {
return null
}`
)
await session.waitForNoRedbox()
expect(next.cliOutput).not.toContain(
`'${variable}' has already been declared`
)
}
})
})

View File

@@ -0,0 +1,377 @@
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import { check } from 'next-test-utils'
import { outdent } from 'outdent'
import path from 'path'
describe('ReactRefreshRegression', () => {
const { isTurbopack, isRspack, next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
dependencies: {
'styled-components': '6.1.16',
'@next/mdx': 'canary',
'@mdx-js/loader': '2.2.1',
'@mdx-js/react': '2.2.1',
},
})
// https://github.com/vercel/next.js/issues/12422
test('styled-components hydration mismatch', async () => {
const files = new Map([
[
'pages/_document.js',
outdent`
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
`,
],
])
await using sandbox = await createSandbox(next, files)
const { session } = sandbox
// We start here.
await session.patch(
'index.js',
`
import React from 'react'
import styled from 'styled-components'
const Title = styled.h1\`
color: red;
font-size: 50px;
\`
export default () => <Title>My page</Title>
`
)
// Verify no hydration mismatch:
await session.waitForNoRedbox()
})
// https://github.com/vercel/next.js/issues/13978
test('can fast refresh a page with getStaticProps', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export function getStaticProps() {
return { props: { } }
}
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('0')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 1')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 2')
})
// https://github.com/vercel/next.js/issues/13978
test('can fast refresh a page with getServerSideProps', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export function getServerSideProps() {
return { props: { } }
}
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('0')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 1')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 2')
})
// https://github.com/vercel/next.js/issues/13978
test('can fast refresh a page with config', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export const config = {}
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
await check(
() => session.evaluate(() => document.querySelector('p').textContent),
'0'
)
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await session.patch(
'pages/index.js',
`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 1')
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 2')
})
// https://github.com/vercel/next.js/issues/11504
test('shows an overlay for a server-side error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'pages/index.js',
`export default function () { throw new Error('pre boom'); }`
)
const didNotReload = await session.patch(
'pages/index.js',
`export default function () { throw new Error('boom'); }`
)
expect(didNotReload).toBe(false)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "boom",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (1:36) @ {default export}
> 1 | export default function () { throw new Error('boom'); }
| ^",
"stack": [
"{default export} pages/index.js (1:36)",
],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "boom",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (1:36) @ __rspack_default_export
> 1 | export default function () { throw new Error('boom'); }
| ^",
"stack": [
"__rspack_default_export pages/index.js (1:36)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "boom",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/index.js (1:36) @ default
> 1 | export default function () { throw new Error('boom'); }
| ^",
"stack": [
"default pages/index.js (1:36)",
],
}
`)
}
})
// https://github.com/vercel/next.js/issues/13574
test('custom loader mdx should have Fast Refresh enabled', async () => {
await using sandbox = await createSandbox(
next,
new Map([
[
'next.config.js',
outdent`
const withMDX = require("@next/mdx")({
extension: /\\.mdx?$/,
});
module.exports = withMDX({
pageExtensions: ["js", "mdx"],
});
`,
],
['pages/mdx.mdx', `Hello World!`],
]),
'/mdx'
)
const { session } = sandbox
expect(
await session.evaluate(
() => document.querySelector('#__next').textContent
)
).toBe('Hello World!')
let didNotReload = await session.patch('pages/mdx.mdx', `Hello Foo!`)
expect(didNotReload).toBe(true)
await session.waitForNoRedbox()
expect(
await session.evaluate(
() => document.querySelector('#__next').textContent
)
).toBe('Hello Foo!')
didNotReload = await session.patch('pages/mdx.mdx', `Hello Bar!`)
expect(didNotReload).toBe(true)
await session.waitForNoRedbox()
expect(
await session.evaluate(
() => document.querySelector('#__next').textContent
)
).toBe('Hello Bar!')
})
})

View File

@@ -0,0 +1,494 @@
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import path from 'path'
describe('ReactRefreshRequire', () => {
const { next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L989-L1048
test('re-runs accepted modules', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'index.js',
`export default function Noop() { return null; };`
)
await session.write(
'./foo.js',
`window.log.push('init FooV1'); require('./bar');`
)
await session.write(
'./bar.js',
`window.log.push('init BarV1'); export default function Bar() { return null; };`
)
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'index.js',
`require('./foo'); export default function Noop() { return null; };`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init FooV1',
'init BarV1',
])
// We only edited Bar, and it accepted.
// So we expect it to re-run alone.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'./bar.js',
`window.log.push('init BarV2'); export default function Bar() { return null; };`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV2',
])
// We only edited Bar, and it accepted.
// So we expect it to re-run alone.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'./bar.js',
`window.log.push('init BarV3'); export default function Bar() { return null; };`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV3',
])
// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled();
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled();
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1050-L1137
test('propagates a hot update to closest accepted module', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'index.js',
`export default function Noop() { return null; };`
)
await session.write(
'./foo.js',
`
window.log.push('init FooV1');
require('./bar');
// Exporting a component marks it as auto-accepting.
export default function Foo() {};
`
)
await session.write('./bar.js', `window.log.push('init BarV1');`)
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'index.js',
`require('./foo'); export default function Noop() { return null; };`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init FooV1',
'init BarV1',
])
// We edited Bar, but it doesn't accept.
// So we expect it to re-run together with Foo which does.
await session.evaluate(() => ((window as any).log = []))
await session.patch('./bar.js', `window.log.push('init BarV2');`)
expect(await session.evaluate(() => (window as any).log)).toEqual([
// // FIXME: Metro order:
// 'init BarV2',
// 'init FooV1',
'init FooV1',
'init BarV2',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])
// We edited Bar, but it doesn't accept.
// So we expect it to re-run together with Foo which does.
await session.evaluate(() => ((window as any).log = []))
await session.patch('./bar.js', `window.log.push('init BarV3');`)
expect(await session.evaluate(() => (window as any).log)).toEqual([
// // FIXME: Metro order:
// 'init BarV3',
// 'init FooV1',
'init FooV1',
'init BarV3',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])
// We edited Bar so that it accepts itself.
// We still re-run Foo because the exports of Bar changed.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'./bar.js',
`
window.log.push('init BarV3');
// Exporting a component marks it as auto-accepting.
export default function Bar() {};
`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
// // FIXME: Metro order:
// 'init BarV3',
// 'init FooV1',
'init FooV1',
'init BarV3',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])
// Further edits to Bar don't re-run Foo.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'./bar.js',
`
window.log.push('init BarV4');
export default function Bar() {};
`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV4',
])
// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled();
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled();
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1139-L1307
test('propagates hot update to all inverse dependencies', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
await session.patch(
'index.js',
`export default function Noop() { return null; };`
)
// This is the module graph:
// MiddleA*
// / \
// Root* - MiddleB* - Leaf
// \
// MiddleC
//
// * - accepts update
//
// We expect that editing Leaf will propagate to
// MiddleA and MiddleB both of which can handle updates.
await session.write(
'root.js',
`
window.log.push('init RootV1');
import './middleA';
import './middleB';
import './middleC';
export default function Root() {};
`
)
await session.write(
'middleA.js',
`
log.push('init MiddleAV1');
import './leaf';
export default function MiddleA() {};
`
)
await session.write(
'middleB.js',
`
log.push('init MiddleBV1');
import './leaf';
export default function MiddleB() {};
`
)
// This one doesn't import leaf and also doesn't export a component (so it
// doesn't accept updates).
await session.write(
'middleC.js',
`log.push('init MiddleCV1'); export default {};`
)
// Doesn't accept its own updates; they will propagate.
await session.write(
'leaf.js',
`log.push('init LeafV1'); export default {};`
)
// Bootstrap:
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'index.js',
`require('./root'); export default function Noop() { return null; };`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init LeafV1',
'init MiddleAV1',
'init MiddleBV1',
'init MiddleCV1',
'init RootV1',
])
// We edited Leaf, but it doesn't accept.
// So we expect it to re-run together with MiddleA and MiddleB which do.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'leaf.js',
`log.push('init LeafV2'); export default {};`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init LeafV2',
'init MiddleAV1',
'init MiddleBV1',
])
// Let's try the same one more time.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'leaf.js',
`log.push('init LeafV3'); export default {};`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init LeafV3',
'init MiddleAV1',
'init MiddleBV1',
])
// Now edit MiddleB. It should accept and re-run alone.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'middleB.js',
`
log.push('init MiddleBV2');
import './leaf';
export default function MiddleB() {};
`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init MiddleBV2',
])
// Finally, edit MiddleC. It didn't accept so it should bubble to Root.
await session.evaluate(() => ((window as any).log = []))
await session.patch(
'middleC.js',
`log.push('init MiddleCV2'); export default {};`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init MiddleCV2',
'init RootV1',
])
// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled()
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled()
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1309-L1406
test('runs dependencies before dependents', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1408-L1498
test('provides fresh value for module.exports in parents', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1500-L1590
test('provides fresh value for exports.* in parents', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1592-L1688
test('provides fresh value for ES6 named import in parents', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1690-L1786
test('provides fresh value for ES6 default import in parents', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1788-L1899
test('stops update propagation after module-level errors', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1901-L2010
test('can continue hot updates after module-level errors with module.exports', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2012-L2123
test('can continue hot updates after module-level errors with ES6 exports', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2125-L2233
test('does not accumulate stale exports over time', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2235-L2279
test('bails out if update bubbles to the root via the only path', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2281-L2371
test('bails out if the update bubbles to the root via one of the paths', async () => {
// TODO:
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2373-L2472
test('propagates a module that stops accepting in next version', async () => {
await using sandbox = await createSandbox(next)
const { session } = sandbox
// Accept in parent
await session.write(
'./foo.js',
`;(typeof global !== 'undefined' ? global : window).log.push('init FooV1'); import './bar'; export default function Foo() {};`
)
// Accept in child
await session.write(
'./bar.js',
`;(typeof global !== 'undefined' ? global : window).log.push('init BarV1'); export default function Bar() {};`
)
// Bootstrap:
await session.patch(
'index.js',
`;(typeof global !== 'undefined' ? global : window).log = []; require('./foo'); export default () => null;`
)
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV1',
'init FooV1',
])
let didFullRefresh = false
// Verify the child can accept itself:
await session.evaluate(() => ((window as any).log = []))
didFullRefresh =
didFullRefresh ||
!(await session.patch(
'./bar.js',
`window.log.push('init BarV1.1'); export default function Bar() {};`
))
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV1.1',
])
// Now let's change the child to *not* accept itself.
// We'll expect that now the parent will handle the evaluation.
await session.evaluate(() => ((window as any).log = []))
didFullRefresh =
didFullRefresh ||
!(await session.patch(
'./bar.js',
// It's important we still export _something_, otherwise webpack will
// also emit an extra update to the parent module. This happens because
// webpack converts the module from ESM to CJS, which means the parent
// module must update how it "imports" the module (drops interop code).
// TODO: propose that webpack interrupts the current update phase when
// `module.hot.invalidate()` is called.
`window.log.push('init BarV2'); export {};`
))
// We re-run Bar and expect to stop there. However,
// it didn't export a component, so we go higher.
// We stop at Foo which currently _does_ export a component.
expect(await session.evaluate(() => (window as any).log)).toEqual([
// Bar evaluates twice:
// 1. To invalidate itself once it realizes it's no longer acceptable.
// 2. As a child of Foo re-evaluating.
'init BarV2',
'init BarV2',
'init FooV1',
])
// Change it back so that the child accepts itself.
await session.evaluate(() => ((window as any).log = []))
didFullRefresh =
didFullRefresh ||
!(await session.patch(
'./bar.js',
`window.log.push('init BarV2'); export default function Bar() {};`
))
// Since the export list changed, we have to re-run both the parent
// and the child.
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV2',
'init FooV1',
])
// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled();
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled();
expect(didFullRefresh).toBe(false)
// But editing the child alone now doesn't reevaluate the parent.
await session.evaluate(() => ((window as any).log = []))
didFullRefresh =
didFullRefresh ||
!(await session.patch(
'./bar.js',
`window.log.push('init BarV3'); export default function Bar() {};`
))
expect(await session.evaluate(() => (window as any).log)).toEqual([
'init BarV3',
])
// Finally, edit the parent in a way that changes the export.
// It would still be accepted on its own -- but it's incompatible
// with the past version which didn't have two exports.
await session.evaluate(() => window.localStorage.setItem('init', ''))
didFullRefresh =
didFullRefresh ||
!(await session.patch(
'./foo.js',
`
if (typeof window !== 'undefined' && window.localStorage) {
window.localStorage.setItem('init', 'init FooV2')
}
export function Foo() {};
export function FooFoo() {};`
))
// Check that we attempted to evaluate, but had to fall back to full refresh.
expect(
await session.evaluate(() => window.localStorage.getItem('init'))
).toEqual('init FooV2')
// expect(Refresh.performFullRefresh).toHaveBeenCalled();
expect(didFullRefresh).toBe(true)
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2474-L2521
test('can replace a module before it is loaded', async () => {
// TODO:
})
})

View File

@@ -0,0 +1,947 @@
/* eslint-env jest */
import { createSandbox } from 'development-sandbox'
import { FileRef, nextTestSetup } from 'e2e-utils'
import { check, retry } from 'next-test-utils'
import { outdent } from 'outdent'
import path from 'path'
const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18
describe('pages/ error recovery', () => {
const { next, isTurbopack, isRspack } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
skipStart: true,
})
test('logbox: can recover from a syntax error without losing state', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
await session.evaluate(() => document.querySelector('button').click())
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await session.patch('index.js', `export default () => <div/`)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Expected '>', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (1:27)
Expected '>', got '<eof>'
> 1 | export default () => <div/
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expected '>', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Expected '>', got '<eof>'
│ ,----
│ 1 | export default () => <div/
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Expected '>', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Expected '>', got '<eof>'
,----
1 | export default () => <div/
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
await check(
() => session.evaluate(() => document.querySelector('p').textContent),
/Count: 1/
)
await session.waitForNoRedbox()
})
test('logbox: can recover from a event handler error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => {
setCount(c => c + 1)
throw new Error('oops')
}, [setCount])
return (
<main>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('0')
await browser.elementByCss('button').click()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('1')
await expect(browser).toDisplayRedbox(`
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (7:11) @ Index.useCallback[increment]
> 7 | throw new Error('oops')
| ^",
"stack": [
"Index.useCallback[increment] index.js (7:11)",
],
}
`)
await session.patch(
'index.js',
outdent`
import { useCallback, useState } from 'react'
export default function Index() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [setCount])
return (
<main>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
)
}
`
)
await session.waitForNoRedbox()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 1')
await browser.elementByCss('button').click()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Count: 2')
await session.waitForNoRedbox()
})
it('logbox: can recover from a component error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
await session.write(
'child.js',
outdent`
export default function Child() {
return <p>Hello</p>;
}
`
)
await session.patch(
'index.js',
outdent`
import Child from './child'
export default function Index() {
return (
<main>
<Child />
</main>
)
}
`
)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello')
await session.patch(
'child.js',
outdent`
// hello
export default function Child() {
throw new Error('oops')
}
`
)
if (isReact18 && isTurbopack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
},
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
},
]
`)
} else if (isReact18 && isRspack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
},
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
},
]
`)
} else {
if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "oops",
"environmentLabel": null,
"label": "Runtime Error",
"source": "child.js (3:9) @ Child
> 3 | throw new Error('oops')
| ^",
"stack": [
"Child child.js (3:9)",
],
}
`)
}
}
const didNotReload = await session.patch(
'child.js',
outdent`
export default function Child() {
return <p>Hello</p>;
}
`
)
expect(didNotReload).toBe(true)
await session.waitForNoRedbox()
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('Hello')
})
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262
it('render error not shown right after syntax error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
// Starting 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')
// Break it with a syntax error:
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;
`
)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Expected '{', got 'return'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (5:5)
Expected '{', got 'return'
> 5 | return <h1>Default Export</h1>;
| ^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expected '{', got 'return'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Expected '{', got 'return'
│ ,-[5:1]
│ 2 |
│ 3 | class ClassDefault extends React.Component {
│ 4 | render()
│ 5 | return <h1>Default Export</h1>;
│ : ^^^^^^
│ 6 | }
│ 7 | }
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Expected '{', got 'return'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Expected '{', got 'return'
,-[5:1]
2 |
3 | class ClassDefault extends React.Component {
4 | render()
5 | return <h1>Default Export</h1>;
: ^^^^^^
6 | }
7 | }
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
// Now change the code to introduce a runtime error without fixing the syntax error:
await session.patch(
'index.js',
outdent`
import * as React from 'react';
class ClassDefault extends React.Component {
render()
throw new Error('nooo');
return <h1>Default Export</h1>;
}
}
export default ClassDefault;
`
)
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Expected '{', got 'throw'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (5:5)
Expected '{', got 'throw'
> 5 | throw new Error('nooo');
| ^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expected '{', got 'throw'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Expected '{', got 'throw'
│ ,-[5:1]
│ 2 |
│ 3 | class ClassDefault extends React.Component {
│ 4 | render()
│ 5 | throw new Error('nooo');
│ : ^^^^^
│ 6 | return <h1>Default Export</h1>;
│ 7 | }
│ 8 | }
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " x Expected '{', got 'throw'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Expected '{', got 'throw'
,-[5:1]
2 |
3 | class ClassDefault extends React.Component {
4 | render()
5 | throw new Error('nooo');
: ^^^^^
6 | return <h1>Default Export</h1>;
7 | }
8 | }
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
// Now fix the syntax error:
await session.patch(
'index.js',
outdent`
import * as React from 'react';
class ClassDefault extends React.Component {
render() {
throw new Error('nooo');
return <h1>Default Export</h1>;
}
}
export default ClassDefault;
`
)
// wait for patch to get applied
await retry(async () => {
await expect(session.getRedboxSource()).resolves.toInclude('render() {')
})
if (isReact18 && isTurbopack) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "nooo",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ ClassDefault.render
> 5 | throw new Error('nooo');
| ^",
"stack": [
"ClassDefault.render index.js (5:11)",
],
},
{
"description": "nooo",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ ClassDefault.render
> 5 | throw new Error('nooo');
| ^",
"stack": [
"ClassDefault.render index.js (5:11)",
],
},
]
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "nooo",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:11) @ ClassDefault.render
> 5 | throw new Error('nooo');
| ^",
"stack": [
"ClassDefault.render index.js (5:11)",
],
}
`)
}
})
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016
it('stuck error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
// We start here.
await session.patch(
'index.js',
outdent`
import * as React from 'react';
function FunctionDefault() {
return <h1>Default Export Function</h1>;
}
export default FunctionDefault;
`
)
// We add a new file. Let's call it Foo.js.
await session.write(
'Foo.js',
outdent`
// intentionally skips export
export default function Foo() {
return React.createElement('h1', null, 'Foo');
}
`
)
// We edit our first file to use it.
await session.patch(
'index.js',
outdent`
import * as React from 'react';
import Foo from './Foo';
function FunctionDefault() {
return <Foo />;
}
export default FunctionDefault;
`
)
if (isReact18 && (isRspack || isTurbopack)) {
await expect(browser).toDisplayRedbox(`
[
{
"description": "React is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "Foo.js (3:3) @ Foo
> 3 | return React.createElement('h1', null, 'Foo');
| ^",
"stack": [
"Foo Foo.js (3:3)",
],
},
{
"description": "React is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "Foo.js (3:3) @ Foo
> 3 | return React.createElement('h1', null, 'Foo');
| ^",
"stack": [
"Foo Foo.js (3:3)",
],
},
]
`)
} else {
if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "React is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "Foo.js (3:3) @ Foo
> 3 | return React.createElement('h1', null, 'Foo');
| ^",
"stack": [
"Foo Foo.js (3:3)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "React is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "Foo.js (3:3) @ Foo
> 3 | return React.createElement('h1', null, 'Foo');
| ^",
"stack": [
"Foo Foo.js (3:3)",
],
}
`)
}
}
// Let's add that to Foo.
await session.patch(
'Foo.js',
outdent`
import * as React from 'react';
export default function Foo() {
return React.createElement('h1', null, 'Foo');
}
`
)
// Expected: this fixes the problem
await session.waitForNoRedbox()
})
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098
test('syntax > runtime error', async () => {
await using sandbox = await createSandbox(next)
const { browser, session } = sandbox
// Start here.
await session.patch(
'index.js',
outdent`
import * as React from 'react';
export default function FunctionNamed() {
return <div />
}
`
)
// TODO: this acts weird without above step
await session.patch(
'index.js',
outdent`
import * as React from 'react';
let i = 0
setInterval(() => {
i++
throw Error('no ' + i)
}, 1000)
export default function FunctionNamed() {
return <div />
}
`
)
await new Promise((resolve) => setTimeout(resolve, 1000))
if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no 1",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:9) @ eval
> 5 | throw Error('no ' + i)
| ^",
"stack": [
"eval index.js (5:9)",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": "no 1",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (5:9) @ eval
> 5 | throw Error('no ' + i)
| ^",
"stack": [
"eval index.js (5:9)",
],
}
`)
}
// Make a syntax error.
await session.patch(
'index.js',
outdent`
import * as React from 'react';
let i = 0
setInterval(() => {
i++
throw Error('no ' + i)
}, 1000)
export default function FunctionNamed() {`
)
await new Promise((resolve) => setTimeout(resolve, 1000))
if (isTurbopack) {
// TODO: Remove this branching once import traces are implemented in Turbopack
await expect(browser).toDisplayRedbox(`
{
"description": "Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (7:42)
Expected '}', got '<eof>'
> 7 | export default function FunctionNamed() {
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Expected '}', got '<eof>'
│ ,-[7:1]
│ 4 | i++
│ 5 | throw Error('no ' + i)
│ 6 | }, 1000)
│ 7 | export default function FunctionNamed() {
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Expected '}', got '<eof>'
,-[7:1]
4 | i++
5 | throw Error('no ' + i)
6 | }, 1000)
7 | export default function FunctionNamed() {
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
// Test that runtime error does not take over:
await new Promise((resolve) => setTimeout(resolve, 2000))
if (isTurbopack) {
// TODO: Remove this branching once import traces are implemented in Turbopack
await expect(browser).toDisplayRedbox(`
{
"description": "Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js (7:42)
Expected '}', got '<eof>'
> 7 | export default function FunctionNamed() {
| ^",
"stack": [],
}
`)
} else if (isRspack) {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
╰─▶ × Error: x Expected '}', got '<eof>'
│ ,-[7:1]
│ 4 | i++
│ 5 | throw Error('no ' + i)
│ 6 | }, 1000)
│ 7 | export default function FunctionNamed() {
\`----
│ Caused by:
│ Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
} else {
await expect({ browser, next }).toDisplayRedbox(`
{
"description": " x Expected '}', got '<eof>'",
"environmentLabel": null,
"label": "Build Error",
"source": "./index.js
Error: x Expected '}', got '<eof>'
,-[7:1]
4 | i++
5 | throw Error('no ' + i)
6 | }, 1000)
7 | export default function FunctionNamed() {
\`----
Caused by:
Syntax Error
Import trace for requested module:
./index.js
./pages/index.js",
"stack": [],
}
`)
}
})
})

View File

@@ -0,0 +1,8 @@
const isClient = typeof window !== 'undefined'
export default function Component() {
return (
<div>
<p>{isClient ? 'client' : 'server'}</p>
</div>
)
}

View File

@@ -0,0 +1,8 @@
import Component from './component'
export default function Mismatch() {
return (
<main>
<Component />
</main>
)
}

View File

@@ -0,0 +1 @@
module.exports = {}

View File

@@ -0,0 +1 @@
export { default } from '../index'

View File

@@ -0,0 +1 @@
export default () => 'new sandbox'

View File

@@ -0,0 +1 @@
module.exports = {}

View File

@@ -0,0 +1 @@
export { default } from '../index'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,432 @@
/* eslint-env jest */
import { nextTestSetup } from 'e2e-utils'
import { createSandbox } from 'development-sandbox'
import { outdent } from 'outdent'
const isRspack = process.env.NEXT_RSPACK !== undefined
const initialFiles = new Map([
['app/_.js', ''], // app dir need to exists, otherwise the SWC RSC checks will not run
[
'pages/index.js',
outdent`
import Comp from '../components/Comp'
export default function Page() { return <Comp /> }
`,
],
[
'components/Comp.js',
outdent`
export default function Comp() {
return <p>Hello world</p>
}
`,
],
])
describe('Error Overlay for server components compiler errors in pages', () => {
const { next } = nextTestSetup({
files: {},
skipStart: true,
})
test("importing 'next/headers' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await session.patch(
'components/Comp.js',
outdent`
import { cookies } from 'next/headers'
export default function Page() {
return <p>hello world</p>
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/That only works in a Server Component/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a component that needs "next/headers". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { cookies } from 'next/headers'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return <p>hello world</p>
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a component that needs "next/headers". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│ ,-[1:1]
│ 1 | import { cookies } from 'next/headers'
│ : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return <p>hello world</p>
\`----"
`)
} else {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a component that needs "next/headers". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { cookies } from 'next/headers'
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return <p>hello world</p>
\`----
Import trace for requested module:
./components/Comp.js
./pages/index.js"
`)
}
})
test("importing 'server-only' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import 'server-only'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/That only works in a Server Component/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a component that needs "server-only". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import 'server-only'
| ^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a component that needs "server-only". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│ ,-[1:1]
│ 1 | import 'server-only'
│ : ^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
\`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a component that needs "server-only". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import 'server-only'
: ^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----
Import trace for requested module:"
`)
}
})
test("importing after from 'next/server' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { after } from 'next/server'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/That only works in a Server Component/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:10)
You're importing a component that needs "after". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { after } from 'next/server'
| ^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a component that needs "after". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│ ,-[1:1]
│ 1 | import { after } from 'next/server'
│ : ^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
\`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a component that needs "after". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { after } from 'next/server'
: ^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----
Import trace for requested module:"
`)
}
})
test("importing 'next/root-params' in pages", async () => {
const files = new Map([
...initialFiles,
[
'components/Comp.js',
outdent`
import { foo } from 'next/root-params'
export default function Page() {
return 'hello world'
}
`,
],
[
// the import is guarded behind an experimental flag
'next.config.js',
outdent`
module.exports = { experimental: { rootParams: true } }
`,
],
])
await using sandbox = await createSandbox(next, files)
const { session } = sandbox
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/That only works in a Server Component/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a component that needs "next/root-params". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { foo } from 'next/root-params'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a component that needs "next/root-params". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│ ,-[1:1]
│ 1 | import { foo } from 'next/root-params'
│ : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
\`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a component that needs "next/root-params". That only works in a Server Component which is not supported in the pages/ directory. Read more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { foo } from 'next/root-params'
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----"
`)
}
})
describe("importing 'next/cache' APIs in pages", () => {
test.each([
'revalidatePath',
'revalidateTag',
'cacheLife',
'cacheTag',
'revalidatePath',
'revalidateTag',
])('%s is not allowed', async (api) => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { ${api} } from 'next/cache'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
`You're importing a component that needs "${api}". That only works in a Server Component which is not supported in the pages/ directory.`
)
})
test.each([
'unstable_cache', // useless in client, but doesn't technically error
'unstable_noStore', // no-op in client, but allowed for legacy reasons
])('%s is allowed', async (api) => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { ${api} } from 'next/cache'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForNoRedbox()
})
})
})
const takeUpToString = (text: string, str: string): string =>
text.includes(str) ? text.slice(0, text.indexOf(str) + str.length) : text