Skip to content

Commit 68a55f1

Browse files
authored
fix: automatically extend extended child components (#595)
1 parent 14c40e6 commit 68a55f1

22 files changed

+168
-55
lines changed

Diff for: docs/en/api/config.md

+15
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,18 @@ VueTestUtils.config.provide['$logger'] = {
7979
}
8080
}
8181
```
82+
83+
### `logModifiedComponents`
84+
85+
- type: `Boolean`
86+
- default: `true`
87+
88+
Logs warning when extended child components are automatically stubbed. Hides warnings when set to `false`. Unlike other config options, this cannot be set on the mounting options.
89+
90+
Example:
91+
92+
```js
93+
import VueTestUtils from '@vue/test-utils'
94+
95+
VueTestUtils.config.logModifiedComponents = false
96+
```

Diff for: flow/options.flow.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ declare type Options = { // eslint-disable-line no-undef
99
stubs?: Object,
1010
context?: Object,
1111
attrs?: Object,
12-
listeners?: Object
12+
listeners?: Object,
13+
logModifiedComponents?: Boolean
1314
}

Diff for: packages/create-instance/compile-template.js

-20
This file was deleted.

Diff for: packages/create-instance/create-instance.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import addListeners from './add-listeners'
99
import addProvide from './add-provide'
1010
import { addEventLogger } from './log-events'
1111
import { createComponentStubs } from 'shared/stub-components'
12-
import { throwError } from 'shared/util'
13-
import { compileTemplate } from './compile-template'
12+
import { throwError, warn } from 'shared/util'
13+
import { compileTemplate } from 'shared/compile-template'
1414
import deleteoptions from './delete-mounting-options'
1515
import createFunctionalComponent from './create-functional-component'
1616
import { componentNeedsCompiling } from 'shared/validators'
@@ -70,6 +70,16 @@ export default function createInstance (
7070
}
7171
}
7272

73+
Object.keys(component.components || {}).forEach((c) => {
74+
if (component.components[c].extendOptions &&
75+
!instanceOptions.components[c]) {
76+
if (options.logModifiedComponents) {
77+
warn(`an extended child component ${c} has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the mocks mounting option.`)
78+
}
79+
instanceOptions.components[c] = vue.extend(component.components[c])
80+
}
81+
})
82+
7383
Object.keys(stubComponents).forEach(c => {
7484
vue.component(c, stubComponents[c])
7585
})

Diff for: packages/server-test-utils/types/index.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ interface VueTestUtilsConfigOptions {
4949
stubs?: Stubs
5050
mocks?: object
5151
methods?: Record<string, Function>
52-
provide?: object
52+
provide?: object,
53+
logModifiedComponents?: Boolean
5354
}
5455

5556
export declare let config: VueTestUtilsConfigOptions

Diff for: packages/shared/compile-template.js

+6
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ export function compileTemplate (component: Component) {
1111
}
1212
})
1313
}
14+
1415
if (component.extends) {
1516
compileTemplate(component.extends)
1617
}
18+
19+
if (component.extendOptions && !component.options.render) {
20+
compileTemplate(component.options)
21+
}
22+
1723
if (component.template) {
1824
Object.assign(component, compileToFunctions(component.template))
1925
}

Diff for: packages/shared/merge-options.js

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function mergeOptions (
2626
): Options {
2727
return {
2828
...options,
29+
logModifiedComponents: config.logModifiedComponents,
2930
stubs: getOptions('stubs', options.stubs, config),
3031
mocks: getOptions('mocks', options.mocks, config),
3132
methods: getOptions('methods', options.methods, config),

Diff for: packages/test-utils/src/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export default {
88
},
99
mocks: {},
1010
methods: {},
11-
provide: {}
11+
provide: {},
12+
logModifiedComponents: true
1213
}

Diff for: packages/test-utils/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import config from './config'
88
import { warn } from 'shared/util'
99

1010
function shallow (component, options) {
11-
warn('shallow has been renamed to shallowMount and will be deprecated in 1.0.0')
11+
warn('shallow has been renamed to shallowMount. shallow will be removed in 1.0.0, use shallowMount instead')
1212
return shallowMount(component, options)
1313
}
1414

Diff for: packages/test-utils/types/index.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ interface VueTestUtilsConfigOptions {
139139
stubs?: Stubs
140140
mocks?: object
141141
methods?: Record<string, Function>
142-
provide?: object
142+
provide?: object,
143+
logModifiedComponents?: Boolean
143144
}
144145

145146
export declare function createLocalVue (): typeof Vue

Diff for: test/resources/utils.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* global describe, it*/
22

33
import Vue from 'vue'
4-
import { shallow, mount } from '~vue/test-utils'
4+
import { shallowMount, mount } from '~vue/test-utils'
55
import { renderToString } from '~vue/server-test-utils'
66

77
export const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
@@ -28,11 +28,10 @@ export const scopedSlotsSupported = vueVersion > 2
2828

2929
const shallowAndMount = process.env.TEST_ENV === 'node'
3030
? []
31-
: [mount, shallow]
32-
console.log(shallowAndMount)
31+
: [mount, shallowMount]
3332
const shallowMountAndRender = process.env.TEST_ENV === 'node'
3433
? [renderToString]
35-
: [mount, shallow]
34+
: [mount, shallowMount]
3635

3736
export function describeWithShallowAndMount (spec, cb) {
3837
if (shallowAndMount.length > 0) {

Diff for: test/specs/components/TransitionStub.spec.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@ import { describeWithShallowAndMount } from '~resources/utils'
33
import { TransitionStub } from '~vue/test-utils'
44

55
describeWithShallowAndMount('TransitionStub', (mountingMethod) => {
6+
let consoleError
7+
8+
beforeEach(() => {
9+
consoleError = sinon.stub(console, 'error')
10+
})
11+
12+
afterEach(() => {
13+
consoleError.restore()
14+
})
15+
616
it('update synchronously when used as stubs for Transition', () => {
7-
console.log(TransitionStub)
817
const wrapper = mountingMethod(ComponentWithTransition, {
918
stubs: {
1019
'transition': TransitionStub
@@ -48,14 +57,12 @@ describeWithShallowAndMount('TransitionStub', (mountingMethod) => {
4857
`
4958
}
5059
const msg = '[vue-test-utils]: <transition> can only be used on a single element. Use <transition-group> for lists.'
51-
const error = sinon.stub(console, 'error')
5260
mountingMethod(TestComponent, {
5361
stubs: {
5462
'transition': TransitionStub
5563
}
5664
})
57-
expect(error).calledWith(msg)
58-
error.restore()
65+
expect(consoleError).calledWith(msg)
5966
})
6067

