Skip to content

Commit 24ab4c5

Browse files
authored
fix: stubs extended component correctly (#767)
1 parent e83cda2 commit 24ab4c5

File tree

4 files changed

+90
-31
lines changed

4 files changed

+90
-31
lines changed

packages/shared/stub-components.js

+58-30
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
import Vue from 'vue'
44
import { compileToFunctions } from 'vue-template-compiler'
5-
import { throwError } from './util'
5+
import {
6+
throwError,
7+
camelize,
8+
capitalize,
9+
hyphenate
10+
} from './util'
611
import {
712
componentNeedsCompiling,
813
templateContainsComponent
@@ -21,28 +26,37 @@ function isValidStub (stub: any) {
2126
)
2227
}
2328

29+
function resolveComponent (obj, component) {
30+
return obj[component] ||
31+
obj[hyphenate(component)] ||
32+
obj[camelize(component)] ||
33+
obj[capitalize(camelize(component))] ||
34+
obj[capitalize(component)] ||
35+
{}
36+
}
37+
2438
function isRequiredComponent (name) {
2539
return (
2640
name === 'KeepAlive' || name === 'Transition' || name === 'TransitionGroup'
2741
)
2842
}
2943

30-
function getCoreProperties (component: Component): Object {
44+
function getCoreProperties (componentOptions: Component): Object {
3145
return {
32-
attrs: component.attrs,
33-
name: component.name,
34-
on: component.on,
35-
key: component.key,
36-
ref: component.ref,
37-
props: component.props,
38-
domProps: component.domProps,
39-
class: component.class,
40-
staticClass: component.staticClass,
41-
staticStyle: component.staticStyle,
42-
style: component.style,
43-
normalizedStyle: component.normalizedStyle,
44-
nativeOn: component.nativeOn,
45-
functional: component.functional
46+
attrs: componentOptions.attrs,
47+
name: componentOptions.name,
48+
on: componentOptions.on,
49+
key: componentOptions.key,
50+
ref: componentOptions.ref,
51+
props: componentOptions.props,
52+
domProps: componentOptions.domProps,
53+
class: componentOptions.class,
54+
staticClass: componentOptions.staticClass,
55+
staticStyle: componentOptions.staticStyle,
56+
style: componentOptions.style,
57+
normalizedStyle: componentOptions.normalizedStyle,
58+
nativeOn: componentOptions.nativeOn,
59+
functional: componentOptions.functional
4660
}
4761
}
4862
function createStubFromString (
@@ -62,24 +76,31 @@ function createStubFromString (
6276
throwError('options.stub cannot contain a circular reference')
6377
}
6478

79+
const componentOptions = typeof originalComponent === 'function'
80+
? originalComponent.extendOptions
81+
: originalComponent
82+
6583
return {
66-
...getCoreProperties(originalComponent),
84+
...getCoreProperties(componentOptions),
6785
...compileToFunctions(templateString)
6886
}
6987
}
7088

71-
function createBlankStub (originalComponent: Component) {
72-
const name = `${originalComponent.name}-stub`
89+
function createBlankStub (originalComponent: Component, name: string) {
90+
const componentOptions = typeof originalComponent === 'function'
91+
? originalComponent.extendOptions
92+
: originalComponent
93+
const tagName = `${name}-stub`
7394

7495
// ignoreElements does not exist in Vue 2.0.x
7596
if (Vue.config.ignoredElements) {
76-
Vue.config.ignoredElements.push(name)
97+
Vue.config.ignoredElements.push(tagName)
7798
}
7899

79100
return {
80-
...getCoreProperties(originalComponent),
101+
...getCoreProperties(componentOptions),
81102
render (h) {
82-
return h(name)
103+
return h(tagName)
83104
}
84105
}
85106
}
@@ -101,7 +122,9 @@ export function createComponentStubs (
101122
if (typeof stub !== 'string') {
102123
throwError(`each item in an options.stubs array must be a ` + `string`)
103124
}
104-
components[stub] = createBlankStub({ name: stub })
125+
const component = resolveComponent(originalComponents, stub)
126+
127+
components[stub] = createBlankStub(component, stub)
105128
})
106129
} else {
107130
Object.keys(stubs).forEach(stub => {
@@ -114,7 +137,8 @@ export function createComponentStubs (
114137
)
115138
}
116139
if (stubs[stub] === true) {
117-
components[stub] = createBlankStub({ name: stub })
140+
const component = resolveComponent(originalComponents, stub)
141+
components[stub] = createBlankStub(component, stub)
118142
return
119143
}
120144

@@ -162,12 +186,16 @@ export function createComponentStubs (
162186

163187
function stubComponents (components: Object, stubbedComponents: Object) {
164188
Object.keys(components).forEach(component => {
189+
const cmp = components[component]
190+
const componentOptions = typeof cmp === 'function'
191+
? cmp.extendOptions
192+
: cmp
165193
// Remove cached constructor
166-
delete components[component]._Ctor
167-
if (!components[component].name) {
168-
components[component].name = component
194+
delete componentOptions._Ctor
195+
if (!componentOptions.name) {
196+
componentOptions.name = component
169197
}
170-
stubbedComponents[component] = createBlankStub(components[component])
198+
stubbedComponents[component] = createBlankStub(componentOptions, component)
171199
})
172200
}
173201

@@ -178,7 +206,7 @@ export function createComponentStubsForAll (component: Component): Object {
178206
stubComponents(component.components, stubbedComponents)
179207
}
180208

181-
stubbedComponents[component.name] = createBlankStub(component)
209+
stubbedComponents[component.name] = createBlankStub(component, component.name)
182210

183211
let extended = component.extends
184212

@@ -204,7 +232,7 @@ export function createComponentStubsForGlobals (instance: Component): Object {
204232
return
205233
}
206234

207-
components[c] = createBlankStub(instance.options.components[c])
235+
components[c] = createBlankStub(instance.options.components[c], c)
208236
delete instance.options.components[c]._Ctor
209237
delete components[c]._Ctor
210238
})

packages/test-utils/src/find-vue-components.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default function findVueComponents (
106106
)
107107
}
108108
const nameSelector =
109-
typeof selector === 'function' ? selector.options.name : selector.name
109+
typeof selector === 'function' ? selector.extendOptions.name : selector.name
110110
const components = root._isVue
111111
? findAllVueComponentsFromVm(root)
112112
: findAllVueComponentsFromVnode(root)

test/specs/shallow-mount.spec.js

+17
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,23 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'shallowMount', () => {
239239
).to.equal(3)
240240
})
241241

