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)
+ }})
+ })
})
})