6168
it('handles keyed transitions', () => {

Diff for: test/specs/config.spec.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1-
import { describeWithShallowAndMount, itDoNotRunIf } from '~resources/utils'
1+
import {
2+
describeWithShallowAndMount,
3+
itDoNotRunIf,
4+
itSkipIf,
5+
vueVersion
6+
} from '~resources/utils'
27
import { config, TransitionStub, TransitionGroupStub, createLocalVue } from '~vue/test-utils'
8+
import Vue from 'vue'
39

410
describeWithShallowAndMount('config', (mountingMethod) => {
511
let configStubsSave
12+
let consoleError
13+
let configLogSave
14+
615
beforeEach(() => {
716
TransitionGroupStub.name = 'another-temp-name'
817
TransitionStub.name = 'a-temp-name'
918
configStubsSave = config.stubs
19+
configLogSave = config.logModifiedComponents
20+
consoleError = sinon.stub(console, 'error')
1021
})
1122

1223
afterEach(() => {
1324
TransitionGroupStub.name = 'transition-group'
1425
TransitionStub.name = 'transition'
1526
config.stubs = configStubsSave
27+
config.logModifiedComponents = configLogSave
28+
consoleError.restore()
1629
})
1730

18-
itDoNotRunIf(mountingMethod.name === 'shallow',
31+
itDoNotRunIf(mountingMethod.name === 'shallowMount',
1932
'stubs transition and transition-group by default', () => {
2033
const testComponent = {
2134
template: `
@@ -121,4 +134,21 @@ describeWithShallowAndMount('config', (mountingMethod) => {
121134
expect(wrapper.contains(TransitionGroupStub)).to.equal(false)
122135
expect(wrapper.contains(TransitionStub)).to.equal(false)
123136
})
137+
138+
itSkipIf(
139+
vueVersion < 2.3,
140+
'does not log when component is extended if logModifiedComponents is false', () => {
141+
const ChildComponent = Vue.extend({
142+
template: '<span />'
143+
})
144+
const TestComponent = {
145+
template: '<child-component />',
146+
components: {
147+
ChildComponent
148+
}
149+
}
150+
config.logModifiedComponents = false
151+
mountingMethod(TestComponent)
152+
expect(consoleError.called).to.equal(false)
153+
})
124154
})

Diff for: test/specs/create-local-vue.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describeWithShallowAndMount('createLocalVue', (mountingMethod) => {
7070
})
7171

7272
itDoNotRunIf(
73-
mountingMethod.name === 'shallow',
73+
mountingMethod.name === 'shallowMount',
7474
'Router should work properly with local Vue', () => {
7575
const localVue = createLocalVue()
7676
localVue.use(VueRouter)

Diff for: test/specs/mount.spec.js

+25
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import { injectSupported, vueVersion, describeIf } from '~resources/utils'
88

99
describeIf(process.env.TEST_ENV !== 'node',
1010
'mount', () => {
11+
let consoleError
12+
13+
beforeEach(() => {
14+
consoleError = sinon.stub(console, 'error')
15+
})
16+
17+
afterEach(() => {
18+
consoleError.restore()
19+
})
20+
1121
it('returns new VueWrapper with mounted Vue instance if no options are passed', () => {
1222
const compiled = compileToFunctions('<div><input /></div>')
1323
const wrapper = mount(compiled)
@@ -120,6 +130,21 @@ describeIf(process.env.TEST_ENV !== 'node',
120130
expect(wrapper.html()).to.equal(`<div>foo</div>`)
121131
})
122132

133+
it('logs if component is extended', () => {
134+
const msg = '[vue-test-utils]: an extended child component ChildComponent has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the mocks mounting option.'
135+
const ChildComponent = Vue.extend({
136+
template: '<span />'
137+
})
138+
const TestComponent = {
139+
template: '<child-component />',
140+
components: {
141+
ChildComponent
142+
}
143+
}
144+
mount(TestComponent)
145+
expect(consoleError).calledWith(msg)
146+
})
147+
123148
it('deletes mounting options before passing options to component', () => {
124149
const wrapper = mount({
125150
render: h => h('div')

Diff for: test/specs/mounting-options/localVue.spec.js

+40-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import Vue from 'vue'
22
import {
33
describeWithMountingMethods,
44
itSkipIf,
5-
isRunningPhantomJS
5+
isRunningPhantomJS,
6+
vueVersion
67
} from '~resources/utils'
8+
import { createLocalVue } from '~vue/test-utils'
9+
import Vuex from 'vuex'
710

811
describeWithMountingMethods('options.localVue', (mountingMethod) => {
912
itSkipIf(
@@ -30,4 +33,40 @@ describeWithMountingMethods('options.localVue', (mountingMethod) => {
3033
: freshWrapper.html()
3134
expect(freshHTML).to.not.contain('some value')
3235
})
36+
37+
itSkipIf(
38+
vueVersion < 2.3,
39+
'works correctly with extended children', () => {
40+
const localVue = createLocalVue()
41+
localVue.use(Vuex)
42+
const store = new Vuex.Store({
43+
state: { val: 2 }
44+
})
45+
const ChildComponent = Vue.extend({
46+
template: '<span>{{val}}</span>',
47+
computed: {
48+
val () {
49+
return this.$store.state.val
50+
}
51+
}
52+
})
53+
const TestComponent = {
54+
template: '<div><child-component /></div>',
55+
components: {
56+
ChildComponent
57+
}
58+
}
59+
const wrapper = mountingMethod(TestComponent, {
60+
localVue,
61+
store
62+
})
63+
const HTML = mountingMethod.name === 'renderToString'
64+
? wrapper
65+
: wrapper.html()
66+
if (mountingMethod.name === 'shallowMount') {
67+
expect(HTML).to.not.contain('2')
68+
} else {
69+
expect(HTML).to.contain('2')
70+
}
71+
})
3372
})

Diff for: test/specs/mounting-options/methods.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ describeWithMountingMethods('options.methods', (mountingMethod) => {
2323
const HTML = mountingMethod.name === 'renderToString'
2424
? wrapper
2525
: wrapper.html()
26-
console.log(wrapper)
2726
expect(HTML).to.contain('methodFromOptions')
2827
})
2928
})

0 commit comments

Comments
 (0)