Skip to content

Usage of VueProxy with functions expecting Vue type #125

Closed
@IlCallo

Description

@IlCallo

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 ac3581b, 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
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 = <V extends Vue>(
  component: VueClass<V>,
  options: {
    quasar?: Partial<QuasarPluginOptionsExt>;
    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<V extends Vue>(
  component: VueClass<V>,
  options: {
    quasar?: Partial<QuasarPluginOptionsExt>;
    ssr?: boolean;
    cookies?: any;
    plugins?: any;
  } = {},
) {
  // TODO: add prop typings based on component
  return (propsData?: any) => mountQuasar(component, { ...options, propsData });
}
Usage
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();
  });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions