cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.7k stars 3.16k forks source link

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

Open mbp opened 1 year ago

mbp commented 1 year ago

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

warrensplayer commented 1 year ago

@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.

lmiller1990 commented 1 year ago

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

mbp commented 1 year ago

@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
> vue3-vite@0.0.0 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 commented 1 year ago

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
marktnoonan commented 1 year ago

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

xmatthias commented 1 year ago

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

warrensplayer commented 1 year ago

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.

mbp commented 1 year ago

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.

WaldemarEnns commented 1 year ago

Very similar error, here is my reproduction:

Setup

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

Jicmou commented 10 months ago

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:

yhanssens commented 7 months ago

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?

mbp commented 7 months ago

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

I use this ugly workaround:

mount(MyComponent as any, { ... });
lmiller1990 commented 7 months ago

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).

WaldemarEnns commented 5 months ago

Has anything happened so far?

lmiller1990 commented 5 months ago

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.

WaldemarEnns commented 5 months ago

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.