cypress icon indicating copy to clipboard operation
cypress copied to clipboard

Not able to use Component Testing with Vite/Vue after upgrade to TypeScript 5

Open mbp opened this issue 1 year ago • 16 comments

Current behavior

I am using Vue 3.2.47, Vite 4.3.3, TypeScript 5.0.4 and I get TypeScript error message after upgrade. It seems similar as the issue that was fixed in #25538

Desired behavior

No error message

Test code to reproduce

MyComponent.ts:

<template>
    <span>
      <slot name="item" v-bind="{ item: item }"></slot>
    </span>
</template>

<script setup lang="ts">
interface Props {
  item: any;
}

defineProps<Props>();
</script>

MyComponent.cypress.ts:

import { mount } from 'cypress/vue';
import { h } from 'vue';
import MyComponent from './MyComponent .vue';

describe('MyComponent ', () => {
  it('should render', () => {
    mount(MyComponent , {
      props: {
        item: {
          id: 1,
          text: 'First element',
        }
      },
      slots: {
        item: ({ item }: { item: any }) =>
          h('div', {}, `${item.id} - ${item.text}`),
      },
    });
  });
});

Error is:

MyComponent.cypress.ts:7:11 - 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, ... 4 more ..., {}>, { ...; }>' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<__VLS_TypePropsToRuntimeProps<Props>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, string[], string>'.
      Type '__VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<Props>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>, { ...; }>' is not assignable to type 'ComponentOptionsBase<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<Props>>> & { [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; }, ... 10 more ..., string>'.
        Types of property 'emits' are incompatible.
          Type 'ThisType<void> | (string[] & ThisType<void>) | undefined' is not assignable to type '(string[] & ThisType<void>) | undefined'.
            Type 'ThisType<void>' is not assignable to type 'string[] & ThisType<void>'.
              Type 'ThisType<void>' is missing the following properties from type 'string[]': length, pop, push, concat, and 31 more.

