Files
Jiachi LiuandGitHub 14c8900e70 style(dev-overlay): refine the error message header styling (#63823)
### What

Polish the UX based on the feedbacks from @sambecker 

* Fix the font that still use mono
* Align the color, to use red for the warnings
* Give the title to build error
* Only highlight the nextjs doc url as link

### After vs Before

#### Runtime Error


<img width="335" alt="image"
src="https://github.com/vercel/next.js/assets/4800338/64f2f692-1eae-41db-9287-046aff9ba112">
<img width="335" alt="image"
src="https://github.com/vercel/next.js/assets/4800338/2d4bad15-745e-4083-ba57-8968c0f321c2">

#### Build Error

<img width="335" alt="image"
src="https://github.com/vercel/next.js/assets/4800338/ecfc3883-4522-40c6-b042-6d85e7ea970e">
<img width="335" alt="image"
src="https://github.com/vercel/next.js/assets/4800338/bbf37ec5-b639-4b95-a87f-9911182e431c">



Closes NEXT-2958
2024-04-05 23:39:22 +02:00

312 lines
9.5 KiB
TypeScript

import { check, getRedboxSource, hasRedbox } from 'next-test-utils'
import stripAnsi from 'strip-ansi'
import { createNextDescribe } from 'e2e-utils'
createNextDescribe(
'middleware - development errors',
{
files: __dirname,
env: { __NEXT_TEST_WITH_DEVTOOL: '1' },
},
({ next }) => {
beforeEach(async () => {
await next.stop()
})
describe('when middleware throws synchronously', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
export default function () {
throw new Error('boom')
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
const output = stripAnsi(next.cliOutput)
await check(() => {
if (process.env.TURBOPACK) {
expect(stripAnsi(next.cliOutput)).toMatch(
/middleware.js \(\d+:\d+\) @ Object.__TURBOPACK__default__export__ \[as handler\]/
)
} else {
expect(stripAnsi(next.cliOutput)).toMatch(
/middleware.js \(\d+:\d+\) @ Object.default \[as handler\]/
)
}
expect(stripAnsi(next.cliOutput)).toMatch(/boom/)
return 'success'
}, 'success')
expect(output).not.toContain(
'webpack-internal:///(middleware)/./middleware.js'
)
})
it('renders the error correctly and recovers', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(true)
await next.patchFile('middleware.js', `export default function () {}`)
await hasRedbox(browser)
})
})
describe('when middleware contains an unhandled rejection', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
import { NextResponse } from 'next/server'
async function throwError() {
throw new Error('async boom!')
}
export default function () {
throwError()
return NextResponse.next()
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
await check(
() => stripAnsi(next.cliOutput),
new RegExp(`unhandledRejection: Error: async boom!`, 'm')
)
// expect(output).not.toContain(
// 'webpack-internal:///(middleware)/./middleware.js'
// )
})
it('does not render the error', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(false)
expect(await browser.elementByCss('#page-title')).toBeTruthy()
})
})
describe('when running invalid dynamic code with eval', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
import { NextResponse } from 'next/server'
export default function () {
eval('test')
return NextResponse.next()
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
const output = stripAnsi(next.cliOutput)
await check(() => {
expect(stripAnsi(next.cliOutput)).toMatch(
/middleware.js \(\d+:\d+\) @ eval/
)
expect(stripAnsi(next.cliOutput)).toMatch(/test is not defined/)
return 'success'
}, 'success')
expect(output).not.toContain(
'webpack-internal:///(middleware)/./middleware.js'
)
})
it('renders the error correctly and recovers', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxSource(browser)).toContain(`eval('test')`)
await next.patchFile('middleware.js', `export default function () {}`)
await hasRedbox(browser)
})
})
describe('when throwing while loading the module', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
import { NextResponse } from 'next/server'
throw new Error('booooom!')
export default function () {
return NextResponse.next()
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
const output = stripAnsi(next.cliOutput)
await check(() => {
expect(stripAnsi(next.cliOutput)).toMatch(
/middleware.js \(\d+:\d+\) @ <unknown>/
)
expect(stripAnsi(next.cliOutput)).toMatch(/booooom!/)
return 'success'
}, 'success')
expect(output).not.toContain(
'webpack-internal:///(middleware)/./middleware.js'
)
})
it('renders the error correctly and recovers', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(true)
const source = await getRedboxSource(browser)
expect(source).toContain(`throw new Error('booooom!')`)
expect(source).toContain('middleware.js')
expect(source).not.toContain('//middleware.js')
await next.patchFile('middleware.js', `export default function () {}`)
await hasRedbox(browser)
})
})
describe('when there is an unhandled rejection while loading the module', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
import { NextResponse } from 'next/server'
(async function(){
throw new Error('you shall see me')
})()
export default function () {
return NextResponse.next()
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
await check(
() => stripAnsi(next.cliOutput),
new RegExp(`unhandledRejection: Error: you shall see me`, 'm')
)
// expect(output).not.toContain(
// 'webpack-internal:///(middleware)/./middleware.js'
// )
})
it('does not render the error', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(false)
expect(await browser.elementByCss('#page-title')).toBeTruthy()
})
})
describe('when there is an unhandled rejection while loading a dependency', () => {
beforeEach(async () => {
await next.patchFile(
'middleware.js',
`
import { NextResponse } from 'next/server'
import './lib/unhandled'
export default function () {
return NextResponse.next()
}`
)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
const output = stripAnsi(next.cliOutput)
await check(
() => stripAnsi(next.cliOutput),
new RegExp(
` uncaughtException: Error: This file asynchronously fails while loading`,
'm'
)
)
expect(output).not.toContain(
'webpack-internal:///(middleware)/./middleware.js'
)
})
it('does not render the error', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(false)
expect(await browser.elementByCss('#page-title')).toBeTruthy()
})
})
describe('when there is a compilation error from boot', () => {
beforeEach(async () => {
await next.patchFile('middleware.js', `export default function () }`)
await next.start()
})
it('logs the error correctly', async () => {
await next.fetch('/')
await check(async () => {
expect(next.cliOutput).toContain(`Expected '{', got '}'`)
expect(
next.cliOutput.split(`Expected '{', got '}'`).length
).toBeGreaterThanOrEqual(2)
return 'success'
}, 'success')
})
it('renders the error correctly and recovers', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(true)
expect(
await browser.elementByCss('#nextjs__container_errors_desc').text()
).toEqual('Failed to compile')
await next.patchFile('middleware.js', `export default function () {}`)
await hasRedbox(browser)
expect(await browser.elementByCss('#page-title')).toBeTruthy()
})
})
describe('when there is a compilation error after boot', () => {
beforeEach(async () => {
await next.patchFile('middleware.js', `export default function () {}`)
await next.start()
})
it('logs the error correctly', async () => {
await next.patchFile('middleware.js', `export default function () }`)
await next.fetch('/')
await check(() => {
expect(next.cliOutput).toContain(`Expected '{', got '}'`)
expect(next.cliOutput.split(`Expected '{', got '}'`).length).toEqual(
2
)
return 'success'
}, 'success')
})
it('renders the error correctly and recovers', async () => {
const browser = await next.browser('/')
expect(await hasRedbox(browser)).toBe(false)
await next.patchFile('middleware.js', `export default function () }`)
expect(await hasRedbox(browser)).toBe(true)
await next.patchFile('middleware.js', `export default function () {}`)
expect(await hasRedbox(browser)).toBe(false)
expect(await browser.elementByCss('#page-title')).toBeTruthy()
})
})
}
)