From be28f976af31ba9d89ff3935ffc0921f30bdbf54 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 30 Aug 2019 16:08:10 -0400 Subject: [PATCH] test: tests for error handling --- .../__tests__/errorHandling.spec.ts | 328 +++++++++++++++++- packages/runtime-core/src/apiLifecycle.ts | 9 +- packages/runtime-core/src/component.ts | 1 - packages/runtime-core/src/errorHandling.ts | 2 +- 4 files changed, 329 insertions(+), 11 deletions(-) diff --git a/packages/runtime-core/__tests__/errorHandling.spec.ts b/packages/runtime-core/__tests__/errorHandling.spec.ts index b91fdab8..fb752a6b 100644 --- a/packages/runtime-core/__tests__/errorHandling.spec.ts +++ b/packages/runtime-core/__tests__/errorHandling.spec.ts @@ -1,17 +1,331 @@ +import { + onMounted, + onErrorCaptured, + render, + h, + nodeOps, + watch, + ref, + nextTick +} from '@vue/runtime-test' + describe('error handling', () => { - test.todo('in lifecycle hooks') + test('propagtaion', () => { + const err = new Error('foo') + const fn = jest.fn() - test.todo('in onErrorCaptured') + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info, 'root') + return true + }) + return () => h(Child) + } + } - test.todo('in setup function') + const Child = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info, 'child') + }) + return () => h(GrandChild) + } + } - test.todo('in render function') + const GrandChild = { + setup() { + onMounted(() => { + throw err + }) + return () => null + } + } - test.todo('in watch (simple usage)') + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledTimes(2) + expect(fn).toHaveBeenCalledWith(err, 'mounted hook', 'root') + expect(fn).toHaveBeenCalledWith(err, 'mounted hook', 'child') + }) - test.todo('in watch (with source)') + test('propagation stoppage', () => { + const err = new Error('foo') + const fn = jest.fn() - test.todo('in component event handler') + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info, 'root') + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info, 'child') + return true + }) + return () => h(GrandChild) + } + } + + const GrandChild = { + setup() { + onMounted(() => { + throw err + }) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledTimes(1) + expect(fn).toHaveBeenCalledWith(err, 'mounted hook', 'child') + }) + + test('error thrown in onErrorCaptured', () => { + const err = new Error('foo') + const err2 = new Error('bar') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + onErrorCaptured(() => { + throw err2 + }) + return () => h(GrandChild) + } + } + + const GrandChild = { + setup() { + onMounted(() => { + throw err + }) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledTimes(2) + expect(fn).toHaveBeenCalledWith(err, 'mounted hook') + expect(fn).toHaveBeenCalledWith(err2, 'errorCaptured hook') + }) + + test('setup function', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + throw err + }, + render() {} + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'setup function') + }) + + test('in render function', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + return () => { + throw err + } + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'render function') + }) + + test('in watch (simple usage)', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + watch(() => { + throw err + }) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'watcher callback') + }) + + test('in watch getter', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + watch( + () => { + throw err + }, + () => {} + ) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'watcher getter') + }) + + test('in watch callback', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + watch( + () => 1, + () => { + throw err + } + ) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'watcher callback') + }) + + test('in watch cleanup', async () => { + const err = new Error('foo') + const count = ref(0) + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => h(Child) + } + } + + const Child = { + setup() { + watch(onCleanup => { + count.value + onCleanup(() => { + throw err + }) + }) + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + + count.value++ + await nextTick() + expect(fn).toHaveBeenCalledWith(err, 'watcher cleanup function') + }) + + test('in component event handler', () => { + const err = new Error('foo') + const fn = jest.fn() + + const Comp = { + setup() { + onErrorCaptured((err, instance, info) => { + fn(err, info) + return true + }) + return () => + h(Child, { + onFoo: () => { + throw err + } + }) + } + } + + const Child = { + setup(props: any, { emit }: any) { + emit('foo') + return () => null + } + } + + render(h(Comp), nodeOps.createElement('div')) + expect(fn).toHaveBeenCalledWith(err, 'component event handler') + }) // native event handler handling should be tested in respective renderers }) diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index 4ffdd0e5..cded357b 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -2,7 +2,8 @@ import { ComponentInstance, LifecycleHooks, currentInstance, - setCurrentInstance + setCurrentInstance, + ComponentRenderProxy } from './component' import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling' import { warn } from './warning' @@ -92,7 +93,11 @@ export function onRenderTracked( } export function onErrorCaptured( - hook: Function, + hook: ( + err: Error, + instance: ComponentRenderProxy | null, + info: string + ) => boolean | void, target: ComponentInstance | null = currentInstance ) { injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 82242d69..67803d5b 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -443,6 +443,5 @@ function hasPropsChanged(prevProps: Data, nextProps: Data): boolean { return true } } - console.log(111) return false } diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index d868c001..0a013145 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -88,7 +88,7 @@ export function handleError( errorCapturedHooks[i]( err, instance && instance.renderProxy, - contextVNode + ErrorTypeStrings[type] ) ) { return