mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
fix(vue-app): prevent looping on error during render of error page (#6217)
This commit is contained in:
parent
7c90310166
commit
93a0924754
@ -34,6 +34,15 @@ export default {
|
|||||||
default: 'default'
|
default: 'default'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
errorCaptured (error) {
|
||||||
|
// if we receive and error while showing the NuxtError component
|
||||||
|
// capture the error and force an immediate update so we re-render
|
||||||
|
// without the NuxtError component
|
||||||
|
if (this.displayingNuxtError) {
|
||||||
|
this.errorFromNuxtError = error
|
||||||
|
this.$forceUpdate()
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
routerViewKey() {
|
routerViewKey() {
|
||||||
// If nuxtChildKey prop is given or current route has children
|
// If nuxtChildKey prop is given or current route has children
|
||||||
@ -65,18 +74,36 @@ export default {
|
|||||||
Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt)
|
Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt)
|
||||||
},
|
},
|
||||||
render(h) {
|
render(h) {
|
||||||
// If there is some error
|
// if there is no error
|
||||||
if (this.nuxt.err) {
|
if (!this.nuxt.err) {
|
||||||
return h('NuxtError', {
|
// Directly return nuxt child
|
||||||
props: {
|
return h('NuxtChild', {
|
||||||
error: this.nuxt.err
|
key: this.routerViewKey,
|
||||||
}
|
props: this.$props
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Directly return nuxt child
|
|
||||||
return h('NuxtChild', {
|
// if an error occured within NuxtError show a simple
|
||||||
key: this.routerViewKey,
|
// error message instead to prevent looping
|
||||||
props: this.$props
|
if (this.errorFromNuxtError) {
|
||||||
|
this.$nextTick(() => (this.errorFromNuxtError = false))
|
||||||
|
|
||||||
|
return h('div', {}, [
|
||||||
|
h('h2', 'An error occured while showing the error page'),
|
||||||
|
h('p', 'Unfortunately an error occured and while showing the error page another error occured'),
|
||||||
|
h('p', `Error details: ${this.errorFromNuxtError.toString()}`),
|
||||||
|
h('nuxt-link', { props: { to: '/' } }, 'Go back to home')
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// track if we are showing the NuxtError component
|
||||||
|
this.displayingNuxtError = true
|
||||||
|
this.$nextTick(() => (this.displayingNuxtError = false))
|
||||||
|
|
||||||
|
return h(NuxtError, {
|
||||||
|
props: {
|
||||||
|
error: this.nuxt.err
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
60
test/e2e/error.test.js
Normal file
60
test/e2e/error.test.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import Browser from '../utils/browser'
|
||||||
|
import { loadFixture, getPort, Nuxt } from '../utils'
|
||||||
|
|
||||||
|
let port
|
||||||
|
const browser = new Browser()
|
||||||
|
const url = route => 'http://localhost:' + port + route
|
||||||
|
|
||||||
|
let nuxt = null
|
||||||
|
let page = null
|
||||||
|
|
||||||
|
describe('basic browser', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const config = await loadFixture('error')
|
||||||
|
nuxt = new Nuxt(config)
|
||||||
|
await nuxt.ready()
|
||||||
|
|
||||||
|
port = await getPort()
|
||||||
|
await nuxt.server.listen(port, 'localhost')
|
||||||
|
|
||||||
|
await browser.start({
|
||||||
|
// slowMo: 50,
|
||||||
|
// headless: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Open /', async () => {
|
||||||
|
page = await browser.page(url('/'))
|
||||||
|
|
||||||
|
expect(await page.$text('h1')).toBe('Error Loop incoming page')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/squared doesnt loop due to error on error page', async () => {
|
||||||
|
await page.nuxt.navigate('/squared')
|
||||||
|
|
||||||
|
expect(await page.$text('h2')).toBe('An error occured while showing the error page')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/about loads normally', async () => {
|
||||||
|
await page.nuxt.navigate('/about')
|
||||||
|
|
||||||
|
expect(await page.$text('h1')).toBe('About')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/info prints empty page', async () => {
|
||||||
|
await page.nuxt.navigate('/info')
|
||||||
|
|
||||||
|
expect(await page.$text('#__layout')).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close server and ask nuxt to stop listening to file changes
|
||||||
|
afterAll(async () => {
|
||||||
|
await nuxt.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Stop browser
|
||||||
|
afterAll(async () => {
|
||||||
|
await page.close()
|
||||||
|
await browser.close()
|
||||||
|
})
|
||||||
|
})
|
17
test/fixtures/error/layouts/error.vue
vendored
Normal file
17
test/fixtures/error/layouts/error.vue
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>Details: {{ this.$route.path === '/squared' ? error.response.data : error.message }}</p>
|
||||||
|
|
||||||
|
<nuxt-link to="/">
|
||||||
|
back
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
props: ['error']
|
||||||
|
}
|
||||||
|
</script>
|
2
test/fixtures/error/nuxt.config.js
vendored
2
test/fixtures/error/nuxt.config.js
vendored
@ -1,2 +0,0 @@
|
|||||||
export default {
|
|
||||||
}
|
|
8
test/fixtures/error/pages/about.vue
vendored
Normal file
8
test/fixtures/error/pages/about.vue
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>About</h1>
|
||||||
|
<nuxt-link to="/">
|
||||||
|
back
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
10
test/fixtures/error/pages/error.vue
vendored
Normal file
10
test/fixtures/error/pages/error.vue
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<h1>Error page</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* eslint no-undef: 0 */
|
||||||
|
export default {
|
||||||
|
not_defined
|
||||||
|
}
|
||||||
|
</script>
|
20
test/fixtures/error/pages/index.vue
vendored
20
test/fixtures/error/pages/index.vue
vendored
@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1>Error page</h1>
|
<div>
|
||||||
|
<h1>Error Loop incoming page</h1>
|
||||||
|
<nuxt-link id="squared" to="/squared">
|
||||||
|
Error during error
|
||||||
|
</nuxt-link>
|
||||||
|
<nuxt-link id="about" to="/about">
|
||||||
|
About
|
||||||
|
</nuxt-link>
|
||||||
|
<nuxt-link id="info" to="/info">
|
||||||
|
Info (with error)
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
/* eslint no-undef: 0 */
|
|
||||||
export default {
|
|
||||||
not_defined
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
19
test/fixtures/error/pages/info.vue
vendored
Normal file
19
test/fixtures/error/pages/info.vue
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Info</h1>
|
||||||
|
<p>Details: {{ info.message.title }}</p>
|
||||||
|
<nuxt-link to="/">
|
||||||
|
back
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
info: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -19,7 +19,7 @@ describe('error', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/ should display an error', async () => {
|
test('/ should display an error', async () => {
|
||||||
await expect(nuxt.server.renderRoute('/')).rejects.toMatchObject({
|
await expect(nuxt.server.renderRoute('/error')).rejects.toMatchObject({
|
||||||
message: expect.stringContaining('not_defined is not defined')
|
message: expect.stringContaining('not_defined is not defined')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -30,14 +30,14 @@ describe('error', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/ with renderAndGetWindow()', async () => {
|
test('/ with renderAndGetWindow()', async () => {
|
||||||
await expect(nuxt.server.renderAndGetWindow(url('/'))).rejects.toMatchObject({
|
await expect(nuxt.server.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
||||||
statusCode: 500
|
statusCode: 500
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Error: resolvePath()', () => {
|
test('Error: resolvePath()', () => {
|
||||||
expect(() => nuxt.resolver.resolvePath()).toThrowError()
|
expect(() => nuxt.resolver.resolvePath()).toThrowError()
|
||||||
expect(() => nuxt.resolver.resolvePath('@/pages/about.vue')).toThrowError('Cannot resolve "@/pages/about.vue"')
|
expect(() => nuxt.resolver.resolvePath('@/pages/not-found.vue')).toThrowError('Cannot resolve "@/pages/not-found.vue"')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Error: callHook()', async () => {
|
test('Error: callHook()', async () => {
|
||||||
@ -56,6 +56,24 @@ describe('error', () => {
|
|||||||
expect(consola.fatal).toHaveBeenCalledWith(error)
|
expect(consola.fatal).toHaveBeenCalledWith(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('/info should display an error', async () => {
|
||||||
|
await expect(nuxt.server.renderRoute('/info')).rejects.toMatchObject({
|
||||||
|
message: expect.stringContaining(`Cannot read property 'title' of undefined`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/about should work', async () => {
|
||||||
|
await expect(nuxt.server.renderRoute('/about')).resolves.toMatchObject({
|
||||||
|
html: expect.stringContaining('About')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/error-square should display an error', async () => {
|
||||||
|
await expect(nuxt.server.renderRoute('/squared')).rejects.toMatchObject({
|
||||||
|
message: expect.stringContaining(`Cannot read property 'data' of undefined`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Close server and ask nuxt to stop listening to file changes
|
// Close server and ask nuxt to stop listening to file changes
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await nuxt.close()
|
await nuxt.close()
|
||||||
|
Loading…
Reference in New Issue
Block a user