Skip to content

Commit

Permalink
fix: handle global stubs and functional extended components (#943)
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyerburgh committed Aug 26, 2018
1 parent 9bb9a87 commit 0d1ddd1
Show file tree
Hide file tree
Showing 15 changed files with 127 additions and 56 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -63,6 +63,8 @@
"rollup": "^0.58.2",
"sinon": "^2.3.2",
"sinon-chai": "^2.10.0",
"typescript": "^3.0.1",
"vee-validate": "2.1.0-beta.5",
"vue": "2.5.16",
"vue-class-component": "^6.1.2",
"vue-loader": "^13.6.2",
Expand All @@ -71,7 +73,6 @@
"vue-template-compiler": "2.5.16",
"vuepress": "^0.14.2",
"vuepress-theme-vue": "^1.0.3",
"vuetify": "^0.16.9",
"vuex": "^3.0.1",
"webpack": "^3.0.1",
"webpack-node-externals": "^1.6.0"
Expand Down
10 changes: 10 additions & 0 deletions packages/create-instance/add-hook.js
@@ -0,0 +1,10 @@
// This is used instead of Vue.mixin. The reason is that
// Vue.mixin is slower, and remove modfied options
// https://github.com/vuejs/vue/issues/8710

export function addHook (options, hook, fn) {
if (options[hook] && !Array.isArray(options[hook])) {
options[hook] = [options[hook]]
}
(options[hook] || (options[hook] = [])).push(fn)
}
11 changes: 5 additions & 6 deletions packages/create-instance/add-stubs.js
@@ -1,4 +1,5 @@
import { createStubsFromStubsObject } from 'shared/create-component-stubs'
import { addHook } from './add-hook'

export function addStubs (component, stubs, _Vue) {
const stubComponents = createStubsFromStubsObject(
Expand All @@ -13,10 +14,8 @@ export function addStubs (component, stubs, _Vue) {
)
}

_Vue.mixin({
beforeMount: addStubComponentsMixin,
// beforeCreate is for components created in node, which
// never mount
beforeCreate: addStubComponentsMixin
})
addHook(_Vue.options, 'beforeMount', addStubComponentsMixin)
// beforeCreate is for components created in node, which
// never mount
addHook(_Vue.options, 'beforeCreate', addStubComponentsMixin)
}
17 changes: 8 additions & 9 deletions packages/create-instance/create-instance.js
Expand Up @@ -16,7 +16,6 @@ import { componentNeedsCompiling, isPlainObject } from 'shared/validators'
import { validateSlots } from './validate-slots'
import createScopedSlots from './create-scoped-slots'
import { extendExtendedComponents } from './extend-extended-components'
import Vue from 'vue'

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

export default function createInstance (
component: Component,
options: Options
options: Options,
_Vue: Component
): Component {
// Remove cached constructor
delete component._Ctor

const _Vue = options.localVue
? options.localVue.extend()
: Vue.extend()

// make sure all extends are based on this instance
_Vue.options._base = _Vue

Expand Down Expand Up @@ -75,7 +71,8 @@ export default function createInstance (
component = createFunctionalComponent(component, options)
} else if (options.context) {
throwError(
`mount.context can only be used when mounting a ` + `functional component`
`mount.context can only be used when mounting a ` +
`functional component`
)
}

Expand All @@ -84,13 +81,15 @@ export default function createInstance (
}

// Replace globally registered components with components extended
// from localVue. This makes sure the beforeMount mixins to add stubs
// is applied to globally registered components.
// from localVue.
// Vue version must be 2.3 or greater, because of a bug resolving
// extended constructor options (https://github.com/vuejs/vue/issues/4976)
if (vueVersion > 2.2) {
for (const c in _Vue.options.components) {
if (!isRequiredComponent(c)) {
const extendedComponent = _Vue.extend(_Vue.options.components[c])
extendedComponent.options.$_vueTestUtils_original =
_Vue.options.components[c]
_Vue.component(c, _Vue.extend(_Vue.options.components[c]))
}
}
Expand Down
25 changes: 11 additions & 14 deletions packages/create-instance/extend-extended-components.js
@@ -1,4 +1,5 @@
import { warn } from 'shared/util'
import { addHook } from './add-hook'

function createdFrom (extendOptions, componentOptions) {
while (extendOptions) {
Expand Down Expand Up @@ -75,12 +76,10 @@ export function extendExtendedComponents (
`config.logModifiedComponents option to false.`
)
}

const extendedComp = _Vue.extend(comp)
// used to identify instance when calling find with component selector
if (extendedComp.extendOptions.options) {
extendedComp.extendOptions.options.$_vueTestUtils_original = comp
}
extendedComp.extendOptions.$_vueTestUtils_original = comp
// Used to identify component in a render tree
extendedComp.options.$_vueTestUtils_original = comp
extendedComponents[c] = extendedComp
}
// If a component has been replaced with an extended component
Expand All @@ -93,15 +92,13 @@ export function extendExtendedComponents (
shouldExtendComponent
)
})
if (extendedComponents) {
_Vue.mixin({
created () {
if (createdFrom(this.constructor, component)) {
Object.assign(
this.$options.components,
extendedComponents
)
}
if (Object.keys(extendedComponents).length > 0) {
addHook(_Vue.options, 'beforeCreate', function addExtendedOverwrites () {
if (createdFrom(this.constructor, component)) {
Object.assign(
this.$options.components,
extendedComponents
)
}
})
}
Expand Down
16 changes: 8 additions & 8 deletions packages/create-instance/log-events.js
@@ -1,4 +1,5 @@
// @flow
import { addHook } from './add-hook'

export function logEvents (
vm: Component,
Expand All @@ -13,12 +14,11 @@ export function logEvents (
}
}

export function addEventLogger (vue: Component): void {
vue.mixin({
beforeCreate: function () {
this.__emitted = Object.create(null)
this.__emittedByOrder = []
logEvents(this, this.__emitted, this.__emittedByOrder)
}
})
export function addEventLogger (_Vue: Component): void {
addHook(_Vue.options, 'beforeCreate', function () {
this.__emitted = Object.create(null)
this.__emittedByOrder = []
logEvents(this, this.__emitted, this.__emittedByOrder)
}
)
}
2 changes: 1 addition & 1 deletion packages/server-test-utils/package.json
Expand Up @@ -37,7 +37,7 @@
"typescript": "^2.6.2"
},
"peerDependencies": {
"@vue/test-utils": "1.x",
"@vue/test-utils": "*",
"vue": "2.x",
"vue-server-renderer": "2.x",
"vue-template-compiler": "^2.x"
Expand Down
4 changes: 3 additions & 1 deletion packages/server-test-utils/src/renderToString.js
Expand Up @@ -6,6 +6,7 @@ import { throwError } from 'shared/util'
import { createRenderer } from 'vue-server-renderer'
import { mergeOptions } from 'shared/merge-options'
import config from './config'
import testUtils from '@vue/test-utils'

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

const vm = createInstance(
component,
mergeOptions(options, config)
mergeOptions(options, config),
testUtils.createLocalVue(options.localVue)
)
let renderedString = ''

Expand Down
8 changes: 6 additions & 2 deletions packages/test-utils/src/matches.js
Expand Up @@ -43,8 +43,12 @@ export function matches (node, selector) {
return element && element.matches && element.matches(selector.value)
}

const componentInstance = selector.value.functional
? node && node[FUNCTIONAL_OPTIONS]
const isFunctionalSelector = typeof selector.value === 'function'
? selector.value.options.functional
: selector.value.functional

const componentInstance = isFunctionalSelector
? node[FUNCTIONAL_OPTIONS]
: node.child

if (!componentInstance) {
Expand Down
5 changes: 3 additions & 2 deletions packages/test-utils/src/mount.js
Expand Up @@ -12,7 +12,7 @@ import { mergeOptions } from 'shared/merge-options'
import config from './config'
import warnIfNoWindow from './warn-if-no-window'
import createWrapper from './create-wrapper'

import createLocalVue from './create-local-vue'
Vue.config.productionTip = false
Vue.config.devtools = false

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

const parentVm = createInstance(
component,
mergedOptions
mergedOptions,
createLocalVue(options.localVue)
)

const vm = parentVm.$mount(elm).$refs.vm
Expand Down
6 changes: 0 additions & 6 deletions test/specs/create-local-vue.spec.js
@@ -1,6 +1,5 @@
import Vue from 'vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
import VueRouter from 'vue-router'
import { createLocalVue } from '~vue/test-utils'
import Component from '~resources/components/component.vue'
Expand Down Expand Up @@ -112,11 +111,6 @@ describeWithShallowAndMount('createLocalVue', mountingMethod => {
localVue.use(plugin, pluginOptions)
})

it('installs Vutify successfuly', () => {
const localVue = createLocalVue()
localVue.use(Vuetify)
})

it('installs plugin into local Vue regardless of previous install in Vue', () => {
let installCount = 0

Expand Down
17 changes: 17 additions & 0 deletions test/specs/external-libraries.spec.js
@@ -0,0 +1,17 @@
import { createLocalVue, mount } from '~vue/test-utils'
import VeeValidate from 'vee-validate'
import { describeWithShallowAndMount } from '~resources/utils'

describeWithShallowAndMount('external libraries', () => {
it.skip('works with vee validate', () => {
const TestComponent = {
template: '<div />'
}
const localVue = createLocalVue()
localVue.use(VeeValidate)
const wrapper = mount(TestComponent, {
localVue
})
wrapper.vm.errors.collect('text')
})
})
28 changes: 27 additions & 1 deletion test/specs/mount.spec.js
Expand Up @@ -6,7 +6,7 @@ import ComponentWithProps from '~resources/components/component-with-props.vue'
import ComponentWithMixin from '~resources/components/component-with-mixin.vue'
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
import { injectSupported, vueVersion } from '~resources/utils'
import { describeRunIf, itDoNotRunIf } from 'conditional-specs'
import { describeRunIf, itDoNotRunIf, itSkipIf } from 'conditional-specs'
import Vuex from 'vuex'

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

itSkipIf(
vueVersion < 2.3,
'handles extended components added to Vue constructor', () => {
const ChildComponent = Vue.extend({
template: '<div />',
mounted () {
this.$route.params
}
})
Vue.component('child-component', ChildComponent)
const TestComponent = {
template: '<child-component />'
}
let wrapper
try {
wrapper = mount(TestComponent, {
mocks: {
$route: {}
}
})
} catch (err) {} finally {
delete Vue.options.components['child-component']
expect(wrapper.find(ChildComponent).exists()).to.equal(true)
}
})

it('does not use cached component', () => {
ComponentWithMixin.methods.someMethod = sinon.stub()
mount(ComponentWithMixin)
Expand Down
16 changes: 16 additions & 0 deletions test/specs/wrapper/find.spec.js
Expand Up @@ -344,6 +344,22 @@ describeWithShallowAndMount('find', mountingMethod => {
expect(wrapper.find({ ref: 'foo' })).to.be.an('object')
})

itSkipIf(vueVersion < 2.3,
'returns functional extended component', () => {
const FunctionalExtendedComponent = Vue.extend({
functional: true,
render: h => h('div')
})
const TestComponent = {
template: '<div><functional-extended-component /></div>',
components: {
FunctionalExtendedComponent
}
}
const wrapper = mountingMethod(TestComponent)
expect(wrapper.find(FunctionalExtendedComponent).exists()).to.equal(true)
})

it('returns Wrapper of Vue Component matching the extended component', () => {
const BaseComponent = {
template: '<div><a-component /></div>',
Expand Down
15 changes: 10 additions & 5 deletions yarn.lock
Expand Up @@ -10788,6 +10788,11 @@ typescript@^2.6.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836"
integrity sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==

typescript@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb"
integrity sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==

uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
Expand Down Expand Up @@ -11139,6 +11144,11 @@ vary@^1.0.0:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=

vee-validate@2.1.0-beta.5:
version "2.1.0-beta.5"
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-2.1.0-beta.5.tgz#311c4629face2383964beb06768b379500f25a69"
integrity sha512-XN0FCHBEWKbxKprjzIwIr7ff5U+aFaJZlkoKr1ReuncMwHgG/6iW0hEI4bDuNXx1YKPmm4SyhoXkAOBHIUIwTA==

vendors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
Expand Down Expand Up @@ -11370,11 +11380,6 @@ vuepress@^0.14.2:
webpackbar "^2.6.1"
workbox-build "^3.1.0"

vuetify@^0.16.9:
version "0.16.9"
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-0.16.9.tgz#fd61f219e4a40d7afe5e24a803df5658a40b38e4"
integrity sha512-HVzdKnMETHINURJhGCMGT3wFw6R8eEHCVcqdC6o/z+eAWNQH7xg1k2hCsKo680lkjiL7tp3vRd9lF+3DpLm/+g==

vuex@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
Expand Down

0 comments on commit 0d1ddd1

Please sign in to comment.