vue2/test/unit/features/options/errorCaptured.spec.ts

427 lines
8.3 KiB
TypeScript

import Vue from 'vue'
describe('Options errorCaptured', () => {
let globalSpy
beforeEach(() => {
globalSpy = Vue.config.errorHandler = vi.fn()
})
afterEach(() => {
Vue.config.errorHandler = undefined
})
it('should capture error from child component', () => {
const spy = vi.fn()
let child
let err
const Child = {
created() {
child = this
err = new Error('child')
throw err
},
render() {}
}
new Vue({
errorCaptured: spy,
render: h => h(Child)
}).$mount()
expect(spy).toHaveBeenCalledWith(err, child, 'created hook')
// should propagate by default
expect(globalSpy).toHaveBeenCalledWith(err, child, 'created hook')
})
it('should be able to render the error in itself', done => {
let child
const Child = {
created() {
child = this
throw new Error('error from child')
},
render() {}
}
const vm = new Vue({
data: {
error: null
},
errorCaptured(e, vm, info) {
expect(vm).toBe(child)
this.error = e.toString() + ' in ' + info
},
render(h) {
if (this.error) {
return h('pre', this.error)
}
return h(Child)
}
}).$mount()
waitForUpdate(() => {
expect(vm.$el.textContent).toContain('error from child')
expect(vm.$el.textContent).toContain('in created hook')
}).then(done)
})
it('should not propagate to global handler when returning true', () => {
const spy = vi.fn()
let child
let err
const Child = {
created() {
child = this
err = new Error('child')
throw err
},
render() {}
}
new Vue({
errorCaptured(err, vm, info) {
spy(err, vm, info)
return false
},
render: h => h(Child, {})
}).$mount()
expect(spy).toHaveBeenCalledWith(err, child, 'created hook')
// should not propagate
expect(globalSpy).not.toHaveBeenCalled()
})
it('should propagate to global handler if itself throws error', () => {
let child
let err
const Child = {
created() {
child = this
err = new Error('child')
throw err
},
render() {}
}
let err2
const vm = new Vue({
errorCaptured() {
err2 = new Error('foo')
throw err2
},
render: h => h(Child, {})
}).$mount()
expect(globalSpy).toHaveBeenCalledWith(err, child, 'created hook')
expect(globalSpy).toHaveBeenCalledWith(err2, vm, 'errorCaptured hook')
})
it('should work across multiple parents, mixins and extends', () => {
const calls: any[] = []
const Child = {
created() {
throw new Error('child')
},
render() {}
}
const ErrorBoundaryBase = {
errorCaptured() {
calls.push(1)
}
}
const mixin = {
errorCaptured() {
calls.push(2)
}
}
const ErrorBoundaryExtended = {
extends: ErrorBoundaryBase,
mixins: [mixin],
errorCaptured() {
calls.push(3)
},
render: h => h(Child)
}
Vue.config.errorHandler = () => {
calls.push(5)
}
new Vue({
errorCaptured() {
calls.push(4)
},
render: h => h(ErrorBoundaryExtended)
}).$mount()
expect(calls).toEqual([1, 2, 3, 4, 5])
})
it('should work across multiple parents, mixins and extends with return false', () => {
const calls: any[] = []
const Child = {
created() {
throw new Error('child')
},
render() {}
}
const ErrorBoundaryBase = {
errorCaptured() {
calls.push(1)
}
}
const mixin = {
errorCaptured() {
calls.push(2)
}
}
const ErrorBoundaryExtended = {
extends: ErrorBoundaryBase,
mixins: [mixin],
errorCaptured() {
calls.push(3)
return false
},
render: h => h(Child)
}
Vue.config.errorHandler = () => {
calls.push(5)
}
new Vue({
errorCaptured() {
calls.push(4)
},
render: h => h(ErrorBoundaryExtended)
}).$mount()
expect(calls).toEqual([1, 2, 3])
})
// ref: https://github.com/vuejs/vuex/issues/1505
it('should not add watchers to render deps if they are referred from errorCaptured callback', done => {
const store = new Vue({
data: {
errors: []
}
})
const Child = {
computed: {
test() {
throw new Error('render error')
}
},
render(h) {
return h('div', {
attrs: {
'data-test': this.test
}
})
}
}
new Vue({
errorCaptured(error) {
store.errors.push(error)
},
render: h => h(Child)
}).$mount()
// Ensure not to trigger infinite loop
waitForUpdate(() => {
expect(store.errors.length).toBe(1)
expect(store.errors[0]).toEqual(new Error('render error'))
}).then(done)
})
it('should capture error from watcher', done => {
const spy = vi.fn()
let child
let err
const Child = {
data() {
return {
foo: null
}
},
watch: {
foo() {
err = new Error('userWatcherCallback error')
throw err
}
},
created() {
child = this
},
render() {}
}
new Vue({
errorCaptured: spy,
render: h => h(Child)
}).$mount()
child.foo = 'bar'
waitForUpdate(() => {
expect(spy).toHaveBeenCalledWith(err, child, 'callback for watcher "foo"')
expect(globalSpy).toHaveBeenCalledWith(
err,
child,
'callback for watcher "foo"'
)
}).then(done)
})
it('should capture promise error from watcher', done => {
const spy = vi.fn()
let child
let err
const Child = {
data() {
return {
foo: null
}
},
watch: {
foo() {
err = new Error('userWatcherCallback error')
return Promise.reject(err)
}
},
created() {
child = this
},
render() {}
}
new Vue({
errorCaptured: spy,
render: h => h(Child)
}).$mount()
child.foo = 'bar'
child.$nextTick(() => {
waitForUpdate(() => {
expect(spy).toHaveBeenCalledWith(
err,
child,
'callback for watcher "foo" (Promise/async)'
)
expect(globalSpy).toHaveBeenCalledWith(
err,
child,
'callback for watcher "foo" (Promise/async)'
)
}).then(done)
})
})
it('should capture error from immediate watcher', done => {
const spy = vi.fn()
let child
let err
const Child = {
data() {
return {
foo: 'foo'
}
},
watch: {
foo: {
immediate: true,
handler() {
err = new Error('userImmediateWatcherCallback error')
throw err
}
}
},
created() {
child = this
},
render() {}
}
new Vue({
errorCaptured: spy,
render: h => h(Child)
}).$mount()
waitForUpdate(() => {
expect(spy).toHaveBeenCalledWith(
err,
child,
'callback for immediate watcher "foo"'
)
expect(globalSpy).toHaveBeenCalledWith(
err,
child,
'callback for immediate watcher "foo"'
)
}).then(done)
})
it('should capture promise error from immediate watcher', done => {
const spy = vi.fn()
let child
let err
const Child = {
data() {
return {
foo: 'foo'
}
},
watch: {
foo: {
immediate: true,
handler() {
err = new Error('userImmediateWatcherCallback error')
return Promise.reject(err)
}
}
},
created() {
child = this
},
render() {}
}
new Vue({
errorCaptured: spy,
render: h => h(Child)
}).$mount()
waitForUpdate(() => {
expect(spy).toHaveBeenCalledWith(
err,
child,
'callback for immediate watcher "foo" (Promise/async)'
)
expect(globalSpy).toHaveBeenCalledWith(
err,
child,
'callback for immediate watcher "foo" (Promise/async)'
)
}).then(done)
})
})