Skip to content

Commit

Permalink
fix #1377 string stubs dropping props (#1473)
Browse files Browse the repository at this point in the history
* test(issue #1377): add test for props on stubbed child component

* feat(create-instance):  convert string stubs to template

close #1377  This also fixes the component name being dropped for template stubs

* docs(api): update stubs documentation

* test(stubs): limit stub props to 2.2 and above

* docs(api): updated docs wording
  • Loading branch information
Stoom committed Mar 26, 2020
1 parent 9d5b642 commit 0c4a811
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 38 deletions.
8 changes: 6 additions & 2 deletions docs/api/mount.md
Expand Up @@ -116,9 +116,9 @@ describe('Foo', () => {
it('renders a div', () => {
const wrapper = mount(Foo, {
stubs: {
Bar: '<div class="stubbed" />',
BarFoo: true,
FooBar: Faz
FooBar: Faz,
Bar: { template: '<div class="stubbed" />' }
}
})
expect(wrapper.contains('.stubbed')).toBe(true)
Expand All @@ -127,4 +127,8 @@ describe('Foo', () => {
})
```

**Deprecation Notice:**

When stubbing components, supplying a string (`ComponentToStub: '<div class="stubbed" />`) is no longer supported.

- **See also:** [Wrapper](wrapper/)
8 changes: 6 additions & 2 deletions docs/api/options.md
Expand Up @@ -208,9 +208,13 @@ const wrapper = mount(WrapperComp).find(ComponentUnderTest)

## stubs

- type: `{ [name: string]: Component | boolean } | Array<string>`
- type: `{ [name: string]: Component | string | boolean } | Array<string>`

Stubs child components. Can be an Array of component names to stub, or an object. If `stubs` is an Array, every stub is `<${component name}-stub>`.
Stubs child components can be an Array of component names to stub, or an object. If `stubs` is an Array, every stub is `<${component name}-stub>`.

**Deprecation Notice:**

When stubbing components, supplying a string (`ComponentToStub: '<div class="stubbed" />`) is no longer supported.

Example:

Expand Down
43 changes: 26 additions & 17 deletions packages/create-instance/create-component-stubs.js
Expand Up @@ -6,7 +6,8 @@ import {
camelize,
capitalize,
hyphenate,
keys
keys,
warn
} from '../shared/util'
import {
componentNeedsCompiling,
Expand All @@ -15,7 +16,7 @@ import {
isDynamicComponent,
isConstructor
} from '../shared/validators'
import { compileTemplate, compileFromString } from '../shared/compile-template'
import { compileTemplate } from '../shared/compile-template'

function isVueComponentStub(comp): boolean {
return (comp && comp.template) || isVueComponent(comp)
Expand Down Expand Up @@ -156,24 +157,31 @@ export function createStubFromComponent(
}
}

function createStubFromString(
templateString: string,
originalComponent: Component = {},
name: string,
_Vue: Component
): Component {
// DEPRECATED: converts string stub to template stub.
function createStubFromString(templateString: string, name: string): Component {
warn('String stubs are deprecated and will be removed in future versions')

if (templateContainsComponent(templateString, name)) {
throwError('options.stub cannot contain a circular reference')
}
const componentOptions = resolveOptions(originalComponent, _Vue)

return {
...getCoreProperties(componentOptions),
$_doNotStubChildren: true,
...compileFromString(templateString)
template: templateString,
$_doNotStubChildren: true
}
}

function setStubComponentName(
stub: Object,
originalComponent: Component = {},
_Vue: Component
) {
if (stub.name) return

const componentOptions = resolveOptions(originalComponent, _Vue)
stub.name = getCoreProperties(componentOptions).name
}

function validateStub(stub) {
if (!isValidStub(stub)) {
throwError(`options.stub values must be passed a string or ` + `component`)
Expand All @@ -186,26 +194,27 @@ export function createStubsFromStubsObject(
_Vue: Component
): Components {
return Object.keys(stubs || {}).reduce((acc, stubName) => {
const stub = stubs[stubName]
let stub = stubs[stubName]

validateStub(stub)

if (stub === false) {
return acc
}

const component = resolveComponent(originalComponents, stubName)

if (stub === true) {
const component = resolveComponent(originalComponents, stubName)
acc[stubName] = createStubFromComponent(component, stubName, _Vue)
return acc
}

if (typeof stub === 'string') {
const component = resolveComponent(originalComponents, stubName)
acc[stubName] = createStubFromString(stub, component, stubName, _Vue)
return acc
stub = createStubFromString(stub, stubName)
stubs[stubName]
}

setStubComponentName(stub, component, _Vue)
if (componentNeedsCompiling(stub)) {
compileTemplate(stub)
}
Expand Down
24 changes: 12 additions & 12 deletions packages/shared/compile-template.js
Expand Up @@ -4,19 +4,16 @@ import { compileToFunctions } from 'vue-template-compiler'
import { componentNeedsCompiling } from './validators'
import { throwError } from './util'

export function compileFromString(str: string) {
if (!compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}
return compileToFunctions(str)
}

export function compileTemplate(component: Component): void {
if (component.template) {
if (!compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}

if (component.template.charAt('#') === '#') {
var el = document.querySelector(component.template)
if (!el) {
Expand All @@ -27,7 +24,10 @@ export function compileTemplate(component: Component): void {
component.template = el.innerHTML
}

Object.assign(component, compileToFunctions(component.template))
Object.assign(component, {
...compileToFunctions(component.template),
name: component.name
})
}

if (component.components) {
Expand Down
@@ -0,0 +1,22 @@
<template>
<div>
<span>
<slot-component prop1="foobar" prop2="fizzbuzz" />
<child-component prop1="foobar" prop2="fizzbuzz" />
<original-component prop1="foobar" prop2="fizzbuzz" />
</span>
</div>
</template>

<script>
import ComponentWithProps from './component-with-props.vue'
export default {
name: 'component-with-nested-children',
components: {
ChildComponent: ComponentWithProps,
SlotComponent: ComponentWithProps,
OriginalComponent: ComponentWithProps
}
}
</script>
63 changes: 58 additions & 5 deletions test/specs/mounting-options/stubs.spec.js
Expand Up @@ -2,6 +2,7 @@ import ComponentWithChild from '~resources/components/component-with-child.vue'
import ComponentWithNestedChildren from '~resources/components/component-with-nested-children.vue'
import Component from '~resources/components/component.vue'
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
import ComponentWithNestedChildrenAndAttributes from '~resources/components/component-with-nested-childern-and-attributes.vue'
import { createLocalVue, config } from '@vue/test-utils'
import { config as serverConfig } from '@vue/server-test-utils'
import Vue from 'vue'
Expand All @@ -18,6 +19,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
serverConfigSave = serverConfig.stubs
config.stubs = {}
serverConfig.stubs = {}
sandbox.stub(console, 'error').callThrough()
})

afterEach(() => {
Expand All @@ -32,21 +34,24 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
const ComponentWithoutRender = { template: '<div></div>' }
const ExtendedComponent = { extends: ComponentWithRender }
const SubclassedComponent = Vue.extend({ template: '<div></div>' })
const StringComponent = '<div></div>'
mountingMethod(ComponentWithChild, {
stubs: {
ChildComponent: ComponentWithRender,
ChildComponent2: ComponentAsAClass,
ChildComponent3: ComponentWithoutRender,
ChildComponent4: ExtendedComponent,
ChildComponent5: SubclassedComponent
ChildComponent5: SubclassedComponent,
ChildComponent6: StringComponent
}
})
})

it('replaces component with template string ', () => {
const Stub = { template: '<div class="stub"></div>' }
const wrapper = mountingMethod(ComponentWithChild, {
stubs: {
ChildComponent: '<div class="stub"></div>'
ChildComponent: Stub
}
})
expect(wrapper.findAll('.stub').length).to.equal(1)
Expand Down Expand Up @@ -321,7 +326,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {

const wrapper = mountingMethod(TestComponent, {
stubs: {
'span-component': '<p />'
'span-component': { template: '<p />' }
},
localVue
})
Expand All @@ -342,7 +347,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {

const wrapper = mountingMethod(TestComponent, {
stubs: {
'time-component': '<span />'
'time-component': { template: '<span />' }
},
localVue
})
Expand Down Expand Up @@ -414,7 +419,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
expect(wrapper.html()).contains('No render function')
})

it('throws an error when passed a circular reference', () => {
it('throws an error when passed a circular reference for string stubs', () => {
const names = ['child-component', 'ChildComponent', 'childComponent']
const validValues = [
'<NAME-suffix />',
Expand Down Expand Up @@ -590,4 +595,52 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
expect(result.props().propA).to.equal('A')
delete Vue.options.components['child-component']
})

itRunIf(
vueVersion >= 2.2,
'renders props in the element as attributes',
() => {
const ComponentStub = { template: '<div id="component-stub" />' }
const StringStub = '<div id="string-stub" />'
const BooleanStub = true

const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, {
stubs: {
SlotComponent: ComponentStub,
ChildComponent: StringStub,
OriginalComponent: BooleanStub
}
})

expect(wrapper.find('#component-stub').attributes()).to.eql({
id: 'component-stub',
prop1: 'foobar',
prop2: 'fizzbuzz'
})
expect(wrapper.find('#string-stub').attributes()).to.eql({
id: 'string-stub',
prop1: 'foobar',
prop2: 'fizzbuzz'
})
expect(wrapper.find('originalcomponent-stub').attributes()).to.eql({
prop1: 'foobar',
prop2: 'fizzbuzz'
})
}
)

it('warns when passing a string', () => {
const StringComponent = '<div></div>'
mountingMethod(ComponentWithChild, {
stubs: {
ChildComponent6: StringComponent
}
})

expect(console.error).calledWith(
sandbox.match(
'[vue-test-utils]: String stubs are deprecated and will be removed in future versions'
)
)
})
})

0 comments on commit 0c4a811

Please sign in to comment.