From a91ad8e2a582f8e3c7e8adc7c37c51cfbfbf52f4 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 20 Jan 2019 10:37:37 +0000 Subject: [PATCH 1/4] refactor: use validators --- .../create-instance/create-component-stubs.js | 22 ++++----- packages/create-instance/create-instance.js | 45 ++++++++++--------- .../create-instance/patch-create-element.js | 19 ++++---- packages/shared/validators.js | 12 +++++ packages/test-utils/src/matches.js | 7 +-- test/specs/mount.spec.js | 26 +++++++++++ test/specs/mounting-options/stubs.spec.js | 2 +- 7 files changed, 85 insertions(+), 48 deletions(-) diff --git a/packages/create-instance/create-component-stubs.js b/packages/create-instance/create-component-stubs.js index 33470ae76..2d948bfbc 100644 --- a/packages/create-instance/create-component-stubs.js +++ b/packages/create-instance/create-component-stubs.js @@ -10,7 +10,9 @@ import { import { componentNeedsCompiling, templateContainsComponent, - isVueComponent + isVueComponent, + isDynamicComponent, + isConstructor } from '../shared/validators' import { compileTemplate, @@ -65,11 +67,11 @@ function createClassString (staticClass, dynamicClass) { } function resolveOptions (component, _Vue) { - if (typeof component === 'function' && !component.cid) { + if (isDynamicComponent(component)) { return {} } - return typeof component === 'function' + return isConstructor(component) ? component.options : _Vue.extend(component).options } @@ -112,19 +114,16 @@ export function createStubFromComponent ( } } -export function createStubFromString ( +function createStubFromString ( templateString: string, originalComponent: Component = {}, - name: string + name: string, + _Vue ): Component { if (templateContainsComponent(templateString, name)) { throwError('options.stub cannot contain a circular reference') } - - const componentOptions = - typeof originalComponent === 'function' && originalComponent.cid - ? originalComponent.extendOptions - : originalComponent + const componentOptions = resolveOptions(originalComponent, _Vue) return { ...getCoreProperties(componentOptions), @@ -167,7 +166,8 @@ export function createStubsFromStubsObject ( acc[stubName] = createStubFromString( stub, component, - stubName + stubName, + _Vue ) return acc } diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 6e1cdd458..11fd3e0ec 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -17,6 +17,12 @@ import { validateSlots } from './validate-slots' import createScopedSlots from './create-scoped-slots' import { createStubsFromStubsObject } from './create-component-stubs' import { patchCreateElement } from './patch-create-element' +import { + isReservedTag, + isConstructor, + isDynamicComponent, + isComponentOptions +} from 'shared/validators' function vueExtendUnsupportedOption (option: string) { return `options.${option} is not supported for ` + @@ -43,9 +49,7 @@ export default function createInstance ( _Vue.options._base = _Vue if ( - VUE_VERSION < 2.3 && - typeof component === 'function' && - component.options + VUE_VERSION < 2.3 && isConstructor(component) ) { UNSUPPORTED_VERSION_OPTIONS.forEach((option) => { if (options[option]) { @@ -54,11 +58,15 @@ export default function createInstance ( }) } + let componentOptions = isConstructor(component) + ? component.options + : component + // instance options are options that are passed to the // root instance when it's instantiated const instanceOptions = extractInstanceOptions(options) const stubComponentsObject = createStubsFromStubsObject( - component.components, + componentOptions.components, // $FlowIgnore options.stubs, _Vue @@ -69,11 +77,8 @@ export default function createInstance ( addStubs(_Vue, stubComponentsObject) patchCreateElement(_Vue, stubComponentsObject, options.shouldProxy) - if ( - (component.options && component.options.functional) || - component.functional - ) { - component = createFunctionalComponent(component, options, _Vue) + if (componentOptions.functional) { + componentOptions = createFunctionalComponent(componentOptions, options, _Vue) } else if (options.context) { throwError( `mount.context can only be used when mounting a ` + @@ -81,19 +86,13 @@ export default function createInstance ( ) } - if (componentNeedsCompiling(component)) { - compileTemplate(component) + if (componentNeedsCompiling(componentOptions)) { + compileTemplate(componentOptions) } - if (component.options) { - component.options._base = _Vue - } + componentOptions._base = _Vue - // extend component from _Vue to add properties and mixins - // extend does not work correctly for sub class components in Vue < 2.2 - const Constructor = typeof component === 'function' - ? _Vue.extend(component.options).extend(instanceOptions) - : _Vue.extend(component).extend(instanceOptions) + const Constructor = _Vue.extend(componentOptions).extend(instanceOptions) // used to identify extended component using constructor Constructor.options.$_vueTestUtils_original = component @@ -120,10 +119,12 @@ export default function createInstance ( const scopedSlots = createScopedSlots(options.scopedSlots, _Vue) - if (options.parentComponent && !isPlainObject(options.parentComponent)) { + if ( + options.parentComponent && + !isPlainObject(options.parentComponent) + ) { throwError( - `options.parentComponent should be a valid Vue component ` + - `options object` + `options.parentComponent should be a valid Vue component options object` ) } diff --git a/packages/create-instance/patch-create-element.js b/packages/create-instance/patch-create-element.js index 272d47772..52da983d7 100644 --- a/packages/create-instance/patch-create-element.js +++ b/packages/create-instance/patch-create-element.js @@ -1,6 +1,11 @@ import { createStubFromComponent } from './create-component-stubs' import { resolveComponent } from 'shared/util' -import { isReservedTag } from 'shared/validators' +import { + isReservedTag, + isConstructor, + isDynamicComponent, + isComponentOptions +} from 'shared/validators' import { BEFORE_RENDER_LIFECYCLE_HOOK, CREATE_ELEMENT_ALIAS @@ -8,11 +13,10 @@ import { const isWhitelisted = (el, whitelist) => resolveComponent(el, whitelist) const isAlreadyStubbed = (el, stubs) => stubs.has(el) -const isDynamicComponent = cmp => typeof cmp === 'function' && !cmp.cid function shouldExtend (component, _Vue) { return ( - (typeof component === 'function' && !isDynamicComponent(component)) || + isConstructor(component) || (component && component.extends) ) } @@ -20,6 +24,7 @@ function shouldExtend (component, _Vue) { function extend (component, _Vue) { const stub = _Vue.extend(component.options) stub.options.$_vueTestUtils_original = component + stub.options._base = _Vue return stub } @@ -41,14 +46,6 @@ function shouldNotBeStubbed (el, whitelist, modifiedComponents) { ) } -function isConstructor (el) { - return typeof el === 'function' -} - -function isComponentOptions (el) { - return typeof el === 'object' && (el.template || el.render) -} - export function patchCreateElement (_Vue, stubs, stubAllComponents) { // This mixin patches vm.$createElement so that we can stub all components // before they are rendered in shallow mode. We also need to ensure that diff --git a/packages/shared/validators.js b/packages/shared/validators.js index c24453a2d..ffa8db8b7 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -76,6 +76,18 @@ export function isNameSelector (nameOptionsObject: any): boolean { return !!nameOptionsObject.name } +export function isConstructor (el) { + return typeof el === 'function' +} + +export function isDynamicComponent (cmp) { + return typeof cmp === 'function' && !cmp.cid +} + +export function isComponentOptions (el) { + return typeof el === 'object' && (el.template || el.render) +} + export function templateContainsComponent ( template: string, name: string diff --git a/packages/test-utils/src/matches.js b/packages/test-utils/src/matches.js index 87f95bb33..3d728f6ae 100644 --- a/packages/test-utils/src/matches.js +++ b/packages/test-utils/src/matches.js @@ -3,6 +3,7 @@ import { COMPONENT_SELECTOR, FUNCTIONAL_OPTIONS } from 'shared/consts' +import { isConstructor } from 'shared/validators'; export function vmMatchesName (vm, name) { return !!name && ( @@ -19,7 +20,7 @@ function vmCtorMatches (vm, component) { return true } - const Ctor = typeof component === 'function' + const Ctor = isConstructor(component) ? component.options._Ctor : component._Ctor @@ -46,7 +47,7 @@ export function matches (node, selector) { return element && element.matches && element.matches(selector.value) } - const isFunctionalSelector = typeof selector.value === 'function' + const isFunctionalSelector = isConstructor(selector.value) ? selector.value.options.functional : selector.value.functional @@ -66,7 +67,7 @@ export function matches (node, selector) { // Fallback to name selector for COMPONENT_SELECTOR for Vue < 2.1 const nameSelector = - typeof selector.value === 'function' + isConstructor(selector.value) ? selector.value.extendOptions.name : selector.value.name return vmMatchesName(componentInstance, nameSelector) diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js index cdbdfcef4..5f059040c 100644 --- a/test/specs/mount.spec.js +++ b/test/specs/mount.spec.js @@ -415,4 +415,30 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => { }) expect(wrapper.findAll(ChildComponent).length).to.equal(1) }) + + it('handles nested components with extends', () => { + const GrandChildComponent = { + template: '
', + created () { + this.$route.params + } + } + const ChildComponent = Vue.extend({ + template: '', + components: { + GrandChildComponent + } + }) + const TestComponent = { + template: '', + components: { + ChildComponent + } + } + const localVue = createLocalVue() + localVue.prototype.$route = {} + mount(TestComponent, { + localVue + }) + }) }) diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js index 9abac1bf3..b959ef397 100644 --- a/test/specs/mounting-options/stubs.spec.js +++ b/test/specs/mounting-options/stubs.spec.js @@ -216,7 +216,7 @@ describeWithMountingMethods('options.stub', mountingMethod => { components: { GrandChildComponent } } const TestComponent = { - template: '
', + template: '', components: { ChildComponent } } const wrapper = mountingMethod(Vue.extend(TestComponent), { From a0032474382318eefccdd1c7da9d45f809d9ebe6 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 20 Jan 2019 11:16:22 +0000 Subject: [PATCH 2/4] refactor: remove unused reasign --- packages/create-instance/patch-create-element.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/create-instance/patch-create-element.js b/packages/create-instance/patch-create-element.js index 52da983d7..4f607d949 100644 --- a/packages/create-instance/patch-create-element.js +++ b/packages/create-instance/patch-create-element.js @@ -89,13 +89,6 @@ export function patchCreateElement (_Vue, stubs, stubAllComponents) { return originalCreateElement(el, ...args) } - if ( - original.options && - original.options.$_vueTestUtils_original - ) { - original = original.options.$_vueTestUtils_original - } - if (isDynamicComponent(original)) { return originalCreateElement(el, ...args) } From 12c6686102769108cbe0a4adff2ddd4718340c5d Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 20 Jan 2019 11:55:12 +0000 Subject: [PATCH 3/4] refactor: tweaks --- packages/create-instance/create-instance.js | 17 +++++++---------- .../create-instance/patch-create-element.js | 2 +- packages/test-utils/src/matches.js | 2 +- test/specs/mount.spec.js | 2 ++ 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 11fd3e0ec..fb6f71958 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -17,12 +17,7 @@ import { validateSlots } from './validate-slots' import createScopedSlots from './create-scoped-slots' import { createStubsFromStubsObject } from './create-component-stubs' import { patchCreateElement } from './patch-create-element' -import { - isReservedTag, - isConstructor, - isDynamicComponent, - isComponentOptions -} from 'shared/validators' +import { isConstructor } from 'shared/validators' function vueExtendUnsupportedOption (option: string) { return `options.${option} is not supported for ` + @@ -45,9 +40,6 @@ export default function createInstance ( options: Options, _Vue: Component ): Component { - // make sure all extends are based on this instance - _Vue.options._base = _Vue - if ( VUE_VERSION < 2.3 && isConstructor(component) ) { @@ -78,7 +70,11 @@ export default function createInstance ( patchCreateElement(_Vue, stubComponentsObject, options.shouldProxy) if (componentOptions.functional) { - componentOptions = createFunctionalComponent(componentOptions, options, _Vue) + componentOptions = createFunctionalComponent( + componentOptions, + options, + _Vue + ) } else if (options.context) { throwError( `mount.context can only be used when mounting a ` + @@ -90,6 +86,7 @@ export default function createInstance ( compileTemplate(componentOptions) } + // make sure all extends are based on this instance componentOptions._base = _Vue const Constructor = _Vue.extend(componentOptions).extend(instanceOptions) diff --git a/packages/create-instance/patch-create-element.js b/packages/create-instance/patch-create-element.js index 9c6041476..741866cf3 100644 --- a/packages/create-instance/patch-create-element.js +++ b/packages/create-instance/patch-create-element.js @@ -84,7 +84,7 @@ export function patchCreateElement (_Vue, stubs, stubAllComponents) { } if (typeof el === 'string') { - let original = resolveComponent(el, originalComponents) + const original = resolveComponent(el, originalComponents) if (!original) { return originalCreateElement(el, ...args) diff --git a/packages/test-utils/src/matches.js b/packages/test-utils/src/matches.js index 3d728f6ae..0739932c6 100644 --- a/packages/test-utils/src/matches.js +++ b/packages/test-utils/src/matches.js @@ -3,7 +3,7 @@ import { COMPONENT_SELECTOR, FUNCTIONAL_OPTIONS } from 'shared/consts' -import { isConstructor } from 'shared/validators'; +import { isConstructor } from 'shared/validators' export function vmMatchesName (vm, name) { return !!name && ( diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js index aadc01f07..10f15c8d2 100644 --- a/test/specs/mount.spec.js +++ b/test/specs/mount.spec.js @@ -408,6 +408,8 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => { mount(TestComponent, { localVue }) + }) + it('throws if component throws during update', () => { const TestComponent = { template: '
', From 3bef7858f7048b5bc181f507ddf3de07bf7769e6 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 20 Jan 2019 12:19:23 +0000 Subject: [PATCH 4/4] refactor: rename params --- .../create-instance/create-component-stubs.js | 2 +- packages/create-instance/create-instance.js | 5 +--- packages/shared/validators.js | 28 +++++++++---------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/create-instance/create-component-stubs.js b/packages/create-instance/create-component-stubs.js index 2d948bfbc..381e8ce63 100644 --- a/packages/create-instance/create-component-stubs.js +++ b/packages/create-instance/create-component-stubs.js @@ -118,7 +118,7 @@ function createStubFromString ( templateString: string, originalComponent: Component = {}, name: string, - _Vue + _Vue: Component ): Component { if (templateContainsComponent(templateString, name)) { throwError('options.stub cannot contain a circular reference') diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index fb6f71958..6cd7b2247 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -116,10 +116,7 @@ export default function createInstance ( const scopedSlots = createScopedSlots(options.scopedSlots, _Vue) - if ( - options.parentComponent && - !isPlainObject(options.parentComponent) - ) { + if (options.parentComponent && !isPlainObject(options.parentComponent)) { throwError( `options.parentComponent should be a valid Vue component options object` ) diff --git a/packages/shared/validators.js b/packages/shared/validators.js index ffa8db8b7..3dfb6a7dc 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -28,24 +28,24 @@ export function isDomSelector (selector: any): boolean { } } -export function isVueComponent (component: any): boolean { - if (typeof component === 'function' && component.options) { +export function isVueComponent (c: any): boolean { + if (isConstructor(c)) { return true } - if (component === null || typeof component !== 'object') { + if (c === null || typeof c !== 'object') { return false } - if (component.extends || component._Ctor) { + if (c.extends || c._Ctor) { return true } - if (typeof component.template === 'string') { + if (typeof c.template === 'string') { return true } - return typeof component.render === 'function' + return typeof c.render === 'function' } export function componentNeedsCompiling (component: Component): boolean { @@ -76,16 +76,16 @@ export function isNameSelector (nameOptionsObject: any): boolean { return !!nameOptionsObject.name } -export function isConstructor (el) { - return typeof el === 'function' +export function isConstructor (c: any) { + return typeof c === 'function' && c.cid } -export function isDynamicComponent (cmp) { - return typeof cmp === 'function' && !cmp.cid +export function isDynamicComponent (c: any) { + return typeof c === 'function' && !c.cid } -export function isComponentOptions (el) { - return typeof el === 'object' && (el.template || el.render) +export function isComponentOptions (c: any) { + return typeof c === 'object' && (c.template || c.render) } export function templateContainsComponent ( @@ -98,8 +98,8 @@ export function templateContainsComponent ( }) } -export function isPlainObject (obj: any): boolean { - return Object.prototype.toString.call(obj) === '[object Object]' +export function isPlainObject (c: any): boolean { + return Object.prototype.toString.call(c) === '[object Object]' } export function isRequiredComponent (name: string): boolean {