vuejs / composition-api

Composition API plugin for Vue 2
https://composition-api.vuejs.org/
MIT License
4.19k stars 342 forks source link

Usage of VueProxy with functions expecting Vue type #125

Closed IlCallo closed 5 years ago

IlCallo commented 5 years ago

Hi, thank you for the great work!

I have some functions (wrappers for Jest mount() helper) which expect a Vue parameter to provide autocomplete for the vm.

Until https://github.com/vuejs/composition-api/commit/ac3581b2eb11f285584fc4ee1279cb2981e7be75, autocomplete didn't worked but at least it didn't fire off any error because the component inerithed from VueConstructor<never> and was recognized as a Vue instance.

Now I get an error because a lot of Vue properties are missing from VueProxy (rightfully).

I tryed to find a way to make the function work both with Vue and VueProxy (because as a matter of fact I don't really need all the stuff on Vue type, VueProxy should be enough), but it seems there is no VueClass equivalent which manages VueProxy.

Also, VueProxy itself is not exported and, even if it was, I would have to manually specify PropsOptions and RawBindings instead of having them inferred from the component.

Any hint on how to proceed? For the time being I just reverted VueConstructorProxy typings in my local setup, but I'd like to solve the problem and make everything work properly.

This issue is related to my previous comment here and have the same root cause. Official mount() and shallowMount() testing helpers don't have autocomplete because of the VueProxy usage.

Maybe an helper can be provided which converts back from VueProxy to a Vue instance?

Bonus: if there is some way to extract props typings from VueProxy, I'm interested in that as well.

My factory generator functions ```ts import VueCompositionApi from '@vue/composition-api'; import { createLocalVue, shallowMount, VueClass } from '@vue/test-utils'; import { Cookies, Quasar, QuasarPluginOptionsExt } from 'quasar'; import Vue from 'vue'; const mockSsrContext = () => { return { req: { headers: {}, }, res: { setHeader: () => undefined, }, }; }; export const mountQuasar = ( component: VueClass, options: { quasar?: Partial; ssr?: boolean; cookies?: any; plugins?: any; propsData?: any; } = {}, ) => { const localVue = createLocalVue(); const app = {}; localVue.use(Quasar, options.quasar); localVue.use(VueCompositionApi); if (options) { const ssrContext = options.ssr ? mockSsrContext() : null; if (options.cookies) { const cookieStorage = ssrContext ? Cookies.parseSSR(ssrContext) : Cookies; const cookies = options.cookies; Object.keys(cookies).forEach(key => { cookieStorage.set(key, cookies[key]); }); } if (options.plugins) { options.plugins.forEach((plugin: any) => { plugin({ app, Vue: localVue, ssrContext, }); }); } } // mock vue-i18n const $t = () => {}; const $tc = () => {}; const $n = () => {}; const $d = () => {}; // Works both with Vue file and TS file because of some underlying black magic // See https://github.com/vuejs/vue-jest/issues/188 return shallowMount(component, { propsData: options.propsData, localVue, mocks: { $t, $tc, $n, $d }, // Injections for Components with a QPage root Element provide: { pageContainer: true, layout: { header: {}, right: {}, footer: {}, left: {}, }, }, }); }; export function mountFactory( component: VueClass, options: { quasar?: Partial; ssr?: boolean; cookies?: any; plugins?: any; } = {}, ) { // TODO: add prop typings based on component return (propsData?: any) => mountQuasar(component, { ...options, propsData }); } ```
Usage ```ts import { QBtn, QIcon, QItem, QItemSection, QList } from 'quasar'; import { mountFactory } from 'test/jest/utils'; import MyComponent from './my-component'; const factory = mountFactory(MyComponent, { // Throws error because MyComponent is not of type Vue quasar: { components: { QBtn, QItemSection, QItem, QIcon, QList, }, }, }); describe('MyComponent', () => { it('is a Vue instance', () => { const wrapper = factory({ propName: 'propValue' }); console.log(wrapper.vm.propName); // Even with previous typings, autocomplete for this wouldn't have worked expect(wrapper.isVueInstance()).toBeTruthy(); }); }); ```
liximomo commented 5 years ago

Fixed in 0.3.2

IlCallo commented 5 years ago

@liximomo I upgraded to 0.3.2. Hard compilations errors are solved now, tweaking a bit my factories generation code by using ComponentOptions<V> instead of VueClass<V>.

Yet, mounting a component like shallowMount(MyComponent) returns a Wrapper<Vue> instead of Wrapper<MyComponent>, which breaks autocomplete capabilities.

I attach my updated code for reference, but the problem is at shallowMount level, independent from my code scope.

My factory generator functions ```ts import VueCompositionApi from '@vue/composition-api'; import { createLocalVue, shallowMount, VueClass } from '@vue/test-utils'; import { Cookies, Quasar, QuasarPluginOptionsExt } from 'quasar'; import Vue from 'vue'; const mockSsrContext = () => { return { req: { headers: {}, }, res: { setHeader: () => undefined, }, }; }; // We cannot infer component type from `shallowMount` using `Parameters` // because it has overloads but the last signature isn't the most general one, while `Parameters<...>` // will automatically resolve to the last signature thinking it's the most generic one. // See https://github.com/Microsoft/TypeScript/issues/24275#issuecomment-390701982 export const mountQuasar = ( component: ComponentOptions, options: { quasar?: Partial; ssr?: boolean; cookies?: any; plugins?: any; propsData?: any; } = {}, ) => { const localVue = createLocalVue(); const app = {}; localVue.use(Quasar, options.quasar); localVue.use(VueCompositionApi); if (options) { const ssrContext = options.ssr ? mockSsrContext() : null; if (options.cookies) { const cookieStorage = ssrContext ? Cookies.parseSSR(ssrContext) : Cookies; const cookies = options.cookies; Object.keys(cookies).forEach(key => { cookieStorage.set(key, cookies[key]); }); } if (options.plugins) { options.plugins.forEach((plugin: any) => { plugin({ app, Vue: localVue, ssrContext, }); }); } } // mock vue-i18n const $t = () => {}; const $tc = () => {}; const $n = () => {}; const $d = () => {}; // Works both with Vue file and TS file because of some underlying black magic // See https://github.com/vuejs/vue-jest/issues/188 return shallowMount(component, { propsData: options.propsData, localVue, mocks: { $t, $tc, $n, $d }, // Injections for Components with a QPage root Element provide: { pageContainer: true, layout: { header: {}, right: {}, footer: {}, left: {}, }, }, }); }; export function mountFactory( ...[component, options]: Parameters ) { // TODO: add prop typings based on component return (propsData?: any) => mountQuasar(component, { ...options, propsData }); } ```
Usage ```ts import { QBtn, QIcon, QItem, QItemSection, QList } from 'quasar'; import { mountFactory } from 'test/jest/utils'; import MyComponent from './my-component'; const factory = mountFactory(MyComponent, { // Doesn't throw error because it recognize MyComponent as a ComponentOptions instance quasar: { components: { QBtn, QItemSection, QItem, QIcon, QList, }, }, }); describe('MyComponent', () => { it('is a Vue instance', () => { const wrapper = factory({ propName: 'propValue' }); console.log(wrapper.vm.propName); // Component typings still doesn't work expect(wrapper.isVueInstance()).toBeTruthy(); }); }); ```
IlCallo commented 4 years ago

@pikax hey there, I came back to this issue after some months as I'm trying to finish up the work for Quasar Framework jest App Extension, but the same problem as my last post persists: vue-test-utils mount functions aren't able to infer the underlying Vue instance because VueProxy doesn't expose it.

Is there a possibility to get this working or shall I just accept there is no way to fix this and hope in Vue 3 better TS support?

pikax commented 4 years ago

Hey @IlCallo,

Can you open a new issue with a reproduction code, a lot of things changed since 0.3.2, I'm currently doing some v3 stuff so I will most likely mix information if I have a proper look now.

Create a new issue to keep track otherwise this will get lost.

There's plans to improve typings on 2.7.x https://github.com/vuejs/vue/pull/11488 but is still on hold, because if that goes forward it will add a lot of breaking changes.

IlCallo commented 4 years ago

Checked again, resolved by changing my function types overloads order (VueClass version must be before ComponentOptions one). Works as expected, sorry for bothering