mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### What? Reverts #94617, which had reverted #94610. This re-lands the stabilization of the `catchError` API and the `retry` error prop by removing their `unstable_` prefix across source, docs, and tests. ### Why? #94610 was reverted in #94617 to unblock another change. This re-applies it now that it is no longer blocked. ### How? `git revert` of the revert commit. The repo's custom errors.json merge driver handled the error registry: the original codes `1324`/`1325` had been reclaimed by `instant` errors after the revert, so the reintroduced messages were minted as new codes `1360` (`retry()` can only be used in the App Router) and `1361` (`catchError` can only be used in Client Components), keeping errors.json append-only. The docs changelog adds `v16.3.0 | catchError became stable.` while retaining the historical `v16.2.0 | unstable_catchError introduced.` row. ### Verification - `pnpm update-error-codes` (check_error_codes passes; errors.json in sync) - `pnpm build` (full JS build, exit 0) - `pnpm --filter=next types` (exit 0) - Not run: Rust/cargo build (`react_server_components.rs` change is a one-line allow-list string revert; left to CI) <!-- NEXT_JS_LLM_PR -->
150 lines
5.3 KiB
TypeScript
150 lines
5.3 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
|
|
describe('app-dir - catchError', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
it('should recover Client Component error after reset', async () => {
|
|
const browser = await next.browser('/client-component')
|
|
|
|
// Try triggering and resetting a few times in a row
|
|
for (let i = 0; i < 5; i++) {
|
|
await browser
|
|
.elementByCss('#error-trigger-button')
|
|
.click()
|
|
.waitForElementByCss('#error-boundary-message')
|
|
|
|
expect(await browser.elementByCss('#error-boundary-message').text()).toBe(
|
|
'this is a test'
|
|
)
|
|
|
|
await browser
|
|
.elementByCss('#reset')
|
|
.click()
|
|
.waitForElementByCss('#error-trigger-button')
|
|
|
|
expect(await browser.elementByCss('#error-trigger-button').text()).toBe(
|
|
'Trigger Error!'
|
|
)
|
|
}
|
|
})
|
|
|
|
it('should recover Client Component error after retry', async () => {
|
|
const browser = await next.browser('/client-component')
|
|
|
|
// Try triggering and retrying a few times in a row
|
|
for (let i = 0; i < 5; i++) {
|
|
await browser
|
|
.elementByCss('#error-trigger-button')
|
|
.click()
|
|
.waitForElementByCss('#error-boundary-message')
|
|
|
|
expect(await browser.elementByCss('#error-boundary-message').text()).toBe(
|
|
'this is a test'
|
|
)
|
|
|
|
await browser
|
|
.elementByCss('#retry')
|
|
.click()
|
|
.waitForElementByCss('#error-trigger-button')
|
|
|
|
expect(await browser.elementByCss('#error-trigger-button').text()).toBe(
|
|
'Trigger Error!'
|
|
)
|
|
}
|
|
})
|
|
|
|
it('should recover Server Component error after retry', async () => {
|
|
const browser = await next.browser('/server-component')
|
|
|
|
expect(await browser.elementByCss('#error-boundary-message').text()).toBe(
|
|
isNextDev
|
|
? 'this is a test'
|
|
: 'Minified React error #441; visit https://react.dev/errors/441 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.'
|
|
)
|
|
|
|
await browser.elementByCss('#retry').click().waitForElementByCss('#recover')
|
|
|
|
expect(await browser.elementByCss('#recover').text()).toBe('Recovered')
|
|
})
|
|
|
|
it('should render fallback when undefined is thrown from a Client Component', async () => {
|
|
const browser = await next.browser('/client-component/throw-undefined')
|
|
|
|
await browser.elementByCss('#error-trigger-button').click()
|
|
expect(
|
|
await browser.waitForElementByCss('#error-boundary-message').text()
|
|
).toBe('An error occurred: undefined')
|
|
})
|
|
|
|
it('should render fallback when null is thrown from a Client Component', async () => {
|
|
const browser = await next.browser('/client-component/throw-null')
|
|
|
|
await browser.elementByCss('#error-trigger-button').click()
|
|
expect(
|
|
await browser.waitForElementByCss('#error-boundary-message').text()
|
|
).toBe('An error occurred: null')
|
|
})
|
|
|
|
it('should render fallback when undefined is thrown from a Server Component', async () => {
|
|
const browser = await next.browser('/server-component/throw-undefined')
|
|
// non-error values thrown during rendering get wrapped in an Error when transported over RSC.
|
|
expect(
|
|
await browser.waitForElementByCss('#error-boundary-message').text()
|
|
).toBe(
|
|
isNextDev
|
|
? 'An error occurred: Error: undefined'
|
|
: 'An error occurred: Error: Minified React error #441; visit https://react.dev/errors/441 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.'
|
|
)
|
|
})
|
|
|
|
it('should render fallback when null is thrown from a Server Component', async () => {
|
|
const browser = await next.browser('/server-component/throw-null')
|
|
// non-error values thrown during rendering get wrapped in an Error when transported over RSC.
|
|
expect(
|
|
await browser.waitForElementByCss('#error-boundary-message').text()
|
|
).toBe(
|
|
isNextDev
|
|
? 'An error occurred: Error: null'
|
|
: 'An error occurred: Error: Minified React error #441; visit https://react.dev/errors/441 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.'
|
|
)
|
|
})
|
|
|
|
it('should recover after reset on Pages Router', async () => {
|
|
const browser = await next.browser('/pages-router')
|
|
|
|
await browser
|
|
.elementByCss('#pages-trigger')
|
|
.click()
|
|
.waitForElementByCss('#pages-error-message')
|
|
|
|
expect(await browser.elementByCss('#pages-error-message').text()).toBe(
|
|
'this is a pages test'
|
|
)
|
|
|
|
await browser.eval(`document.getElementById('pages-reset')?.click()`)
|
|
await browser.waitForElementByCss('#pages-trigger')
|
|
|
|
expect(await browser.elementByCss('#pages-trigger').text()).toBe(
|
|
'Trigger Error!'
|
|
)
|
|
})
|
|
|
|
it('should throw when retry is called on Pages Router', async () => {
|
|
const browser = await next.browser('/pages-router')
|
|
|
|
await browser
|
|
.elementByCss('#pages-trigger')
|
|
.click()
|
|
.waitForElementByCss('#pages-error-message')
|
|
|
|
await browser.eval(`document.getElementById('pages-retry')?.click()`)
|
|
await browser.waitForElementByCss('#pages-retry-error')
|
|
|
|
expect(await browser.elementByCss('#pages-retry-error').text()).toBe(
|
|
'`retry()` can only be used in the App Router. Use `reset()` in the Pages Router.'
|
|
)
|
|
})
|
|
})
|