Skip to content
This repository was archived by the owner on Feb 28, 2024. It is now read-only.

Custom commands that pass prevSubject don't work with TypeScript #4

Open
shcallaway opened this issue Jan 4, 2018 · 4 comments
Open

Comments

@shcallaway
Copy link

shcallaway commented Jan 4, 2018

The following code produces a compilation error: "Expected 1 arguments, but got 0."

// support/commands.ts

function doSomething(subject) {
  console.log(subject);
}

Cypress.Commands.add(
  "doSomething",
  {
    prevSubject: true
  },
  doSomething
);

declare namespace Cypress {
  interface Chainable {
    doSomething: typeof doSomething;
  }
}
// integration/test.ts

it('works', () => {
  cy.get('body').doSomething();
});
@KoJoVe
Copy link

KoJoVe commented Nov 29, 2018

When you use typeof, Typescript declares the method using exactly the same interface of itself (in this case, (subject: any) => void).

When you call the method, you pass one less argument and Typescript understands this as a wrong call.

What you need to do is adapt the type of the method in the interface you are declaring, in your case, like this:

declare namespace Cypress {
  interface Chainable {
    doSomething: () => void;
  }
}

Another example:

function doSomethingWithArgs(subject, arg1, arg2) {
  console.log(subject, arg1, arg2);
  return true;
}

Cypress.Commands.add(
  "doSomethingWithArgs",
  {
    prevSubject: true
  },
  doSomethingWithArgs
);

declare namespace Cypress {
  interface Chainable {
    doSomethingWithArgs: (arg1: any, arg2: any) => boolean;
}

That will work!

@aesfer
Copy link

aesfer commented Aug 6, 2021

If you have complex types for your args that you don't want to re-type inside the declaration you can do something like this:

type ChainableCommand<T extends (...args: any) => any>
  = T extends (subject: infer I, ...args: infer P) => infer R
    ? (...args: P) => R
    : never;

declare namespace Cypress {
  interface Chainable<Subject> {
    doSomethingWithArgs: ChainableCommand<typeof doSomethingWithArgs>;
  }
}

Cypress.Commands.add('doSomethingWithArgs', { prevSubject: true }, doSomethingWithArgs);

function doSomethingWithArgs(subject: Cypress.Chainable, other: string, args: number) {
  console.log(subject, other, args);
  return subject;
}

Explanation: ChainableCommand maps the type of a function A into the type of a function B that takes the same parameters as A excluding the first one and returns the same thing as A.

@momesana
Copy link

momesana commented Dec 5, 2021

The solutions proposed here work fine with Cypress up to version 8.x but stopped working in Cypress 9. Any idea on how to make it work with the newest version of Cypress without having to resort to brutally overriding the type system by doing the following?

Cypress.Commands.add('myShinyNewCommand', { prevSubject: true }, (myCmd  as unknown) as MyTypeWithoutSubject);

@Smashman
Copy link

Smashman commented Dec 7, 2021

@momesana See cypress-io/cypress#19003

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

No branches or pull requests

5 participants