cypress
cypress copied to clipboard
mount type definitions incompatible with Vue 3.2.38 SFC using <slot>
Current behavior
Upgrading from "vue": "3.2.37"
to "vue": "3.2.38"
has resulted in TypeScript errors appearing in my component test files, specifically when using mount
with an SFC that has a <slot />
.
<script lang="ts" setup>
defineProps<{
label: string;
}>();
</script>
<template>
<button type="button" :title="label" :aria-label="label">
<slot />
</button>
</template>
import Button from "./Button.vue";
describe("Button", () => {
it("should render", () => {
cy.mount(Button); // TypeScript Error ts(2769)
});
});
src/Button.cy.ts:5:14 - error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'ComponentPublicInstanceConstructor<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, ...' is not assignable to parameter of type 'ComponentOptionsWithObjectProps<__VLS_TypePropsToRuntimeProps<{ label: string; }>, {}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, string[], string, Readonly<...> & { ...; }, {}>'.
Type 'ComponentPublicInstanceConstructor<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, ...' is not assignable to type 'ComponentOptionsBase<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & { [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; }, ... 8 more ..., {}>'.
Types of property 'setup' are incompatible.
Type '((this: void, props: Readonly<LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>>>>, ctx: SetupContext<...>) => void | ... 2 more ... | Promise<...>) | undefined' is not assignable to type '((this: void, props: Readonly<LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & { [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; }>>, ctx: SetupContext<...>) => void | ... 2 more ... | Promise<...>) | undefined'.
Type '(this: void, props: Readonly<LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>>>>, ctx: SetupContext<...>) => void | ... 2 more ... | Promise<...>' is not assignable to type '(this: void, props: Readonly<LooseRequired<Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{ label: string; }>>> & { [x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined; }>>, ctx: SetupContext<...>) => void | ... 2 more ... | Promise<...>'.
Types of parameters 'ctx' and 'ctx' are incompatible.
Type 'SetupContext<string[]>' is not assignable to type 'SetupContext<{}>'.
Type '{}' is missing the following properties from type 'string[]': length, pop, push, concat, and 29 more.
5 cy.mount(Button); // TypeScript Error ts(2769)
~~~~~~
node_modules/cypress/vue/dist/index.d.ts:82:25
82 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?: CyMountOptions<ExtractPropTypes<PropsOptions> & PublicProps, D>): Cypress.Chainable;
~~~~~
The last overload is declared here.
Desired behavior
Cypress should allow me to mount
a Vue SFC component with the following syntax:
import Button from "./Button.vue";
describe("Button", () => {
it("should render", () => {
cy.mount(Button); // Should not throw a TypeScript error
});
});
Test code to reproduce
Fork: https://github.com/JoostKersjes/cypress-test-tiny
Specific file that contains the error: https://github.com/JoostKersjes/cypress-test-tiny/blob/vue-3.2.38-type-error/src/Button.cy.ts
Use npm run check
to output the TypeScript error to the terminal.
Cypress Version
10.7.0
Node version
v18.2.0
Operating System
Kubuntu 20.04
Debug Logs
No response
Other
No response
Hmm I think Vue 3.2.28 broke some of our typings. Most likely this will be an issue for Test Utils, too, which we use internally: https://github.com/vuejs/test-utils/issues
We probably need to update Test Utils to support 3.2.28, than upgrade cypress/vue to use that, too.
This should not take too long, but in the meantime, unfortunately, you'll need do cy.mount(Button as any)
or ts-ignore
.
I tried updating our types but it wasn't that simple. I am still looking into this. I wonder why the presence of <slot />
breaks things.
Hey team! Please add your planning poker estimate with Zenhub @amehta265 @astone123 @lmiller1990 @marktnoonan @mike-plummer @rockindahizzy @warrensplayer @ZachJW34
For a real world example, you can clone my personal project - the branch associated w/ this PR specifically https://github.com/lmiller1990/rhythm/pull/19/files. In that PR if you do cmd+f for ts-ignore
you can see where I had to ignore errors due to this problem. It's not the exact same problem (no slots) but it's in the same area.
We've got some weird hack with the types here: https://github.com/cypress-io/cypress/blob/develop/npm/vue/inline-types.ts. I thought this was a good idea initially to capture the huge type definition here https://github.com/vuejs/test-utils/blob/main/src/mount.ts#L98-L294.
I am thinking we probably just want something more simple potentially... I wonder if just make the type of component we pass as the first arg to mount
to be very permissive, like any
, or at least something less complex than what we currently do.
The complexity is we need to support any version of Vue 3, which changes it's types fairly regularly (not usually a problem, except for projects like ours, where we ship a pre-bundled Test Utils). I think having worst types (eg, any
for the first arg of mount
would be better than having broken types.
I'm interested in picking this up, unless someone else really wants it!
Working on this, also reproduced it here: https://github.com/lmiller1990/cypress-issue-23653, good to know it's not specific to any one repo.
It only shows if I run yarn vue-tsc --noEmit
.
The error boils down to this
Type 'SetupContext<string[]>' is not assignable to type 'SetupContext<{}>'
I thought about this more, I'm going to explore updating Test Utils to expose a better API to let people (like Cypress, Testing Library Vue) build on top of it. Then we can get all the types "for free" without having to mess around too much, or resort to weird work arounds.
Specifically, I think MountOptions
needs to have some additional generics args to let third parties add their own mounting option types. That should help us out, as well as other people using Test Utils to build things.
@lmiller1990 Playwright component testing, Vue Testing Library and probably a bunch of other libraries have the same issues. I wish we had a similar util type in Vue to the one in Svelte:
import type { SvelteComponent, ComponentProps } from 'svelte/types/runtime'
type Component = SvelteComponent;
type Props = ComponentProps<SvelteComponent>;
// mount function could look like:
declare function mount<Component extends SvelteComponent>(component: Component, { props: ComponentProps<Component> });
Is it an idea to create an issue at the Vue repo to solve this problem there?
ComponentPublicInstance
or DefineComponent
would be the equivalent in Vue - the reason Test Utils has that overly complex type is to give type safety for props, events, etc etc... there's too many ways to write a Vue component. How does Svelte give type safety for events? Eg on:message
?
I agree with the Playwright team on this - it is too complex to maintain. I don't think there's really an issue to open in Vue for this - there's a lot of ways to define a Vue component, and each one needs to be typed.
I will experiment in Test Utils a bit today, at least if we can keep the complexity in one repo, other maintainers (like Cypress, Playwright, who do not have the domain specific knowledge) won't have to deal with it. I don't think copy-pasting a type that the maintainers don't understand is sustainable.
This is proving to be quite difficult. Ideally, I'd like some way to extend Test Utils, so we don't need to mess around with a complex type. Initial attempt: https://github.com/vuejs/test-utils/pull/1779. It includes a TS Playground link.
Backup plan would just be typing the first arg to mount
as any
... not ideal.
Someone made a neat suggestion in the TypeScript Discord... https://discord.com/channels/508357248330760243/1021617359917682708
Another neat snippet that is probably close to what we want
type MountFunc<Extra> = <T>(component: Comp<T>, options?: Opts<T> & Extra) => MountResult;
const mount: MountFunc<{ someSpecificArg: boolean }> = (comp, args) => {
}
In conjunction w/ some changes here: https://github.com/vuejs/test-utils/blob/main/src/mount.ts
Edit: I think the "copy paste + modify types" is the only practical strategy, at least for now - I cannot find a good way to make the Test Utils mount
interface reusable.