+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()
+
+ 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 () => 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', 'root')
+ expect(fn).toHaveBeenCalledWith(err, 'mounted hook', 'child')
+ })
+
+ test('propagation stoppage', () => {
+ const err = new Error('foo')
+ const fn = jest.fn()
+
+ 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
+ }
+ }
- test.todo('in onErrorCaptured')
+ render(h(Comp), nodeOps.createElement('div'))
- test.todo('in setup function')
+ count.value++
+ await nextTick()
+ expect(fn).toHaveBeenCalledWith(err, 'watcher cleanup function')
+ })
- test.todo('in render function')
+ test('in component event handler', () => {
+ const err = new Error('foo')
+ const fn = jest.fn()
- test.todo('in watch (simple usage)')
+ const Comp = {
+ setup() {
+ onErrorCaptured((err, instance, info) => {
+ fn(err, info)
+ return true
+ })
+ return () =>
+ h(Child, {
+ onFoo: () => {
+ throw err
+ }
+ })
+ }
+ }
- test.todo('in watch (with source)')
+ const Child = {
+ setup(props: any, { emit }: any) {
+ emit('foo')
+ return () => null
+ }
+ }
- test.todo('in component event handler')
+ render(h(Comp), nodeOps.createElement('div'))
+ expect(fn).toHaveBeenCalledWith(err, 'component event handler')
+ })
// native event handler handling should be tested in respective renderers
})