Skip to content

Commit 0d1ddd1

Browse files
authored
fix: handle global stubs and functional extended components (#943)
1 parent 9bb9a87 commit 0d1ddd1

15 files changed

+127
-56
lines changed

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
"rollup": "^0.58.2",
6464
"sinon": "^2.3.2",
6565
"sinon-chai": "^2.10.0",
66+
"typescript": "^3.0.1",
67+
"vee-validate": "2.1.0-beta.5",
6668
"vue": "2.5.16",
6769
"vue-class-component": "^6.1.2",
6870
"vue-loader": "^13.6.2",
@@ -71,7 +73,6 @@
7173
"vue-template-compiler": "2.5.16",
7274
"vuepress": "^0.14.2",
7375
"vuepress-theme-vue": "^1.0.3",
74-
"vuetify": "^0.16.9",
7576
"vuex": "^3.0.1",
7677
"webpack": "^3.0.1",
7778
"webpack-node-externals": "^1.6.0"

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

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This is used instead of Vue.mixin. The reason is that
2+
// Vue.mixin is slower, and remove modfied options
3+
// https://github.com/vuejs/vue/issues/8710
4+
5+
export function addHook (options, hook, fn) {
6+
if (options[hook] && !Array.isArray(options[hook])) {
7+
options[hook] = [options[hook]]
8+
}
9+
(options[hook] || (options[hook] = [])).push(fn)
10+
}

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createStubsFromStubsObject } from 'shared/create-component-stubs'
2+
import { addHook } from './add-hook'
23

34
export function addStubs (component, stubs, _Vue) {
45
const stubComponents = createStubsFromStubsObject(
@@ -13,10 +14,8 @@ export function addStubs (component, stubs, _Vue) {
1314
)
1415
}
1516

16-
_Vue.mixin({
17-
beforeMount: addStubComponentsMixin,
18-
// beforeCreate is for components created in node, which
19-
// never mount
20-
beforeCreate: addStubComponentsMixin
21-
})
17+
addHook(_Vue.options, 'beforeMount', addStubComponentsMixin)
18+
// beforeCreate is for components created in node, which
19+
// never mount
20+
addHook(_Vue.options, 'beforeCreate', addStubComponentsMixin)
2221
}

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

+8-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { componentNeedsCompiling, isPlainObject } from 'shared/validators'
1616
import { validateSlots } from './validate-slots'
1717
import createScopedSlots from './create-scoped-slots'
1818
import { extendExtendedComponents } from './extend-extended-components'
19-
import Vue from 'vue'
2019