7     mount(ListInteractive, {
            ~~~~~~~~~~~~~~~

  node_modules/cypress/vue/dist/index.d.ts:1339:18
    1339 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 MyComponent.cypress.ts:7

Cypress Version

12.11.0

Node version

v18

Operating System

Windows

Debug Logs

No response

Other

No response

mbp avatar Apr 28 '23 23:04 mbp

@mbp I am not able to reproduce the same error using those versions of the libraries you mention, but I might not have setup my project the same as you. Please provide a reproducible example of the issue you're encountering. Here are some tips for providing a Short, Self Contained, Correct, Example and our own Troubleshooting Cypress guide.

warrensplayer avatar May 02 '23 18:05 warrensplayer

https://github.com/cypress-io/cypress/pull/26633 may solve the issue.

lmiller1990 avatar May 03 '23 05:05 lmiller1990

@warrensplayer the issue can be reproduced by the same repro that is in #25538

So steps to reproduce:

  1. Clone https://github.com/lmiller1990/cypress-issue-23653
  2. Update cypress dependency to latest 12.11.0
  3. Call npm run build - confirm it still compiles (TypeScript 4.9)
  4. Now update remaining dependencies to latest versions (find list below)
  5. Call npm run build. Compilation error occurs
> [email protected] build
          Type 'ThisType<void> | (string[] & ThisType<void>) | undefined' is not assignable to type '(string[] & ThisType<void>) | undefined'.
            Type 'ThisType<void>' is not assignable to type 'string[] & ThisType<void>'.
              Type 'ThisType<void>' is missing the following properties from type 'string[]': length, pop, push, concat, and 31 more.

6     cy.mount(HelloWorld, {})               ~~~~~~~~~~
  node_modules/cypress/vue/dist/index.d.ts:1339:18
    1339 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/components/HelloWorld.cy.ts:6
  "dependencies": {
    "vue": "3.2.47"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "4.2.1",
    "cypress": "12.11.0",
    "typescript": "5.0.4",
    "vite": "4.3.3",
    "vue-tsc": "1.6.3"
  }

I don't know how to test if #26633 fixes the issue

mbp avatar May 03 '23 07:05 mbp

Now that #26633 is part of 12.12.0, I tested again with the updated dependency. But it still does not compile. Line numbers changed a little though:

src/components/HelloWorld.cy.ts:6:14 - error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '__VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<{ label: string; }>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>, { ...; }>' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<__VLS_TypePropsToRuntimeProps<{ label: string; }>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, string[], string>'.
      Type '__VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<{ label: string; }>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>, { ...; }>' is not assignable to type 'ComponentOptionsBase<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & { [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; }, ... 10 more ..., string>'.
        Types of property 'emits' are incompatible.
          Type 'ThisType<void> | (string[] & ThisType<void>) | undefined' is not assignable to type '(string[] & ThisType<void>) | undefined'.
            Type 'ThisType<void>' is not assignable to type 'string[] & ThisType<void>'.
              Type 'ThisType<void>' is missing the following properties from type 'string[]': length, pop, push, concat, and 31 more.

6     cy.mount(HelloWorld, {})
               ~~~~~~~~~~

  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/components/HelloWorld.cy.ts:6

mbp avatar May 10 '23 06:05 mbp

Thanks for the updated info @mbp, we'll look into this some more.

marktnoonan avatar May 11 '23 13:05 marktnoonan

I'm having the same error on a project of mine Interestingly, it's only impacting one component - but not 2 others, which are similarly simple (the only difference is that the other components also have a computed value, which the "failing" one doesn't. As such, i think that my setup is correct - but something else is messing with that specific component.

for now, i've @ts-expect-error'ed this ... but having this correct would be great

xmatthias avatar May 12 '23 16:05 xmatthias

I was able to confirm this type error still exists when updating dependencies in the sample repo as documented in the comment above here: https://github.com/cypress-io/cypress/issues/26628#issuecomment-1532579168

The issue is with the slot in the button. If that is removed, then the typing is fine. Look at updating the mount types appropriately in the built-in Vue adaptor in Cypress.

warrensplayer avatar May 31 '23 16:05 warrensplayer

I am just guessing here - but I believe there is chance this could be fixed with latest type file from @vue/test-utils. Please see related issue, which is also about combination of slots and props: https://github.com/vuejs/test-utils/issues/2054

This fix is coming in @vue/test-utils 2.4.0, but not released yet.

mbp avatar Jun 01 '23 06:06 mbp

Very similar error, here is my reproduction:

Setup

  • cypress 12.17.4
  • vue 3.2.41
  • typescript 5.0.2
  • tailwind 3.2.1 (since my example tests include tailwind)
  • vuetify 3.3.14 (since my app is based on vuetify and I have a custom mount() function implementation)

package.json

{
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vue-tsc && vite build",
        "test:e2e": "cypress open"
    },
    "devDependencies": {
        "@fortawesome/fontawesome-free": "^6.4.2",
        "@inertiajs/vue3": "^1.0.0",
        "@tailwindcss/forms": "^0.5.3",
        "@types/ziggy-js": "^1.3.2",
        "@vitejs/plugin-vue": "^4.0.0",
        "autoprefixer": "^10.4.12",
        "axios": "^1.1.2",
        "cypress": "^12.17.4",
        "laravel-vite-plugin": "^0.7.5",
        "postcss": "^8.4.18",
        "tailwindcss": "^3.2.1",
        "typescript": "^5.0.2",
        "vite": "^4.0.0",
        "vue": "^3.2.41",
        "vue-tsc": "^1.2.0"
    },
    "dependencies": {
        "vuetify": "^3.3.14"
    }
}

InputLabel.vue

<script setup lang="ts">
defineProps<{
    value?: string;
}>();
</script>

<template>
    <label class="block font-medium text-sm text-gray-700 dark:text-gray-300">
        <span v-if="value">{{ value }}</span>
        <span v-else><slot /></span>
    </label>
</template>

InputLabel.cy.ts

import InputLabel from '../../../resources/js/Components/InputLabel.vue'

describe('<InputLabel />', () => {
  it('renders', () => {
    cy.mount(InputLabel, {
      props: {
        value: 'Test Label'
      }
    })
  })
})

Error for InputLabel.cy.ts (sorry it's german)

Der Typ "{ value: string; }" kann dem Typ "VNodeProps & { __v_isVNode?: undefined; [Symbol.iterator]?: undefined; } & Record<string, any> & { [x: number]: unknown; } & { readonly filter?: Prop<unknown, unknown> | { <S extends string>(predicate: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (predicate: (value: st..." nicht zugewiesen werden.
  Der Typ "{ value: string; }" kann dem Typ "{ readonly filter?: Prop<unknown, unknown> | { <S extends string>(predicate: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (predicate: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): string[]; } | null | undefined; ... 22 more ...; r..." nicht zugewiesen werden.
    Die Typen der Eigenschaft "toString" sind nicht kompatibel.
      Der Typ "() => string" kann dem Typ "string" nicht zugewiesen werden.ts(2322)

PChip.vue (bunch of vuetify components)

<script setup lang="ts">
defineProps<{
  value?: string;
}>()
</script>

<template>
  <v-container class="pa-4">
    <v-chip class="mb-6">
      {{ value }}
    </v-chip>

    <v-text-field label="Label"></v-text-field>

    <v-avatar
      color="brown"
    >
      <span class="text-h5">WE</span>
    </v-avatar>
    
    <div class="mt-2">
      <v-btn color="error" class="mb-2 mr-2">Test</v-btn>
      <v-btn color="warning" class="mb-2 mr-2">Test</v-btn>
      <v-btn color="success" class="mb-2 mr-2">Test</v-btn>
    </div>
  </v-container>
</template>

PChip.cy.ts

import PChip from '../../../resources/js/Components/PChip.vue'

describe('<PChip />', () => {
  it('renders', () => {
    cy.mount(PChip, {
      props: {
        value: 'Test Chip'
      }
    })
  })
})

Error for PChip.cy.ts

Der Typ "{ value: string; }" kann dem Typ "VNodeProps & { __v_isVNode?: undefined; [Symbol.iterator]?: undefined; } & Record<string, any> & { [x: number]: unknown; } & { readonly filter?: Prop<unknown, unknown> | { <S extends string>(predicate: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (predicate: (value: st..." nicht zugewiesen werden.
  Der Typ "{ value: string; }" kann dem Typ "{ readonly filter?: Prop<unknown, unknown> | { <S extends string>(predicate: (value: string, index: number, array: readonly string[]) => value is S, thisArg?: any): S[]; (predicate: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): string[]; } | null | undefined; ... 22 more ...; r..." nicht zugewiesen werden.
    Die Typen der Eigenschaft "toString" sind nicht kompatibel.
      Der Typ "() => string" kann dem Typ "string" nicht zugewiesen werden.ts(2322)

component.ts

// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Import styles
import '../../../resources/css/app.css'
import 'vuetify/styles'
import '@fortawesome/fontawesome-free/css/all.css'

// Alternatively you can use CommonJS syntax:
// require('./commands')

import { mount } from 'cypress/vue'
import { h } from 'vue'
import { VApp } from 'vuetify/components'
import vuetify from '../../../resources/js/plugins/vuetify'

Cypress.Commands.add('mount', (component, options = {}) => {
  options.global = options.global || {}
  options.global.plugins = options.global.plugins || []

  options.global.plugins.push({
    install(app) {
      app.use(vuetify)
    },
  })
  
  return mount(() => {
    return h(VApp, [h(component, options.props)])
  }, options)
})

// Example use:
// cy.mount(MyComponent)

type MountParams = Parameters<typeof mount>
type OptionsParam = MountParams[1]

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Helper mount function for Vue Components
       * @param component Vue Component or JSX Element to mount
       * @param options Options passed to Vue Test Utils
       */
      mount(component: any, options?: OptionsParam): Chainable<any>
    }
  }
}

