diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 1981dbe5c..918c2a85a 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -1,5 +1,6 @@ // @flow +import Vue from 'vue' import { createSlotVNodes } from './create-slot-vnodes' import addMocks from './add-mocks' import { addEventLogger } from './log-events' @@ -109,10 +110,35 @@ export default function createInstance ( component.options._base = _Vue } + function getRootVueProto (obj) { + while (obj) { + if (Object.getPrototypeOf(obj) === Vue.prototype) { + return obj + } + + obj = Object.getPrototypeOf(obj) + } + } + + function getExtendedComponent (component, instanceOptions) { + // extend _Vue to merge the mixins on _Vue + const extendedComponent = component.extend(_Vue).extend(instanceOptions) + + // cache subclass constructor + component.options._Ctor[extendedComponent.cid] = extendedComponent + + // to keep the possible overridden prototype and _Vue mocks on prototype, + // we need change the proto chains manually + // @see https://github.com/vuejs/vue-test-utils/pull/856 + const root = getRootVueProto(extendedComponent.prototype) + Object.setPrototypeOf(root, _Vue.prototype) + + return extendedComponent + } + // extend component from _Vue to add properties and mixins - // extend does not work correctly for sub class components in Vue < 2.2 - const Constructor = typeof component === 'function' && vueVersion < 2.3 - ? component.extend(instanceOptions) + const Constructor = typeof component === 'function' + ? getExtendedComponent(component, instanceOptions) : _Vue.extend(component).extend(instanceOptions) // Keep reference to component mount was called with diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js index e5ab2c6cd..20127b822 100644 --- a/test/specs/mount.spec.js +++ b/test/specs/mount.spec.js @@ -156,27 +156,29 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => { expect(stub).not.called }) - it.skip('overrides component prototype', () => { - const mountSpy = sinon.spy() - const destroySpy = sinon.spy() - const Component = Vue.extend({}) - const { $mount: originalMount, $destroy: originalDestroy } = Component.prototype - Component.prototype.$mount = function (...args) { - originalMount.apply(this, args) - mountSpy() - return this - } - Component.prototype.$destroy = function () { - originalDestroy.apply(this) - destroySpy() - } + itDoNotRunIf( + vueVersion < 2.3, + 'overrides component prototype', () => { + const mountSpy = sinon.spy() + const destroySpy = sinon.spy() + const Component = Vue.extend({}) + const { $mount: originalMount, $destroy: originalDestroy } = Component.prototype + Component.prototype.$mount = function (...args) { + originalMount.apply(this, args) + mountSpy() + return this + } + Component.prototype.$destroy = function () { + originalDestroy.apply(this) + destroySpy() + } - const wrapper = mount(Component) - expect(mountSpy).called - expect(destroySpy).not.called - wrapper.destroy() - expect(destroySpy).called - }) + const wrapper = mount(Component) + expect(mountSpy).called + expect(destroySpy).not.called + wrapper.destroy() + expect(destroySpy).called + }) // Problems accessing options of twice extended components in Vue < 2.3 itDoNotRunIf(vueVersion < 2.3, 'compiles extended components', () => {