Fixes for middleware compilation errors (#37984)
* Refactor `runDependingOnPageType` * Throw specific error when the compiled middleware cannot be found * Do not render `MiddlewareNotFoundError` on dev * Allow to invalidate compilers by type * Show compile errors when middleware fails to build * Add tests
This commit is contained in:
parent
a590c4e7f3
commit
a8757135e2
9 changed files with 254 additions and 87 deletions
|
@ -442,25 +442,24 @@ export function runDependingOnPageType<T>(params: {
|
|||
pageRuntime: PageRuntime
|
||||
}) {
|
||||
if (isMiddlewareFile(params.page)) {
|
||||
return [params.onEdgeServer()]
|
||||
return { edgeServer: params.onEdgeServer() }
|
||||
} else if (params.page.match(API_ROUTE)) {
|
||||
return params.pageRuntime === 'edge'
|
||||
? [params.onEdgeServer()]
|
||||
: [params.onServer()]
|
||||
? { edgeServer: params.onEdgeServer() }
|
||||
: { server: params.onServer() }
|
||||
} else if (params.page === '/_document') {
|
||||
return [params.onServer()]
|
||||
return { server: params.onServer() }
|
||||
} else if (
|
||||
params.page === '/_app' ||
|
||||
params.page === '/_error' ||
|
||||
params.page === '/404' ||
|
||||
params.page === '/500'
|
||||
) {
|
||||
return [params.onClient(), params.onServer()]
|
||||
return { client: params.onClient(), server: params.onServer() }
|
||||
} else {
|
||||
return [
|
||||
params.onClient(),
|
||||
params.pageRuntime === 'edge' ? params.onEdgeServer() : params.onServer(),
|
||||
]
|
||||
return params.pageRuntime === 'edge'
|
||||
? { client: params.onClient(), edgeServer: params.onEdgeServer() }
|
||||
: { client: params.onClient(), server: params.onServer() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ export type AmpPageStatus = {
|
|||
type BuildStatusStore = {
|
||||
client: WebpackStatus
|
||||
server: WebpackStatus
|
||||
edgeServer?: WebpackStatus
|
||||
edgeServer: WebpackStatus
|
||||
trigger: string | undefined
|
||||
amp: AmpPageStatus
|
||||
}
|
||||
|
@ -122,11 +122,10 @@ buildStore.subscribe((state) => {
|
|||
clientWasLoading = (!buildWasDone && clientWasLoading) || client.loading
|
||||
serverWasLoading = (!buildWasDone && serverWasLoading) || server.loading
|
||||
edgeServerWasLoading =
|
||||
(!buildWasDone && serverWasLoading) || !!edgeServer?.loading
|
||||
(!buildWasDone && edgeServerWasLoading) || edgeServer.loading
|
||||
buildWasDone = false
|
||||
return
|
||||
}
|
||||
if (edgeServer?.loading) return
|
||||
|
||||
buildWasDone = true
|
||||
|
||||
|
@ -145,7 +144,7 @@ buildStore.subscribe((state) => {
|
|||
(edgeServerWasLoading ? edgeServer?.modules || 0 : 0),
|
||||
hasEdgeServer: !!edgeServer,
|
||||
}
|
||||
if (client.errors) {
|
||||
if (client.errors && clientWasLoading) {
|
||||
// Show only client errors
|
||||
consoleStore.setState(
|
||||
{
|
||||
|
@ -155,8 +154,7 @@ buildStore.subscribe((state) => {
|
|||
} as OutputState,
|
||||
true
|
||||
)
|
||||
} else if (server.errors) {
|
||||
// Show only server errors
|
||||
} else if (server.errors && serverWasLoading) {
|
||||
consoleStore.setState(
|
||||
{
|
||||
...partialState,
|
||||
|
@ -165,8 +163,7 @@ buildStore.subscribe((state) => {
|
|||
} as OutputState,
|
||||
true
|
||||
)
|
||||
} else if (edgeServer && edgeServer.errors) {
|
||||
// Show only edge server errors
|
||||
} else if (edgeServer.errors && edgeServerWasLoading) {
|
||||
consoleStore.setState(
|
||||
{
|
||||
...partialState,
|
||||
|
@ -180,7 +177,7 @@ buildStore.subscribe((state) => {
|
|||
const warnings = [
|
||||
...(client.warnings || []),
|
||||
...(server.warnings || []),
|
||||
...((edgeServer && edgeServer.warnings) || []),
|
||||
...(edgeServer.warnings || []),
|
||||
].concat(formatAmpMessages(amp) || [])
|
||||
|
||||
consoleStore.setState(
|
||||
|
@ -236,13 +233,13 @@ export function watchCompilers(
|
|||
buildStore.setState({
|
||||
client: { loading: true },
|
||||
server: { loading: true },
|
||||
edgeServer: edgeServer ? { loading: true } : undefined,
|
||||
edgeServer: { loading: true },
|
||||
trigger: 'initial',
|
||||
})
|
||||
|
||||
function tapCompiler(
|
||||
key: string,
|
||||
compiler: any,
|
||||
key: 'client' | 'server' | 'edgeServer',
|
||||
compiler: webpack5.Compiler,
|
||||
onEvent: (status: WebpackStatus) => void
|
||||
) {
|
||||
compiler.hooks.invalid.tap(`NextJsInvalid-${key}`, () => {
|
||||
|
@ -272,7 +269,11 @@ export function watchCompilers(
|
|||
}
|
||||
|
||||
tapCompiler('client', client, (status) => {
|
||||
if (!status.loading && !buildStore.getState().server.loading) {
|
||||
if (
|
||||
!status.loading &&
|
||||
!buildStore.getState().server.loading &&
|
||||
!buildStore.getState().edgeServer.loading
|
||||
) {
|
||||
buildStore.setState({
|
||||
client: status,
|
||||
trigger: undefined,
|
||||
|
@ -284,7 +285,11 @@ export function watchCompilers(
|
|||
}
|
||||
})
|
||||
tapCompiler('server', server, (status) => {
|
||||
if (!status.loading && !buildStore.getState().client.loading) {
|
||||
if (
|
||||
!status.loading &&
|
||||
!buildStore.getState().client.loading &&
|
||||
!buildStore.getState().edgeServer.loading
|
||||
) {
|
||||
buildStore.setState({
|
||||
server: status,
|
||||
trigger: undefined,
|
||||
|
@ -295,14 +300,22 @@ export function watchCompilers(
|
|||
})
|
||||
}
|
||||
})
|
||||
if (edgeServer) {
|
||||
tapCompiler('edgeServer', edgeServer, (status) => {
|
||||
tapCompiler('edgeServer', edgeServer, (status) => {
|
||||
if (
|
||||
!status.loading &&
|
||||
!buildStore.getState().client.loading &&
|
||||
!buildStore.getState().server.loading
|
||||
) {
|
||||
buildStore.setState({
|
||||
edgeServer: status,
|
||||
trigger: undefined,
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
buildStore.setState({
|
||||
edgeServer: status,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
previousClient = client
|
||||
previousServer = server
|
||||
|
|
|
@ -23,19 +23,21 @@
|
|||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack'
|
||||
import type ws from 'ws'
|
||||
import { isMiddlewareFilename } from '../../build/utils'
|
||||
import { nonNullable } from '../../lib/non-nullable'
|
||||
|
||||
export class WebpackHotMiddleware {
|
||||
eventStream: EventStream
|
||||
latestStats: webpack.Stats | null
|
||||
clientLatestStats: webpack.Stats | null
|
||||
clientLatestStats: { ts: number; stats: webpack.Stats } | null
|
||||
middlewareLatestStats: { ts: number; stats: webpack.Stats } | null
|
||||
serverLatestStats: { ts: number; stats: webpack.Stats } | null
|
||||
closed: boolean
|
||||
serverError: boolean
|
||||
|
||||
constructor(compilers: webpack.Compiler[]) {
|
||||
this.eventStream = new EventStream()
|
||||
this.latestStats = null
|
||||
this.clientLatestStats = null
|
||||
this.serverError = false
|
||||
this.middlewareLatestStats = null
|
||||
this.serverLatestStats = null
|
||||
this.closed = false
|
||||
|
||||
compilers[0].hooks.invalid.tap(
|
||||
|
@ -43,57 +45,91 @@ export class WebpackHotMiddleware {
|
|||
this.onClientInvalid
|
||||
)
|
||||
compilers[0].hooks.done.tap('webpack-hot-middleware', this.onClientDone)
|
||||
|
||||
compilers[1].hooks.invalid.tap(
|
||||
'webpack-hot-middleware',
|
||||
this.onServerInvalid
|
||||
)
|
||||
compilers[1].hooks.done.tap('webpack-hot-middleware', this.onServerDone)
|
||||
compilers[2].hooks.done.tap('webpack-hot-middleware', this.onEdgeServerDone)
|
||||
compilers[2].hooks.invalid.tap(
|
||||
'webpack-hot-middleware',
|
||||
this.onEdgeServerInvalid
|
||||
)
|
||||
}
|
||||
|
||||
onClientInvalid = () => {
|
||||
if (this.closed || this.serverLatestStats?.stats.hasErrors()) return
|
||||
this.eventStream.publish({ action: 'building' })
|
||||
}
|
||||
|
||||
onClientDone = (statsResult: webpack.Stats) => {
|
||||
this.clientLatestStats = { ts: Date.now(), stats: statsResult }
|
||||
if (this.closed || this.serverLatestStats?.stats.hasErrors()) return
|
||||
this.publishStats('built', statsResult)
|
||||
}
|
||||
|
||||
onServerInvalid = () => {
|
||||
if (!this.serverError) return
|
||||
|
||||
this.serverError = false
|
||||
|
||||
if (this.clientLatestStats) {
|
||||
this.latestStats = this.clientLatestStats
|
||||
this.publishStats('built', this.latestStats)
|
||||
if (!this.serverLatestStats?.stats.hasErrors()) return
|
||||
this.serverLatestStats = null
|
||||
if (this.clientLatestStats?.stats) {
|
||||
this.publishStats('built', this.clientLatestStats.stats)
|
||||
}
|
||||
}
|
||||
onClientInvalid = () => {
|
||||
if (this.closed || this.serverError) return
|
||||
this.latestStats = null
|
||||
this.eventStream.publish({ action: 'building' })
|
||||
}
|
||||
|
||||
onServerDone = (statsResult: webpack.Stats) => {
|
||||
if (this.closed) return
|
||||
// Keep hold of latest stats so they can be propagated to new clients
|
||||
// this.latestStats = statsResult
|
||||
// this.publishStats('built', this.latestStats)
|
||||
this.serverError = statsResult.hasErrors()
|
||||
|
||||
if (this.serverError) {
|
||||
this.latestStats = statsResult
|
||||
this.publishStats('built', this.latestStats)
|
||||
if (statsResult.hasErrors()) {
|
||||
this.serverLatestStats = { ts: Date.now(), stats: statsResult }
|
||||
this.publishStats('built', statsResult)
|
||||
}
|
||||
}
|
||||
onClientDone = (statsResult: webpack.Stats) => {
|
||||
this.clientLatestStats = statsResult
|
||||
|
||||
if (this.closed || this.serverError) return
|
||||
// Keep hold of latest stats so they can be propagated to new clients
|
||||
this.latestStats = statsResult
|
||||
this.publishStats('built', this.latestStats)
|
||||
onEdgeServerInvalid = () => {
|
||||
if (!this.middlewareLatestStats?.stats.hasErrors()) return
|
||||
this.middlewareLatestStats = null
|
||||
if (this.clientLatestStats?.stats) {
|
||||
this.publishStats('built', this.clientLatestStats.stats)
|
||||
}
|
||||
}
|
||||
|
||||
onEdgeServerDone = (statsResult: webpack.Stats) => {
|
||||
if (!isMiddlewareStats(statsResult)) {
|
||||
this.onServerDone(statsResult)
|
||||
return
|
||||
}
|
||||
|
||||
if (statsResult.hasErrors()) {
|
||||
this.middlewareLatestStats = { ts: Date.now(), stats: statsResult }
|
||||
this.publishStats('built', statsResult)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To sync we use the most recent stats but also we append middleware
|
||||
* errors. This is because it is possible that middleware fails to compile
|
||||
* and we still want to show the client overlay with the error while
|
||||
* the error page should be rendered just fine.
|
||||
*/
|
||||
onHMR = (client: ws) => {
|
||||
if (this.closed) return
|
||||
this.eventStream.handler(client)
|
||||
if (this.latestStats) {
|
||||
// Explicitly not passing in `log` fn as we don't want to log again on
|
||||
// the server
|
||||
this.publishStats('sync', this.latestStats)
|
||||
|
||||
const [latestStats] = [this.clientLatestStats, this.serverLatestStats]
|
||||
.filter(nonNullable)
|
||||
.sort((statsA, statsB) => statsB.ts - statsA.ts)
|
||||
|
||||
if (latestStats?.stats) {
|
||||
const stats = statsToJson(latestStats.stats)
|
||||
const middlewareStats = statsToJson(this.middlewareLatestStats?.stats)
|
||||
this.eventStream.publish({
|
||||
action: 'sync',
|
||||
hash: stats.hash,
|
||||
errors: [...(stats.errors || []), ...(middlewareStats.errors || [])],
|
||||
warnings: [
|
||||
...(stats.warnings || []),
|
||||
...(middlewareStats.warnings || []),
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,3 +194,23 @@ class EventStream {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
function isMiddlewareStats(stats: webpack.Stats) {
|
||||
for (const key of stats.compilation.entrypoints.keys()) {
|
||||
if (isMiddlewareFilename(key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function statsToJson(stats?: webpack.Stats | null) {
|
||||
if (!stats) return {}
|
||||
return stats.toJson({
|
||||
all: false,
|
||||
errors: true,
|
||||
hash: true,
|
||||
warnings: true,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -901,7 +901,6 @@ export default class HotReloader {
|
|||
|
||||
this.onDemandEntries = onDemandEntryHandler({
|
||||
multiCompiler,
|
||||
watcher: this.watcher,
|
||||
pagesDir: this.pagesDir,
|
||||
appDir: this.appDir,
|
||||
rootDir: this.dir,
|
||||
|
|
|
@ -48,7 +48,7 @@ import { findPageFile } from '../lib/find-page-file'
|
|||
import { getNodeOptionsWithoutInspect } from '../lib/utils'
|
||||
import { withCoalescedInvoke } from '../../lib/coalesced-function'
|
||||
import { loadDefaultErrorComponents } from '../load-components'
|
||||
import { DecodeError } from '../../shared/lib/utils'
|
||||
import { DecodeError, MiddlewareNotFoundError } from '../../shared/lib/utils'
|
||||
import {
|
||||
createOriginalStackFrame,
|
||||
getErrorSource,
|
||||
|
@ -675,7 +675,16 @@ export default class DevServer extends Server {
|
|||
if (error instanceof DecodeError) {
|
||||
throw error
|
||||
}
|
||||
this.logErrorWithOriginalStack(error)
|
||||
|
||||
/**
|
||||
* We only log the error when it is not a MiddlewareNotFound error as
|
||||
* in that case we should be already displaying a compilation error
|
||||
* which is what makes the module not found.
|
||||
*/
|
||||
if (!(error instanceof MiddlewareNotFoundError)) {
|
||||
this.logErrorWithOriginalStack(error)
|
||||
}
|
||||
|
||||
const err = getProperError(error)
|
||||
;(err as any).middleware = true
|
||||
const { request, response, parsedUrl } = params
|
||||
|
|
|
@ -66,7 +66,6 @@ export function onDemandEntryHandler({
|
|||
pagesDir,
|
||||
rootDir,
|
||||
appDir,
|
||||
watcher,
|
||||
}: {
|
||||
maxInactiveAge: number
|
||||
multiCompiler: webpack.MultiCompiler
|
||||
|
@ -75,9 +74,8 @@ export function onDemandEntryHandler({
|
|||
pagesDir: string
|
||||
rootDir: string
|
||||
appDir?: string
|
||||
watcher: any
|
||||
}) {
|
||||
invalidator = new Invalidator(watcher)
|
||||
invalidator = new Invalidator(multiCompiler)
|
||||
const doneCallbacks: EventEmitter | null = new EventEmitter()
|
||||
const lastClientAccessPages = ['']
|
||||
|
||||
|
@ -245,7 +243,7 @@ export function onDemandEntryHandler({
|
|||
nextConfig,
|
||||
})
|
||||
|
||||
const promises = runDependingOnPageType({
|
||||
const result = runDependingOnPageType({
|
||||
page: pagePathData.page,
|
||||
pageRuntime: staticInfo.runtime,
|
||||
onClient: () => addPageEntry('client'),
|
||||
|
@ -253,13 +251,14 @@ export function onDemandEntryHandler({
|
|||
onEdgeServer: () => addPageEntry('edge-server'),
|
||||
})
|
||||
|
||||
const promises = Object.values(result)
|
||||
if (entryAdded) {
|
||||
reportTrigger(
|
||||
!clientOnly && promises.length > 1
|
||||
? `${pagePathData.page} (client and server)`
|
||||
: pagePathData.page
|
||||
)
|
||||
invalidator.invalidate()
|
||||
invalidator.invalidate(Object.keys(result))
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
|
@ -315,18 +314,18 @@ function disposeInactiveEntries(
|
|||
// Make sure only one invalidation happens at a time
|
||||
// Otherwise, webpack hash gets changed and it'll force the client to reload.
|
||||
class Invalidator {
|
||||
private watcher: any
|
||||
private multiCompiler: webpack.MultiCompiler
|
||||
private building: boolean
|
||||
public rebuildAgain: boolean
|
||||
|
||||
constructor(watcher: any) {
|
||||
this.watcher = watcher
|
||||
constructor(multiCompiler: webpack.MultiCompiler) {
|
||||
this.multiCompiler = multiCompiler
|
||||
// contains an array of types of compilers currently building
|
||||
this.building = false
|
||||
this.rebuildAgain = false
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
invalidate(keys: string[] = []) {
|
||||
// If there's a current build is processing, we won't abort it by invalidating.
|
||||
// (If aborted, it'll cause a client side hard reload)
|
||||
// But let it to invalidate just after the completion.
|
||||
|
@ -337,7 +336,23 @@ class Invalidator {
|
|||
}
|
||||
|
||||
this.building = true
|
||||
this.watcher.invalidate()
|
||||
|
||||
if (!keys || keys.length === 0) {
|
||||
this.multiCompiler.compilers[0].watching.invalidate()
|
||||
this.multiCompiler.compilers[1].watching.invalidate()
|
||||
this.multiCompiler.compilers[2].watching.invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
for (const key of keys) {
|
||||
if (key === 'client') {
|
||||
this.multiCompiler.compilers[0].watching.invalidate()
|
||||
} else if (key === 'server') {
|
||||
this.multiCompiler.compilers[1].watching.invalidate()
|
||||
} else if (key === 'edgeServer') {
|
||||
this.multiCompiler.compilers[2].watching.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startBuilding() {
|
||||
|
|
|
@ -2,7 +2,12 @@ import './node-polyfill-fetch'
|
|||
import './node-polyfill-web-streams'
|
||||
|
||||
import type { Route } from './router'
|
||||
import { CacheFs, DecodeError, PageNotFoundError } from '../shared/lib/utils'
|
||||
import {
|
||||
CacheFs,
|
||||
DecodeError,
|
||||
PageNotFoundError,
|
||||
MiddlewareNotFoundError,
|
||||
} from '../shared/lib/utils'
|
||||
import type { MiddlewareManifest } from '../build/webpack/plugins/middleware-plugin'
|
||||
import type RenderResult from './render-result'
|
||||
import type { FetchEventResult } from './web/types'
|
||||
|
@ -1091,14 +1096,18 @@ export default class NextNodeServer extends BaseServer {
|
|||
try {
|
||||
foundPage = denormalizePagePath(normalizePagePath(params.page))
|
||||
} catch (err) {
|
||||
throw new PageNotFoundError(params.page)
|
||||
return null
|
||||
}
|
||||
|
||||
let pageInfo = params.middleware
|
||||
? manifest.middleware[foundPage]
|
||||
: manifest.functions[foundPage]
|
||||
|
||||
if (!pageInfo) {
|
||||
throw new PageNotFoundError(foundPage)
|
||||
if (!params.middleware) {
|
||||
throw new PageNotFoundError(foundPage)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1121,14 +1130,8 @@ export default class NextNodeServer extends BaseServer {
|
|||
pathname: string,
|
||||
_isSSR?: boolean
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
return (
|
||||
this.getEdgeFunctionInfo({ page: pathname, middleware: true }).paths
|
||||
.length > 0
|
||||
)
|
||||
} catch (_) {}
|
||||
|
||||
return false
|
||||
const info = this.getEdgeFunctionInfo({ page: pathname, middleware: true })
|
||||
return Boolean(info && info.paths.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1209,6 +1212,10 @@ export default class NextNodeServer extends BaseServer {
|
|||
middleware: !middleware.ssr,
|
||||
})
|
||||
|
||||
if (!middlewareInfo) {
|
||||
throw new MiddlewareNotFoundError()
|
||||
}
|
||||
|
||||
result = await run({
|
||||
name: middlewareInfo.name,
|
||||
paths: middlewareInfo.paths,
|
||||
|
@ -1524,6 +1531,10 @@ export default class NextNodeServer extends BaseServer {
|
|||
return null
|
||||
}
|
||||
|
||||
if (!middlewareInfo) {
|
||||
return null
|
||||
}
|
||||
|
||||
// For middleware to "fetch" we must always provide an absolute URL
|
||||
const url = getRequestMeta(params.req, '__NEXT_INIT_URL')!
|
||||
if (!url.startsWith('http')) {
|
||||
|
|
|
@ -413,6 +413,15 @@ export class PageNotFoundError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class MiddlewareNotFoundError extends Error {
|
||||
code: string
|
||||
constructor() {
|
||||
super()
|
||||
this.code = 'ENOENT'
|
||||
this.message = `Cannot find the middleware module`
|
||||
}
|
||||
}
|
||||
|
||||
export interface CacheFs {
|
||||
readFile(f: string): Promise<string>
|
||||
readFileSync(f: string): string
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
check,
|
||||
fetchViaHTTP,
|
||||
File,
|
||||
findPort,
|
||||
|
@ -241,4 +242,59 @@ describe('Middleware development errors', () => {
|
|||
expect(await browser.elementByCss('#page-title')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is a compilation error from boot', () => {
|
||||
beforeEach(() => {
|
||||
context.middleware.write(`export default function () }`)
|
||||
})
|
||||
|
||||
it('logs the error correctly', async () => {
|
||||
await fetchViaHTTP(context.appPort, '/')
|
||||
expect(context.logs.output).toContain(`Expected '{', got '}'`)
|
||||
expect(context.logs.output.split(`Expected '{', got '}'`).length).toEqual(
|
||||
2
|
||||
)
|
||||
})
|
||||
|
||||
it('renders the error correctly and recovers', async () => {
|
||||
const browser = await webdriver(context.appPort, '/')
|
||||
expect(await hasRedbox(browser, true)).toBe(true)
|
||||
expect(
|
||||
await browser
|
||||
.elementByCss('#nextjs__container_build_error_label')
|
||||
.text()
|
||||
).toEqual('Failed to compile')
|
||||
context.middleware.write(`export default function () {}`)
|
||||
await hasRedbox(browser, false)
|
||||
expect(await browser.elementByCss('#page-title')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is a compilation error after boot', () => {
|
||||
beforeEach(() => {
|
||||
context.middleware.write(`export default function () {}`)
|
||||
})
|
||||
|
||||
it('logs the error correctly', async () => {
|
||||
await fetchViaHTTP(context.appPort, '/')
|
||||
context.middleware.write(`export default function () }`)
|
||||
await check(() => {
|
||||
expect(context.logs.output).toContain(`Expected '{', got '}'`)
|
||||
expect(
|
||||
context.logs.output.split(`Expected '{', got '}'`).length
|
||||
).toEqual(2)
|
||||
return 'success'
|
||||
}, 'success')
|
||||
})
|
||||
|
||||
it('renders the error correctly and recovers', async () => {
|
||||
const browser = await webdriver(context.appPort, '/')
|
||||
expect(await hasRedbox(browser, false)).toBe(false)
|
||||
context.middleware.write(`export default function () }`)
|
||||
expect(await hasRedbox(browser, true)).toBe(true)
|
||||
context.middleware.write(`export default function () {}`)
|
||||
expect(await hasRedbox(browser, false)).toBe(false)
|
||||
expect(await browser.elementByCss('#page-title')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue