test: tests for error handling

This commit is contained in:
Evan You 2019-08-30 16:08:10 -04:00
parent d5684e1a9d
commit be28f976af
4 changed files with 329 additions and 11 deletions

View File

@ -1,17 +1,331 @@
import {
onMounted,
onErrorCaptured,
render,
h,
nodeOps,
watch,
ref,
nextTick
} from '@vue/runtime-test'
describe('error handling', () => { 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 // native event handler handling should be tested in respective renderers
}) })

View File

@ -2,7 +2,8 @@ import {
ComponentInstance, ComponentInstance,
LifecycleHooks, LifecycleHooks,
currentInstance, currentInstance,
setCurrentInstance setCurrentInstance,
ComponentRenderProxy
} from './component' } from './component'
import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling' import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
import { warn } from './warning' import { warn } from './warning'
@ -92,7 +93,11 @@ export function onRenderTracked(
} }
export function onErrorCaptured( export function onErrorCaptured(
hook: Function, hook: (
err: Error,
instance: ComponentRenderProxy | null,
info: string
) => boolean | void,
target: ComponentInstance | null = currentInstance target: ComponentInstance | null = currentInstance
) { ) {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)

View File

@ -443,6 +443,5 @@ function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
return true return true
} }
} }
console.log(111)
return false return false
} }

View File

@ -88,7 +88,7 @@ export function handleError(
errorCapturedHooks[i]( errorCapturedHooks[i](
err, err,
instance && instance.renderProxy, instance && instance.renderProxy,
contextVNode ErrorTypeStrings[type]
) )
) { ) {
return return