diff --git a/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx b/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx
index ee85548be45103f..931a1c93ca74517 100644
--- a/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx
+++ b/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx
@@ -76,9 +76,9 @@ class ReactDevOverlay extends React.PureComponent<
) : hasBuildError ? (
) : hasRuntimeErrors ? (
-
+
) : reactError ? (
-
+
) : undefined}
) : undefined}
diff --git a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
index 84d83cd46f58c65..22d0c90777f4043 100644
--- a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
+++ b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
@@ -24,10 +24,15 @@ export type SupportedErrorEvent = {
id: number
event: UnhandledErrorAction | UnhandledRejectionAction
}
-export type ErrorsProps = { errors: SupportedErrorEvent[] }
+export type ErrorsProps = {
+ errors: SupportedErrorEvent[]
+ initialDisplayState: DisplayState
+}
type ReadyErrorEvent = ReadyRuntimeError
+type DisplayState = 'minimized' | 'fullscreen' | 'hidden'
+
function getErrorSignature(ev: SupportedErrorEvent): string {
const { event } = ev
switch (event.type) {
@@ -73,7 +78,10 @@ const HotlinkedText: React.FC<{
)
}
-export const Errors: React.FC = function Errors({ errors }) {
+export const Errors: React.FC = function Errors({
+ errors,
+ initialDisplayState,
+}) {
const [lookups, setLookups] = React.useState(
{} as { [eventId: string]: ReadyErrorEvent }
)
@@ -137,9 +145,8 @@ export const Errors: React.FC = function Errors({ errors }) {
}
}, [nextError])
- const [displayState, setDisplayState] = React.useState<
- 'minimized' | 'fullscreen' | 'hidden'
- >('fullscreen')
+ const [displayState, setDisplayState] =
+ React.useState(initialDisplayState)
const [activeIdx, setActiveIndex] = React.useState(0)
const previous = React.useCallback((e?: MouseEvent | TouchEvent) => {
e?.preventDefault()
diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
index 3a372d42d427698..6e73c94dfa900eb 100644
--- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts
+++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
@@ -49,7 +49,7 @@ describe('ReactRefreshLogBox app', () => {
)
await session.evaluate(() => document.querySelector('a').click())
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
expect(await session.getRedboxSource()).toMatchSnapshot()
await cleanup()
@@ -481,7 +481,7 @@ describe('ReactRefreshLogBox app', () => {
)
await new Promise((resolve) => setTimeout(resolve, 1000))
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
if (process.platform === 'win32') {
expect(await session.getRedboxSource()).toMatchSnapshot()
} else {
@@ -568,7 +568,7 @@ describe('ReactRefreshLogBox app', () => {
`export default function FunctionDefault() { throw new Error('no'); }`
)
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
expect(await session.getRedboxSource()).toMatchSnapshot()
expect(
await session.evaluate(() => document.querySelector('h2').textContent)
@@ -770,9 +770,8 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
const header = await session.getRedboxDescription()
expect(header).toMatchSnapshot()
@@ -816,9 +815,8 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
const header2 = await session.getRedboxDescription()
expect(header2).toMatchSnapshot()
@@ -862,9 +860,8 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
const header3 = await session.getRedboxDescription()
expect(header3).toMatchSnapshot()
@@ -908,9 +905,8 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- expect(await session.hasRedbox(true)).toBe(true)
+ await session.waitForAndOpenRuntimeError()
const header4 = await session.getRedboxDescription()
expect(header4).toMatchInlineSnapshot(
@@ -1085,4 +1081,76 @@ describe('ReactRefreshLogBox app', () => {
await cleanup()
})
+
+ test('Unhandled errors and rejections opens up in the minimized state', async () => {
+ const { session, browser, cleanup } = await sandbox(next)
+
+ const file = `
+ export default function Index() {
+ //
+ setTimeout(() => {
+ throw new Error('Unhandled error')
+ }, 0)
+ setTimeout(() => {
+ Promise.reject(new Error('Undhandled rejection'))
+ }, 0)
+ return (
+ <>
+
+
+ >
+ )
+ }
+ `
+
+ await session.patch('index.js', file)
+
+ // Unhandled error and rejection in setTimeout
+ expect(
+ await browser.waitForElementByCss('.nextjs-toast-errors').text()
+ ).toBe('2 errors')
+
+ // Unhandled error in event handler
+ await browser.elementById('unhandled-error').click()
+ await check(
+ () => browser.elementByCss('.nextjs-toast-errors').text(),
+ /3 errors/
+ )
+
+ // Unhandled rejection in event handler
+ await browser.elementById('unhandled-rejection').click()
+ await check(
+ () => browser.elementByCss('.nextjs-toast-errors').text(),
+ /4 errors/
+ )
+ expect(await session.hasRedbox()).toBe(false)
+
+ // Add Component error
+ await session.patch(
+ 'index.js',
+ file.replace(
+ '//',
+ "if (typeof window !== 'undefined') throw new Error('Component error')"
+ )
+ )
+
+ // Render error should "win" and show up in fullscreen
+ expect(await session.hasRedbox(true)).toBe(true)
+
+ await cleanup()
+ })
})
diff --git a/test/development/acceptance-app/helpers.ts b/test/development/acceptance-app/helpers.ts
index f0787fb84b76dad..c97d311cb6bc74f 100644
--- a/test/development/acceptance-app/helpers.ts
+++ b/test/development/acceptance-app/helpers.ts
@@ -112,6 +112,9 @@ export async function sandbox(
}
return source
},
+ async waitForAndOpenRuntimeError() {
+ return browser.waitForElementByCss('[data-nextjs-toast]').click()
+ },
},
async cleanup() {
await browser.close()