Skip to content

Commit efab983

Browse files
authoredJan 20, 2019
fix: handle errors in destroy (#1106)
1 parent d2f26e8 commit efab983

File tree

11 files changed

+107
-81
lines changed

11 files changed

+107
-81
lines changed
 

‎packages/create-instance/patch-create-element.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ function shouldExtend (component, _Vue) {
1818
}
1919

2020
function extend (component, _Vue) {
21-
const stub = _Vue.extend(component.options)
21+
const componentOptions = component.options ? component.options : component
22+
const stub = _Vue.extend(componentOptions)
2223
stub.options.$_vueTestUtils_original = component
2324
return stub
2425
}
@@ -92,17 +93,16 @@ export function patchCreateElement (_Vue, stubs, stubAllComponents) {
9293
return originalCreateElement(el, ...args)
9394
}
9495

96+
if (isDynamicComponent(original)) {
97+
return originalCreateElement(el, ...args)
98+
}
99+
95100
if (
96101
original.options &&
97102
original.options.$_vueTestUtils_original
98103
) {
99104
original = original.options.$_vueTestUtils_original
100105
}
101-
102-
if (isDynamicComponent(original)) {
103-
return originalCreateElement(el, ...args)
104-
}
105-
106106
const stub = createStubIfNeeded(stubAllComponents, original, _Vue, el)
107107

108108
if (stub) {

‎packages/server-test-utils/src/renderToString.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function renderToString (
3737
// $FlowIgnore
3838
renderer.renderToString(vm, (err, res) => {
3939
if (err) {
40-
console.log(err)
40+
throw err
4141
}
4242
renderedString = res
4343
})

‎packages/test-utils/src/create-local-vue.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import Vue from 'vue'
44
import cloneDeep from 'lodash/cloneDeep'
5-
import errorHandler from './error-handler'
65

76
function createLocalVue (_Vue: Component = Vue): Component {
87
const instance = _Vue.extend()
@@ -27,7 +26,7 @@ function createLocalVue (_Vue: Component = Vue): Component {
2726
// config is not enumerable
2827
instance.config = cloneDeep(Vue.config)
2928

30-
instance.config.errorHandler = errorHandler
29+
instance.config.errorHandler = Vue.config.errorHandler
3130

3231
// option merge strategies need to be exposed by reference
3332
// so that merge strats registered by plugins can work properly

‎packages/test-utils/src/error-handler.js

-15
This file was deleted.

‎packages/test-utils/src/error.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { warn } from 'shared/util'
2+
import { findAllInstances } from './find'
3+
4+
function errorHandler (errorOrString, vm) {
5+
const error =
6+
typeof errorOrString === 'object'
7+
? errorOrString
8+
: new Error(errorOrString)
9+
10+
vm._error = error
11+
throw error
12+
}
13+
14+
export function throwIfInstancesThrew (vm) {
15+
const instancesWithError = findAllInstances(vm).filter(
16+
_vm => _vm._error
17+
)
18+
19+
if (instancesWithError.length > 0) {
20+
throw instancesWithError[0]._error
21+
}
22+
}
23+
24+
let hasWarned = false
25+
26+
// Vue swallows errors thrown by instances, even if the global error handler
27+
// throws. In order to throw in the test, we add an _error property to an
28+
// instance when it throws. Then we loop through the instances with
29+
// throwIfInstancesThrew and throw an error in the test context if any
30+
// instances threw.
31+
export function addGlobalErrorHandler (_Vue) {
32+
const existingErrorHandler = _Vue.config.errorHandler
33+
34+
if (existingErrorHandler === errorHandler) {
35+
return
36+
}
37+
38+
if (_Vue.config.errorHandler && !hasWarned) {
39+
warn(
40+
`Global error handler detected (Vue.config.errorHandler). \n` +
41+
`Vue Test Utils sets a custom error handler to throw errors ` +
42+
`thrown by instances. If you want this behavior in ` +
43+
`your tests, you must remove the global error handler.`
44+
)
45+
hasWarned = true
46+
} else {
47+
_Vue.config.errorHandler = errorHandler
48+
}
49+
}

‎packages/test-utils/src/mount.js

+14-18
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,52 @@ import Vue from 'vue'
66
import VueWrapper from './vue-wrapper'
77
import createInstance from 'create-instance'
88
import createElement from './create-element'
9-
import errorHandler from './error-handler'
10-
import { findAllInstances } from './find'
9+
import {
10+
throwIfInstancesThrew,
11+
addGlobalErrorHandler
12+
} from './error'
1113
import { mergeOptions } from 'shared/merge-options'
1214
import config from './config'
1315
import warnIfNoWindow from './warn-if-no-window'
1416
import createWrapper from './create-wrapper'
1517
import createLocalVue from './create-local-vue'
18+
1619
Vue.config.productionTip = false
1720
Vue.config.devtools = false
1821

1922
export default function mount (
2023
component: Component,
2124
options: Options = {}
2225
): VueWrapper | Wrapper {
23-
const existingErrorHandler = Vue.config.errorHandler
24-
Vue.config.errorHandler = errorHandler
25-
2626
warnIfNoWindow()
2727

28-
const elm = options.attachToDocument ? createElement() : undefined
28+
addGlobalErrorHandler(Vue)
29+
30+
const _Vue = createLocalVue(options.localVue)
2931

3032
const mergedOptions = mergeOptions(options, config)
3133

3234
const parentVm = createInstance(
3335
component,
3436
mergedOptions,
35-
createLocalVue(options.localVue)
37+
_Vue
3638
)
3739

38-
const vm = parentVm.$mount(elm).$refs.vm
39-
40-
const componentsWithError = findAllInstances(vm).filter(
41-
c => c._error
42-
)
40+
const el = options.attachToDocument ? createElement() : undefined
41+
const vm = parentVm.$mount(el).$refs.vm
4342

44-
if (componentsWithError.length > 0) {
45-
throw componentsWithError[0]._error
46-
}
43+
component._Ctor = {}
4744

48-
Vue.config.errorHandler = existingErrorHandler
45+
throwIfInstancesThrew(vm)
4946

5047
const wrapperOptions = {
5148
attachedToDocument: !!mergedOptions.attachToDocument,
5249
sync: mergedOptions.sync
5350
}
51+
5452
const root = vm.$options._isFunctionalContainer
5553
? vm._vnode
5654
: vm
5755

58-
component._Ctor = []
59-
6056
return createWrapper(root, wrapperOptions)
6157
}

‎packages/test-utils/src/wrapper.js

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { orderWatchers } from './order-watchers'
2222
import { recursivelySetData } from './recursively-set-data'
2323
import { matches } from './matches'
2424
import createDOMEvent from './create-dom-event'
25+
import { throwIfInstancesThrew } from './error'
2526

2627
export default class Wrapper implements BaseWrapper {
2728
+vnode: VNode | null;
@@ -152,6 +153,7 @@ export default class Wrapper implements BaseWrapper {
152153
}
153154
// $FlowIgnore
154155
this.vm.$destroy()
156+
throwIfInstancesThrew(this.vm)
155157
}
156158

157159
/**

‎test/specs/create-local-vue.spec.js

-6
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,4 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => {
131131
}
132132
expect(installCount).to.equal(2)
133133
})
134-
135-
it('has an errorHandler', () => {
136-
const localVue = createLocalVue()
137-
138-
expect(localVue.config.errorHandler).to.be.an('function')
139-
})
140134
})

‎test/specs/mount.spec.js

+19-33
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
1313
const windowSave = window
1414

1515
beforeEach(() => {
16-
sinon.stub(console, 'error')
16+
sinon.stub(console, 'error').callThrough()
1717
})
1818

1919
afterEach(() => {
@@ -322,38 +322,6 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
322322
expect(fn).to.throw('Error in mounted')
323323
})
324324

325-
itDoNotRunIf(vueVersion < 2.2, 'logs errors once after mount', done => {
326-
Vue.config.errorHandler = null
327-
const TestComponent = {
328-
template: '<div/>',
329-
updated: function () {
330-
throw new Error('Error in updated')
331-
}
332-
}
333-
334-
const wrapper = mount(TestComponent, {
335-
sync: false
336-
})
337-
wrapper.vm.$forceUpdate()
338-
setTimeout(() => {
339-
vueVersion > 2.1
340-
? expect(console.error).calledTwice
341-
: expect(console.error).calledOnce
342-
done()
343-
})
344-
})
345-
346-
it('restores user error handler after mount', () => {
347-
const existingErrorHandler = () => {}
348-
Vue.config.errorHandler = existingErrorHandler
349-
const TestComponent = {
350-
template: '<div/>'
351-
}
352-
mount(TestComponent)
353-
expect(Vue.config.errorHandler).to.equal(existingErrorHandler)
354-
Vue.config.errorHandler = null
355-
})
356-
357325
it('adds unused propsData as attributes', () => {
358326
const wrapper = mount(
359327
ComponentWithProps, {
@@ -415,4 +383,22 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
415383
})
416384
expect(wrapper.findAll(ChildComponent).length).to.equal(1)
417385
})
386+
387+
it('throws if component throws during update', () => {
388+
const TestComponent = {
389+
template: '<div :p="a" />',
390+
updated () {
391+
throw new Error('err')
392+
},
393+
data: () => ({
394+
a: 1
395+
})
396+
}
397+
const wrapper = mount(TestComponent)
398+
const fn = () => {
399+
wrapper.vm.a = 2
400+
}
401+
expect(fn).to.throw()
402+
wrapper.destroy()
403+
})
418404
})

‎test/specs/mounting-options/localVue.spec.js

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ describeWithMountingMethods('options.localVue', mountingMethod => {
9292
localVue.prototype.$route = {}
9393

9494
const Extends = {
95+
template: '<div />',
9596
created () {
9697
console.log(this.$route.params)
9798
}

‎test/specs/wrapper/destroy.spec.js

+14
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,18 @@ describeWithShallowAndMount('destroy', mountingMethod => {
3030
wrapper.destroy()
3131
expect(wrapper.vm.$el.parentNode).to.be.null
3232
})
33+
34+
it('throws if component throws during destroy', () => {
35+
const TestComponent = {
36+
template: '<div :p="a" />',
37+
beforeDestroy () {
38+
throw new Error('error')
39+
},
40+
data: () => ({
41+
a: 1
42+
})
43+
}
44+
const wrapper = mountingMethod(TestComponent)
45+
expect(() => wrapper.destroy()).to.throw()
46+
})
3347
})

0 commit comments

Comments
 (0)
Please sign in to comment.