Skip to content

Commit e91effe

Browse files
authored
createLocalVue errorHandler Option (#1670)
* refactor(createlocalvue): move createLocalVue to shared utils * refactor(createlocalvue): rename createLocalVue to _createLocalVue Rename createLocalVue to _createLocalVue to indicate private use * improvement(components): add Sync and Async components for testing * improvement(flow): add VueConfig to Flow * improvement(index): export new createLocalVue as default in Index * improvement(_createlocalvue): allow registration of user defined config Allow VueConfig to be passed in via createLocalVue to be registered on the created vue instance * improvement(find): add findAllParentInstances to the find API add findAllParentInstances method to traverse a component's parent to find globally registered properties via createLocalVue * improvement(mount): pass localVue into mounted createLocalVue Pass localVue into mounted createLocalVue to register localVue properties on component * improvement(error): call user defined errorHandler if defined Call the user defined errorHandler created on the localVue instance via createLocalVue. This is called in the VTU global error handler * improvement(createlocalvue): add tests to createLocalVue errorHandler add tests to createLocalVue errorHandler to test invocation on sync and async throws * docs(createlocalvue): document public createLocalVue API * docs(createlocalvue): document the createLocalVue internal API * docs(createlocalvue): document the errorHandler option in createLocalVue * fix(createlocalvue tests): wrap createLocalVue async test in try/finally Wrap async error test for createLocalVue errorHandler in try/finally to prevent global errors * fix(createlocalvue): skip async component throws for vue < 2.6 * improvement(find and error): add additional type safety to find & error Add additional type safety to find and error for older versions of vue that might propagate a vm value of null/undefined * fix(createlocalvue): only run sync error tests for vue versions < 2.4
1 parent 085ef2a commit e91effe

File tree

14 files changed

+309
-59
lines changed

14 files changed

+309
-59
lines changed

docs/api/createLocalVue.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## createLocalVue()
22

3+
- **Arguments:**
4+
5+
- `{Object} options`
6+
- `{Function} errorHandler`
7+
38
- **Returns:**
49

510
- `{Component}`
@@ -8,8 +13,12 @@
813

914
`createLocalVue` returns a Vue class for you to add components, mixins and install plugins without polluting the global Vue class.
1015

16+
The `errorHandler` option can be used to handle uncaught errors during component render function and watchers.
17+
1118
Use it with `options.localVue`:
1219

20+
**Without options:**
21+
1322
```js
1423
import { createLocalVue, shallowMount } from '@vue/test-utils'
1524
import MyPlugin from 'my-plugin'
@@ -27,4 +36,24 @@ const freshWrapper = shallowMount(Foo)
2736
expect(freshWrapper.vm.foo).toBe(false)
2837
```
2938

39+
**With the [`errorHandler`](https://vuejs.org/v2/api/#errorHandler) option:**
40+
41+
```js
42+
import { createLocalVue, shallowMount } from '@vue/test-utils'
43+
import Foo from './Foo.vue'
44+
45+
const errorHandler = (err, vm, info) => {
46+
expect(err).toBeInstanceOf(Error)
47+
}
48+
49+
const localVue = createLocalVue({
50+
errorHandler
51+
})
52+
53+
// Foo throws an error inside a lifecycle hook
54+
const wrapper = shallowMount(Foo, {
55+
localVue
56+
})
57+
```
58+
3059
- **See also:** [Common Tips](../guides/common-tips.md#applying-global-plugins-and-mixins)

docs/ja/api/createLocalVue.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## createLocalVue()
22

3+
- **引数:**
4+
5+
- `{Object} options`
6+
- `{Function} errorHandler`
7+
38
- **戻り値:**
49

510
- `{Component}`
@@ -8,8 +13,12 @@
813

914
`createLocalVue` は、グローバル Vue クラスを汚染することなくコンポーネント、ミックスイン、プラグインを追加するための Vue クラスを返します。
1015

16+
[errorHandler](https://jp.vuejs.org/v2/api/index.html#errorHandler)オプションを使用すると、コンポーネントのレンダー機能とウォッチャー中にキャッチされなかったエラーを処理できます。
17+
1118
`options.localVue` と一緒に使用してください。
1219

20+
**オプションなし:**
21+
1322
```js
1423
import { createLocalVue, shallowMount } from '@vue/test-utils'
1524
import Foo from './Foo.vue'
@@ -25,4 +34,24 @@ const freshWrapper = shallowMount(Foo)
2534
expect(freshWrapper.vm.foo).toBe(false)
2635
```
2736

37+
**[errorHandler](https://jp.vuejs.org/v2/api/index.html#errorHandler)オプションを使用:**
38+
39+
```js
40+
import { createLocalVue, shallowMount } from '@vue/test-utils'
41+
import Foo from './Foo.vue'
42+
43+
const errorHandler = (err, vm, info) => {
44+
expect(err).toBeInstanceOf(Error)
45+
}
46+
47+
const localVue = createLocalVue({
48+
errorHandler
49+
})
50+
51+
// Fooがライフサイクルフック内でエラーをスローする
52+
const wrapper = shallowMount(Foo, {
53+
localVue
54+
})
55+
```
56+
2857
- **参照:** [一般的なヒント](../guides/common-tips.md#グローバルプラグインとミックスインの適用)

docs/ru/api/createLocalVue.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## createLocalVue()
22

3+
- **Аргументы:**
4+
5+
- `{Object} options`
6+
- `{Function} errorHandler`
7+
38
- **Возвращает:**
49

510
- `{Component}`
@@ -8,8 +13,12 @@
813

914
`createLocalVue` возвращает класс Vue, чтобы вы могли добавлять компоненты, примеси и устанавливать плагины без загрязнения глобального класса Vue.
1015

16+
Опция [errorHandler](https://ru.vuejs.org/v2/api/index.html#errorHandler) может использоваться для обработки неперехваченных ошибок во время функции визуализации компонента и наблюдателей.
17+
1118
Используйте вместе с `options.localVue`:
1219

20+
**Без опций:**
21+
1322
```js
1423
import { createLocalVue, shallowMount } from '@vue/test-utils'
1524
import Foo from './Foo.vue'
@@ -25,4 +34,24 @@ const freshWrapper = shallowMount(Foo)
2534
expect(freshWrapper.vm.foo).toBe(false)
2635
```
2736

37+
**С опцией [errorHandler](https://ru.vuejs.org/v2/api/index.html#errorHandler):**
38+
39+
```js
40+
import { createLocalVue, shallowMount } from '@vue/test-utils'
41+
import Foo from './Foo.vue'
42+
43+
const errorHandler = (err, vm, info) => {
44+
expect(err).toBeInstanceOf(Error)
45+
}
46+
47+
const localVue = createLocalVue({
48+
errorHandler
49+
})
50+
51+
// Foo выдает ошибку внутри ловушки жизненного цикла
52+
const wrapper = shallowMount(Foo, {
53+
localVue
54+
})
55+
```
56+
2857
- **См. также:** [Общие советы](../guides/common-tips.md#добавnение-гnобаnьных-пnагинов-и-примесей)

docs/zh/api/createLocalVue.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## createLocalVue()
22

3+
- **参数:**
4+
5+
- `{Object} options`
6+
- `{Function} errorHandler`
7+
38
- **返回值:**
49

510
- `{Component}`
@@ -8,8 +13,12 @@
813

914
`createLocalVue` 返回一个 Vue 的类供你添加组件、混入和安装插件而不会污染全局的 Vue 类。
1015

16+
在组件渲染功能和观察者期间,[`errorHandler`](https://cn.vuejs.org/v2/api/index.html#errorHandler)选项可用于处理未捕获的错误。
17+
1118
可通过 `options.localVue` 来使用:
1219

20+
**Without options:**
21+
1322
```js
1423
import { createLocalVue, shallowMount } from '@vue/test-utils'
1524
import MyPlugin from 'my-plugin'
@@ -27,4 +36,24 @@ const freshWrapper = shallowMount(Foo)
2736
expect(freshWrapper.vm.foo).toBe(false)
2837
```
2938

39+
**使用[`errorHandler`](https://cn.vuejs.org/v2/api/index.html#errorHandler)选项:**
40+
41+
```js
42+
import { createLocalVue, shallowMount } from '@vue/test-utils'
43+
import Foo from './Foo.vue'
44+
45+
const errorHandler = (err, vm, info) => {
46+
expect(err).toBeInstanceOf(Error)
47+
}
48+
49+
const localVue = createLocalVue({
50+
errorHandler
51+
})
52+
53+
// Foo在生命周期挂钩中引发错误
54+
const wrapper = shallowMount(Foo, {
55+
localVue
56+
})
57+
```
58+
3059
- **延伸阅读:**[常用技巧](../guides/common-tips.md#applying-global-plugins-and-mixins)

flow/options.flow.js

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ declare type NormalizedOptions = {
3636
shouldProxy?: boolean
3737
}
3838

39+
declare type VueConfig = {
40+
errorHandler?: Function
41+
}
42+
3943
declare type SlotValue = Component | string | Array<Component | string>
4044

4145
declare type SlotsObject = { [name: string]: SlotValue }

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { throwError } from 'shared/util'
66
import { createRenderer } from 'vue-server-renderer'
77
import { mergeOptions } from 'shared/merge-options'
88
import config from './config'
9-
import testUtils from '@vue/test-utils'
9+
import _createLocalVue from 'shared/create-local-vue'
1010
import { validateOptions } from 'shared/validate-options'
1111

1212
Vue.config.productionTip = false
@@ -34,7 +34,7 @@ export default function renderToString(
3434
const vm = createInstance(
3535
component,
3636
mergedOptions,
37-
testUtils.createLocalVue(options.localVue)
37+
_createLocalVue(options.localVue)
3838
)
3939

4040
return renderer.renderToString(vm)

packages/shared/create-local-vue.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// @flow
2+
3+
import Vue from 'vue'
4+
import cloneDeep from 'lodash/cloneDeep'
5+
6+
/**
7+
* Used internally by vue-server-test-utils and test-utils to propagate/create vue instances.
8+
* This method is wrapped by createLocalVue in test-utils to provide a different public API signature
9+
* @param {Component} _Vue
10+
* @param {VueConfig} config
11+
* @returns {Component}
12+
*/
13+
function _createLocalVue(
14+
_Vue: Component = Vue,
15+
config: VueConfig = {}
16+
): Component {
17+
const instance = _Vue.extend()
18+
19+
// clone global APIs
20+
Object.keys(_Vue).forEach(key => {
21+
if (!instance.hasOwnProperty(key)) {
22+
const original = _Vue[key]
23+
// cloneDeep can fail when cloning Vue instances
24+
// cloneDeep checks that the instance has a Symbol
25+
// which errors in Vue < 2.17 (https://github.com/vuejs/vue/pull/7878)
26+
try {
27+
instance[key] =
28+
typeof original === 'object' ? cloneDeep(original) : original
29+
} catch (e) {
30+
instance[key] = original
31+
}
32+
}
33+
})
34+
35+
// config is not enumerable
36+
instance.config = cloneDeep(Vue.config)
37+
38+
// if a user defined errorHandler is defined by a localVue instance via createLocalVue, register it
39+
instance.config.errorHandler = config.errorHandler || Vue.config.errorHandler
40+
41+
// option merge strategies need to be exposed by reference
42+
// so that merge strats registered by plugins can work properly
43+
instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies
44+
45+
// make sure all extends are based on this instance.
46+
// this is important so that global components registered by plugins,
47+
// e.g. router-link are created using the correct base constructor
48+
instance.options._base = instance
49+
50+
// compat for vue-router < 2.7.1 where it does not allow multiple installs
51+
if (instance._installedPlugins && instance._installedPlugins.length) {
52+
instance._installedPlugins.length = 0
53+
}
54+
const use = instance.use
55+
instance.use = (plugin, ...rest) => {
56+
if (plugin.installed === true) {
57+
plugin.installed = false
58+
}
59+
if (plugin.install && plugin.install.installed === true) {
60+
plugin.install.installed = false
61+
}
62+
use.call(instance, plugin, ...rest)
63+
}
64+
return instance
65+
}
66+
67+
export default _createLocalVue
+9-51
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,14 @@
11
// @flow
22

3-
import Vue from 'vue'
4-
import cloneDeep from 'lodash/cloneDeep'
5-
6-
function createLocalVue(_Vue: Component = Vue): Component {
7-
const instance = _Vue.extend()
8-
9-
// clone global APIs
10-
Object.keys(_Vue).forEach(key => {
11-
if (!instance.hasOwnProperty(key)) {
12-
const original = _Vue[key]
13-
// cloneDeep can fail when cloning Vue instances
14-
// cloneDeep checks that the instance has a Symbol
15-
// which errors in Vue < 2.17 (https://github.com/vuejs/vue/pull/7878)
16-
try {
17-
instance[key] =
18-
typeof original === 'object' ? cloneDeep(original) : original
19-
} catch (e) {
20-
instance[key] = original
21-
}
22-
}
23-
})
24-
25-
// config is not enumerable
26-
instance.config = cloneDeep(Vue.config)
27-
28-
instance.config.errorHandler = Vue.config.errorHandler
29-
30-
// option merge strategies need to be exposed by reference
31-
// so that merge strats registered by plugins can work properly
32-
instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies
33-
34-
// make sure all extends are based on this instance.
35-
// this is important so that global components registered by plugins,
36-
// e.g. router-link are created using the correct base constructor
37-
instance.options._base = instance
38-
39-
// compat for vue-router < 2.7.1 where it does not allow multiple installs
40-
if (instance._installedPlugins && instance._installedPlugins.length) {
41-
instance._installedPlugins.length = 0
42-
}
43-
const use = instance.use
44-
instance.use = (plugin, ...rest) => {
45-
if (plugin.installed === true) {
46-
plugin.installed = false
47-
}
48-
if (plugin.install && plugin.install.installed === true) {
49-
plugin.install.installed = false
50-
}
51-
use.call(instance, plugin, ...rest)
52-
}
53-
return instance
3+
import _createLocalVue from 'shared/create-local-vue'
4+
5+
/**
6+
* Returns a local vue instance to add components, mixins and install plugins without polluting the global Vue class
7+
* @param {VueConfig} config
8+
* @returns {Component}
9+
*/
10+
function createLocalVue(config: VueConfig = {}): Component {
11+
return _createLocalVue(undefined, config)
5412
}
5513

5614
export default createLocalVue

packages/test-utils/src/error.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
import { warn } from 'shared/util'
2-
import { findAllInstances } from './find'
2+
import { findAllInstances, findAllParentInstances } from './find'
33

4-
function errorHandler(errorOrString, vm) {
4+
function errorHandler(errorOrString, vm, info) {
55
const error =
66
typeof errorOrString === 'object' ? errorOrString : new Error(errorOrString)
77

8+
// If a user defined errorHandler was register via createLocalVue
9+
// find and call the user defined errorHandler
10+
const instancedErrorHandlers = findAllParentInstances(vm)
11+
.filter(
12+
_vm =>
13+
_vm &&
14+
_vm.$options &&
15+
_vm.$options.localVue &&
16+
_vm.$options.localVue.config &&
17+
_vm.$options.localVue.config.errorHandler
18+
)
19+
.map(_vm => _vm.$options.localVue.config.errorHandler)
20+
821
if (vm) {
922
vm._error = error
1023
}
1124

25+
// should be one error handler, as only once can be registered with local vue
26+
// regardless, if more exist (for whatever reason), invoke the other user defined error handlers
27+
instancedErrorHandlers.forEach(instancedErrorHandler => {
28+
instancedErrorHandler(error, vm, info)
29+
})
30+
1231
throw error
1332
}
1433

0 commit comments

Comments
 (0)