paroi-tech / direct-vuex

Use and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.
Creative Commons Zero v1.0 Universal
258 stars 14 forks source link

Q: How to setup @vue/test-utils? #25

Closed viT-1 closed 4 years ago

viT-1 commented 4 years ago

I changed my code to use direct-vuex in actions, browser seems to work without errors, but when I run jest tests, test suite failed to run, cause of typing when I mount component with store

src/unique.blocks/SomeForm/SomeForm.spec.ts:30:19 - error TS2769: No overload matches this call.
      Overload 1 of 3, '(component: VueClass<SomeForm>, options?: ThisTypedMountOptions<SomeForm> | undefined): Wrapper<SomeForm>', gave the following error.
        Argument of type '{ store: { readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null;
readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }; localVue: VueConstructor<...>; }' is not assignable to parameter of type 'ThisTypedMountOptions<SomeForm>'.
          Type '{ store: { readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null; readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }; localVue: VueConstructor<...>; }' is not assignable to type 'MountOptions<SomeForm>'.
            Types of property 'store' are incompatible.
              Type '{ readonly state: { modSomeForm: { iamSelect: { value: IOption | null; data: IData; }; }; }; getters: { modSomeForm: { readonly someValue: IOption | null; readonly someValues: IOption[]; }; }; commit: { ...; }; dispatch: { ...; }; original: { ...; }; }' is missing the following properties from type 'Store<any>': replaceState, subscribe, subscribeAction, watch, and 3 more.
      Overload 2 of 3, '(component: ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, options?: ThisTypedMountOptions<...> | undefined): Wrapper<...>', gave the following error.
        Value of type 'typeof SomeForm' has no properties in common with type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'. Did you mean to call it?
      Overload 3 of 3, '(component: FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>, options?: MountOptions<Vue> | undefined): Wrapper<...>', gave the following error.
        Argument of type 'typeof SomeForm' is not assignable to parameter of type 'FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>'.
          Property 'functional' is missing in type 'typeof SomeForm' but required in type 'FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>'.

    30          const wrapper = mount(SomeForm, { store, localVue });
                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

      src/unique.blocks/SomeForm/SomeForm.spec.ts:30:25
        30      const wrapper = mount(SomeForm, { store, localVue });
                                      ~~~~~~~~
        Did you mean to use 'new' with this expression?
      node_modules/vue/types/options.d.ts:132:3
        132   functional: boolean;
              ~~~~~~~~~~
        'functional' is declared here.

What am I doing wrong?

viT-1 commented 4 years ago

Cutted jest code:

import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createDirectStore } from 'direct-vuex';

import { modSomeForm } from './store';
import { SomeForm } from './index'; // with vue-class-component decorated

const localVue = createLocalVue();
localVue.use(Vuex);

const storeConf = {
    modules: {
        modSomeForm,
    },
};

const { store } = createDirectStore(storeConf);

describe('@Component SomeForm', () => {
    it('all mock data are rendered', () => {
        expect.assertions(1);

        const wrapper = mount(SomeForm, { store, localVue });
        wrapper.vm.$nextTick();

        expect(...).toBe(true);
    });
});
viT-1 commented 4 years ago

Just updated config to see problem directly: npm run test

paleo commented 4 years ago

Maybe:

        const wrapper = mount(SomeForm, { store: store.original, localVue });
viT-1 commented 4 years ago

This fix is helped, now I got another error (test suite is still failed to run):

TypeError: Cannot read property 'getters' of undefined
...
at forEachValue (../node_modules/vuex/dist/vuex.common.js:80:20)
      at ModuleCollection.register (../node_modules/vuex/dist/vuex.common.js:210:5)
      at new ModuleCollection (../node_modules/vuex/dist/vuex.common.js:171:8)
      at new Store (../node_modules/vuex/dist/vuex.common.js:322:19)
      at createDirectStore (../node_modules/direct-vuex/dist/direct-vuex.umd.js:17:24)
      at Object.<anonymous> (VueApp/store/VueApp.store.ts:19:47)

