From 4b652df204507962033784827778497042fe3916 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 16 Mar 2024 17:49:04 +0100 Subject: [PATCH 1/3] fix(error-overlay): matching html tag with brackets in hydration error --- .../RuntimeError/component-stack-pseudo-html.tsx | 5 ++++- .../internal/container/RuntimeError/index.tsx | 2 +- .../internal/helpers/hydration-error-info.ts | 11 +++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx index db1bec664331a..88501559a8a6d 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx @@ -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 = '' 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 9f72196cf5889..8e9ec07791633 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 @@ -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); } ` diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts index bfd111d268762..78fd18aa89d4b 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts @@ -20,9 +20,9 @@ const isHtmlTagsWarning = (msg: NullableText) => Boolean(msg && htmlTagsWarnings.has(msg)) const isTextMismatchWarning = (msg: NullableText) => - Boolean(msg && textMismatchWarnings.has(msg)) + Boolean(msg && textMismatchWarning === msg) const isTextInTagsMismatchWarning = (msg: NullableText) => - Boolean(msg && textInTagsMismatchWarnings.has(msg)) + Boolean(msg && textAndTagsMismatchWarnings.has(msg)) const isKnownHydrationWarning = (msg: NullableText) => isHtmlTagsWarning(msg) || @@ -37,13 +37,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. From 6d323da56b48dffab936f9fc42145cd90cc28cca Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 16 Mar 2024 18:08:01 +0100 Subject: [PATCH 2/3] add test --- .../acceptance-app/hydration-error.test.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/test/development/acceptance-app/hydration-error.test.ts b/test/development/acceptance-app/hydration-error.test.ts index 3465997d0d7dc..30818fc4b08c9 100644 --- a/test/development/acceptance-app/hydration-error.test.ts +++ b/test/development/acceptance-app/hydration-error.test.ts @@ -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([ @@ -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 ( +

+

Nested div under p tag
+

+ ) + } + `, + ], + ]) + ) + + 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,
cannot be a descendant of

.\nThis will cause a hydration error.' + ) + + const pseudoHtml = await session.getRedboxComponentStack() + + // Turbopack currently has longer component stack trace + if (isTurbopack) { + expect(pseudoHtml).toMatchInlineSnapshot(` + "... + +

+ ^^^ +

+ ^^^^^" + `) + } else { + expect(pseudoHtml).toMatchInlineSnapshot(` + " +

+ ^^^ +

+ ^^^^^" + `) + } + + await cleanup() + }) + it('should show the highlighted bad nesting html snippet when bad nesting happened', async () => { const { cleanup, session } = await sandbox( next, From d0c5c595c718faac52cc64342abc150f831fc244 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 16 Mar 2024 18:26:57 +0100 Subject: [PATCH 3/3] simplify --- .../react-dev-overlay/internal/helpers/hydration-error-info.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts index 78fd18aa89d4b..0832cd19b31f6 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts @@ -19,8 +19,7 @@ export const getHydrationWarningType = ( const isHtmlTagsWarning = (msg: NullableText) => Boolean(msg && htmlTagsWarnings.has(msg)) -const isTextMismatchWarning = (msg: NullableText) => - Boolean(msg && textMismatchWarning === msg) +const isTextMismatchWarning = (msg: NullableText) => textMismatchWarning === msg const isTextInTagsMismatchWarning = (msg: NullableText) => Boolean(msg && textAndTagsMismatchWarnings.has(msg))