Skip to content

Commit

Permalink
fix: use regex to test for circular references (#672)
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyerburgh authored May 29, 2018
1 parent 50f01a6 commit 6a40f8a
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 19 deletions.
23 changes: 15 additions & 8 deletions packages/shared/stub-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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')
}

Expand All @@ -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
Expand Down Expand Up @@ -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],
Expand Down
5 changes: 4 additions & 1 deletion packages/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 13 additions & 1 deletion packages/shared/validators.js
Original file line number Diff line number Diff line change
@@ -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') {
Expand Down Expand Up @@ -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)
})
}
40 changes: 31 additions & 9 deletions test/specs/mounting-options/stubs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
'<NAME-suffix />',
'<prefix-NAME />',
'<cmp NAME></cmp>',
'<cmp something="NAME"></cmp>',
'<NAMEl />'
]
const invalidValues = [
'<NAME />',
'<NAME />',
'<NAME></NAME>',
'<NAME aProp="something"></NAME>',
'<NAME ></NAME>'
]
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)
}})
})
})
})

Expand Down

0 comments on commit 6a40f8a

Please sign in to comment.