From 48f1c141bb41e6022f8f8c1b92e3d9b000db8b84 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 25 Aug 2020 22:29:19 -0400 Subject: [PATCH 01/17] refactor(createlocalvue): move createLocalVue to shared utils --- packages/server-test-utils/src/renderToString.js | 4 ++-- packages/{test-utils/src => shared}/create-local-vue.js | 0 packages/test-utils/src/index.js | 2 +- packages/test-utils/src/mount.js | 2 +- test/specs/create-local-vue.spec.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename packages/{test-utils/src => shared}/create-local-vue.js (100%) diff --git a/packages/server-test-utils/src/renderToString.js b/packages/server-test-utils/src/renderToString.js index c6d0b11b5..667da4ac0 100644 --- a/packages/server-test-utils/src/renderToString.js +++ b/packages/server-test-utils/src/renderToString.js @@ -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 @@ -34,7 +34,7 @@ export default function renderToString( const vm = createInstance( component, mergedOptions, - testUtils.createLocalVue(options.localVue) + createLocalVue(options.localVue) ) return renderer.renderToString(vm) diff --git a/packages/test-utils/src/create-local-vue.js b/packages/shared/create-local-vue.js similarity index 100% rename from packages/test-utils/src/create-local-vue.js rename to packages/shared/create-local-vue.js diff --git a/packages/test-utils/src/index.js b/packages/test-utils/src/index.js index 851e64114..e41d66697 100644 --- a/packages/test-utils/src/index.js +++ b/packages/test-utils/src/index.js @@ -1,7 +1,7 @@ import shallowMount from './shallow-mount' import mount from './mount' import { enableAutoDestroy, resetAutoDestroyState } from './auto-destroy' -import createLocalVue from './create-local-vue' +import createLocalVue from 'shared/create-local-vue' import RouterLinkStub from './components/RouterLinkStub' import createWrapper from './create-wrapper' import Wrapper from './wrapper' diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js index 0a0ef4128..99f7d93b1 100644 --- a/packages/test-utils/src/mount.js +++ b/packages/test-utils/src/mount.js @@ -7,7 +7,7 @@ import config from './config' import warnIfNoWindow from './warn-if-no-window' import polyfill from './polyfill' import createWrapper from './create-wrapper' -import createLocalVue from './create-local-vue' +import createLocalVue from 'shared/create-local-vue' import { validateOptions } from 'shared/validate-options' Vue.config.productionTip = false diff --git a/test/specs/create-local-vue.spec.js b/test/specs/create-local-vue.spec.js index 09a928a01..84bae88ff 100644 --- a/test/specs/create-local-vue.spec.js +++ b/test/specs/create-local-vue.spec.js @@ -1,7 +1,7 @@ import Vue from 'vue' import Vuex from 'vuex' import VueRouter from 'vue-router' -import { createLocalVue } from 'packages/test-utils/src' +import createLocalVue from 'packages/shared/create-local-vue' import Component from '~resources/components/component.vue' import ComponentWithVuex from '~resources/components/component-with-vuex.vue' import ComponentWithRouter from '~resources/components/component-with-router.vue' From 808fe89cf38b15ad73b5fd6c0169c6c94584adb4 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 25 Aug 2020 22:37:35 -0400 Subject: [PATCH 02/17] refactor(createlocalvue): rename createLocalVue to _createLocalVue Rename createLocalVue to _createLocalVue to indicate private use --- packages/server-test-utils/src/renderToString.js | 4 ++-- packages/shared/create-local-vue.js | 4 ++-- packages/test-utils/src/create-local-vue.js | 9 +++++++++ packages/test-utils/src/index.js | 4 ++-- packages/test-utils/src/mount.js | 4 ++-- ...e.spec.js => create-local-vue-internal.spec.js} | 14 +++++++------- 6 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 packages/test-utils/src/create-local-vue.js rename test/specs/{create-local-vue.spec.js => create-local-vue-internal.spec.js} (92%) diff --git a/packages/server-test-utils/src/renderToString.js b/packages/server-test-utils/src/renderToString.js index 667da4ac0..3e0e10143 100644 --- a/packages/server-test-utils/src/renderToString.js +++ b/packages/server-test-utils/src/renderToString.js @@ -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 createLocalVue from 'shared/create-local-vue' +import _createLocalVue from 'shared/create-local-vue' import { validateOptions } from 'shared/validate-options' Vue.config.productionTip = false @@ -34,7 +34,7 @@ export default function renderToString( const vm = createInstance( component, mergedOptions, - createLocalVue(options.localVue) + _createLocalVue(options.localVue) ) return renderer.renderToString(vm) diff --git a/packages/shared/create-local-vue.js b/packages/shared/create-local-vue.js index 6c81f93b4..b92de5ef1 100644 --- a/packages/shared/create-local-vue.js +++ b/packages/shared/create-local-vue.js @@ -3,7 +3,7 @@ import Vue from 'vue' import cloneDeep from 'lodash/cloneDeep' -function createLocalVue(_Vue: Component = Vue): Component { +function _createLocalVue(_Vue: Component = Vue): Component { const instance = _Vue.extend() // clone global APIs @@ -53,4 +53,4 @@ function createLocalVue(_Vue: Component = Vue): Component { return instance } -export default createLocalVue +export default _createLocalVue diff --git a/packages/test-utils/src/create-local-vue.js b/packages/test-utils/src/create-local-vue.js new file mode 100644 index 000000000..e86012b09 --- /dev/null +++ b/packages/test-utils/src/create-local-vue.js @@ -0,0 +1,9 @@ +// @flow + +import _createLocalVue from 'shared/create-local-vue' + +function createLocalVue(config: VueConfig = {}): Component { + return _createLocalVue(undefined, config) +} + +export default createLocalVue diff --git a/packages/test-utils/src/index.js b/packages/test-utils/src/index.js index e41d66697..6bb5f8b27 100644 --- a/packages/test-utils/src/index.js +++ b/packages/test-utils/src/index.js @@ -1,7 +1,7 @@ import shallowMount from './shallow-mount' import mount from './mount' import { enableAutoDestroy, resetAutoDestroyState } from './auto-destroy' -import createLocalVue from 'shared/create-local-vue' +import _createLocalVue from 'shared/create-local-vue' import RouterLinkStub from './components/RouterLinkStub' import createWrapper from './create-wrapper' import Wrapper from './wrapper' @@ -18,7 +18,7 @@ function shallow(component, options) { } export { - createLocalVue, + _createLocalVue as createLocalVue, createWrapper, config, enableAutoDestroy, diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js index 99f7d93b1..ba446f8ea 100644 --- a/packages/test-utils/src/mount.js +++ b/packages/test-utils/src/mount.js @@ -7,7 +7,7 @@ import config from './config' import warnIfNoWindow from './warn-if-no-window' import polyfill from './polyfill' import createWrapper from './create-wrapper' -import createLocalVue from 'shared/create-local-vue' +import _createLocalVue from 'shared/create-local-vue' import { validateOptions } from 'shared/validate-options' Vue.config.productionTip = false @@ -20,7 +20,7 @@ export default function mount(component, options = {}) { addGlobalErrorHandler(Vue) - const _Vue = createLocalVue(options.localVue) + const _Vue = _createLocalVue(options.localVue) const mergedOptions = mergeOptions(options, config) diff --git a/test/specs/create-local-vue.spec.js b/test/specs/create-local-vue-internal.spec.js similarity index 92% rename from test/specs/create-local-vue.spec.js rename to test/specs/create-local-vue-internal.spec.js index 84bae88ff..89fa3d417 100644 --- a/test/specs/create-local-vue.spec.js +++ b/test/specs/create-local-vue-internal.spec.js @@ -1,7 +1,7 @@ import Vue from 'vue' import Vuex from 'vuex' import VueRouter from 'vue-router' -import createLocalVue from 'packages/shared/create-local-vue' +import _createLocalVue from 'packages/shared/create-local-vue' import Component from '~resources/components/component.vue' import ComponentWithVuex from '~resources/components/component-with-vuex.vue' import ComponentWithRouter from '~resources/components/component-with-router.vue' @@ -10,7 +10,7 @@ import { itDoNotRunIf } from 'conditional-specs' describeWithShallowAndMount('createLocalVue', mountingMethod => { it('installs Vuex without polluting global Vue', () => { - const localVue = createLocalVue() + const localVue = _createLocalVue() localVue.use(Vuex) const store = new Vuex.Store({ state: { @@ -27,7 +27,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { }) it('Vuex should work properly with local Vue', async () => { - const localVue = createLocalVue() + const localVue = _createLocalVue() localVue.use(Vuex) const store = new Vuex.Store({ state: { @@ -53,7 +53,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { }) it('installs Router without polluting global Vue', () => { - const localVue = createLocalVue() + const localVue = _createLocalVue() localVue.use(VueRouter) const routes = [{ path: '/foo', component: Component }] const router = new VueRouter({ @@ -69,7 +69,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { mountingMethod.name === 'shallowMount', 'Router should work properly with local Vue', () => { - const localVue = createLocalVue() + const localVue = _createLocalVue() localVue.use(VueRouter) const routes = [ { @@ -102,7 +102,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { ) it('use can take additional arguments', () => { - const localVue = createLocalVue() + const localVue = _createLocalVue() const pluginOptions = { foo: 'bar' } const plugin = { install: function(_Vue, options) { @@ -124,7 +124,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { } Vue.use(Plugin) - const localVue = createLocalVue() + const localVue = _createLocalVue() localVue.use(Plugin) if (localVue._installedPlugins) { From 98103d62ec075fca60b05e55a04a97b9a4a8d110 Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 26 Aug 2020 21:44:19 -0400 Subject: [PATCH 03/17] improvement(components): add Sync and Async components for testing --- .../components/component-with-async-error.vue | 12 ++++++++++++ .../components/component-with-sync-error.vue | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 test/resources/components/component-with-async-error.vue create mode 100644 test/resources/components/component-with-sync-error.vue diff --git a/test/resources/components/component-with-async-error.vue b/test/resources/components/component-with-async-error.vue new file mode 100644 index 000000000..3cc17dd93 --- /dev/null +++ b/test/resources/components/component-with-async-error.vue @@ -0,0 +1,12 @@ + + + diff --git a/test/resources/components/component-with-sync-error.vue b/test/resources/components/component-with-sync-error.vue new file mode 100644 index 000000000..ae5152b4e --- /dev/null +++ b/test/resources/components/component-with-sync-error.vue @@ -0,0 +1,11 @@ + + + From fc3be6cf5ff0f7e70f85a095e460baaa94b22b8a Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 26 Aug 2020 22:04:04 -0400 Subject: [PATCH 04/17] improvement(flow): add VueConfig to Flow --- flow/options.flow.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flow/options.flow.js b/flow/options.flow.js index 992cf3d8b..476f73695 100644 --- a/flow/options.flow.js +++ b/flow/options.flow.js @@ -36,6 +36,10 @@ declare type NormalizedOptions = { shouldProxy?: boolean } +declare type VueConfig = { + errorHandler?: Function +} + declare type SlotValue = Component | string | Array declare type SlotsObject = { [name: string]: SlotValue } From 0dca5808ffc0c37085205ecf3df60e87ebd3a90f Mon Sep 17 00:00:00 2001 From: Bill Date: Wed, 26 Aug 2020 22:08:48 -0400 Subject: [PATCH 05/17] improvement(index): export new createLocalVue as default in Index --- packages/test-utils/src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/test-utils/src/index.js b/packages/test-utils/src/index.js index 6bb5f8b27..851e64114 100644 --- a/packages/test-utils/src/index.js +++ b/packages/test-utils/src/index.js @@ -1,7 +1,7 @@ import shallowMount from './shallow-mount' import mount from './mount' import { enableAutoDestroy, resetAutoDestroyState } from './auto-destroy' -import _createLocalVue from 'shared/create-local-vue' +import createLocalVue from './create-local-vue' import RouterLinkStub from './components/RouterLinkStub' import createWrapper from './create-wrapper' import Wrapper from './wrapper' @@ -18,7 +18,7 @@ function shallow(component, options) { } export { - _createLocalVue as createLocalVue, + createLocalVue, createWrapper, config, enableAutoDestroy, From 094e7db0acde99620e720cf2a598b6f298674ae1 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 27 Aug 2020 00:00:24 -0400 Subject: [PATCH 06/17] improvement(_createlocalvue): allow registration of user defined config Allow VueConfig to be passed in via createLocalVue to be registered on the created vue instance --- packages/shared/create-local-vue.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/shared/create-local-vue.js b/packages/shared/create-local-vue.js index b92de5ef1..586ab5f53 100644 --- a/packages/shared/create-local-vue.js +++ b/packages/shared/create-local-vue.js @@ -3,7 +3,10 @@ import Vue from 'vue' import cloneDeep from 'lodash/cloneDeep' -function _createLocalVue(_Vue: Component = Vue): Component { +function _createLocalVue( + _Vue: Component = Vue, + config: VueConfig = {} +): Component { const instance = _Vue.extend() // clone global APIs @@ -25,7 +28,8 @@ function _createLocalVue(_Vue: Component = Vue): Component { // config is not enumerable instance.config = cloneDeep(Vue.config) - instance.config.errorHandler = Vue.config.errorHandler + // if a user defined errorHandler is defined by a localVue instance via createLocalVue, register it + instance.config.errorHandler = config.errorHandler || Vue.config.errorHandler // option merge strategies need to be exposed by reference // so that merge strats registered by plugins can work properly From 3db2482d41c8d649011e89efd579f795792a2221 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 27 Aug 2020 00:01:58 -0400 Subject: [PATCH 07/17] improvement(find): add findAllParentInstances to the find API add findAllParentInstances method to traverse a component's parent to find globally registered properties via createLocalVue --- packages/test-utils/src/find.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/test-utils/src/find.js b/packages/test-utils/src/find.js index d769f6c76..473efd348 100644 --- a/packages/test-utils/src/find.js +++ b/packages/test-utils/src/find.js @@ -10,6 +10,26 @@ import { import { throwError } from 'shared/util' import { matches } from './matches' +/** + * Traverses a vue instance for its parents and returns them in an array format + * @param {Component} vm + * @returns {Component[]} The component and its corresponding parents, in order from left to right + */ +export function findAllParentInstances(childVm: any) { + const instances = [childVm] + + function getParent(_vm) { + if (_vm.$parent) { + instances.push(_vm.$parent) + return getParent(_vm.$parent) + } + return _vm + } + + getParent(childVm) + return instances +} + export function findAllInstances(rootVm: any) { const instances = [rootVm] let i = 0 From f1005a9669f2cedd080634f3a267b6dba92c535e Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 27 Aug 2020 00:04:00 -0400 Subject: [PATCH 08/17] improvement(mount): pass localVue into mounted createLocalVue Pass localVue into mounted createLocalVue to register localVue properties on component --- packages/test-utils/src/mount.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js index ba446f8ea..4d3b8a6ce 100644 --- a/packages/test-utils/src/mount.js +++ b/packages/test-utils/src/mount.js @@ -20,7 +20,10 @@ export default function mount(component, options = {}) { addGlobalErrorHandler(Vue) - const _Vue = _createLocalVue(options.localVue) + const _Vue = _createLocalVue( + options.localVue, + options.localVue ? options.localVue.config : undefined + ) const mergedOptions = mergeOptions(options, config) From 72f1fdb23ef76acec2ff285b84a02376a8e08504 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 27 Aug 2020 00:05:46 -0400 Subject: [PATCH 09/17] 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 --- packages/test-utils/src/error.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/test-utils/src/error.js b/packages/test-utils/src/error.js index c20ad4003..8f81a38d4 100644 --- a/packages/test-utils/src/error.js +++ b/packages/test-utils/src/error.js @@ -1,14 +1,31 @@ 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) + .filter( + _vm => + _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 } From 55647c4cc6dd46ccf261199d9e3adb05ffd01767 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 27 Aug 2020 00:33:53 -0400 Subject: [PATCH 10/17] improvement(createlocalvue): add tests to createLocalVue errorHandler add tests to createLocalVue errorHandler to test invocation on sync and async throws --- ...ernal.spec.js => create-local-vue.spec.js} | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) rename test/specs/{create-local-vue-internal.spec.js => create-local-vue.spec.js} (69%) diff --git a/test/specs/create-local-vue-internal.spec.js b/test/specs/create-local-vue.spec.js similarity index 69% rename from test/specs/create-local-vue-internal.spec.js rename to test/specs/create-local-vue.spec.js index 89fa3d417..71c52556f 100644 --- a/test/specs/create-local-vue-internal.spec.js +++ b/test/specs/create-local-vue.spec.js @@ -1,16 +1,18 @@ import Vue from 'vue' import Vuex from 'vuex' import VueRouter from 'vue-router' -import _createLocalVue from 'packages/shared/create-local-vue' +import { createLocalVue } from 'packages/test-utils/src' import Component from '~resources/components/component.vue' import ComponentWithVuex from '~resources/components/component-with-vuex.vue' import ComponentWithRouter from '~resources/components/component-with-router.vue' +import ComponentWithSyncError from '~resources/components/component-with-sync-error.vue' +import ComponentWithAsyncError from '~resources/components/component-with-async-error.vue' import { describeWithShallowAndMount } from '~resources/utils' -import { itDoNotRunIf } from 'conditional-specs' +import { itDoNotRunIf, itSkipIf } from 'conditional-specs' describeWithShallowAndMount('createLocalVue', mountingMethod => { it('installs Vuex without polluting global Vue', () => { - const localVue = _createLocalVue() + const localVue = createLocalVue() localVue.use(Vuex) const store = new Vuex.Store({ state: { @@ -27,7 +29,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { }) it('Vuex should work properly with local Vue', async () => { - const localVue = _createLocalVue() + const localVue = createLocalVue() localVue.use(Vuex) const store = new Vuex.Store({ state: { @@ -53,7 +55,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { }) it('installs Router without polluting global Vue', () => { - const localVue = _createLocalVue() + const localVue = createLocalVue() localVue.use(VueRouter) const routes = [{ path: '/foo', component: Component }] const router = new VueRouter({ @@ -69,7 +71,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { mountingMethod.name === 'shallowMount', 'Router should work properly with local Vue', () => { - const localVue = _createLocalVue() + const localVue = createLocalVue() localVue.use(VueRouter) const routes = [ { @@ -102,7 +104,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { ) it('use can take additional arguments', () => { - const localVue = _createLocalVue() + const localVue = createLocalVue() const pluginOptions = { foo: 'bar' } const plugin = { install: function(_Vue, options) { @@ -124,7 +126,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { } Vue.use(Plugin) - const localVue = _createLocalVue() + const localVue = createLocalVue() localVue.use(Plugin) if (localVue._installedPlugins) { @@ -132,4 +134,36 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { } expect(installCount).toEqual(2) }) + + it('Calls `errorHandler` when an error is thrown synchronously', () => { + const errorHandler = jest.fn() + const localVue = createLocalVue({ + errorHandler + }) + try { + mountingMethod(ComponentWithSyncError, { localVue }) + } catch (e) { + // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details + expect(errorHandler).toHaveBeenCalledTimes(1) + } + }) + + itSkipIf( + process.env.TEST_ENV === 'browser', + 'Calls `errorHandler` when an error is thrown asynchronously', + async () => { + const errorHandler = jest.fn() + const localVue = createLocalVue({ + errorHandler + }) + + mountingMethod(ComponentWithAsyncError, { localVue }) + + await Vue.nextTick() + await setTimeout() + + // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details + expect(errorHandler).toHaveBeenCalledTimes(1) + } + ) }) From 8bbc3d15406859e526e977d9bb0e5fed17c27be3 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 01:08:56 -0400 Subject: [PATCH 11/17] docs(createlocalvue): document public createLocalVue API --- packages/test-utils/src/create-local-vue.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/test-utils/src/create-local-vue.js b/packages/test-utils/src/create-local-vue.js index e86012b09..328b7d15d 100644 --- a/packages/test-utils/src/create-local-vue.js +++ b/packages/test-utils/src/create-local-vue.js @@ -2,6 +2,11 @@ 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) } From 75d0afc210395cdad343046424991437877b9216 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 01:09:30 -0400 Subject: [PATCH 12/17] docs(createlocalvue): document the createLocalVue internal API --- packages/shared/create-local-vue.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/shared/create-local-vue.js b/packages/shared/create-local-vue.js index 586ab5f53..34f3b0d22 100644 --- a/packages/shared/create-local-vue.js +++ b/packages/shared/create-local-vue.js @@ -3,6 +3,13 @@ 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 = {} From ba9f6777e4159f7bc5e7f35502371863fe3621fc Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 00:51:40 -0400 Subject: [PATCH 13/17] docs(createlocalvue): document the errorHandler option in createLocalVue --- docs/api/createLocalVue.md | 29 +++++++++++++++++++++++++++++ docs/ja/api/createLocalVue.md | 29 +++++++++++++++++++++++++++++ docs/ru/api/createLocalVue.md | 29 +++++++++++++++++++++++++++++ docs/zh/api/createLocalVue.md | 29 +++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+) diff --git a/docs/api/createLocalVue.md b/docs/api/createLocalVue.md index d7066a99f..84025e653 100644 --- a/docs/api/createLocalVue.md +++ b/docs/api/createLocalVue.md @@ -1,5 +1,10 @@ ## createLocalVue() +- **Arguments:** + + - `{Object} options` + - `{Function} errorHandler` + - **Returns:** - `{Component}` @@ -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' @@ -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) diff --git a/docs/ja/api/createLocalVue.md b/docs/ja/api/createLocalVue.md index 294e67934..303dee96d 100644 --- a/docs/ja/api/createLocalVue.md +++ b/docs/ja/api/createLocalVue.md @@ -1,5 +1,10 @@ ## createLocalVue() +- **引数:** + + - `{Object} options` + - `{Function} errorHandler` + - **戻り値:** - `{Component}` @@ -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' @@ -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#グローバルプラグインとミックスインの適用) diff --git a/docs/ru/api/createLocalVue.md b/docs/ru/api/createLocalVue.md index 6fd97811a..c39746c16 100644 --- a/docs/ru/api/createLocalVue.md +++ b/docs/ru/api/createLocalVue.md @@ -1,5 +1,10 @@ ## createLocalVue() +- **Аргументы:** + + - `{Object} options` + - `{Function} errorHandler` + - **Возвращает:** - `{Component}` @@ -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' @@ -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агинов-и-примесей) diff --git a/docs/zh/api/createLocalVue.md b/docs/zh/api/createLocalVue.md index cf0128dbe..b11ca3728 100644 --- a/docs/zh/api/createLocalVue.md +++ b/docs/zh/api/createLocalVue.md @@ -1,5 +1,10 @@ ## createLocalVue() +- **参数:** + + - `{Object} options` + - `{Function} errorHandler` + - **返回值:** - `{Component}` @@ -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' @@ -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) From 3272a85955b4d18d534edb4fbc9323ae0a1374cf Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 17:32:13 -0400 Subject: [PATCH 14/17] fix(createlocalvue tests): wrap createLocalVue async test in try/finally Wrap async error test for createLocalVue errorHandler in try/finally to prevent global errors --- test/specs/create-local-vue.spec.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/specs/create-local-vue.spec.js b/test/specs/create-local-vue.spec.js index 71c52556f..bc26b91d2 100644 --- a/test/specs/create-local-vue.spec.js +++ b/test/specs/create-local-vue.spec.js @@ -157,13 +157,15 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { errorHandler }) - mountingMethod(ComponentWithAsyncError, { localVue }) - - await Vue.nextTick() - await setTimeout() - - // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details - expect(errorHandler).toHaveBeenCalledTimes(1) + try { + mountingMethod(ComponentWithAsyncError, { localVue }) + + await Vue.nextTick() + await setTimeout() + } finally { + // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details + expect(errorHandler).toHaveBeenCalledTimes(1) + } } ) }) From 5783bb62ac98222c4b142edf52729025aaec69d5 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 17:58:38 -0400 Subject: [PATCH 15/17] fix(createlocalvue): skip async component throws for vue < 2.6 --- test/specs/create-local-vue.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/specs/create-local-vue.spec.js b/test/specs/create-local-vue.spec.js index bc26b91d2..be34ec1c4 100644 --- a/test/specs/create-local-vue.spec.js +++ b/test/specs/create-local-vue.spec.js @@ -7,7 +7,7 @@ import ComponentWithVuex from '~resources/components/component-with-vuex.vue' import ComponentWithRouter from '~resources/components/component-with-router.vue' import ComponentWithSyncError from '~resources/components/component-with-sync-error.vue' import ComponentWithAsyncError from '~resources/components/component-with-async-error.vue' -import { describeWithShallowAndMount } from '~resources/utils' +import { describeWithShallowAndMount, vueVersion } from '~resources/utils' import { itDoNotRunIf, itSkipIf } from 'conditional-specs' describeWithShallowAndMount('createLocalVue', mountingMethod => { @@ -149,7 +149,7 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { }) itSkipIf( - process.env.TEST_ENV === 'browser', + process.env.TEST_ENV === 'browser' || vueVersion < 2.6, 'Calls `errorHandler` when an error is thrown asynchronously', async () => { const errorHandler = jest.fn() From 5ed0f283721a8ceaf4edd1f34dfdbd895605b39a Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 18:37:08 -0400 Subject: [PATCH 16/17] 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 --- packages/test-utils/src/error.js | 2 ++ packages/test-utils/src/find.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/test-utils/src/error.js b/packages/test-utils/src/error.js index 8f81a38d4..dc2ba1a53 100644 --- a/packages/test-utils/src/error.js +++ b/packages/test-utils/src/error.js @@ -10,6 +10,8 @@ function errorHandler(errorOrString, vm, info) { const instancedErrorHandlers = findAllParentInstances(vm) .filter( _vm => + _vm && + _vm.$options && _vm.$options.localVue && _vm.$options.localVue.config && _vm.$options.localVue.config.errorHandler diff --git a/packages/test-utils/src/find.js b/packages/test-utils/src/find.js index 473efd348..2cf2be47c 100644 --- a/packages/test-utils/src/find.js +++ b/packages/test-utils/src/find.js @@ -19,7 +19,7 @@ export function findAllParentInstances(childVm: any) { const instances = [childVm] function getParent(_vm) { - if (_vm.$parent) { + if (_vm && _vm.$parent) { instances.push(_vm.$parent) return getParent(_vm.$parent) } From d73e8d9c7a576128e5e9ee421da1e2fb88dd0b97 Mon Sep 17 00:00:00 2001 From: Bill Date: Fri, 28 Aug 2020 18:38:02 -0400 Subject: [PATCH 17/17] fix(createlocalvue): only run sync error tests for vue versions < 2.4 --- test/specs/create-local-vue.spec.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test/specs/create-local-vue.spec.js b/test/specs/create-local-vue.spec.js index be34ec1c4..f8815a6de 100644 --- a/test/specs/create-local-vue.spec.js +++ b/test/specs/create-local-vue.spec.js @@ -135,18 +135,22 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => { expect(installCount).toEqual(2) }) - it('Calls `errorHandler` when an error is thrown synchronously', () => { - const errorHandler = jest.fn() - const localVue = createLocalVue({ - errorHandler - }) - try { - mountingMethod(ComponentWithSyncError, { localVue }) - } catch (e) { - // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details - expect(errorHandler).toHaveBeenCalledTimes(1) + itSkipIf( + vueVersion < 2.4, + 'Calls `errorHandler` when an error is thrown synchronously', + () => { + const errorHandler = jest.fn() + const localVue = createLocalVue({ + errorHandler + }) + try { + mountingMethod(ComponentWithSyncError, { localVue }) + } catch (e) { + // asserting arguments is a bit difficult due to multiple Vue version support. Please see https://vuejs.org/v2/api/#errorHandler for more details + expect(errorHandler).toHaveBeenCalledTimes(1) + } } - }) + ) itSkipIf( process.env.TEST_ENV === 'browser' || vueVersion < 2.6,