Browse Source

test: tests for error handling

Evan You 6 years ago
parent
commit
be28f976af

+ 321 - 7
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()
+
+    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
 })

+ 7 - 2
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)

+ 0 - 1
packages/runtime-core/src/component.ts

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

+ 1 - 1
packages/runtime-core/src/errorHandling.ts

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