From 2334de2bde105534f82086d1da14875dbf1045a2 Mon Sep 17 00:00:00 2001
From: devjiwonchoi
Date: Thu, 16 Jan 2025 01:21:42 +0900
Subject: [PATCH] [DevOverlay] Enable new UI when PPR testing is enabled
---
.github/workflows/build_and_test.yml | 6 +-
.../webpack/plugins/define-env-plugin.ts | 6 +-
.../_experimental/app/error-boundary.tsx | 28 +-
.../_experimental/app/react-dev-overlay.tsx | 55 ++--
.../components/code-frame/code-frame.tsx | 4 +-
.../dev-tools-indicator.tsx | 7 +-
.../internal/next-logo.tsx | 3 +-
.../internal/components/terminal/terminal.tsx | 4 +-
.../_experimental/pages/error-boundary.tsx | 10 +-
.../_experimental/pages/react-dev-overlay.tsx | 63 ++--
.../internal/container/RuntimeError/index.tsx | 2 +-
.../acceptance-app/ReactRefreshLogBox.test.ts | 36 ++-
.../acceptance/ReactRefreshLogBox.test.ts | 24 +-
.../capture-console-error-owner-stack.test.ts | 272 +++++++++---------
.../capture-console-error.test.ts | 262 +++++++++--------
.../app-dir/dynamic-error-trace/index.test.ts | 5 +-
.../error-ignored-frames.test.ts | 270 ++++++++---------
.../invalid-element-type.test.ts | 24 +-
.../owner-stack-invalid-element-type.test.ts | 36 ++-
...owner-stack-react-missing-key-prop.test.ts | 24 +-
.../react-missing-key-prop.test.ts | 16 +-
.../app-dir/owner-stack/owner-stack.test.ts | 199 ++++++++-----
.../prerender-indicator.test.ts | 54 ++--
...r-component-next-dynamic-ssr-false.test.ts | 48 +++-
.../app-dir/ssr-in-rsc/ssr-in-rsc.test.ts | 32 ++-
.../error-on-next-codemod-comment.test.ts | 47 ++-
.../non-root-project-monorepo.test.ts | 22 +-
.../use-cache-unknown-cache-kind.test.ts | 43 ++-
.../use-cache-without-dynamic-io.test.ts | 95 ++++--
test/lib/next-test-utils.ts | 91 +++++-
30 files changed, 1134 insertions(+), 654 deletions(-)
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index f750f6c673c65..0936964a0749f 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -578,7 +578,7 @@ jobs:
uses: ./.github/workflows/build_reusable.yml
with:
nodeVersion: 18.18.2
- afterBuild: __NEXT_EXPERIMENTAL_PPR=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" node run-tests.js --timings -c ${TEST_CONCURRENCY} --type integration
+ afterBuild: __NEXT_EXPERIMENTAL_PPR=true __NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" node run-tests.js --timings -c ${TEST_CONCURRENCY} --type integration
stepName: 'test-ppr-integration'
secrets: inherit
@@ -593,7 +593,7 @@ jobs:
group: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
uses: ./.github/workflows/build_reusable.yml
with:
- afterBuild: __NEXT_EXPERIMENTAL_PPR=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" NEXT_TEST_MODE=dev node run-tests.js --timings -g ${{ matrix.group }} -c ${TEST_CONCURRENCY} --type development
+ afterBuild: __NEXT_EXPERIMENTAL_PPR=true __NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" NEXT_TEST_MODE=dev node run-tests.js --timings -g ${{ matrix.group }} -c ${TEST_CONCURRENCY} --type development
stepName: 'test-ppr-dev-${{ matrix.group }}'
secrets: inherit
@@ -608,7 +608,7 @@ jobs:
group: [1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 7/7]
uses: ./.github/workflows/build_reusable.yml
with:
- afterBuild: __NEXT_EXPERIMENTAL_PPR=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" NEXT_TEST_MODE=start node run-tests.js --timings -g ${{ matrix.group }} -c ${TEST_CONCURRENCY} --type production
+ afterBuild: __NEXT_EXPERIMENTAL_PPR=true __NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY=true NEXT_EXTERNAL_TESTS_FILTERS="test/ppr-tests-manifest.json" NEXT_TEST_MODE=start node run-tests.js --timings -g ${{ matrix.group }} -c ${TEST_CONCURRENCY} --type production
stepName: 'test-ppr-prod-${{ matrix.group }}'
secrets: inherit
diff --git a/packages/next/src/build/webpack/plugins/define-env-plugin.ts b/packages/next/src/build/webpack/plugins/define-env-plugin.ts
index 417df966c57a0..f15fb1c4b0c83 100644
--- a/packages/next/src/build/webpack/plugins/define-env-plugin.ts
+++ b/packages/next/src/build/webpack/plugins/define-env-plugin.ts
@@ -286,7 +286,11 @@ export function getDefineEnv({
}
: undefined),
'process.env.__NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY':
- config.experimental.newDevOverlay ?? false,
+ // When `__NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY` is set on CI,
+ // we need to pass it here so it can be enabled.
+ process.env.__NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY === 'true' ||
+ config.experimental.newDevOverlay ||
+ false,
}
const userDefines = config.compiler?.define ?? {}
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/app/error-boundary.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/app/error-boundary.tsx
index 804918cda20ab..799d73a03892a 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/app/error-boundary.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/app/error-boundary.tsx
@@ -3,13 +3,14 @@ import type { GlobalErrorComponent } from '../../../error-boundary'
import { PureComponent } from 'react'
import { RuntimeErrorHandler } from '../../../errors/runtime-error-handler'
-type DevToolsErrorBoundaryProps = {
+type DevOverlayErrorBoundaryProps = {
children: React.ReactNode
- onError: (value: boolean) => void
+ devOverlay: React.ReactNode
globalError: [GlobalErrorComponent, React.ReactNode]
+ onError: (value: boolean) => void
}
-type DevToolsErrorBoundaryState = {
+type DevOverlayErrorBoundaryState = {
isReactError: boolean
reactError: unknown
}
@@ -37,9 +38,9 @@ function ErroredHtml({
)
}
-export class DevToolsErrorBoundary extends PureComponent<
- DevToolsErrorBoundaryProps,
- DevToolsErrorBoundaryState
+export class DevOverlayErrorBoundary extends PureComponent<
+ DevOverlayErrorBoundaryProps,
+ DevOverlayErrorBoundaryState
> {
state = { isReactError: false, reactError: null }
@@ -61,13 +62,18 @@ export class DevToolsErrorBoundary extends PureComponent<
}
render() {
+ const { children, globalError, devOverlay } = this.props
+ const { isReactError, reactError } = this.state
+
const fallback = (
-
+
)
- return this.state.isReactError ? fallback : this.props.children
+ return (
+ <>
+ {isReactError ? fallback : children}
+ {devOverlay}
+ >
+ )
}
}
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.tsx
index 589d0a56a157b..fbcac3085401d 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.tsx
@@ -2,7 +2,7 @@ import type { OverlayState } from '../../shared'
import type { GlobalErrorComponent } from '../../../error-boundary'
import { useState } from 'react'
-import { DevToolsErrorBoundary } from './error-boundary'
+import { DevOverlayErrorBoundary } from './error-boundary'
import { ShadowPortal } from '../internal/components/shadow-portal'
import { Base } from '../internal/styles/base'
import { ComponentStyles } from '../internal/styles/component-styles'
@@ -24,34 +24,35 @@ export default function ReactDevOverlay({
const [isErrorOverlayOpen, setIsErrorOverlayOpen] = useState(false)
const { readyErrors } = useErrorHook({ errors: state.errors, isAppDir: true })
- return (
- <>
-
- {children}
-
+ const devOverlay = (
+
+
+
+
+
-
-
-
-
-
+
-
+
+
+ )
-
-
- >
+ return (
+
+ {children}
+
)
}
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/code-frame/code-frame.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/code-frame/code-frame.tsx
index ee870c4b0bc4f..e26a42e109526 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/code-frame/code-frame.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/code-frame/code-frame.tsx
@@ -36,9 +36,7 @@ export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {
.map((line, a) =>
~(a = line.indexOf('|'))
? line.substring(0, a) +
- line
- .substring(a + 1)
- .replace(`^\\ {${miniLeadingSpacesLength}}`, '')
+ line.substring(a).replace(`^\\ {${miniLeadingSpacesLength}}`, '')
: line
)
.join('\n')
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/dev-tools-indicator.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/dev-tools-indicator.tsx
index e238741f80d85..a281ee3f099f9 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/dev-tools-indicator.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/dev-tools-indicator.tsx
@@ -126,6 +126,7 @@ const DevToolsPopover = ({
return (
@@ -205,14 +207,15 @@ const IndicatorRow = ({
label,
value,
onClick,
+ ...props
}: {
label: string
value: React.ReactNode
onClick?: () => void
-}) => {
+} & React.HTMLAttributes) => {
const Wrapper = onClick ? 'button' : 'div'
return (
-
+
{label}
{value}
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/internal/next-logo.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/internal/next-logo.tsx
index d669d16b5b237..125168b061061 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/internal/next-logo.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dev-tools-indicator/internal/next-logo.tsx
@@ -301,7 +301,8 @@ export const NextLogo = ({
aria-label="Open issues overlay"
onClick={onIssuesClick}
>
- {issueCount} {issueCount === 1 ? 'Issue' : 'Issues'}
+ {issueCount}{' '}
+ {issueCount === 1 ? 'Issue' : 'Issues'}
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/pages/error-boundary.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/pages/error-boundary.tsx
index 8eda7feccdfae..92ef7b7db7013 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/pages/error-boundary.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/pages/error-boundary.tsx
@@ -1,15 +1,15 @@
import * as React from 'react'
-type DevToolsErrorBoundaryProps = {
+type DevOverlayErrorBoundaryProps = {
children?: React.ReactNode
onError: (error: Error, componentStack: string | null) => void
isMounted?: boolean
}
-type DevToolsErrorBoundaryState = { error: Error | null }
+type DevOverlayErrorBoundaryState = { error: Error | null }
-export class DevToolsErrorBoundary extends React.PureComponent<
- DevToolsErrorBoundaryProps,
- DevToolsErrorBoundaryState
+export class DevOverlayErrorBoundary extends React.PureComponent<
+ DevOverlayErrorBoundaryProps,
+ DevOverlayErrorBoundaryState
> {
state = { error: null }
diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/pages/react-dev-overlay.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/pages/react-dev-overlay.tsx
index 7cf676c91ff18..72246638322a2 100644
--- a/packages/next/src/client/components/react-dev-overlay/_experimental/pages/react-dev-overlay.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/_experimental/pages/react-dev-overlay.tsx
@@ -6,7 +6,7 @@ import { Base } from '../internal/styles/base'
import { ComponentStyles } from '../internal/styles/component-styles'
import { CssReset } from '../internal/styles/css-reset'
-import { DevToolsErrorBoundary } from './error-boundary'
+import { DevOverlayErrorBoundary } from './error-boundary'
import { usePagesReactDevOverlay } from '../../pages/hooks'
import { Colors } from '../internal/styles/colors'
import { ErrorOverlay } from '../internal/components/errors/error-overlay/error-overlay'
@@ -20,41 +20,50 @@ interface ReactDevOverlayProps {
}
export default function ReactDevOverlay({ children }: ReactDevOverlayProps) {
- const { isMounted, state, onComponentError, hasRuntimeErrors } =
- usePagesReactDevOverlay()
-
- const [isErrorOverlayOpen, setIsErrorOverlayOpen] = useState(hasRuntimeErrors)
+ const {
+ isMounted,
+ state,
+ onComponentError,
+ hasRuntimeErrors,
+ hasBuildError,
+ } = usePagesReactDevOverlay()
const { readyErrors } = useErrorHook({
errors: state.errors,
isAppDir: false,
})
+ const [isErrorOverlayOpen, setIsErrorOverlayOpen] = useState(true)
+
return (
<>
-
+
{children ?? null}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {isMounted && (
+
+
+
+
+
+
+
+
+ {(hasRuntimeErrors || hasBuildError) && (
+
+ )}
+
+ )}
>
)
}
diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx
index d1dfabbbac359..cb4b88b5bd1ff 100644
--- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx
+++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx
@@ -33,7 +33,7 @@ export function RuntimeError({ error }: RuntimeErrorProps) {
? []
: filteredFrames.slice(0, firstFirstPartyFrameIndex),
trailingCallStackFrames: filteredFrames.slice(
- firstFirstPartyFrameIndex + 1
+ firstFirstPartyFrameIndex < 0 ? 0 : firstFirstPartyFrameIndex
),
}
}, [error.frames, isIgnoredExpanded])
diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
index ef1623a5671b0..ab425f3519000 100644
--- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts
+++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
@@ -799,7 +799,12 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
const stackFrames = (
await Promise.all(stackFrameElements.map((f) => f.innerText()))
).filter(Boolean)
- expect(stackFrames).toEqual([])
+ expect(stackFrames).toEqual([
+ outdent`
+ Page
+ app/page.js (4:11)
+ `,
+ ])
})
test('Call stack for server error', async () => {
@@ -831,7 +836,12 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
const stackFrames = (
await Promise.all(stackFrameElements.map((f) => f.innerText()))
).filter(Boolean)
- expect(stackFrames).toEqual([])
+ expect(stackFrames).toEqual([
+ outdent`
+ Page
+ app/page.js (2:9)
+ `,
+ ])
})
test('should hide unrelated frames in stack trace with unknown anonymous calls', async () => {
@@ -876,12 +886,20 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
// TODO: investigate the column number is off by 1 between turbo and webpack
process.env.TURBOPACK
? [
+ outdent`
+
+ app/page.js (4:13)
+ `,
outdent`
Page
app/page.js (5:6)
`,
]
: [
+ outdent`
+ eval
+ app/page.js (4:13)
+ `,
outdent`
Page
app/page.js (5:5)
@@ -923,8 +941,8 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
stackFrameElements.map((f) => f.innerText())
)
- // No following rest of displayed stack frames by default
- expect(stackFrames.length).toBe(0)
+ // No ignore-listed frames to be displayed by default
+ expect(stackFrames.length).toBe(1)
})
test('Server component errors should open up in fullscreen', async () => {
@@ -1211,13 +1229,15 @@ export default function Home() {
if (isTurbopack) {
// FIXME: display the sourcemapped stack frames
- expect(stackFrames).toMatchInlineSnapshot(
- `"at [project]/app/page.js [app-client] (ecmascript) (app/page.js (2:1))"`
- )
+ expect(stackFrames).toMatchInlineSnapshot(`
+ "at [project]/app/utils.ts [app-client] (ecmascript) (app/utils.ts (1:7))
+ at [project]/app/page.js [app-client] (ecmascript) (app/page.js (2:1))"
+ `)
} else {
// FIXME: Webpack stack frames are not source mapped
expect(stackFrames).toMatchInlineSnapshot(`
- "at ./app/utils.ts ()
+ "at eval (app/utils.ts (1:7))
+ at ./app/utils.ts ()
at options.factory ()
at __webpack_require__ ()
at fn ()
diff --git a/test/development/acceptance/ReactRefreshLogBox.test.ts b/test/development/acceptance/ReactRefreshLogBox.test.ts
index 643a7b54f23d6..66f748ce99405 100644
--- a/test/development/acceptance/ReactRefreshLogBox.test.ts
+++ b/test/development/acceptance/ReactRefreshLogBox.test.ts
@@ -793,10 +793,19 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
await session.assertHasRedbox()
const stack = await getStackFramesContent(browser)
- expect(stack).toMatchInlineSnapshot(`
- "at Array.map ()
- at Page (pages/index.js (2:13))"
- `)
+ if (process.env.TURBOPACK) {
+ expect(stack).toMatchInlineSnapshot(`
+ "at (pages/index.js (3:11))
+ at Array.map ()
+ at Page (pages/index.js (2:13))"
+ `)
+ } else {
+ expect(stack).toMatchInlineSnapshot(`
+ "at eval (pages/index.js (3:11))
+ at Array.map ()
+ at Page (pages/index.js (2:13))"
+ `)
+ }
})
test('should collapse nodejs internal stack frames from stack trace', async () => {
@@ -824,9 +833,10 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
await session.assertHasRedbox()
const stack = await getStackFramesContent(browser)
- expect(stack).toMatchInlineSnapshot(
- `"at getServerSideProps (pages/index.js (8:3))"`
- )
+ expect(stack).toMatchInlineSnapshot(`
+ "at createURL (pages/index.js (4:3))
+ at getServerSideProps (pages/index.js (8:3))"
+ `)
await toggleCollapseCallStackFrames(browser)
const stackCollapsed = await getStackFramesContent(browser)
diff --git a/test/development/app-dir/capture-console-error-owner-stack/capture-console-error-owner-stack.test.ts b/test/development/app-dir/capture-console-error-owner-stack/capture-console-error-owner-stack.test.ts
index d4f3395677ec5..196b009b1e247 100644
--- a/test/development/app-dir/capture-console-error-owner-stack/capture-console-error-owner-stack.test.ts
+++ b/test/development/app-dir/capture-console-error-owner-stack/capture-console-error-owner-stack.test.ts
@@ -42,45 +42,49 @@ describe('app-dir - capture-console-error-owner-stack', () => {
// TODO(veil): Inconsistent cursor position for the "Page" frame
if (process.env.TURBOPACK) {
expect(result).toMatchInlineSnapshot(`
- {
- "callStacks": "button
- (0:0)
- Page
- app/browser/event/page.js (5:5)",
- "count": 1,
- "description": "trigger an console ",
- "source": "app/browser/event/page.js (7:17) @ onClick
-
- 5 |