diff --git a/packages/shared/stub-components.js b/packages/shared/stub-components.js index 6c44d17bc..f166ef1cf 100644 --- a/packages/shared/stub-components.js +++ b/packages/shared/stub-components.js @@ -3,9 +3,11 @@ import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' import { throwError } from './util' -import { componentNeedsCompiling } from './validators' +import { + componentNeedsCompiling, + templateContainsComponent +} from './validators' import { compileTemplate } from './compile-template' -import { capitalize, camelize, hyphenate } from './util' function isVueComponent (comp) { return comp && (comp.render || comp.template || comp.options) @@ -40,14 +42,16 @@ function getCoreProperties (component: Component): Object { functional: component.functional } } -function createStubFromString (templateString: string, originalComponent: Component): Object { +function createStubFromString ( + templateString: string, + originalComponent: Component, + name: string +): 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) { + if (templateContainsComponent(templateString, name)) { throwError('options.stub cannot contain a circular reference') } @@ -66,7 +70,10 @@ function createBlankStub (originalComponent: Component) { } } -export function createComponentStubs (originalComponents: Object = {}, stubs: Object): Object { +export function createComponentStubs ( + originalComponents: Object = {}, + stubs: Object +): Object { const components = {} if (!stubs) { return components @@ -103,7 +110,7 @@ export function createComponentStubs (originalComponents: Object = {}, stubs: Ob // Remove cached constructor delete originalComponents[stub]._Ctor if (typeof stubs[stub] === 'string') { - components[stub] = createStubFromString(stubs[stub], originalComponents[stub]) + components[stub] = createStubFromString(stubs[stub], originalComponents[stub], stub) } else { components[stub] = { ...stubs[stub], diff --git a/packages/shared/util.js b/packages/shared/util.js index c68044467..cf1b3e374 100644 --- a/packages/shared/util.js +++ b/packages/shared/util.js @@ -9,7 +9,10 @@ export function warn (msg: string) { } const camelizeRE = /-(\w)/g -export const camelize = (str: string) => str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +export const camelize = (str: string) => { + const camelizedStr = str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') + return camelizedStr.charAt(0).toLowerCase() + camelizedStr.slice(1) +} /** * Capitalize a string. diff --git a/packages/shared/validators.js b/packages/shared/validators.js index da7cc7b65..bfecebca4 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -1,5 +1,10 @@ // @flow -import { throwError } from './util' +import { + throwError, + capitalize, + camelize, + hyphenate +} from './util' export function isDomSelector (selector: any) { if (typeof selector !== 'string') { @@ -62,3 +67,10 @@ export function isNameSelector (nameOptionsObject: any) { return !!nameOptionsObject.name } + +export function templateContainsComponent (template: string, name: string) { + return [capitalize, camelize, hyphenate].some((format) => { + const re = new RegExp(`<${format(name)}\\s*(\\s|>|(\/>))`, 'g') + return re.test(template) + }) +} diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js index bab14ccad..85487d3d9 100644 --- a/test/specs/mounting-options/stubs.spec.js +++ b/test/specs/mounting-options/stubs.spec.js @@ -314,15 +314,37 @@ describeWithMountingMethods('options.stub', (mountingMethod) => { expect(HTML).contains('No render function') }) - it.skip('throws an error when passed a circular reference', () => { - const invalidValues = ['child-component', 'ChildComponent', 'childComponent'] - invalidValues.forEach(invalidValue => { - const error = '[vue-test-utils]: options.stub cannot contain a circular reference' - const fn = () => mountingMethod(ComponentWithChild, { - stubs: { - ChildComponent: `<${invalidValue} />` - }}) - expect(fn).to.throw().with.property('message', error) + it('throws an error when passed a circular reference', () => { + const names = ['child-component', 'ChildComponent', 'childComponent'] + const validValues = [ + '', + '', + '', + '', + '' + ] + const invalidValues = [ + '', + '', + '', + '', + '' + ] + const error = '[vue-test-utils]: options.stub cannot contain a circular reference' + names.forEach((name) => { + invalidValues.forEach(invalidValue => { + const fn = () => mountingMethod(ComponentWithChild, { + stubs: { + ChildComponent: invalidValue.replace(/NAME/g, name) + }}) + expect(fn).to.throw().with.property('message', error) + }) + validValues.forEach((validValue) => { + mountingMethod(ComponentWithChild, { + stubs: { + ChildComponent: validValue.replace(/NAME/g, name) + }}) + }) }) })