File size: 7,788 Bytes
b91e262 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | import stripAnsi from 'strip-ansi'
import { nextTestSetup } from 'e2e-utils'
import {
waitForNoRedbox,
waitForNoErrorToast,
hasErrorToast,
retry,
} from 'next-test-utils'
import { outdent } from 'outdent'
describe('Cache Components Dev Errors', () => {
const { isTurbopack, next, isRspack } = nextTestSetup({
files: __dirname,
})
it('should show a red box error on the SSR render', async () => {
const browser = await next.browser('/error')
// TODO(veil): The "Page <anonymous>" frame should be omitted.
// Interestingly, it only appears on initial load, and not when
// soft-navigating to the page (see test below).
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Route "/error" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/error/page.tsx (2:23) @ Page
> 2 | const random = Math.random()
| ^",
"stack": [
"Page app/error/page.tsx (2:23)",
"Page <anonymous>",
],
}
`)
})
it('should not show a red box error on client navigations', async () => {
const browser = await next.browser('/no-error')
await retry(async () => {
expect(await hasErrorToast(browser)).toBe(false)
})
await browser.elementByCss("[href='/error']").click()
await waitForNoErrorToast(browser)
await browser.loadPage(`${next.url}/error`)
// TODO: React should not include the anon stack in the Owner Stack.
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Route "/error" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/error/page.tsx (2:23) @ Page
> 2 | const random = Math.random()
| ^",
"stack": [
"Page app/error/page.tsx (2:23)",
"Page <anonymous>",
],
}
`)
})
it('should not log unhandled rejections for persistently thrown top-level errors', async () => {
const cliOutputLength = next.cliOutput.length
const res = await next.fetch('/top-level-error')
expect(res.status).toBe(500)
await retry(() => {
const cliOutput = stripAnsi(next.cliOutput.slice(cliOutputLength))
expect(cliOutput).toContain('GET /top-level-error 500')
})
expect(next.cliOutput.slice(cliOutputLength)).not.toContain(
'unhandledRejection'
)
})
// NOTE: when update this snapshot, use `pnpm build` in packages/next to avoid next source code get mapped to source.
it('should display error when component accessed data without suspense boundary', async () => {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/no-accessed-data')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: Route "/no-accessed-data"'
)
})
expect(stripAnsi(next.cliOutput.slice(outputIndex))).toContain(
'https://nextjs.org/docs/messages/blocking-route'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Data that blocks navigation was accessed outside of <Suspense>
This delays the entire page from rendering, resulting in a slow user experience. Next.js uses this error to ensure your app loads instantly on every navigation. Uncached data such as fetch(...), cached data with a low expire time, or connection() are all examples of data that only resolve on navigation.
To fix this, you can either:
Provide a fallback UI using <Suspense> around this component. This allows Next.js to stream its contents to the user as soon as it's ready, without blocking the rest of the app.
or
Move the asynchronous await into a Cache Component ("use cache"). This allows Next.js to statically prerender the component as part of the HTML document, so it's instantly visible to the user.
Learn more: https://nextjs.org/docs/messages/blocking-route",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/no-accessed-data/page.js (2:9) @ Page
> 2 | await new Promise((r) => setTimeout(r, 200))
| ^",
"stack": [
"Page app/no-accessed-data/page.js (2:9)",
],
}
`)
})
it('should clear segment errors after correcting them', async () => {
let browser: any
await next.patchFile(
'app/page.tsx',
outdent`
export const revalidate = 10
export default function Page() {
return (
<div>Hello World</div>
);
}
`,
async () => {
browser = await next.browser('/')
if (isTurbopack) {
await expect(browser).toDisplayRedbox(`
{
"description": "Ecmascript file had an error",
"environmentLabel": null,
"label": "Build Error",
"source": "./app/page.tsx (1:14)
Ecmascript file had an error
> 1 | export const revalidate = 10
| ^^^^^^^^^^",
"stack": [],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": " ╰─▶ × Error: x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.",
"environmentLabel": null,
"label": "Build Error",
"source": "./app/page.tsx
╰─▶ × Error: x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.
│ ,-[1:1]
│ 1 | export const revalidate = 10
│ : ^^^^^^^^^^
│ 2 | export default function Page() {
│ 3 | return (
│ 4 | <div>Hello World</div>
│ \`----
│",
"stack": [],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
{
"description": " x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.",
"environmentLabel": null,
"label": "Build Error",
"source": "./app/page.tsx
Error: x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.
,-[1:1]
1 | export const revalidate = 10
: ^^^^^^^^^^
2 | export default function Page() {
3 | return (
4 | <div>Hello World</div>
\`----",
"stack": [],
}
`)
}
}
)
await waitForNoRedbox(browser)
})
})
|