Results

The components get rendered correctly within cypress but I get the mentioned TS errors. Also, my component is labeled in Cypress as undefined:

InputLabel.cy.ts

unknown component

PChip.cy.ts

unknown component vuetify

WaldemarEnns avatar Sep 01 '23 06:09 WaldemarEnns

We had a very similar error, when having a slot in a tested component. In the end we could silence the error by setting strictFunctionTypes: false in tsconfig.json. We consider this a workaround rather than a fix though, and it could be nice not to be forced to have a less strict typescript configuration while using cypress and vue :pray:

Jicmou avatar Nov 16 '23 17:11 Jicmou

I am just guessing here - but I believe there is chance this could be fixed with latest type file from @vue/test-utils. Please see related issue, which is also about combination of slots and props: vuejs/test-utils#2054

This fix is coming in @vue/test-utils 2.4.0, but not released yet.

This still seems to be the issue. Did you manage to find a workaround?

yhanssens avatar Feb 12 '24 10:02 yhanssens

This still seems to be the issue. Did you manage to find a workaround?

I use this ugly workaround:

mount(MyComponent as any, { ... });

mbp avatar Feb 14 '24 07:02 mbp

It may be the type definitions in cypress/vue need updating to sync with the latest version of Test Utils. You can see here it is on 2.3.2 still https://github.com/cypress-io/cypress/blob/e0255f99c253ab2cce83d858369b8c6de2e60637/npm/vue/package.json#L21

