Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createLocalVue errorHandler Option #1670

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
48f1c14
refactor(createlocalvue): move createLocalVue to shared utils
AtofStryker Aug 26, 2020
808fe89
refactor(createlocalvue): rename createLocalVue to _createLocalVue
AtofStryker Aug 26, 2020
98103d6
improvement(components): add Sync and Async components for testing
AtofStryker Aug 27, 2020
fc3be6c
improvement(flow): add VueConfig to Flow
AtofStryker Aug 27, 2020
0dca580
improvement(index): export new createLocalVue as default in Index
AtofStryker Aug 27, 2020
094e7db
improvement(_createlocalvue): allow registration of user defined config
AtofStryker Aug 27, 2020
3db2482
improvement(find): add findAllParentInstances to the find API
AtofStryker Aug 27, 2020
f1005a9
improvement(mount): pass localVue into mounted createLocalVue
AtofStryker Aug 27, 2020
72f1fdb
improvement(error): call user defined errorHandler if defined
AtofStryker Aug 27, 2020
55647c4
improvement(createlocalvue): add tests to createLocalVue errorHandler
AtofStryker Aug 27, 2020
8bbc3d1
docs(createlocalvue): document public createLocalVue API
AtofStryker Aug 28, 2020
75d0afc
docs(createlocalvue): document the createLocalVue internal API
AtofStryker Aug 28, 2020
ba9f677
docs(createlocalvue): document the errorHandler option in createLocalVue
AtofStryker Aug 28, 2020
3272a85
fix(createlocalvue tests): wrap createLocalVue async test in try/finally
AtofStryker Aug 28, 2020
5783bb6
fix(createlocalvue): skip async component throws for vue < 2.6
AtofStryker Aug 28, 2020
5ed0f28
improvement(find and error): add additional type safety to find & error
AtofStryker Aug 28, 2020
d73e8d9
fix(createlocalvue): only run sync error tests for vue versions < 2.4
AtofStryker Aug 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/api/createLocalVue.md
@@ -1,5 +1,10 @@
## createLocalVue()

- **Arguments:**

- `{Object} options`
- `{Function} errorHandler`

- **Returns:**

- `{Component}`
Expand All @@ -8,8 +13,12 @@

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

The `errorHandler` option can be used to handle uncaught errors during component render function and watchers.

Use it with `options.localVue`:

**Without options:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import MyPlugin from 'my-plugin'
Expand All @@ -27,4 +36,24 @@ const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)
```

**With the [`errorHandler`](https://vuejs.org/v2/api/#errorHandler) option:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const errorHandler = (err, vm, info) => {
expect(err).toBeInstanceOf(Error)
}

const localVue = createLocalVue({
errorHandler
})

// Foo throws an error inside a lifecycle hook
const wrapper = shallowMount(Foo, {
localVue
})
```

- **See also:** [Common Tips](../guides/common-tips.md#applying-global-plugins-and-mixins)
29 changes: 29 additions & 0 deletions docs/ja/api/createLocalVue.md
@@ -1,5 +1,10 @@
## createLocalVue()

- **引数:**

- `{Object} options`
- `{Function} errorHandler`

- **戻り値:**

- `{Component}`
Expand All @@ -8,8 +13,12 @@

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

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

`options.localVue` と一緒に使用してください。

**オプションなし:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
Expand All @@ -25,4 +34,24 @@ const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)
```

**[errorHandler](https://jp.vuejs.org/v2/api/index.html#errorHandler)オプションを使用:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const errorHandler = (err, vm, info) => {
expect(err).toBeInstanceOf(Error)
}

const localVue = createLocalVue({
errorHandler
})

// Fooがライフサイクルフック内でエラーをスローする
const wrapper = shallowMount(Foo, {
localVue
})
```

