Skip to content

Commit

Permalink
fix(error-overlay): matching html tag with brackets in hydration error (
Browse files Browse the repository at this point in the history
#63365)

### What

* The tags extracted from hydration errors could be "<div>" and "p", we
need to stripe the brackets "<>" for matching the tag name from
component stack
* Change the dimmed text color to lighter so you can see it better in
light mode

#### After vs Before

<img width="450"
src="https://github.com/vercel/next.js/assets/4800338/6caed5ac-c073-41cc-a699-eb29f3785d59">

<img width="450"
src="https://github.com/vercel/next.js/assets/4800338/926aa80f-2a49-4362-b77e-16b819955b0a">


Closes NEXT-2828
  • Loading branch information
huozhi authored Mar 16, 2024
1 parent dc2be64 commit 53c2188
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ export function PseudoHtmlDiff({
const [isHtmlCollapsed, toggleCollapseHtml] = useState(shouldCollapse)

const htmlComponents = useMemo(() => {
const tagNames = isHtmlTagsWarning ? [firstContent, secondContent] : []
const tagNames = isHtmlTagsWarning
? // tags could have < or > in the name, so we always remove them to match
[firstContent.replace(/<|>/g, ''), secondContent.replace(/<|>/g, '')]
: []
const nestedHtmlStack: React.ReactNode[] = []
let lastText = ''

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,6 @@ export const styles = css`
font-size: 0;
}
[data-nextjs-container-errors-pseudo-html--tag-adjacent='false'] {
color: var(--color-accents-3);
color: var(--color-accents-1);
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ export const getHydrationWarningType = (
const isHtmlTagsWarning = (msg: NullableText) =>
Boolean(msg && htmlTagsWarnings.has(msg))

const isTextMismatchWarning = (msg: NullableText) =>
Boolean(msg && textMismatchWarnings.has(msg))
const isTextMismatchWarning = (msg: NullableText) => textMismatchWarning === msg
const isTextInTagsMismatchWarning = (msg: NullableText) =>
Boolean(msg && textInTagsMismatchWarnings.has(msg))
Boolean(msg && textAndTagsMismatchWarnings.has(msg))

const isKnownHydrationWarning = (msg: NullableText) =>
isHtmlTagsWarning(msg) ||
Expand All @@ -37,13 +36,12 @@ const htmlTagsWarnings = new Set([
'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s',
])
const textInTagsMismatchWarnings = new Set([
const textAndTagsMismatchWarnings = new Set([
'Warning: Expected server HTML to contain a matching text node for "%s" in <%s>.%s',
'Warning: Did not expect server HTML to contain the text node "%s" in <%s>.%s',
])
const textMismatchWarnings = new Set([
'Warning: Text content did not match. Server: "%s" Client: "%s"%s',
])
const textMismatchWarning =
'Warning: Text content did not match. Server: "%s" Client: "%s"%s'

/**
* Patch console.error to capture hydration errors.
Expand Down
65 changes: 64 additions & 1 deletion test/development/acceptance-app/hydration-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ describe('Error overlay for hydration errors', () => {
await cleanup()
})

it('should only show one hydration error when bad nesting happened', async () => {
it('should only show one hydration error when bad nesting happened - p under p', async () => {
const { cleanup, session, browser } = await sandbox(
next,
new Map([
Expand Down Expand Up @@ -412,6 +412,69 @@ describe('Error overlay for hydration errors', () => {
await cleanup()
})

it('should only show one hydration error when bad nesting happened - div under p', async () => {
const { cleanup, session, browser } = await sandbox(
next,
new Map([
[
'app/page.js',
outdent`
'use client'
export default function Page() {
return (
<p>
<div>Nested div under p tag</div>
</p>
)
}
`,
],
])
)

await session.waitForAndOpenRuntimeError()
expect(await session.hasRedbox()).toBe(true)

const totalErrorCount = await browser
.elementByCss('[data-nextjs-dialog-header-total-count]')
.text()
expect(totalErrorCount).toBe('1')

const description = await session.getRedboxDescription()
expect(description).toContain(
'Error: Hydration failed because the initial UI does not match what was rendered on the server.'
)
const warning = await session.getRedboxDescriptionWarning()
expect(warning).toContain(
'In HTML, <div> cannot be a descendant of <p>.\nThis will cause a hydration error.'
)

const pseudoHtml = await session.getRedboxComponentStack()

// Turbopack currently has longer component stack trace
if (isTurbopack) {
expect(pseudoHtml).toMatchInlineSnapshot(`
"...
<Page>
<p>
^^^
<div>
^^^^^"
`)
} else {
expect(pseudoHtml).toMatchInlineSnapshot(`
"<Page>
<p>
^^^
<div>
^^^^^"
`)
}

await cleanup()
})

it('should show the highlighted bad nesting html snippet when bad nesting happened', async () => {
const { cleanup, session } = await sandbox(
next,
Expand Down

0 comments on commit 53c2188

Please sign in to comment.