diff --git a/packages/vue-app/template/components/nuxt.js b/packages/vue-app/template/components/nuxt.js index b9564dfd6f..2d29bf378d 100644 --- a/packages/vue-app/template/components/nuxt.js +++ b/packages/vue-app/template/components/nuxt.js @@ -34,6 +34,15 @@ export 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: { routerViewKey() { // 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) }, render(h) { - // If there is some error - if (this.nuxt.err) { - return h('NuxtError', { - props: { - error: this.nuxt.err - } + // if there is no error + if (!this.nuxt.err) { + // Directly return nuxt child + return h('NuxtChild', { + key: this.routerViewKey, + props: this.$props }) } - // Directly return nuxt child - return h('NuxtChild', { - key: this.routerViewKey, - props: this.$props + + // if an error occured within NuxtError show a simple + // error message instead to prevent looping + 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 + } }) } } diff --git a/test/e2e/error.test.js b/test/e2e/error.test.js new file mode 100644 index 0000000000..d997398c57 --- /dev/null +++ b/test/e2e/error.test.js @@ -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() + }) +}) diff --git a/test/fixtures/error/layouts/error.vue b/test/fixtures/error/layouts/error.vue new file mode 100644 index 0000000000..f024b262a9 --- /dev/null +++ b/test/fixtures/error/layouts/error.vue @@ -0,0 +1,17 @@ + + + diff --git a/test/fixtures/error/nuxt.config.js b/test/fixtures/error/nuxt.config.js deleted file mode 100644 index efba7fa697..0000000000 --- a/test/fixtures/error/nuxt.config.js +++ /dev/null @@ -1,2 +0,0 @@ -export default { -} diff --git a/test/fixtures/error/pages/about.vue b/test/fixtures/error/pages/about.vue new file mode 100644 index 0000000000..71e48bb1a0 --- /dev/null +++ b/test/fixtures/error/pages/about.vue @@ -0,0 +1,8 @@ + diff --git a/test/fixtures/error/pages/error.vue b/test/fixtures/error/pages/error.vue new file mode 100644 index 0000000000..a683233742 --- /dev/null +++ b/test/fixtures/error/pages/error.vue @@ -0,0 +1,10 @@ + + + diff --git a/test/fixtures/error/pages/index.vue b/test/fixtures/error/pages/index.vue index a683233742..8e4c7c2063 100644 --- a/test/fixtures/error/pages/index.vue +++ b/test/fixtures/error/pages/index.vue @@ -1,10 +1,14 @@ - - diff --git a/test/fixtures/error/pages/info.vue b/test/fixtures/error/pages/info.vue new file mode 100644 index 0000000000..de79ba7367 --- /dev/null +++ b/test/fixtures/error/pages/info.vue @@ -0,0 +1,19 @@ + + + diff --git a/test/unit/error.test.js b/test/unit/error.test.js index 0745b2c0f6..63dc88e943 100644 --- a/test/unit/error.test.js +++ b/test/unit/error.test.js @@ -19,7 +19,7 @@ describe('error', () => { }) 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') }) }) @@ -30,14 +30,14 @@ describe('error', () => { }) test('/ with renderAndGetWindow()', async () => { - await expect(nuxt.server.renderAndGetWindow(url('/'))).rejects.toMatchObject({ + await expect(nuxt.server.renderAndGetWindow(url('/error'))).rejects.toMatchObject({ statusCode: 500 }) }) test('Error: resolvePath()', () => { 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 () => { @@ -56,6 +56,24 @@ describe('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 afterAll(async () => { await nuxt.close()