Skip to content
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

fix #1377 string stubs dropping props #1473

Merged
merged 5 commits into from
Mar 26, 2020
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
8 changes: 6 additions & 2 deletions docs/api/mount.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just drop it entirely

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we dont have versioned docs, I think it makes sense to leave it. We are deprecating, but have not yet removed it. (We saw what removing sync did without mentioning it in the docs any more).

Maybe we can add an example what is now deprecated?

Example:

Expand Down
43 changes: 26 additions & 17 deletions packages/create-instance/create-component-stubs.js
Original file line number Diff line number Diff line change
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 @@ -145,24 +146,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 @@ -175,26 +183,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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'
)
)
})
})