- **参照:** [一般的なヒント](../guides/common-tips.md#グローバルプラグインとミックスインの適用)
29 changes: 29 additions & 0 deletions docs/ru/api/createLocalVue.md
@@ -1,5 +1,10 @@
## createLocalVue()

- **Аргументы:**

- `{Object} options`
- `{Function} errorHandler`

- **Возвращает:**

- `{Component}`
Expand All @@ -8,8 +13,12 @@

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

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

Используйте вместе с `options.localVue`:

**Без опций:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
Expand All @@ -25,4 +34,24 @@ const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)
```

**С опцией [errorHandler](https://ru.vuejs.org/v2/api/index.html#errorHandler):**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const errorHandler = (err, vm, info) => {
expect(err).toBeInstanceOf(Error)
}

const localVue = createLocalVue({
errorHandler
})

// Foo выдает ошибку внутри ловушки жизненного цикла
const wrapper = shallowMount(Foo, {
localVue
})
```

- **См. также:** [Общие советы](../guides/common-tips.md#добавnение-гnобаnьных-пnагинов-и-примесей)
29 changes: 29 additions & 0 deletions docs/zh/api/createLocalVue.md
@@ -1,5 +1,10 @@
## createLocalVue()

- **参数:**

- `{Object} options`
- `{Function} errorHandler`

- **返回值:**

- `{Component}`
Expand All @@ -8,8 +13,12 @@

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

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

可通过 `options.localVue` 来使用:

**Without options:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import MyPlugin from 'my-plugin'
Expand All @@ -27,4 +36,24 @@ const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)
```

**使用[`errorHandler`](https://cn.vuejs.org/v2/api/index.html#errorHandler)选项:**

```js
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const errorHandler = (err, vm, info) => {
expect(err).toBeInstanceOf(Error)
}

const localVue = createLocalVue({
errorHandler
})