Strange, but in VueApp.store.ts (which is seems not used in SomeForm.spec.ts) export const { store, moduleActionContext } = createDirectStore(storeConf);

May be it is because of using standard Vuex getters, not createGetters.

Similar issue: https://github.com/championswimmer/vuex-module-decorators/issues/63

viT-1 commented 4 years ago

Also another test suite (SomeForm/store/actions.spec.ts) failed to run:

src/unique.blocks/SomeForm/store/actions.spec.ts:13:31 - error TS2345: Argument of type '{ dispatch: jest.Mock<any, any>; }' is not assignable to parameter of type 'ActionContext<IState, IState>'.
      Type '{ dispatch: Mock<any, any>; }' is missing the following properties from type 'ActionContext<IState, IState>': commit, state, getters, rootState, rootGetters

    13          await actions.getSomeValues({ dispatch }, { label: 'Рим' });

It seems context parameter in test (or actions) code should be changed but I don't understand how? On classic Vuex In actions I am using destructuring input parameter, but now I am using ActionContext from 'vuex'.

viT-1 commented 4 years ago

Can't find any example of using direct-vuex actions with tests =(

viT-1 commented 4 years ago

About getters same error. Fixed this in jest.config.js, for example:

moduleNameMapper: {
    // special fix (not included in tsconfig.paths.json) for Vue warning about version of Vue
    'vue$': 'vue/dist/vue.common.dev.js',
    // fix for 'getters' error https://github.com/vuejs/vuex/issues/264#issuecomment-23694847
    'vue': 'vue/dist/vue.js',
},

After fix I have another errors, investigating...

viT-1 commented 4 years ago

https://github.com/vuejs/vue-test-utils/issues/363

viT-1 commented 4 years ago

I have located problem with direct-vuex: Configured jest test with createDirectStore:

const storeConf = {
    modules: {
        modSomeForm,
    },
};
const { store: _store } = createDirectStore(storeConf);
const store = _store.original;
...
const wrapper = mount(SomeForm, { store, localVue });

If I use classic actions, all is ok: SomeForm.spec.ts suite is runned & actions.spec.ts is runned. If I change actions.ts to using direct-vuex (just uncomment lines 3, 6, 12, 22, 23 & comment 24, 25) I have a problem with getters error (SomeForm.spec.ts suite is failed to run), but actions.spec.ts is ok - test suite is runned & passed.

@paleo please check this by cloning project & npm run test

paleo commented 4 years ago

When I execute npm run test:

