Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript error when using data/props mounting option #194

Closed
lmiller1990 opened this issue Aug 31, 2020 · 37 comments
Closed

TypeScript error when using data/props mounting option #194

lmiller1990 opened this issue Aug 31, 2020 · 37 comments
Labels

Comments

@lmiller1990
Copy link
Member

lmiller1990 commented Aug 31, 2020

For those coming from Google/elsewhere: If you have some problems with typing, for example when writing tests with ts-jest and using SFC (.vue) files, the work-around is to as as any. Eg:

const wrapper = mount(Comp, {
  data() {
    return { foo: 'bar' }
  }
} as any)

Or try this shim:

declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent;
  export default component;
}

In the future we aim to have type inference with vue files too. For now, you may need to use as any to avoid compilation errors in your tests.


Original issue:

No idea what's going on.

Some days I wonder if TS is actually making the DX better or worse, lol.

import { mount } from '@vue/test-utils'

const Layout = {
  template: `
    <div>
      <header>
        <slot name="header" />
      </header>

      <main>
        <slot name="main" />
      </main>
      <footer>
        <slot name="footer" />
      </footer>
    </div>
  `
}

test('layout full page layout', () => {
  const wrapper = mount(Layout, {
    slots: {
      header: '<div>Header</div>',
      main: '<div>Main Content</div>' ,
      footer: {
        template: '<div>Footer</div>'
      }
    }
  })

  expect(wrapper.html()).toContain('Main Content')
})

image

@calebbergman
Copy link

calebbergman commented Sep 4, 2020

I'm running into an issue with a somewhat similar error message.

This works fine when the Component is declared within the spec file (I just discovered)

example.spec.ts

import { mount} from '@vue/test-utils'

const Layout = {
  template: `
  <div>
    <button v-if="loggedIn" data-test="logoutbtn">Logout</button>
  </div>
  `,
  data: () => ({
    loggedIn: false,
  })
}

describe('Layout.vue', () => {
  it('renders button when logged in', () => {
    const wrapper = mount(Layout, {
      data() {
        return {
          loggedIn: true
        }
      },
    });
    expect(wrapper.findAll('[data-test="logoutbtn"]').length === 1).toBe(true);
  });
});

But fails with a No overload matches this call error when trying to import the Component.

Layout.vue

<template>
  <div>
    <button v-if="loggedIn" data-test="logoutbtn">Logout</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Layout',
  data: () => ({
    loggedIn: false,
  }),
});
</script>

example.spec.ts

import { mount} from '@vue/test-utils'
import Layout from '@/components/Layout.vue';

describe('Layout.vue', () => {
  it('renders button when logged in', () => {
    const wrapper = mount(Layout, {
      data() {
        return {
          loggedIn: true
        }
      },
    });
    expect(wrapper.findAll('[data-test="logoutbtn"]').length === 1).toBe(true);
  });
});
Test suite failed to run

    TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
    tests/unit/example.spec.ts:18:27 - error TS2769: No overload matches this call.
      The last overload gave the following error.
        Argument of type 'ComponentPublicInstanceConstructor<({ $: ComponentInternalInstance; $data: {}; $props: ({ [x: number]: string; } & { length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; ... 19 more ...; flat?: unknown[] | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustom...' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<(readonly string[] & ThisType<void>) | (Readonly<ComponentObjectPropsOptions<Record<string, unknown>>> & ThisType<void>), ... 8 more ..., Readonly<...> | Readonly<...>>'.
          Type 'ComponentPublicInstanceConstructor<({ $: ComponentInternalInstance; $data: {}; $props: ({ [x: number]: string; } & { length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; ... 19 more ...; flat?: unknown[] | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustom...' is not assignable to type 'ComponentOptionsBase<Readonly<{} & { [x: number]: string | undefined; length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: 
string | undefined; ... 17 more ...; flat?: unknown[] | undefined; }> | Readonly<...>, ... 7 more ..., string>'.  
            Types of property 'setup' are incompatible.
              Type '((this: void, props: Readonly<{ [x: number]: string; } & { length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: string | undefined; ... 17 more ...; flat?: unknown[] | undefined; }> | Readonly<...>, ctx: SetupContext<...>) => unknown) ...' 
