Skip to content

Commit 68640c1

Browse files
author
Jamie Stumme
committed
feat(create-instance): convert string stubs to template
close #1377 This also fixes the component name being dropped for template stubs
1 parent aebcc13 commit 68640c1

File tree

4 files changed

+86
-53
lines changed

4 files changed

+86
-53
lines changed

packages/create-instance/create-component-stubs.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
camelize,
77
capitalize,
88
hyphenate,
9-
keys
9+
keys,
10+
warn
1011
} from '../shared/util'
1112
import {
1213
componentNeedsCompiling,
@@ -15,7 +16,7 @@ import {
1516
isDynamicComponent,
1617
isConstructor
1718
} from '../shared/validators'
18-
import { compileTemplate, compileFromString } from '../shared/compile-template'
19+
import { compileTemplate } from '../shared/compile-template'
1920

2021
function isVueComponentStub(comp): boolean {
2122
return (comp && comp.template) || isVueComponent(comp)
@@ -145,24 +146,31 @@ export function createStubFromComponent(
145146
}
146147
}
147148

148-
function createStubFromString(
149-
templateString: string,
150-
originalComponent: Component = {},
151-
name: string,
152-
_Vue: Component
153-
): Component {
149+
// DEPRECATED: converts string stub to template stub.
150+
function createStubFromString(templateString: string, name: string): Component {
151+
warn('String stubs are deprecated and will be removed in future versions')
152+
154153
if (templateContainsComponent(templateString, name)) {
155154
throwError('options.stub cannot contain a circular reference')
156155
}
157-
const componentOptions = resolveOptions(originalComponent, _Vue)
158156

159157
return {
160-
...getCoreProperties(componentOptions),
161-
$_doNotStubChildren: true,
162-
...compileFromString(templateString)
158+
template: templateString,
159+
$_doNotStubChildren: true
163160
}
164161
}
165162

163+
function setStubComponentName(
164+
stub: Object,
165+
originalComponent: Component = {},
166+
_Vue: Component
167+
) {
168+
if (stub.name) return
169+
170+
const componentOptions = resolveOptions(originalComponent, _Vue)
171+
stub.name = getCoreProperties(componentOptions).name
172+
}
173+
166174
function validateStub(stub) {
167175
if (!isValidStub(stub)) {
168176
throwError(`options.stub values must be passed a string or ` + `component`)
@@ -175,26 +183,27 @@ export function createStubsFromStubsObject(
175183
_Vue: Component
176184
): Components {
177185
return Object.keys(stubs || {}).reduce((acc, stubName) => {
178-
const stub = stubs[stubName]
186+
let stub = stubs[stubName]
179187

180188
validateStub(stub)
181189

182190
if (stub === false) {
183191
return acc
184192
}
185193

194+
const component = resolveComponent(originalComponents, stubName)
195+
186196
if (stub === true) {
187-
const component = resolveComponent(originalComponents, stubName)
188197
acc[stubName] = createStubFromComponent(component, stubName, _Vue)
189198
return acc
190199
}
191200

192201
if (typeof stub === 'string') {
193-
const component = resolveComponent(originalComponents, stubName)
194-
acc[stubName] = createStubFromString(stub, component, stubName, _Vue)
195-
return acc
202+
stub = createStubFromString(stub, stubName)
203+
stubs[stubName]
196204
}
197205

206+
setStubComponentName(stub, component, _Vue)
198207
if (componentNeedsCompiling(stub)) {
199208
compileTemplate(stub)
200209
}

packages/shared/compile-template.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,16 @@ import { compileToFunctions } from 'vue-template-compiler'
44
import { componentNeedsCompiling } from './validators'
55
import { throwError } from './util'
66

7-
export function compileFromString(str: string) {
8-
if (!compileToFunctions) {
9-
throwError(
10-
`vueTemplateCompiler is undefined, you must pass ` +
11-
`precompiled components if vue-template-compiler is ` +
12-
`undefined`
13-
)
14-
}
15-
return compileToFunctions(str)
16-
}
17-
187
export function compileTemplate(component: Component): void {
198
if (component.template) {
9+
if (!compileToFunctions) {
10+
throwError(
11+
`vueTemplateCompiler is undefined, you must pass ` +
12+
`precompiled components if vue-template-compiler is ` +
13+
`undefined`
14+
)
15+
}
16+
2017
if (component.template.charAt('#') === '#') {
2118
var el = document.querySelector(component.template)
2219
if (!el) {
@@ -27,7 +24,10 @@ export function compileTemplate(component: Component): void {
2724
component.template = el.innerHTML
2825
}
2926

30-
Object.assign(component, compileToFunctions(component.template))
27+
Object.assign(component, {
28+
...compileToFunctions(component.template),
29+
name: component.name
30+
})
3131
}
3232

3333
if (component.components) {
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
<template>
22
<div>
33
<span>
4-
<slot-component>
5-
<template v-slot:newSyntax>
6-
<child-component prop1="foobar" prop2="fizzbuzz" />
7-
</template>
8-
</slot-component>
4+
<slot-component prop1="foobar" prop2="fizzbuzz" />
5+
<child-component prop1="foobar" prop2="fizzbuzz" />
6+
<original-component prop1="foobar" prop2="fizzbuzz" />
97
</span>
108
</div>
119
</template>
1210

1311
<script>
1412
import ComponentWithProps from './component-with-props.vue'
15-
import ComponentWithSlots from './component-with-v-slot.vue'
1613
1714
export default {
1815
name: 'component-with-nested-children',
1916
components: {
2017
ChildComponent: ComponentWithProps,
21-
SlotComponent: ComponentWithSlots
18+
SlotComponent: ComponentWithProps,
19+
OriginalComponent: ComponentWithProps
2220
}
2321
}
2422
</script>

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

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
1919
serverConfigSave = serverConfig.stubs
2020
config.stubs = {}
2121
serverConfig.stubs = {}
22+
sandbox.stub(console, 'error').callThrough()
2223
})
2324

2425
afterEach(() => {
@@ -33,21 +34,24 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
3334
const ComponentWithoutRender = { template: '<div></div>' }
3435
const ExtendedComponent = { extends: ComponentWithRender }
3536
const SubclassedComponent = Vue.extend({ template: '<div></div>' })
37+
const StringComponent = '<div></div>'
3638
mountingMethod(ComponentWithChild, {
3739
stubs: {
3840
ChildComponent: ComponentWithRender,
3941
ChildComponent2: ComponentAsAClass,
4042
ChildComponent3: ComponentWithoutRender,
4143
ChildComponent4: ExtendedComponent,
42-
ChildComponent5: SubclassedComponent
44+
ChildComponent5: SubclassedComponent,
45+
ChildComponent6: StringComponent
4346
}
4447
})
4548
})
4649

4750
it('replaces component with template string ', () => {
51+
const Stub = { template: '<div class="stub"></div>' }
4852
const wrapper = mountingMethod(ComponentWithChild, {
4953
stubs: {
50-
ChildComponent: '<div class="stub"></div>'
54+
ChildComponent: Stub
5155
}
5256
})
5357
expect(wrapper.findAll('.stub').length).to.equal(1)
@@ -322,7 +326,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
322326

323327
const wrapper = mountingMethod(TestComponent, {
324328
stubs: {
325-
'span-component': '<p />'
329+
'span-component': { template: '<p />' }
326330
},
327331
localVue
328332
})
@@ -343,7 +347,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
343347

344348
const wrapper = mountingMethod(TestComponent, {
345349
stubs: {
346-
'time-component': '<span />'
350+
'time-component': { template: '<span />' }
347351
},
348352
localVue
349353
})
@@ -415,7 +419,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
415419
expect(wrapper.html()).contains('No render function')
416420
})
417421

418-
it('throws an error when passed a circular reference', () => {
422+
it('throws an error when passed a circular reference for string stubs', () => {
419423
const names = ['child-component', 'ChildComponent', 'childComponent']
420424
const validValues = [
421425
'<NAME-suffix />',
@@ -592,25 +596,47 @@ describeWithShallowAndMount('options.stub', mountingMethod => {
592596
delete Vue.options.components['child-component']
593597
})
594598

595-
it.only('replaces component with a component and inherits attributes', () => {
596-
const mounted = sandbox.stub()
597-
const Stub = {
598-
template: '<div id="SlotComponent"><slot name="newSyntax"/></div>',
599-
mounted
600-
}
599+
it('renders props in the element as attributes', () => {
600+
const ComponentStub = { template: '<div id="component-stub" />' }
601+
const StringStub = '<div id="string-stub" />'
602+
const BooleanStub = true
603+
601604
const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, {
602605
stubs: {
603-
SlotComponent: Stub,
604-
ChildComponent: '<div id="child-component"/>'
606+
SlotComponent: ComponentStub,
607+
ChildComponent: StringStub,
608+
OriginalComponent: BooleanStub
605609
}
606610
})
607611

608-
const childStub = wrapper.find('#child-component')
609-
expect(wrapper.vm.$el.innerHTML).to.include('prop1="foobar"')
610-
expect(wrapper.vm.$el.innerHTML).to.include('prop2="fizzbuzz"')
611-
expect(childStub.attributes()).to.eql({
612+
expect(wrapper.find('#component-stub').attributes()).to.eql({
613+
id: 'component-stub',
614+
prop1: 'foobar',
615+
prop2: 'fizzbuzz'
616+
})
617+
expect(wrapper.find('#string-stub').attributes()).to.eql({
618+
id: 'string-stub',
619+
prop1: 'foobar',
620+
prop2: 'fizzbuzz'
621+
})
622+
expect(wrapper.find('originalcomponent-stub').attributes()).to.eql({
612623
prop1: 'foobar',
613624
prop2: 'fizzbuzz'
614625
})
615626
})
627+
628+
it('warns when passing a string', () => {
629+
const StringComponent = '<div></div>'
630+
mountingMethod(ComponentWithChild, {
631+
stubs: {
632+
ChildComponent6: StringComponent
633+
}
634+
})
635+
636+
expect(console.error).calledWith(
637+
sandbox.match(
638+
'[vue-test-utils]: String stubs are deprecated and will be removed in future versions'
639+
)
640+
)
641+
})
616642
})

0 commit comments

Comments
 (0)