I think someone needs to make a PR to update that.

Note that cypress/vue bundles Test Utils - it will not slurp up the one in your node_modules, so you can't just install the latest Test Utils, Cypress must update the one they are using (just need to do the dependency update and wait for the next Cypress release).

lmiller1990 avatar Feb 15 '24 05:02 lmiller1990

Has anything happened so far?

WaldemarEnns avatar Mar 26 '24 08:03 WaldemarEnns

Not sure, check to see if the internal version of Test Utils was updated, if not you could submit a PR! See above post for more info.

lmiller1990 avatar Mar 26 '24 23:03 lmiller1990

Not sure, check to see if the internal version of Test Utils was updated, if not you could submit a PR! See above post for more info.

I am giving this a try right now. Also, the docs might have to be updated or even extended for Vuetify specifically since the example there is not really working for me and my setup rather looks something like this (not that it is not fully TypeScript compliant and still errors at VLayout etc.):

import { h } from 'vue'
import { mount } from 'cypress/vue'
import { VLayout } from 'vuetify/components'
import vuetify from '@/plugins/vuetify'

type MountParams = Parameters<typeof mount>
type OptionsParam = MountParams[1]

declare global {
  namespace Cypress {
    interface Chainable {
      mount(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        component: any,
        options?: OptionsParam
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      ): Chainable<any>
    }
  }
}

Cypress.Commands.add('mount', (component, options = {}) => {
  options.global = options.global || {}
  options.global.plugins = options.global.plugins || []

  options.global.plugins.push({
    install (app) {
      app.use(vuetify)
    }
  })

  return mount(VLayout, {
    ...options,
    slots: {
      default: h(component, options.props, {
        ...options.slots
      })
    }
  })
})

Note that I use VLayout since the latest version of Vuetify ships changes to the VApp wrapper that messed with my components (e.g. stretched them etc. due to display: flex on the v-applciation wrapper element).

Looking into the official Cypress repo, I noticed that they are using pure JS in component.js instead of TypeScript as it would be in a component.ts setup. So opening a PR that will fix TS, would also require changing the setup to TS as well.

WaldemarEnns avatar Mar 28 '24 09:03 WaldemarEnns