typescriptvue.jsvuejs3cypress

TypeScript: Testing Vue3 component containing a slot with Cypress


I'm having troubles trying to setup a new project. I'm using Vue3, TypeScript, Cypress. And the issue is probably somewhere in the TypeScript configuration.

Below is a MWE. I would appreciate a bit of help.


I have this Vue3 component in src/component/MinimalWorkingExample.vue:

<script setup lang="ts">
interface Props {
  headerText: string;
}

const props = defineProps<Props>();
</script>

<template>
  <div>
    <slot></slot>
  </div>
</template>

<style lang="scss" scoped></style>

And I have a Cypress test for that component in src/component/MinimalWorkingExample.cy.ts:

import MinimalWorkingExample from "./MinimalWorkingExample.vue";

describe("<MinimalWorkingExample />", () => {
  it("renders", () => {
    cy.mount(MinimalWorkingExample, {
      props: { headerText: "Testing" },
      slots: { default: "Testing text ..." },
    });
  });
});

My IDE is red-underlining MinimalWorkingExample in cy.mount(MinimalWorkingExample, ... saying No overload matches this call. When I call vue-tsc --noEmit, it complains about the same with more details:

src/component/MinimalWorkingExample.cy.ts:5:14 - error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '__VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<Props>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 5 more ..., {}>, { ...; }>' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<Readonly<ComponentPropsOptions>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOp
tionsMixin, string[], string>'.
      Type '__VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<Props>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 5 more ..., {}>, { ...; }>' is not assignable to type 'ComponentOptionsBase<{ [x: number]: unknown; [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; readonly length?: numb
er | Prop<unknown, unknown> | null | undefined; readonly concat?: Prop<unknown, unknown> | { (...items: ConcatArray<...>[]): string[]; (...items: (string | ConcatArray<...>)[]): s...'.
        Types of property 'setup' are incompatible.
          Type '((this: void, props: LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<Props>>> & {}>, ctx: { ...; }) => void | ... 2 more ... | Promise<...>) | undefined' is not assignable to type '((this: void, props: LooseRequired<{ [x: number]: unknown; [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; reado
nly length?: number | Prop<unknown, unknown> | null | undefined; readonly concat?: Prop<unknown, unknown> | { ...; } | null | undefined; ... 27 more ...; readonly toLocaleString: (()...'.
            Type '(this: void, props: LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<Props>>> & {}>, ctx: { ...; }) => void | ... 2 more ... | Promise<...>' is not assignable to type '(this: void, props: LooseRequired<{ [x: number]: unknown; [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; readonly length?: 
number | Prop<unknown, unknown> | null | undefined; readonly concat?: Prop<unknown, unknown> | { ...; } | null | undefined; ... 27 more ...; readonly toLocaleString: (() ...'.
              Types of parameters 'props' and 'props' are incompatible.
                Property 'headerText' is missing in type 'LooseRequired<{ [x: number]: unknown; [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; readonly length?: number | Prop<unknown, unknown> | null | undefined; readonly concat?: Prop<unknown, unknown> | { ...; } | null | undefined; ... 27 more ...; readonly toLocaleString: (()
 => string) & string;...' but required in type 'LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<Props>>> & {}>'.

5     cy.mount(MinimalWorkingExample, {
               ~~~~~~~~~~~~~~~~~~~~~

  src/component/MinimalWorkingExample.vue:3:3
    3   headerText: string;
        ~~~~~~~~~~
    'headerText' is declared here.
  node_modules/cypress/vue/dist/index.d.ts:1377:18
    1377 declare function mount<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D extends {}, 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> & PublicProps, D>): Cypress.Chainable<{
                          ~~~~~
    The last overload is declared here.


Found 1 error in src/component/MinimalWorkingExample.cy.ts:5

And now the weird stuff:

When I comment-out the line with <slot></slot>, my IDE stops undrlining the MinimalWorkingExample and vue-tsc --noEmit finishes successfully with no output.

It is also successful, when I keep the <slot></slot> and get rid of the headerText: string; property.

And also using as any in cy.mount(MinimalWorkingExample as any, ... fixes the issue. But I want to have a nice TypeScript.


More details:

Here is my tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": [
      "esnext",
      "dom",
      "dom.iterable"
    ],
    "allowJs": false,
    "jsx": "preserve",
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "noEmit": true,
    "types": [
      "node",
      "vite/client",
      "cypress",
      "./src/shims-vue.d.ts"
    ]
  },
  "include": [
    "src",
    "test/cypress"
  ]
}

Solution

  • Probably fixed by using the following cypress.d.ts:

    import { ComponentMountingOptions, Vue } from "vue";
    
    declare global {
      namespace Cypress {
        interface Chainable {
          mount<Component extends Vue>(
            component: Component,
            options?: ComponentMountingOptions<Component>
          ): Chainable<any>;
        }
      }
    }
    

    (Still no idea what I'm doing.)