You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

229 lines
6.3 KiB

import Vue from 'vue'
import { formatComponentName } from 'core/util/debug'
const components = createErrorTestComponents()
describe('Error handling', () => {
// hooks that prevents the component from rendering, but should not
// break parent component
;[
['render', 'render function'],
['beforeCreate', 'beforeCreate hook'],
['created', 'created hook'],
['beforeMount', 'beforeMount hook']
].forEach(([type, description]) => {
it(`should recover from errors in ${type}`, done => {
const vm = createTestInstance(components[type])
expect(`Error in ${description}`).toHaveBeenWarned()
expect(`Error: ${type}`).toHaveBeenWarned()
assertRootInstanceActive(vm).then(done)
})
})
// error in mounted hook should affect neither child nor parent
it('should recover from errors in mounted hook', done => {
const vm = createTestInstance(components.mounted)
expect(`Error in mounted hook`).toHaveBeenWarned()
expect(`Error: mounted`).toHaveBeenWarned()
assertBothInstancesActive(vm).then(done)
})
// error in beforeUpdate/updated should affect neither child nor parent
;[
['beforeUpdate', 'beforeUpdate hook'],
['updated', 'updated hook']
].forEach(([type, description]) => {
it(`should recover from errors in ${type} hook`, done => {
const vm = createTestInstance(components[type])
assertBothInstancesActive(vm).then(() => {
expect(`Error in ${description}`).toHaveBeenWarned()
expect(`Error: ${type}`).toHaveBeenWarned()
}).then(done)
})
})
;[
['beforeDestroy', 'beforeDestroy hook'],
['destroyed', 'destroyed hook']
].forEach(([type, description]) => {
it(`should recover from errors in ${type} hook`, done => {
const vm = createTestInstance(components[type])
vm.ok = false
waitForUpdate(() => {
expect(`Error in ${description}`).toHaveBeenWarned()
expect(`Error: ${type}`).toHaveBeenWarned()
}).thenWaitFor(next => {
assertRootInstanceActive(vm).end(next)
}).then(done)
})
})
it('should recover from errors in user watcher getter', done => {
const vm = createTestInstance(components.userWatcherGetter)
vm.n++
waitForUpdate(() => {
expect(`Error in getter for watcher`).toHaveBeenWarned()
function getErrorMsg () {
try {
this.a.b.c
} catch (e) {
return e.toString()
}
}
const msg = getErrorMsg.call(vm)
expect(msg).toHaveBeenWarned()
}).thenWaitFor(next => {
assertBothInstancesActive(vm).end(next)
}).then(done)
})
it('should recover from errors in user watcher callback', done => {
const vm = createTestInstance(components.userWatcherCallback)
vm.n++
waitForUpdate(() => {
expect(`Error in callback for watcher "n"`).toHaveBeenWarned()
expect(`Error: userWatcherCallback`).toHaveBeenWarned()
}).thenWaitFor(next => {
assertBothInstancesActive(vm).end(next)
}).then(done)
})
it('config.errorHandler should capture errors', done => {
const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler')
const vm = createTestInstance(components.render)
const args = spy.calls.argsFor(0)
expect(args[0].toString()).toContain('Error: render') // error
expect(args[1]).toBe(vm.$refs.child) // vm
expect(args[2]).toContain('render function') // description
assertRootInstanceActive(vm).then(() => {
Vue.config.errorHandler = null
}).then(done)
})
it('properly format component names', () => {
const vm = new Vue()
expect(formatComponentName(vm)).toBe('<Root>')
vm.$root = null
vm.$options.name = 'hello-there'
expect(formatComponentName(vm)).toBe('<HelloThere>')
vm.$options.name = null
vm.$options._componentTag = 'foo-bar-1'
expect(formatComponentName(vm)).toBe('<FooBar1>')
vm.$options._componentTag = null
vm.$options.__file = '/foo/bar/baz/SomeThing.vue'
expect(formatComponentName(vm)).toBe(`<SomeThing> at ${vm.$options.__file}`)
expect(formatComponentName(vm, false)).toBe('<SomeThing>')
vm.$options.__file = 'C:\\foo\\bar\\baz\\windows_file.vue'
expect(formatComponentName(vm)).toBe(`<WindowsFile> at ${vm.$options.__file}`)
expect(formatComponentName(vm, false)).toBe('<WindowsFile>')
})
})
function createErrorTestComponents () {
const components = {}
// render error
components.render = {
render (h) {
throw new Error('render')
}
}
// lifecycle errors
;['create', 'mount', 'update', 'destroy'].forEach(hook => {
// before
const before = 'before' + hook.charAt(0).toUpperCase() + hook.slice(1)
const beforeComp = components[before] = {
props: ['n'],
render (h) {
return h('div', this.n)
}
}
beforeComp[before] = function () {
throw new Error(before)
}
// after
const after = hook.replace(/e?$/, 'ed')
const afterComp = components[after] = {
props: ['n'],
render (h) {
return h('div', this.n)
}
}
afterComp[after] = function () {
throw new Error(after)
}
})
// user watcher
components.userWatcherGetter = {
props: ['n'],
created () {
this.$watch(function () {
return this.n + this.a.b.c
}, val => {
console.log('user watcher fired: ' + val)
})
},
render (h) {
return h('div', this.n)
}
}
components.userWatcherCallback = {
props: ['n'],
watch: {
n () {
throw new Error('userWatcherCallback error')
}
},
render (h) {
return h('div', this.n)
}
}
return components
}
function createTestInstance (Comp) {
return new Vue({
data: {
n: 0,
ok: true
},
render (h) {
return h('div', [
'n:' + this.n + '\n',
this.ok
? h(Comp, { ref: 'child', props: { n: this.n }})
: null
])
}
}).$mount()
}
function assertRootInstanceActive (vm, chain) {
expect(vm.$el.innerHTML).toContain('n:0\n')
vm.n++
return waitForUpdate(() => {
expect(vm.$el.innerHTML).toContain('n:1\n')
})
}
function assertBothInstancesActive (vm) {
vm.n = 0
return waitForUpdate(() => {
expect(vm.$refs.child.$el.innerHTML).toContain('0')
}).thenWaitFor(next => {
assertRootInstanceActive(vm).then(() => {
expect(vm.$refs.child.$el.innerHTML).toContain('1')
}).end(next)
})
}