Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
896 lines
35 KiB
TypeScript
896 lines
35 KiB
TypeScript
/* eslint-env jest */
|
|
import { FileRef, nextTestSetup } from 'e2e-utils'
|
|
import path from 'path'
|
|
import { outdent } from 'outdent'
|
|
import {
|
|
waitForNoRedbox,
|
|
getRedboxErrorLink,
|
|
getToastErrorCount,
|
|
retry,
|
|
} from 'next-test-utils'
|
|
|
|
describe('Error overlay for hydration errors in App router', () => {
|
|
const { next, isTurbopack } = nextTestSetup({
|
|
files: new FileRef(path.join(__dirname, 'fixtures', 'hydration-errors')),
|
|
})
|
|
|
|
it('includes a React docs link when hydration error does occur', async () => {
|
|
const browser = await next.browser('/text-mismatch', {
|
|
pushErrorAsConsoleLog: true,
|
|
})
|
|
|
|
const logs = await browser.log()
|
|
expect(logs).toEqual(
|
|
expect.arrayContaining([
|
|
{
|
|
// TODO: Should probably link to https://nextjs.org/docs/messages/react-hydration-error instead.
|
|
message: expect.stringContaining(
|
|
'https://react.dev/link/hydration-mismatch'
|
|
),
|
|
source: 'error',
|
|
},
|
|
])
|
|
)
|
|
})
|
|
|
|
it('should show correct hydration error when client and server render different text', async () => {
|
|
const browser = await next.browser('/text-mismatch')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
<main className="child">
|
|
+ client
|
|
- server
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/text-mismatch/page.tsx (8:7) @ Mismatch
|
|
> 8 | <main className="child">{isClient ? 'client' : 'server'}</main>
|
|
| ^",
|
|
"stack": [
|
|
"main <anonymous>",
|
|
"Mismatch app/(default)/text-mismatch/page.tsx (8:7)",
|
|
],
|
|
}
|
|
`)
|
|
|
|
expect(await getRedboxErrorLink(browser)).toMatchInlineSnapshot(
|
|
`"See more info here: https://nextjs.org/docs/messages/react-hydration-error"`
|
|
)
|
|
|
|
await next.patchFile(
|
|
'app/(default)/text-mismatch/page.tsx',
|
|
outdent`
|
|
'use client'
|
|
export default function Mismatch() {
|
|
return (
|
|
<div className="parent">
|
|
<main className="child">Value</main>
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
async () => {
|
|
await waitForNoRedbox(browser)
|
|
expect(await browser.elementByCss('.child').text()).toBe('Value')
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should show correct hydration error when client renders an extra element', async () => {
|
|
const browser = await next.browser('/extra-element-client')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
+ <main className="only">
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-element-client/page.tsx (6:47) @ Mismatch
|
|
> 6 | return <div className="parent">{isClient && <main className="only" />}</div>
|
|
| ^",
|
|
"stack": [
|
|
"main <anonymous>",
|
|
"Mismatch app/(default)/extra-element-client/page.tsx (6:47)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when extra attributes set on server', async () => {
|
|
const browser = await next.browser('/extra-attributes')
|
|
|
|
if (isTurbopack) {
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<script>
|
|
<script>
|
|
<Next.js Internal Component>
|
|
<RootLayout params={Promise}>
|
|
<html
|
|
- className="server-html"
|
|
>
|
|
...
|
|
...",
|
|
"description": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(extra-attributes)/layout.tsx (9:5) @ RootLayout
|
|
> 9 | <html {...(isServer ? { className: 'server-html' } : undefined)}>
|
|
| ^",
|
|
"stack": [
|
|
"html <anonymous>",
|
|
"RootLayout app/(extra-attributes)/layout.tsx (9:5)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<RootLayout params={Promise}>
|
|
<html
|
|
- className="server-html"
|
|
>
|
|
...
|
|
...",
|
|
"description": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(extra-attributes)/layout.tsx (9:5) @ RootLayout
|
|
> 9 | <html {...(isServer ? { className: 'server-html' } : undefined)}>
|
|
| ^",
|
|
"stack": [
|
|
"html <anonymous>",
|
|
"RootLayout app/(extra-attributes)/layout.tsx (9:5)",
|
|
],
|
|
}
|
|
`)
|
|
}
|
|
})
|
|
|
|
it('should show correct hydration error when client renders an extra text node', async () => {
|
|
const browser = await next.browser('/extra-text-node-client')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
<header>
|
|
+ second
|
|
- <footer className="3">
|
|
...
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-text-node-client/page.tsx (7:5) @ Mismatch
|
|
> 7 | <div className="parent">
|
|
| ^",
|
|
"stack": [
|
|
"div <anonymous>",
|
|
"Mismatch app/(default)/extra-text-node-client/page.tsx (7:5)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when server renders an extra element', async () => {
|
|
const browser = await next.browser('/extra-element-server')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
- <main className="only">
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-element-server/page.tsx (6:10) @ Mismatch
|
|
> 6 | return <div className="parent">{isServer && <main className="only" />}</div>
|
|
| ^",
|
|
"stack": [
|
|
"div <anonymous>",
|
|
"Mismatch app/(default)/extra-element-server/page.tsx (6:10)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when server renders an extra text node', async () => {
|
|
const browser = await next.browser('/extra-text-node-server')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
- only
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-text-node-server/page.tsx (6:10) @ Mismatch
|
|
> 6 | return <div className="parent">{isServer && 'only'}</div>
|
|
| ^",
|
|
"stack": [
|
|
"div <anonymous>",
|
|
"Mismatch app/(default)/extra-text-node-server/page.tsx (6:10)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when server renders an extra text node in an invalid place', async () => {
|
|
const browser = await next.browser('/extra-text-node-invalid-place')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(2)
|
|
})
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
> test
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, text nodes cannot be a child of <tr>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/extra-text-node-invalid-place/page.tsx (7:9) @ Page
|
|
> 7 | <tr>test</tr>
|
|
| ^",
|
|
"stack": [
|
|
"tr <anonymous>",
|
|
"Page app/(default)/extra-text-node-invalid-place/page.tsx (7:9)",
|
|
],
|
|
},
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
+ <table>
|
|
- test
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-text-node-invalid-place/page.tsx (5:5) @ Page
|
|
> 5 | <table>
|
|
| ^",
|
|
"stack": [
|
|
"table <anonymous>",
|
|
"Page app/(default)/extra-text-node-invalid-place/page.tsx (5:5)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when server renders an extra whitespace in an invalid place', async () => {
|
|
const browser = await next.browser('/extra-whitespace-invalid-place')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
> <table>
|
|
> {" "}
|
|
...
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, whitespace text nodes cannot be a child of <table>. Make sure you don't have any extra whitespace between tags on each line of your source code.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/extra-whitespace-invalid-place/page.tsx (5:5) @ Page
|
|
> 5 | <table>
|
|
| ^",
|
|
"stack": [
|
|
"table <anonymous>",
|
|
"Page app/(default)/extra-whitespace-invalid-place/page.tsx (5:5)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should show correct hydration error when client renders an extra node inside Suspense content', async () => {
|
|
const browser = await next.browser('/extra-node-suspense')
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Mismatch params={Promise} searchParams={Promise}>
|
|
<div className="parent">
|
|
<Suspense fallback={<p>}>
|
|
<header>
|
|
+ <main className="second">
|
|
- <footer className="3">
|
|
...
|
|
...
|
|
...
|
|
...",
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/extra-node-suspense/page.tsx (12:22) @ Mismatch
|
|
> 12 | {isClient && <main className="second" />}
|
|
| ^",
|
|
"stack": [
|
|
"main <anonymous>",
|
|
"Mismatch app/(default)/extra-node-suspense/page.tsx (12:22)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should not show a hydration error when using `useId` in a client component', async () => {
|
|
const browser = await next.browser('/use-id', {
|
|
pushErrorAsConsoleLog: true,
|
|
})
|
|
|
|
const logs = await browser.log()
|
|
const errors = logs.filter((x) => x.source === 'error')
|
|
expect(errors).toEqual([])
|
|
})
|
|
|
|
it('should only show one hydration error when bad nesting happened - p under p', async () => {
|
|
const browser = await next.browser('/p-under-p')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(2)
|
|
})
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
> <p>
|
|
> <p>
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, <p> cannot be a descendant of <p>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/p-under-p/page.tsx (6:7) @ Page
|
|
> 6 | <p>Nested p tags</p>
|
|
| ^",
|
|
"stack": [
|
|
"p <anonymous>",
|
|
"Page app/(default)/p-under-p/page.tsx (6:7)",
|
|
],
|
|
},
|
|
{
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/p-under-p/page.tsx (6:7) @ Page
|
|
> 6 | <p>Nested p tags</p>
|
|
| ^",
|
|
"stack": [
|
|
"p <anonymous>",
|
|
"Page app/(default)/p-under-p/page.tsx (6:7)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('should only show one hydration error when bad nesting happened - div under p', async () => {
|
|
const browser = await next.browser('/div-under-p')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(2)
|
|
})
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
<div>
|
|
<div>
|
|
> <p>
|
|
> <div>
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, <div> cannot be a descendant of <p>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/div-under-p/page.tsx (8:11) @ Page
|
|
> 8 | <div>Nested div under p tag</div>
|
|
| ^",
|
|
"stack": [
|
|
"div <anonymous>",
|
|
"Page app/(default)/div-under-p/page.tsx (8:11)",
|
|
],
|
|
},
|
|
{
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/div-under-p/page.tsx (8:11) @ Page
|
|
> 8 | <div>Nested div under p tag</div>
|
|
| ^",
|
|
"stack": [
|
|
"div <anonymous>",
|
|
"Page app/(default)/div-under-p/page.tsx (8:11)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('should only show one hydration error when bad nesting happened - div > tr', async () => {
|
|
const browser = await next.browser('/tr-under-div')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(2)
|
|
})
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
> <div>
|
|
> <tr>
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, <tr> cannot be a child of <div>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/tr-under-div/page.tsx (6:7) @ Page
|
|
> 6 | <tr></tr>
|
|
| ^",
|
|
"stack": [
|
|
"tr <anonymous>",
|
|
"Page app/(default)/tr-under-div/page.tsx (6:7)",
|
|
],
|
|
},
|
|
{
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/tr-under-div/page.tsx (6:7) @ Page
|
|
> 6 | <tr></tr>
|
|
| ^",
|
|
"stack": [
|
|
"tr <anonymous>",
|
|
"Page app/(default)/tr-under-div/page.tsx (6:7)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('should show the highlighted bad nesting html snippet when bad nesting happened', async () => {
|
|
const browser = await next.browser('/bad-nesting')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(3)
|
|
})
|
|
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Page params={Promise} searchParams={Promise}>
|
|
> <p>
|
|
<span>
|
|
<span>
|
|
<span>
|
|
<span>
|
|
> <p>
|
|
...
|
|
...
|
|
...",
|
|
"description": "In HTML, <p> cannot be a descendant of <p>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/bad-nesting/page.tsx (10:15) @ Page
|
|
> 10 | <p>hello world</p>
|
|
| ^",
|
|
"stack": [
|
|
"p <anonymous>",
|
|
"Page app/(default)/bad-nesting/page.tsx (10:15)",
|
|
],
|
|
},
|
|
{
|
|
"code": "E394",
|
|
"description": "<p> cannot contain a nested <p>.
|
|
See this log for the ancestor stack trace.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(default)/bad-nesting/page.tsx (5:5) @ Page
|
|
> 5 | <p>
|
|
| ^",
|
|
"stack": [
|
|
"p <anonymous>",
|
|
"Page app/(default)/bad-nesting/page.tsx (5:5)",
|
|
],
|
|
},
|
|
{
|
|
"description": "Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/(default)/bad-nesting/page.tsx (10:15) @ Page
|
|
> 10 | <p>hello world</p>
|
|
| ^",
|
|
"stack": [
|
|
"p <anonymous>",
|
|
"Page app/(default)/bad-nesting/page.tsx (10:15)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
})
|
|
|
|
it('should show error if script is directly placed under html instead of body', async () => {
|
|
const browser = await next.browser('/script-under-html')
|
|
|
|
await retry(async () => {
|
|
expect(await getToastErrorCount(browser)).toBe(
|
|
// One error for "Cannot render a sync or defer <script>"
|
|
3
|
|
)
|
|
})
|
|
|
|
if (isTurbopack) {
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"description": "Cannot render a sync or defer <script> outside the main document without knowing its order. Try adding async="" or moving it into the root <head> tag.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (8:7) @ RootLayout
|
|
> 8 | <Script
|
|
| ^",
|
|
"stack": [
|
|
"RootLayout app/(script-under-html)/layout.tsx (8:7)",
|
|
],
|
|
},
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<script>
|
|
<script>
|
|
<RootLayout>
|
|
> <html>
|
|
<body>
|
|
<Script src="https://ex..." strategy="beforeInte...">
|
|
> <script nonce={undefined} dangerouslySetInnerHTML={{__html:"(self.__ne..."}}>
|
|
...
|
|
...",
|
|
"description": "In HTML, <script> cannot be a child of <html>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (8:7) @ RootLayout
|
|
> 8 | <Script
|
|
| ^",
|
|
"stack": [
|
|
"script <anonymous>",
|
|
"RootLayout app/(script-under-html)/layout.tsx (8:7)",
|
|
],
|
|
},
|
|
{
|
|
"code": "E394",
|
|
"description": "<html> cannot contain a nested <script>.
|
|
See this log for the ancestor stack trace.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (6:5) @ RootLayout
|
|
> 6 | <html>
|
|
| ^",
|
|
"stack": [
|
|
"html <anonymous>",
|
|
"RootLayout app/(script-under-html)/layout.tsx (6:5)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
} else {
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"code": "E394",
|
|
"description": "Cannot render a sync or defer <script> outside the main document without knowing its order. Try adding async="" or moving it into the root <head> tag.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (8:7) @ RootLayout
|
|
> 8 | <Script
|
|
| ^",
|
|
"stack": [
|
|
"RootLayout app/(script-under-html)/layout.tsx (8:7)",
|
|
],
|
|
},
|
|
{
|
|
"code": "E394",
|
|
"componentStack": "...
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<Next.js Internal Component>
|
|
<RootLayout>
|
|
> <html>
|
|
<body>
|
|
<Script src="https://ex..." strategy="beforeInte...">
|
|
> <script nonce={undefined} dangerouslySetInnerHTML={{__html:"(self.__ne..."}}>
|
|
...
|
|
...",
|
|
"description": "In HTML, <script> cannot be a child of <html>.
|
|
This will cause a hydration error.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (8:7) @ RootLayout
|
|
> 8 | <Script
|
|
| ^",
|
|
"stack": [
|
|
"script <anonymous>",
|
|
"RootLayout app/(script-under-html)/layout.tsx (8:7)",
|
|
],
|
|
},
|
|
{
|
|
"code": "E394",
|
|
"description": "<html> cannot contain a nested <script>.
|
|
See this log for the ancestor stack trace.",
|
|
"environmentLabel": null,
|
|
"label": "Console Error",
|
|
"source": "app/(script-under-html)/layout.tsx (6:5) @ RootLayout
|
|
> 6 | <html>
|
|
| ^",
|
|
"stack": [
|
|
"html <anonymous>",
|
|
"RootLayout app/(script-under-html)/layout.tsx (6:5)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
}
|
|
})
|
|
})
|