Skip to content

Commit 6a40f8a

Browse files
authored
fix: use regex to test for circular references (#672)
1 parent 50f01a6 commit 6a40f8a

File tree

4 files changed

+63
-19
lines changed

4 files changed

+63
-19
lines changed

packages/shared/stub-components.js

+15-8
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import Vue from 'vue'
44
import { compileToFunctions } from 'vue-template-compiler'
55
import { throwError } from './util'
6-
import { componentNeedsCompiling } from './validators'
6+
import {
7+
componentNeedsCompiling,
8+
templateContainsComponent
9+
} from './validators'
710
import { compileTemplate } from './compile-template'
8-
import { capitalize, camelize, hyphenate } from './util'
911

1012
function isVueComponent (comp) {
1113
return comp && (comp.render || comp.template || comp.options)
@@ -40,14 +42,16 @@ function getCoreProperties (component: Component): Object {
4042
functional: component.functional
4143
}
4244
}
43-
function createStubFromString (templateString: string, originalComponent: Component): Object {
45+
function createStubFromString (
46+
templateString: string,
47+
originalComponent: Component,
48+
name: string
49+
): Object {
4450
if (!compileToFunctions) {
4551
throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined')
4652
}
4753

48-
if (templateString.indexOf(hyphenate(originalComponent.name)) !== -1 ||
49-
templateString.indexOf(capitalize(originalComponent.name)) !== -1 ||
50-
templateString.indexOf(camelize(originalComponent.name)) !== -1) {
54+
if (templateContainsComponent(templateString, name)) {
5155
throwError('options.stub cannot contain a circular reference')
5256
}
5357

@@ -66,7 +70,10 @@ function createBlankStub (originalComponent: Component) {
6670
}
6771
}
6872

69-
export function createComponentStubs (originalComponents: Object = {}, stubs: Object): Object {
73+
export function createComponentStubs (
74+
originalComponents: Object = {},
75+
stubs: Object
76+
): Object {
7077
const components = {}
7178
if (!stubs) {
7279
return components
@@ -103,7 +110,7 @@ export function createComponentStubs (originalComponents: Object = {}, stubs: Ob
103110
// Remove cached constructor
104111
delete originalComponents[stub]._Ctor
105112
if (typeof stubs[stub] === 'string') {
106-
components[stub] = createStubFromString(stubs[stub], originalComponents[stub])
113+
components[stub] = createStubFromString(stubs[stub], originalComponents[stub], stub)
107114
} else {
108115
components[stub] = {
109116
...stubs[stub],

packages/shared/util.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ export function warn (msg: string) {
99
}
1010

1111
const camelizeRE = /-(\w)/g
12-
export const camelize = (str: string) => str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
12+
export const camelize = (str: string) => {
13+
const camelizedStr = str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
14+
return camelizedStr.charAt(0).toLowerCase() + camelizedStr.slice(1)
15+
}
1316

1417
/**
1518
* Capitalize a string.

packages/shared/validators.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// @flow
2-
import { throwError } from './util'
2+
import {
3+
throwError,
4+
capitalize,
5+
camelize,
6+
hyphenate
7+
} from './util'
38

49
export function isDomSelector (selector: any) {
510
if (typeof selector !== 'string') {
@@ -62,3 +67,10 @@ export function isNameSelector (nameOptionsObject: any) {
6267

6368
return !!nameOptionsObject.name
6469
}
70+
71+
export function templateContainsComponent (template: string, name: string) {
72+
return [capitalize, camelize, hyphenate].some((format) => {
73+
const re = new RegExp(`<${format(name)}\\s*(\\s|>|(\/>))`, 'g')
74+
return re.test(template)
75+
})
76+
}

test/specs/mounting-options/stubs.spec.js

+31-9
Original file line numberDiff line numberDiff line change
@@ -314,15 +314,37 @@ describeWithMountingMethods('options.stub', (mountingMethod) => {
314314
expect(HTML).contains('No render function')
315315
})
316316

317-
it.skip('throws an error when passed a circular reference', () => {
318-
const invalidValues = ['child-component', 'ChildComponent', 'childComponent']
319-
invalidValues.forEach(invalidValue => {
320-
const error = '[vue-test-utils]: options.stub cannot contain a circular reference'
321-
const fn = () => mountingMethod(ComponentWithChild, {
322-
stubs: {
323-
ChildComponent: `<${invalidValue} />`
324-
}})
325-
expect(fn).to.throw().with.property('message', error)
317+
it('throws an error when passed a circular reference', () => {
318+
const names = ['child-component', 'ChildComponent', 'childComponent']
319+
const validValues = [
320+
'<NAME-suffix />',
321+
'<prefix-NAME />',
322+
'<cmp NAME></cmp>',
323+
'<cmp something="NAME"></cmp>',
324+
'<NAMEl />'
325+
]
326+
const invalidValues = [
327+
'<NAME />',
328+
'<NAME />',
329+
'<NAME></NAME>',
330+
'<NAME aProp="something"></NAME>',
331+
'<NAME ></NAME>'
332+
]
333+
const error = '[vue-test-utils]: options.stub cannot contain a circular reference'
334+
names.forEach((name) => {
335+
invalidValues.forEach(invalidValue => {
336+
const fn = () => mountingMethod(ComponentWithChild, {
337+
stubs: {
338+
ChildComponent: invalidValue.replace(/NAME/g, name)
339+
}})
340+
expect(fn).to.throw().with.property('message', error)
341+
})
342+
validValues.forEach((validValue) => {
343+
mountingMethod(ComponentWithChild, {
344+
stubs: {
345+
ChildComponent: validValue.replace(/NAME/g, name)
346+
}})
347+
})
326348
})
327349
})
328350

0 commit comments

Comments
 (0)