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

Refactor: refactor stub-components.js #544

Merged
merged 1 commit into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 7 additions & 8 deletions packages/shared/compile-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
44 changes: 44 additions & 0 deletions packages/shared/stub-components-validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @flow

import { throwError } from './util'
import { compileToFunctions } from 'vue-template-compiler'

export function validateStubOptions (stubOptions: Array<string> | 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<string>): 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)
}
183 changes: 44 additions & 139 deletions packages/shared/stub-components.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<string>): 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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Personally I think public methods should be moved to top of file. Didn't do it to satisfy git blame as much as possible

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should move the position of functions. Most files in this project have public functions at the bottom of the files

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, let it stay like that

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<string>): 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
}, {})
}
10 changes: 3 additions & 7 deletions packages/test-utils/src/shallow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}
})
}