mirror of https://github.com/vuejs/vue.git
427 lines
8.3 KiB
TypeScript
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)
|
|
})
|
|
})
|