242+
it('handles extended stubs', () => {
243+
const ChildComponent = Vue.extend({
244+
template: '<div />',
245+
props: ['propA']
246+
})
247+
const TestComponent = {
248+
template: '<child-component propA="hey" />',
249+
components: { ChildComponent }
250+
}
251+
const wrapper = shallowMount(TestComponent, {
252+
stubs: ['child-component']
253+
})
254+
255+
expect(wrapper.find(ChildComponent).vm.propA)
256+
.to.equal('hey')
257+
})
258+
242259
it('throws an error when the component fails to mount', () => {
243260
expect(() =>
244261
shallowMount({

test/specs/wrapper/find.spec.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { compileToFunctions } from 'vue-template-compiler'
22
import { createLocalVue } from '~vue/test-utils'
3+
import Vue from 'vue'
34
import ComponentWithChild from '~resources/components/component-with-child.vue'
45
import ComponentWithoutName from '~resources/components/component-without-name.vue'
56
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
@@ -79,6 +80,19 @@ describeWithShallowAndMount('find', mountingMethod => {
7980
expect(wrapper.find('#foo').vnode).to.be.an('object')
8081
})
8182

83+
it('returns matching extended component', () => {
84+
const ChildComponent = Vue.extend({
85+
template: '<div />',
86+
props: ['propA']
87+
})
88+
const TestComponent = {
89+
template: '<child-component propA="hey" />',
90+
components: { ChildComponent }
91+
}
92+
const wrapper = mountingMethod(TestComponent)
93+
expect(wrapper.find(ChildComponent).vnode).to.be.an('object')
94+
})
95+
8296
it('returns Wrapper of elements matching attribute selector passed', () => {
8397
const compiled = compileToFunctions('<div><a href="/"></a></div>')
8498
const wrapper = mountingMethod(compiled)

0 commit comments

Comments
 (0)