> tsc --project ./src/tsconfig.dev.json --noEmit && eslint **/*.{ts,json,js} *.* .*.js

Oops! Something went wrong! :(

ESLint: 6.8.0.

No files matching the pattern "express.node" were found.
Please check for typing mistakes in the pattern.

npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! systemjs-ts-es6-vue@0.6.0 lint: `tsc --project ./src/tsconfig.dev.json --noEmit && eslint **/*.{ts,json,js} *.* .*.js`
npm ERR! Exit status 2

Is it the error I should see or another? (On Node 12.)

viT-1 commented 4 years ago

Strange, you can remove lint from pretest by renaming in package.json scripts "pretest" to "pre-something-test".

Is it the error I should see or another?

No, you should see

jest --no-cache --silent -c ./src/jest.config.js
PASS src/unique.blocks/SomeForm/store/mutations.spec.ts
...
PASS  src/unique.blocks/SomeForm/store/actions.spec.ts
...
PASS  src/unique.blocks/SomeForm/SomeForm.spec.ts

and after commening / commenting out in actions, you should see

jest --no-cache --silent -c ./src/jest.config.js
PASS src/unique.blocks/SomeForm/store/mutations.spec.ts
...
PASS  src/unique.blocks/SomeForm/store/actions.spec.ts
...
FAIL  src/unique.blocks/SomeForm/SomeForm.spec.ts
viT-1 commented 4 years ago

@paleo, 'getters' problem looks like code trying to get getters from root store instead of module substore.

viT-1 commented 4 years ago

I see only one workaround: using mocks with classic Vuex for actions in test suites & direct-vuex actions for browser & type checking.

paleo commented 4 years ago

Fyi, in this code:

export const storeConf = {
    namespaced: true as true,
    actions: createActions(actions),
    // getters: createGetters<IState>()(getters),
    getters,
    mutations: createMutations<IState>()(mutations),
    state: defaultState,
    // syntax recommendation https://itnext.io/use-a-vuex-store-with-typing-in-typescript-without-decorators-or-boilerplate-57732d175ff3
    // but we declare type with const above
    // state: defaultState as IState,
} as const;

export const modSomeForm = createModule(storeConf);

createModule, createActions, createMutations are useless.

These functions provides autocompletion and typing in the implementation passed as a parameter. But if you declare first a variable then you pass it in these functions, they provide nothing.

Additionally, storeConf and modSomeForm are exactly the same object with the same typing.

paleo commented 4 years ago

If I use classic actions, all is ok: SomeForm.spec.ts suite is runned & actions.spec.ts is runned. If I change actions.ts to using direct-vuex (just uncomment lines 3, 6, 12, 22, 23 & comment 24, 25) I have a problem with getters error (SomeForm.spec.ts suite is failed to run), but actions.spec.ts is ok - test suite is runned & passed.

I reproduced the error. But your code is complex and I haven't enough time to understand. Maybe the error is an effect of the cyclic dependency because of moduleActionContext. In your unit tests it is necessary to imports the files in the same order than during a regular execution of the project.

viT-1 commented 4 years ago

I have some fixes (not committed yet, it will be soon).

viT-1 commented 4 years ago

It is ready https://github.com/viT-1/systemjs-ts-es6-vue/commit/7ce884aa3bf4230e935c5dfdbfe778e302f09f2a For getting error about 'getters' now comment line 14 with mocking actions is enough.

paleo commented 4 years ago

Hi. Maybe localGetterContext and localActionContext could help. If you don't need to access to rootXxx from action contexts, then localActionContext should help avoiding the circular dependency.

viT-1 commented 4 years ago

@paleo Thank you, I'll try that. And If I can test actions without mocking, I'll closing the issue.

viT-1 commented 4 years ago

@paleo Yes, it's working.

In actions.ts just changed to

import { localActionContext } from 'direct-vuex';
const getTypedContext = (ctx: any) => localActionContext(ctx, modSomeForm);

instead of

import { moduleActionContext } from '~/VueApp/store';
const getTypedContext = (ctx: any) => moduleActionContext(ctx, modSomeForm);

And I can remove standard actions mocking!

viT-1 commented 4 years ago

related: https://github.com/paleo/direct-vuex/issues/29#issuecomment-586256376

viT-1 commented 4 years ago

With node (jest) const getTypedContext = (ctx: any) => localActionContext(ctx, modSomeForm); is ok, but with browser I have error:

Uncaught (in promise) TypeError: localActionContext is not a function
    at getTypedContext (actions.ts:16)
    at actions.ts:27
paleo commented 4 years ago

Indeed, I forgot to export these new functions as properties of default. Update and retry please.

viT-1 commented 4 years ago

v 0.10.4 esm bundle is working, but ie11 (with SystemJS) is not: Unhandled promise rejection TypeError: Object is not support property or method "localActionContext"

You can try my project on last commit npm run deploy && npm run www

viT-1 commented 4 years ago

@paleo, default is good but i configured named imports too (in VueApp.store.ts)

paleo commented 4 years ago

Just update in importmap.system.json. The tools you use are very weird.

viT-1 commented 4 years ago

@paleo Sorry for wasting your time, thank you very much! Just changed importmap.system.json in my project & all working is ok.