is not assignable to type '((this: void, props: Readonly<{} & { [x: number]: string | undefined; length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: string | undefined; ... 17 more ...; flat?: unknown[] | undefined; }> | Readonly<...>, ctx: SetupContext<...>) =...'.
                Type '(this: void, props: Readonly<{ [x: number]: string; } & { length?: number | undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: string | undefined; slice?: string[] | undefined; ... 16 more ...; flat?: unknown[] | undefined; }> | Readonly<...>, ctx: Se...' is not assignable to type '(this: void, props: Readonly<{} & { [x: number]: string | undefined; length?: number 
| undefined; toString?: string | undefined; toLocaleString?: string | undefined; concat?: string[] | undefined; join?: string | undefined; ... 17 more ...; flat?: unknown[] | undefined; }> | Readonly<...>, ctx: SetupContext<...>) =>...'.
                  Types of parameters 'ctx' and 'ctx' are incompatible.
                    Type 'SetupContext<string[]>' is not assignable to type 'SetupContext<EmitsOptions>'.
                      Type 'EmitsOptions' is not assignable to type 'string[]'.
                        Type 'Record<string, ((...args: any[]) => any) | null>' is missing the following properties from type 'string[]': length, pop, push, concat, and 28 more.

    18     const wrapper = mount(Layout, {
                                 ~~~~~~

      node_modules/@vue/test-utils/dist/mount.d.ts:34:25
        34 export declare function mount<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends Record<string, Function> = {}, E extends EmitsOptions = Record<string, any>, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, EE extends string = string>(componentOptions: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, E, Mixin, Extends, EE>, options?: MountingOptions<ExtractPropTypes<PropsOptions>, D>): VueWrapper<ComponentPublicInstance<ExtractPropTypes<PropsOptions>, RawBindings, D, C, M, E, VNodeProps & ExtractPropTypes<PropsOptions, false>>>;
                                   ~~~~~
        The last overload is declared here.

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        2.584s

@lmiller1990
Copy link
Member Author

lmiller1990 commented Sep 5, 2020

Can you share how you are typing .vue files? It's usually in a file like vue-shims.d.ts.

Also possible rc.10 changed something. I think the types might have got refactored. Man, TS is so good until you run into oddities like this. Are you on rc.10?

@lmiller1990
Copy link
Member Author

I also get a typing error for this:

const wrapper = mount(Foo, {
  global: {
    config: {
      errorHandler: () => {}
    }
  }
})

I don't really know how to fix these kind of TS errors yet.

@calebbergman
Copy link

The project was a fresh CLI project using vue@3.0.0-0. Upgrading to vue@3.0.0-rc.10 yields the same error, I'm afraid.

vue-shims.d.ts

declare module '*.vue' {
  import { defineComponent } from 'vue';

  const component: ReturnType<typeof defineComponent>;
  export default component;
}

@lmiller1990
Copy link
Member Author

lmiller1990 commented Sep 9, 2020

So basically the shim is the problem...

What happens when you do something like

declare module '*.vue' {
  const component: any
  export default component;
}

Does it at least let you proceed? I wonder if we can do so something like this in the the code-base here.

Not great but reduced type-checking is much better than ts errors out of the box. I don't know enough about typing Vue components to provide a quick fix, unfortunately, I think getting the types "right" will take some effort.

I know you guys are busy but @cexbrayat or @pikax have done the vast majority of work around types here, is there a quick fix we can do until we have "correct' types? We definitely need to make sure things are working out of the box (I think it's fine not to have perfect typing for props etc inside of mount for now, as long as the tests compile and run). Can we just type the first argument to mount as ReturnType<typeof defineComponent> for now, for example? Any downside to this?

@lmiller1990 lmiller1990 added bug Something isn't working help wanted Extra attention is needed and removed bug Something isn't working labels Sep 9, 2020
@pikax
Copy link
Member

pikax commented Sep 9, 2020

Problem is caused by:

  footer: {
        template: '<div>Footer</div>'
      }

which is not a valid Slot

type Slot = VNode | string | { render: Function } | Function 

adding the Component to it should be enough:

type Slot = VNode | string | { render: Function } | Function | Component

@calebbergman
Copy link

@lmiller1990

Modifying the shim file does resolve the original error, but I still run into an error if I try passing in a data property

example.spec.ts

import { mount } from '@vue/test-utils';
import Layout from '@/components/Layout.vue';

describe('Layout.vue', () => {
  it('renders button when logged in', () => {
    const wrapper = mount(Layout, {
      data() {
        return {
          loggedIn: true
        }
      },
    });
    expect(wrapper.findAll('[data-test="logoutbtn"]').length === 1).toBe(true);
  });
});
Test suite failed to run

TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
tests/unit/example.spec.ts:18:7 - error TS2769: No overload matches this call.
  The last overload gave the following error.
    Type '() => { loggedIn: boolean; }' is not assignable to type '() => never'.
      Type '{ loggedIn: boolean; }' is not assignable to type 'never'.

18       data() {
          ~~~~

  node_modules/@vue/test-utils/dist/mount.d.ts:34:25
    34 export declare function mount<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends Record<string, Function> = {}, E extends EmitsOptions = Record<string, any>, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, EE extends string = string>(componentOptions: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, E, Mixin, Extends, EE>, options?: MountingOptions<ExtractPropTypes<PropsOptions>, D>): VueWrapper<ComponentPublicInstance<ExtractPropTypes<PropsOptions>, RawBindings, D, C, M, E, VNodeProps & ExtractPropTypes<PropsOptions, false>>>;
                                ~~~~~
    The last overload is declared here.

@lmiller1990
Copy link
Member Author

I proposed a fix (well, a work around) in #200. This is really challenging because vue files using a shim do not have any defined types.

@lmiller1990 lmiller1990 changed the title Types are wrong when using slots mounting option Types are wrong when using data mounting option Sep 10, 2020
@lmiller1990
Copy link
Member Author

I fixed slots thanks to pikax, but data is still problematic. We will talk about it more in #200.

@lmiller1990
Copy link
Member Author

lmiller1990 commented Sep 13, 2020

I will focus on the types a bit more soon. I am migrating a lib from VTU v1 + JS -> VTU v2 + TS and it's an absolute nightmare. Note the error in mount(Foo)

Edit: it should be global.mocks but same problem happens.

image

@lmiller1990
Copy link
Member Author

@calebbergman

So actually fixing the types here seems complex - type-safety for vue files seems like it's a fair way off. Works great with components using render functions, though.

Anyway, here is what I would currently recommend:

    const wrapper = mount(Layout, {
      data() {
        return {
          loggedIn: true
        }
      },
    } as any);

Notice as any at the end. Yes this kind of sucks but I don't see a good way to otherwise solve this - I think some work is needed both in Vue core around the types and in VTU's mount types, it's pretty complicated as you can imagine.

I will update the docs - I hope this will let you continue testing your components 👍

@lmiller1990 lmiller1990 changed the title Types are wrong when using data mounting option TypeScript error when using data/props mounting option Sep 17, 2020
@calebbergman
Copy link

That seems to have done the trick; this work around will be fine for now. Thank you for banging your head against this; I wish you success in getting all the types to behave in the future 😉

@Mister-Hope
Copy link

Mister-Hope commented Sep 19, 2020

I have type issues too.

shims-vue.d.ts:

declare module "*.vue" {
  import { ComponentOptions } from "vue";
  const comp: ComponentOptions;
  export default comp;
}

Hello world:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script lang="ts">
export default {
  name: "HelloWorld",
  props: {
    msg: { type: String, default: "Welcome to Your Vue.js App" },
  },
};
</script>

Test files:

import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

describe("HelloWorld.vue", () => {
  it("renders props.msg when passed", () => {
    const msg = "new message";
    const wrapper = shallowMount(HelloWorld, {
      props: { msg },
    });
    expect(wrapper.text()).toMatch(msg);
  });
});

Error:

 FAIL  tests/unit/example.spec.ts
  ● Test suite failed to run
    TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
    tests/unit/example.spec.ts:7:34 - error TS2769: No overload matches this call.
      The last overload gave the following error.
        Argument of type 'ComponentOptions<{}, any, any, any, any, any, any, any>' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<readonly string[] | Readonly<ComponentObjectPropsOptions<Record<string, unknown>>>, any, any, any, any, any, any, any, string, Readonly<...> | Readonly<...>, { ...; } | {}>'.
          Property 'props' is missing in type 'ComponentOptionsBase<{}, any, any, any, any, any, any, any, string, {}> & ThisType<any>' but required in type '{ props: (readonly string[] & ThisType<void>) | (Readonly<ComponentObjectPropsOptions<Record<string, unknown>>> & ThisType<void>); }'.

    7     const wrapper = shallowMount(HelloWorld, {
                                       ~~~~~~~~~~

      node_modules/@vue/runtime-core/dist/runtime-core.d.ts:335:5
        335     props: PropsOptions & ThisType<void>;
                ~~~~~
        'props' is declared here.
      node_modules/@vue/test-utils/dist/mount.d.ts:36:25
        36 export declare function mount<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends Record<string, Function> = {}, E extends EmitsOptions = Record<string, any>, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, EE extends string = string>(componentOptions: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, 
C, M, E, Mixin, Extends, EE>, options?: MountingOptions<ExtractPropTypes<PropsOptions>, D>): VueWrapper<ComponentPublicInstance<ExtractPropTypes<PropsOptions>, RawBindings, D, C, M, E, VNodeProps & ExtractPropTypes<PropsOptions>>>;
                                   ~~~~~
        The last overload is declared here.

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        4.168s
Ran all test suites.

Addtional Info:

Works fine with:

import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

describe("HelloWorld.vue", () => {
  it("renders props.msg when passed", () => {
    const msg = "new message";
    const wrapper = shallowMount(HelloWorld as any, {
      props: { msg },
    });
    expect(wrapper.text()).toMatch(msg);
  });
});

When casting HelloWorld to any

@cexbrayat
Copy link
Member

@Mister-Hope Can you try to use the following shim:

declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent;
  export default component;
}

You have a fairly basic use-case, that we cover in the tests of VTU, so it should be working 🤞

heathharrelson added a commit to heathharrelson/suspenders that referenced this issue Sep 25, 2020
Update TypeScript shim for SFC files according to this GitHub comment:

vuejs/test-utils#194 (comment)

Fixes type errors in tests after upgrading to Vue 3.0.0.
@JrWoland
Copy link

@Mister-Hope Can you try to use the following shim:

'''ts
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent;
export default component;
}
'''

You have a fairly basic use-case, that we cover in the tests of VTU, so it should be working 🤞

Hi. This aolution is not working for me :( I have the same problem
image

@lmiller1990
Copy link
Member Author

Are you on the latest version of Vue 3 (and Test Utils)?

Alternative work-around (using any for the mount).

@JrWoland
Copy link

I've updated npm packages by npm update command and these two have changed:
image
And now it works 👍 :)

@hokauz
Copy link

hokauz commented Dec 22, 2020

@lmiller1990 try to set as MountingOptions.

shallowMount(RecoverPage, {
  global: {
    mocks: {
      $route,
      $router
    }
  }
} as MountingOptions<{}>

Work for me. No need any other changes.

@lmiller1990
Copy link
Member Author

Sure, ideally it would be great if we do not need to do as, but this is a good work around. Another alternative is as any.

@HomyeeKing
Copy link

I don't see any error in my project, could you provide a complete problem code 👀

@lmiller1990
Copy link
Member Author

@HomyeeKing try some of the examples above. It is possible this has been fixed, a lot of work was done around types in core.

OKAUEND added a commit to OKAUEND/ffxiv_craft_tools that referenced this issue Jan 5, 2021
@AndrewBogdanovTSS
Copy link

Both as any and as MountingOptions<{}> works for me. Thanks. But it will be great to see proper fix some day. Is it an issue of vue-test-utils?

@lmiller1990
Copy link
Member Author

  • if you are importing a vue file, we cannot do much for type safety.
  • if you have a bug where mount gives you type warnings for a defineComponent imported from a TS file, it's probably a test utils problem. You could open an issue or share your reproduction here.

@HomyeeKing
Copy link

maybe the vite startup page give you some inspiration:
Provide types for *.vue imports
tsconfig setup:

1. Install and add
@vuedx/typescript-plugin-vue to tsconfig plugins

2. Delete shims-vue.d.ts

3. Open
src/main.ts in VSCode

4. Open VSCode command input

5. Search and run "Select TypeScript version" -> "Use workspace version"

@Chris-Kin
Copy link

updating @vue/test-utils to 2.0.0-rc.6 fixs this problem for me.

@lmiller1990
Copy link
Member Author

Seems this is (mostly) solved. For specific reproductions, please open a new issue and we can fix them on a case by case basis.

@vincerubinetti
Copy link

vincerubinetti commented Sep 23, 2021

Note for people stumbling on this issue from google, improperly formatted mount options can cause issues that look like this (makes you think that the problem is with the component being mounted), and won't be fixed by the shim solution above.

In my case, I was accidentally passing an array of Vue components to components like:

mount(Component, { global: { components: [Button, etc] } })

instead of the object it expects like

mount(Component, { global: { components: { Button, etc } } })

@dalmia
Copy link

dalmia commented Mar 16, 2022

In my case, similar to what @vincerubinetti said, I had assumed initially that the issue was with how it was being mounted.

it("renders title correctly", () => {
    const buttonText = "Test Button";
    const wrapper = mount(IconButton, {
      props: {
        titleConfig: {
          value: buttonText,
        },
      },
    });
    expect(wrapper.text()).toMatch(buttonText);
  });

Turns out, the type for my prop titleConfig was set to an interface that required two keys to be passed and I was passing only one of them (value). Setting the other key as optional in the interface definition removed this error for me.

@michealroberts
Copy link

So if anyone is still having an issue with this, you need to correctly match your types that you're using for your component's PropType with what you're passing in on the test mount.

They need to explicitly match for the typescript engine to confirm parity.

@MrCoder
Copy link

MrCoder commented Sep 12, 2022

@Mister-Hope Can you try to use the following shim:

declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent;
  export default component;
}

You have a fairly basic use-case, that we cover in the tests of VTU, so it should be working 🤞

Where should I put this in?

@lmiller1990
Copy link
Member Author

We just put it here https://github.com/vuejs/test-utils/blob/main/src/vueShims.d.ts.

I think any .d.ts file is okay, for some reason VS Code doesn't always find it, you might need to add it to types in tsconfig.json.

@pwang2
Copy link

pwang2 commented Jan 14, 2023

For some reason, my vs code doesn't find the shim at all. :(

I still suffered from the pain and started to doubt the meaning of the efforts here. To the community, typing information registry is always a dump yard. You code typing could be broken even by just installing a node module (doubt it? try to install storybook to any boilerplate project). In a mono repo with multiple tsconfig.json, getting the type load correctly is even a life mercy.

@lmiller1990
Copy link
Member Author

What if you open the file with the shim? I've noticed sometimes VS Code does not pick it up.

Typing Vue imports is complex, it's unfortunate but it's the reality of the situation. See the type... https://github.com/vuejs/test-utils/blob/main/src/mount.ts#L89-L306

Since using the Volar extension, it's working well for me in all my projects (including some large mono repos with multiple tsconfig.json. It was a bit of messing around, iirc).

If you can reproduce your problem in a minimal project, we can try to fix it. It's also possible that a more recent version of Vue came out and broke something, in which case we need to fix that, too.

@pwang2
Copy link

pwang2 commented Jan 17, 2023

@lmiller1990, Thanks a million for pointing to create a minimal reproducible project.

It turns out it is caused by a complex type in prop type. in Test file, the mount does not agree with the shape literal type. using as TargetType in the props value solves it. The complex type is from highcharts and it is ridiculous complex from nested A extends B syntax. Would be better to define our own type instead of using the provided ones.

@mplayer78
Copy link

Juuust in case anyone hits this again, my problem wasn't with my Shims or anything else, instead it was where I'd imported a JSON file as a prop but I needed to cast it, like const props = { question: (testPollQuestion as IPollQuestion)} wrapper = mount(Poll, { props })

@floroz
Copy link

floroz commented May 3, 2023

So if anyone is still having an issue with this, you need to correctly match your types that you're using for your component's PropType with what you're passing in on the test mount.

They need to explicitly match for the typescript engine to confirm parity.

I want to confirm this comment.

We initially added the shim #194 (comment) to suppress the errors, but ultimately it was an genuine error.

The problem for us was Volar not picking up the TS error, but there was an inconsistency in the shape of the TS types expected from the mount

@Mykola-Veryha
Copy link

Another way to solve the issue is to use InstanceType.

function getWrapper(): VueWrapper<InstanceType<typeof PatientInfo>> {
  return mount(PatientInfo);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests