fix dev overlay pseudo html collapsing (#62728)

Dev overlay should show the full stack trace after clicking the
uncollase button to display the full component stack

Closes NEXT-2658
This commit is contained in:
Jiachi Liu 2024-03-01 19:13:57 +01:00 committed by GitHub
parent 8034042215
commit 4d4b45ec2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 180 additions and 31 deletions

View file

@ -2,9 +2,6 @@ import { useMemo, Fragment, useState } from 'react'
import type { ComponentStackFrame } from '../../helpers/parse-component-stack'
import { CollapseIcon } from '../../icons/CollapseIcon'
// In total it will display 6 rows.
const MAX_NON_COLLAPSED_FRAMES = 6
/**
*
* Format component stack into pseudo HTML
@ -51,6 +48,8 @@ export function PseudoHtmlDiff({
hydrationMismatchType: 'tag' | 'text'
} & React.HTMLAttributes<HTMLPreElement>) {
const isHtmlTagsWarning = hydrationMismatchType === 'tag'
// For text mismatch, mismatched text will take 2 rows, so we display 4 rows of component stack
const MAX_NON_COLLAPSED_FRAMES = isHtmlTagsWarning ? 6 : 4
const shouldCollapse = componentStackFrames.length > MAX_NON_COLLAPSED_FRAMES
const [isHtmlCollapsed, toggleCollapseHtml] = useState(shouldCollapse)
@ -77,16 +76,10 @@ export function PseudoHtmlDiff({
const isLastFewFrames =
!isHtmlTagsWarning && index >= componentList.length - 6
if (
nestedHtmlStack.length >= MAX_NON_COLLAPSED_FRAMES &&
isHtmlCollapsed
) {
return
}
if ((isHtmlTagsWarning && isRelatedTag) || isLastFewFrames) {
const codeLine = (
<span>
<span>{spaces}</span>
{spaces}
<span
{...(isHighlightedTag
? {
@ -112,7 +105,14 @@ export function PseudoHtmlDiff({
)
nestedHtmlStack.push(wrappedCodeLine)
} else {
if ((isHtmlTagsWarning && !isHtmlCollapsed) || isLastFewFrames) {
if (
nestedHtmlStack.length >= MAX_NON_COLLAPSED_FRAMES &&
isHtmlCollapsed
) {
return
}
if (!isHtmlCollapsed || isLastFewFrames) {
nestedHtmlStack.push(
<span key={nestedHtmlStack.length}>
{spaces}
@ -154,6 +154,7 @@ export function PseudoHtmlDiff({
serverContent,
isHtmlTagsWarning,
hydrationMismatchType,
MAX_NON_COLLAPSED_FRAMES,
])
return (

View file

@ -27,20 +27,47 @@ describe('Component Stack in error overlay', () => {
<main>
<Component>
<div>
"server"
"client""
<p>
"server"
"client""
`)
await session.toggleComponentStack()
await session.toggleCollapseComponentStack()
expect(await session.getRedboxComponentStack()).toMatchInlineSnapshot(`
"<InnerLayoutRouter>
<Mismatch>
<main>
<Component>
<div>
<p>
"server"
"client""
"<Root>
<ServerRoot>
<AppRouter>
<ErrorBoundary>
<ErrorBoundaryHandler>
<Router>
<HotReload>
<ReactDevOverlay>
<DevRootNotFoundBoundary>
<NotFoundBoundary>
<NotFoundErrorBoundary>
<RedirectBoundary>
<RedirectErrorBoundary>
<RootLayout>
<html>
<body>
<OuterLayoutRouter>
<RenderFromTemplateContext>
<ScrollAndFocusHandler>
<InnerScrollAndFocusHandler>
<ErrorBoundary>
<LoadingBoundary>
<NotFoundBoundary>
<NotFoundErrorBoundary>
<RedirectBoundary>
<RedirectErrorBoundary>
<InnerLayoutRouter>
<Mismatch>
<main>
<Component>
<div>
<p>
"server"
"client""
`)
} else {
expect(await session.getRedboxComponentStack()).toMatchInlineSnapshot(`

View file

@ -58,8 +58,9 @@ describe('Error overlay for hydration errors', () => {
<InnerLayoutRouter>
<Mismatch>
<div>
"server"
"client""
<main>
"server"
"client""
`)
} else {
expect(pseudoHtml).toMatchInlineSnapshot(`
@ -430,7 +431,9 @@ describe('Error overlay for hydration errors', () => {
^^^
<span>
...
<span>"
<span>
<p>
^^^"
`)
} else {
expect(pseudoHtml).toMatchInlineSnapshot(`
@ -447,4 +450,121 @@ describe('Error overlay for hydration errors', () => {
await cleanup()
})
it('should collapse and uncollapse properly when there are many frames', async () => {
const { cleanup, session } = await sandbox(
next,
new Map([
[
'app/page.js',
outdent`
'use client'
const isServer = typeof window === 'undefined'
function Mismatch() {
return (
<p>
<span>
hello {isServer ? 'server' : 'client'}
</span>
</p>
)
}
export default function Page() {
return (
<div>
<div>
<div>
<div>
<Mismatch />
</div>
</div>
</div>
</div>
)
}
`,
],
])
)
await session.waitForAndOpenRuntimeError()
expect(await session.hasRedbox()).toBe(true)
const pseudoHtml = await session.getRedboxComponentStack()
expect(pseudoHtml).toMatchInlineSnapshot(`
"...
<div>
<div>
<div>
<Mismatch>
<p>
<span>
"server"
"client""
`)
await session.toggleCollapseComponentStack()
const fullPseudoHtml = await session.getRedboxComponentStack()
if (isTurbopack) {
expect(fullPseudoHtml).toMatchInlineSnapshot(`
"<Root>
<ServerRoot>
<AppRouter>
<ErrorBoundary>
<ErrorBoundaryHandler>
<Router>
<HotReload>
<ReactDevOverlay>
<DevRootNotFoundBoundary>
<NotFoundBoundary>
<NotFoundErrorBoundary>
<RedirectBoundary>
<RedirectErrorBoundary>
<RootLayout>
<html>
<body>
<OuterLayoutRouter>
<RenderFromTemplateContext>
<ScrollAndFocusHandler>
<InnerScrollAndFocusHandler>
<ErrorBoundary>
<LoadingBoundary>
<NotFoundBoundary>
<NotFoundErrorBoundary>
<RedirectBoundary>
<RedirectErrorBoundary>
<InnerLayoutRouter>
<Page>
<div>
<div>
<div>
<div>
<Mismatch>
<p>
<span>
"server"
"client""
`)
} else {
expect(fullPseudoHtml).toMatchInlineSnapshot(`
"<Page>
<div>
<div>
<div>
<div>
<Mismatch>
<p>
<span>
"server"
"client""
`)
}
await cleanup()
})
})

View file

@ -22,8 +22,9 @@ createNextDescribe(
<main>
<Component>
<div>
"server"
"client""
<p>
"server"
"client""
`)
} else {
expect(await getRedboxComponentStack(browser)).toMatchInlineSnapshot(`

View file

@ -9,7 +9,7 @@ import {
waitFor,
waitForAndOpenRuntimeError,
getRedboxDescriptionWarning,
toggleComponentStack,
toggleCollapseComponentStack,
} from './next-test-utils'
import webdriver from './next-webdriver'
import { NextInstance } from './next-modes/base'
@ -139,8 +139,8 @@ export async function sandbox(
async getRedboxComponentStack() {
return getRedboxComponentStack(browser)
},
async toggleComponentStack() {
return toggleComponentStack(browser)
async toggleCollapseComponentStack() {
return toggleCollapseComponentStack(browser)
},
async getVersionCheckerText() {
return getVersionCheckerText(browser)

View file

@ -1068,7 +1068,7 @@ export async function getRedboxComponentStack(
return componentStackFrameTexts.join('\n').trim()
}
export async function toggleComponentStack(
export async function toggleCollapseComponentStack(
browser: BrowserInterface
): Promise<void> {
await browser