// Foo在生命周期挂钩中引发错误
const wrapper = shallowMount(Foo, {
localVue
})
```

- **延伸阅读:**[常用技巧](../guides/common-tips.md#applying-global-plugins-and-mixins)
4 changes: 4 additions & 0 deletions flow/options.flow.js
Expand Up @@ -36,6 +36,10 @@ declare type NormalizedOptions = {
shouldProxy?: boolean
}

declare type VueConfig = {
errorHandler?: Function
}

declare type SlotValue = Component | string | Array<Component | string>

declare type SlotsObject = { [name: string]: SlotValue }
Expand Down
4 changes: 2 additions & 2 deletions packages/server-test-utils/src/renderToString.js
Expand Up @@ -6,7 +6,7 @@ import { throwError } from 'shared/util'
import { createRenderer } from 'vue-server-renderer'
import { mergeOptions } from 'shared/merge-options'
import config from './config'
import testUtils from '@vue/test-utils'
import _createLocalVue from 'shared/create-local-vue'
import { validateOptions } from 'shared/validate-options'

Vue.config.productionTip = false
Expand Down Expand Up @@ -34,7 +34,7 @@ export default function renderToString(
const vm = createInstance(
component,
mergedOptions,
testUtils.createLocalVue(options.localVue)
_createLocalVue(options.localVue)
)

return renderer.renderToString(vm)
Expand Down
67 changes: 67 additions & 0 deletions packages/shared/create-local-vue.js
@@ -0,0 +1,67 @@
// @flow

import Vue from 'vue'
import cloneDeep from 'lodash/cloneDeep'

/**
* Used internally by vue-server-test-utils and test-utils to propagate/create vue instances.
* This method is wrapped by createLocalVue in test-utils to provide a different public API signature
* @param {Component} _Vue
* @param {VueConfig} config
* @returns {Component}
*/
function _createLocalVue(
_Vue: Component = Vue,
config: VueConfig = {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added argument config: VueConfig = {}

): Component {
const instance = _Vue.extend()

// clone global APIs
Object.keys(_Vue).forEach(key => {
if (!instance.hasOwnProperty(key)) {
const original = _Vue[key]
// cloneDeep can fail when cloning Vue instances
// cloneDeep checks that the instance has a Symbol
// which errors in Vue < 2.17 (https://github.com/vuejs/vue/pull/7878)
try {
instance[key] =
typeof original === 'object' ? cloneDeep(original) : original
} catch (e) {
instance[key] = original
}
}
})

// config is not enumerable
instance.config = cloneDeep(Vue.config)

// if a user defined errorHandler is defined by a localVue instance via createLocalVue, register it
instance.config.errorHandler = config.errorHandler || Vue.config.errorHandler
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only line that has changed within the logic, IE adding config.errorHandler. If we do not feel confortable reviewing this due to scope of change, we can break out the shared move in a separate PR to more clearly see the change set

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shared is private anyway, this should be fine


// option merge strategies need to be exposed by reference
// so that merge strats registered by plugins can work properly
instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies

// make sure all extends are based on this instance.
// this is important so that global components registered by plugins,
// e.g. router-link are created using the correct base constructor
instance.options._base = instance

// compat for vue-router < 2.7.1 where it does not allow multiple installs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job, this must have been a huge pain to figure out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, it was moved here from somewhere else - even better, that means it's been battle tested

if (instance._installedPlugins && instance._installedPlugins.length) {
instance._installedPlugins.length = 0
}
const use = instance.use
instance.use = (plugin, ...rest) => {
if (plugin.installed === true) {
plugin.installed = false
}
if (plugin.install && plugin.install.installed === true) {
plugin.install.installed = false
}
use.call(instance, plugin, ...rest)
}
return instance
}

export default _createLocalVue
60 changes: 9 additions & 51 deletions packages/test-utils/src/create-local-vue.js
@@ -1,56 +1,14 @@
// @flow

import Vue from 'vue'
import cloneDeep from 'lodash/cloneDeep'

function createLocalVue(_Vue: Component = Vue): Component {
const instance = _Vue.extend()

// clone global APIs
Object.keys(_Vue).forEach(key => {
if (!instance.hasOwnProperty(key)) {
const original = _Vue[key]
// cloneDeep can fail when cloning Vue instances
// cloneDeep checks that the instance has a Symbol
// which errors in Vue < 2.17 (https://github.com/vuejs/vue/pull/7878)
try {
instance[key] =
typeof original === 'object' ? cloneDeep(original) : original
} catch (e) {
instance[key] = original
}
}
})

// config is not enumerable
instance.config = cloneDeep(Vue.config)

instance.config.errorHandler = Vue.config.errorHandler

// option merge strategies need to be exposed by reference
// so that merge strats registered by plugins can work properly
instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies

// make sure all extends are based on this instance.
// this is important so that global components registered by plugins,
// e.g. router-link are created using the correct base constructor
instance.options._base = instance

// compat for vue-router < 2.7.1 where it does not allow multiple installs
if (instance._installedPlugins && instance._installedPlugins.length) {
instance._installedPlugins.length = 0
}
const use = instance.use
instance.use = (plugin, ...rest) => {
if (plugin.installed === true) {
plugin.installed = false
}
if (plugin.install && plugin.install.installed === true) {
plugin.install.installed = false
}
use.call(instance, plugin, ...rest)
}
return instance
import _createLocalVue from 'shared/create-local-vue'

/**
* Returns a local vue instance to add components, mixins and install plugins without polluting the global Vue class
* @param {VueConfig} config
* @returns {Component}
*/
function createLocalVue(config: VueConfig = {}): Component {
return _createLocalVue(undefined, config)
}

export default createLocalVue
23 changes: 21 additions & 2 deletions packages/test-utils/src/error.js
@@ -1,14 +1,33 @@
import { warn } from 'shared/util'
import { findAllInstances } from './find'
import { findAllInstances, findAllParentInstances } from './find'

function errorHandler(errorOrString, vm) {
function errorHandler(errorOrString, vm, info) {
const error =
typeof errorOrString === 'object' ? errorOrString : new Error(errorOrString)

// If a user defined errorHandler was register via createLocalVue
// find and call the user defined errorHandler
const instancedErrorHandlers = findAllParentInstances(vm)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just move this into line 21 that already has the conditional check? A lot of the type checking is for older versions of Vue

.filter(
_vm =>
_vm &&
_vm.$options &&
_vm.$options.localVue &&
_vm.$options.localVue.config &&
_vm.$options.localVue.config.errorHandler
)
.map(_vm => _vm.$options.localVue.config.errorHandler)

if (vm) {
vm._error = error
}

// should be one error handler, as only once can be registered with local vue
// regardless, if more exist (for whatever reason), invoke the other user defined error handlers
instancedErrorHandlers.forEach(instancedErrorHandler => {
instancedErrorHandler(error, vm, info)
})

throw error
}

Expand Down