Skip to content

fix: use regex to test for circular references #672

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

Merged
merged 2 commits into from
May 29, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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