diff --git a/packages/shared/compile-template.js b/packages/shared/compile-template.js index b449bffbc..530a24dde 100644 --- a/packages/shared/compile-template.js +++ b/packages/shared/compile-template.js @@ -3,14 +3,13 @@ import { compileToFunctions } from 'vue-template-compiler' export function compileTemplate (component: Component) { - if (component.components) { - Object.keys(component.components).forEach((c) => { - const cmp = component.components[c] - if (!cmp.render) { - compileTemplate(cmp) - } - }) - } + Object.keys(component.components || {}).forEach((c) => { + const cmp = component.components[c] + if (!cmp.render) { + compileTemplate(cmp) + } + }) + if (component.extends) { compileTemplate(component.extends) } diff --git a/packages/shared/stub-components-validate.js b/packages/shared/stub-components-validate.js new file mode 100644 index 000000000..81d49b6de --- /dev/null +++ b/packages/shared/stub-components-validate.js @@ -0,0 +1,44 @@ +// @flow + +import { throwError } from './util' +import { compileToFunctions } from 'vue-template-compiler' + +export function validateStubOptions (stubOptions: Array | Object) { + if (Array.isArray(stubOptions)) { + if (containsNonStringItem(stubOptions)) { + throwError('each item in an options.stubs array must be a string') + } + } else { + if (containsInvalidOptions(stubOptions)) { + throwError('options.stub values must be passed a string or component') + } + + if (necessaryCompileToFunctionsMissed(stubOptions)) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') + } + } +} + +function containsNonStringItem (array: Array): boolean { + return array.some(name => typeof name !== 'string') +} + +function necessaryCompileToFunctionsMissed (stubOptions: Object): boolean { + return Object.keys(stubOptions) + .map(key => stubOptions[key]) + .some(stub => typeof stub === 'string') && !compileToFunctions +} + +function containsInvalidOptions (stubOptions: Object): boolean { + return Object.keys(stubOptions) + .map(key => stubOptions[key]) + .some(isInvalidStubOption) +} + +function isInvalidStubOption (stub): boolean { + return !['string', 'boolean'].includes(typeof stub) && !isVueComponent(stub) +} + +function isVueComponent (cmp) { + return cmp && (cmp.render || cmp.template || cmp.options) +} diff --git a/packages/shared/stub-components.js b/packages/shared/stub-components.js index fda90dc37..ebf305f14 100644 --- a/packages/shared/stub-components.js +++ b/packages/shared/stub-components.js @@ -1,28 +1,12 @@ // @flow -import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' -import { throwError } from './util' -import { componentNeedsCompiling } from './validators' -import { compileTemplate } from './compile-template' -import { capitalize, camelize, hyphenate } from './util' - -function isVueComponent (comp) { - return comp && (comp.render || comp.template || comp.options) -} - -function isValidStub (stub: any) { - return !!stub && - typeof stub === 'string' || - (stub === true) || - (isVueComponent(stub)) -} - -function isRequiredComponent (name) { - return name === 'KeepAlive' || name === 'Transition' || name === 'TransitionGroup' -} +import { validateStubOptions } from 'shared/stub-components-validate' +import { componentNeedsCompiling } from 'shared/validators' +import { compileTemplate } from 'shared/compile-template' function getCoreProperties (component: Component): Object { + if (!component) return {} return { attrs: component.attrs, name: component.name, @@ -40,147 +24,68 @@ function getCoreProperties (component: Component): Object { functional: component.functional } } -function createStubFromString (templateString: string, originalComponent: Component): Object { - if (!compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') - } - - if (templateString.indexOf(hyphenate(originalComponent.name)) !== -1 || - templateString.indexOf(capitalize(originalComponent.name)) !== -1 || - templateString.indexOf(camelize(originalComponent.name)) !== -1) { - throwError('options.stub cannot contain a circular reference') - } +function createStubFromString (originalComponent: Component, template: string): Object { return { ...getCoreProperties(originalComponent), - ...compileToFunctions(templateString) + ...compileToFunctions(template) } } -function createBlankStub (originalComponent: Component) { +function createBlankStub (originalComponent: Component): Object { return { ...getCoreProperties(originalComponent), render: h => h('') } } -export function createComponentStubs (originalComponents: Object = {}, stubs: Object): Object { - const components = {} - if (!stubs) { - return components - } - if (Array.isArray(stubs)) { - stubs.forEach(stub => { - if (stub === false) { - return - } +function createStubFromComponent (component: Component, name: string): Object { + if (componentNeedsCompiling(component)) compileTemplate(component) + return name ? { ...component, name } : component +} - if (typeof stub !== 'string') { - throwError('each item in an options.stubs array must be a string') - } - components[stub] = createBlankStub({}) - }) +function createStub (originalComponent: Component, stubValue): Object { + if (stubValue === true) { + return createBlankStub(originalComponent) + } else if (typeof stubValue === 'string') { + return createStubFromString(originalComponent, stubValue) } else { - Object.keys(stubs).forEach(stub => { - if (stubs[stub] === false) { - return - } - if (!isValidStub(stubs[stub])) { - throwError('options.stub values must be passed a string or component') - } - if (stubs[stub] === true) { - components[stub] = createBlankStub({}) - return - } - - if (componentNeedsCompiling(stubs[stub])) { - compileTemplate(stubs[stub]) - } - - if (originalComponents[stub]) { - // Remove cached constructor - delete originalComponents[stub]._Ctor - if (typeof stubs[stub] === 'string') { - components[stub] = createStubFromString(stubs[stub], originalComponents[stub]) - } else { - components[stub] = { - ...stubs[stub], - name: originalComponents[stub].name - } - } - } else { - if (typeof stubs[stub] === 'string') { - if (!compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') - } - components[stub] = { - ...compileToFunctions(stubs[stub]) - } - } else { - components[stub] = { - ...stubs[stub] - } - } - } - // ignoreElements does not exist in Vue 2.0.x - if (Vue.config.ignoredElements) { - Vue.config.ignoredElements.push(stub) - } - }) + return createStubFromComponent(stubValue, originalComponent && originalComponent.name) } - return components -} - -function stubComponents (components: Object, stubbedComponents: Object) { - Object.keys(components).forEach(component => { - // Remove cached constructor - delete components[component]._Ctor - if (!components[component].name) { - components[component].name = component - } - stubbedComponents[component] = createBlankStub(components[component]) - - // ignoreElements does not exist in Vue 2.0.x - if (Vue.config.ignoredElements) { - Vue.config.ignoredElements.push(component) - } - }) } -export function createComponentStubsForAll (component: Component): Object { - const stubbedComponents = {} - - if (component.components) { - stubComponents(component.components, stubbedComponents) +function normalizeStubOptions (components: ?Object, stubOptions: ?Object | Array): Object { + if (!stubOptions) { + stubOptions = Object.keys(components || {}) } - - let extended = component.extends - - // Loop through extended component chains to stub all child components - while (extended) { - if (extended.components) { - stubComponents(extended.components, stubbedComponents) - } - extended = extended.extends + if (Array.isArray(stubOptions)) { + stubOptions = stubOptions.reduce((object, name) => { + object[name] = true + return object + }, {}) } + return stubOptions +} - if (component.extendOptions && component.extendOptions.components) { - stubComponents(component.extendOptions.components, stubbedComponents) - } +export function createComponentStubs (components: Object = {}, stubOptions: Object): Object { + validateStubOptions(stubOptions) + return createStubs(components, stubOptions) +} - return stubbedComponents +export function createComponentStubsForAll (component: Component, stubs: Object = {}): Object { + if (!component) return stubs + Object.assign(stubs, createStubs(component.components)) + return createComponentStubsForAll(component.extends || component.extendOptions, stubs) } -export function createComponentStubsForGlobals (instance: Component): Object { - const components = {} - Object.keys(instance.options.components).forEach((c) => { - if (isRequiredComponent(c)) { - return - } +export function createStubs (components: Object, stubOptions: ?Object | Array): Object { + const options: Object = normalizeStubOptions(components, stubOptions) - components[c] = createBlankStub(instance.options.components[c]) - delete instance.options.components[c]._Ctor // eslint-disable-line no-param-reassign - delete components[c]._Ctor // eslint-disable-line no-param-reassign - }) - return components + return Object.keys(options) + .filter(name => !['KeepAlive', 'Transition', 'TransitionGroup'].includes(name)) + .filter(name => options[name] !== false) + .reduce((stubs, name) => { + stubs[name] = createStub(components[name], options[name]) + return stubs + }, {}) } diff --git a/packages/test-utils/src/shallow.js b/packages/test-utils/src/shallow.js index dbbd6cb95..706782d72 100644 --- a/packages/test-utils/src/shallow.js +++ b/packages/test-utils/src/shallow.js @@ -6,7 +6,7 @@ import mount from './mount' import type VueWrapper from './vue-wrapper' import { createComponentStubsForAll, - createComponentStubsForGlobals + createStubs } from 'shared/stub-components' import { camelize, capitalize, @@ -26,15 +26,11 @@ export default function shallow ( delete component.components[hyphenate(component.name)] } - const stubbedComponents = createComponentStubsForAll(component) - const stubbedGlobalComponents = createComponentStubsForGlobals(vue) - return mount(component, { ...options, components: { - // stubbed components are used instead of original components components - ...stubbedGlobalComponents, - ...stubbedComponents + ...createStubs(vue.options.components), + ...createComponentStubsForAll(component) } }) }