2120
function vueExtendUnsupportedOption (option: string) {
2221
return `options.${option} is not supported for ` +
@@ -36,15 +35,12 @@ const UNSUPPORTED_VERSION_OPTIONS = [
3635

3736
export default function createInstance (
3837
component: Component,
39-
options: Options
38+
options: Options,
39+
_Vue: Component
4040
): Component {
4141
// Remove cached constructor
4242
delete component._Ctor
4343

44-
const _Vue = options.localVue
45-
? options.localVue.extend()
46-
: Vue.extend()
47-
4844
// make sure all extends are based on this instance
4945
_Vue.options._base = _Vue
5046

@@ -75,7 +71,8 @@ export default function createInstance (
7571
component = createFunctionalComponent(component, options)
7672
} else if (options.context) {
7773
throwError(
78-
`mount.context can only be used when mounting a ` + `functional component`
74+
`mount.context can only be used when mounting a ` +
75+
`functional component`
7976
)
8077
}
8178

@@ -84,13 +81,15 @@ export default function createInstance (
8481
}
8582

8683
// Replace globally registered components with components extended
87-
// from localVue. This makes sure the beforeMount mixins to add stubs
88-
// is applied to globally registered components.
84+
// from localVue.
8985
// Vue version must be 2.3 or greater, because of a bug resolving
9086
// extended constructor options (https://github.com/vuejs/vue/issues/4976)
9187
if (vueVersion > 2.2) {
9288
for (const c in _Vue.options.components) {
9389
if (!isRequiredComponent(c)) {
90+
const extendedComponent = _Vue.extend(_Vue.options.components[c])
91+
extendedComponent.options.$_vueTestUtils_original =
92+
_Vue.options.components[c]
9493
_Vue.component(c, _Vue.extend(_Vue.options.components[c]))
9594
}
9695
}

Diff for: packages/create-instance/extend-extended-components.js

+11-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { warn } from 'shared/util'
2+
import { addHook } from './add-hook'
23

34
function createdFrom (extendOptions, componentOptions) {
45
while (extendOptions) {
@@ -75,12 +76,10 @@ export function extendExtendedComponents (
7576
`config.logModifiedComponents option to false.`
7677
)
7778
}
79+
7880
const extendedComp = _Vue.extend(comp)
79-
// used to identify instance when calling find with component selector
80-
if (extendedComp.extendOptions.options) {
81-
extendedComp.extendOptions.options.$_vueTestUtils_original = comp
82-
}
83-
extendedComp.extendOptions.$_vueTestUtils_original = comp
81+
// Used to identify component in a render tree
82+
extendedComp.options.$_vueTestUtils_original = comp
8483
extendedComponents[c] = extendedComp
8584
}
8685
// If a component has been replaced with an extended component
@@ -93,15 +92,13 @@ export function extendExtendedComponents (
9392
shouldExtendComponent
9493
)
9594
})
96-
if (extendedComponents) {
97-
_Vue.mixin({
98-
created () {
99-
if (createdFrom(this.constructor, component)) {
100-
Object.assign(
101-
this.$options.components,
102-
extendedComponents
103-
)
104-
}
95+
if (Object.keys(extendedComponents).length > 0) {
96+
addHook(_Vue.options, 'beforeCreate', function addExtendedOverwrites () {
97+
if (createdFrom(this.constructor, component)) {
98+
Object.assign(
99+
this.$options.components,
100+
extendedComponents
101+
)
105102
}
106103
})
107104
}

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @flow
2+
import { addHook } from './add-hook'
23

34
export function logEvents (
45
vm: Component,
@@ -13,12 +14,11 @@ export function logEvents (
1314
}
1415
}
1516

16-
export function addEventLogger (vue: Component): void {
17-
vue.mixin({
18-
beforeCreate: function () {
19-
this.__emitted = Object.create(null)
20-
this.__emittedByOrder = []
21-
logEvents(this, this.__emitted, this.__emittedByOrder)
22-
}
23-
})
17+
export function addEventLogger (_Vue: Component): void {
18+
addHook(_Vue.options, 'beforeCreate', function () {
19+
this.__emitted = Object.create(null)
20+
this.__emittedByOrder = []
21+
logEvents(this, this.__emitted, this.__emittedByOrder)
22+
}
23+
)
2424
}

Diff for: packages/server-test-utils/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"typescript": "^2.6.2"
3838
},
3939
"peerDependencies": {
40-
"@vue/test-utils": "1.x",
40+
"@vue/test-utils": "*",
4141
"vue": "2.x",
4242
"vue-server-renderer": "2.x",
4343
"vue-template-compiler": "^2.x"

Diff for: packages/server-test-utils/src/renderToString.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { throwError } from 'shared/util'
66
import { createRenderer } from 'vue-server-renderer'
77
import { mergeOptions } from 'shared/merge-options'
88
import config from './config'
9+
import testUtils from '@vue/test-utils'
910

1011
Vue.config.productionTip = false
1112
Vue.config.devtools = false
@@ -30,7 +31,8 @@ export default function renderToString (
3031

3132
const vm = createInstance(
3233
component,
33-
mergeOptions(options, config)
34+
mergeOptions(options, config),
35+
testUtils.createLocalVue(options.localVue)
3436
)
3537
let renderedString = ''
3638

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ export function matches (node, selector) {
4343
return element && element.matches && element.matches(selector.value)
4444
}
4545

46-
const componentInstance = selector.value.functional
47-
? node && node[FUNCTIONAL_OPTIONS]
46+
const isFunctionalSelector = typeof selector.value === 'function'
47+
? selector.value.options.functional
48+
: selector.value.functional
49+
50+
const componentInstance = isFunctionalSelector
51+
? node[FUNCTIONAL_OPTIONS]
4852
: node.child
4953

5054
if (!componentInstance) {

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { mergeOptions } from 'shared/merge-options'
1212
import config from './config'
1313
import warnIfNoWindow from './warn-if-no-window'
1414
import createWrapper from './create-wrapper'
15-
15+
import createLocalVue from './create-local-vue'
1616
Vue.config.productionTip = false
1717
Vue.config.devtools = false
1818

@@ -34,7 +34,8 @@ export default function mount (
3434

3535
const parentVm = createInstance(
3636
component,
37-
mergedOptions
37+
mergedOptions,
38+
createLocalVue(options.localVue)
3839
)
3940

4041
const vm = parentVm.$mount(elm).$refs.vm

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

-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Vue from 'vue'
22
import Vuex from 'vuex'
3-
import Vuetify from 'vuetify'
43
import VueRouter from 'vue-router'
54
import { createLocalVue } from '~vue/test-utils'
65
import Component from '~resources/components/component.vue'
@@ -112,11 +111,6 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => {
112111
localVue.use(plugin, pluginOptions)
113112
})
114113

115-
it('installs Vutify successfuly', () => {
116-
const localVue = createLocalVue()
117-
localVue.use(Vuetify)
118-
})
119-
120114
it('installs plugin into local Vue regardless of previous install in Vue', () => {
121115
let installCount = 0
122116

Diff for: test/specs/external-libraries.spec.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createLocalVue, mount } from '~vue/test-utils'
2+
import VeeValidate from 'vee-validate'
3+
import { describeWithShallowAndMount } from '~resources/utils'
4+
5+
describeWithShallowAndMount('external libraries', () => {
6+
it.skip('works with vee validate', () => {
7+
const TestComponent = {
8+
template: '<div />'
9+
}
10+
const localVue = createLocalVue()
11+
localVue.use(VeeValidate)
12+
const wrapper = mount(TestComponent, {
13+
localVue
14+
})
15+
wrapper.vm.errors.collect('text')
16+
})
17+
})

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ComponentWithProps from '~resources/components/component-with-props.vue'
66
import ComponentWithMixin from '~resources/components/component-with-mixin.vue'
77
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
88
import { injectSupported, vueVersion } from '~resources/utils'
9-
import { describeRunIf, itDoNotRunIf } from 'conditional-specs'
9+
import { describeRunIf, itDoNotRunIf, itSkipIf } from 'conditional-specs'
1010
import Vuex from 'vuex'
1111

1212
describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
@@ -101,6 +101,32 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
101101
expect(wrapper.findAll('div').length).to.equal(1)
102102
})
103103

104+
itSkipIf(
105+
vueVersion < 2.3,
106+
'handles extended components added to Vue constructor', () => {
107+
const ChildComponent = Vue.extend({
108+
template: '<div />',
109+
mounted () {
110+
this.$route.params
111+
}
112+
})
113+
Vue.component('child-component', ChildComponent)
114+
const TestComponent = {
115+
template: '<child-component />'
116+
}
117+
let wrapper
118+
try {
119+
wrapper = mount(TestComponent, {
120+
mocks: {
121+
$route: {}
122+
}
123+
})
124+
} catch (err) {} finally {
125+
delete Vue.options.components['child-component']
126+
expect(wrapper.find(ChildComponent).exists()).to.equal(true)
127+
}
128+
})
129+
104130
it('does not use cached component', () => {
105131
ComponentWithMixin.methods.someMethod = sinon.stub()
106132
mount(ComponentWithMixin)

Diff for: test/specs/wrapper/find.spec.js

+16
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,22 @@ describeWithShallowAndMount('find', mountingMethod => {
344344
expect(wrapper.find({ ref: 'foo' })).to.be.an('object')
345345
})
346346

347+
itSkipIf(vueVersion < 2.3,
348+
'returns functional extended component', () => {
349+
const FunctionalExtendedComponent = Vue.extend({
350+
functional: true,
351+
render: h => h('div')
352+
})
353+
const TestComponent = {
354+
template: '<div><functional-extended-component /></div>',
355+
components: {
356+
FunctionalExtendedComponent
357+
}
358+
}
359+
const wrapper = mountingMethod(TestComponent)
360+
expect(wrapper.find(FunctionalExtendedComponent).exists()).to.equal(true)
361+
})
362+
347363
it('returns Wrapper of Vue Component matching the extended component', () => {
348364
const BaseComponent = {
349365
template: '<div><a-component /></div>',

Diff for: yarn.lock

+10-5
Original file line numberDiff line numberDiff line change
@@ -10788,6 +10788,11 @@ typescript@^2.6.2:
1078810788
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836"
1078910789
integrity sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==
1079010790

10791+
typescript@^3.0.1:
10792+
version "3.0.1"
10793+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb"
10794+
integrity sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==
10795+
1079110796
uc.micro@^1.0.1, uc.micro@^1.0.5:
1079210797
version "1.0.5"
1079310798
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
@@ -11139,6 +11144,11 @@ vary@^1.0.0:
1113911144
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
1114011145
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
1114111146

11147+
11148+
version "2.1.0-beta.5"
11149+
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-2.1.0-beta.5.tgz#311c4629face2383964beb06768b379500f25a69"
11150+
integrity sha512-XN0FCHBEWKbxKprjzIwIr7ff5U+aFaJZlkoKr1ReuncMwHgG/6iW0hEI4bDuNXx1YKPmm4SyhoXkAOBHIUIwTA==
11151+
1114211152
vendors@^1.0.0:
1114311153
version "1.0.1"
1114411154
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
@@ -11370,11 +11380,6 @@ vuepress@^0.14.2:
1137011380
webpackbar "^2.6.1"
1137111381
workbox-build "^3.1.0"
1137211382

11373-
vuetify@^0.16.9:
11374-
version "0.16.9"
11375-
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-0.16.9.tgz#fd61f219e4a40d7afe5e24a803df5658a40b38e4"
11376-
integrity sha512-HVzdKnMETHINURJhGCMGT3wFw6R8eEHCVcqdC6o/z+eAWNQH7xg1k2hCsKo680lkjiL7tp3vRd9lF+3DpLm/+g==
11377-
1137811383
vuex@^3.0.1:
1137911384
version "3.0.1"
1138011385
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"

0 commit comments

Comments
 (0)