Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add more precise types to Cypress.Commands #19003

Merged
merged 7 commits into from
Dec 13, 2021

Conversation

xumepadismal
Copy link
Contributor

@xumepadismal xumepadismal commented Nov 19, 2021

User facing changelog

  • Custom command implementations typings take into account prevSubject variants
  • Custom command implementations now allows to NOT return a value
  • Custom command overwrites typings take into account originalFn function

PR Tasks

  • Have tests been added/updated?
  • Has the original issue (or this PR, if no issue exists) been tagged with a release in ZenHub? (user-facing changes only)
  • Has a PR for user-facing changes been opened in cypress-documentation?
  • Have API changes been updated in the type definitions?
  • N/A Have new configuration options been added to the cypress.schema.json?

@xumepadismal xumepadismal requested a review from a team as a code owner November 19, 2021 16:56
@xumepadismal xumepadismal requested review from jennifer-shehane and removed request for a team November 19, 2021 16:56
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Nov 19, 2021

Thanks for taking the time to open a PR!

@CLAassistant
Copy link

CLAassistant commented Nov 19, 2021

CLA assistant check
All committers have signed the CLA.

@xumepadismal xumepadismal changed the title Add more precise types to Cypress.Commands fix: Add more precise types to Cypress.Commands Nov 19, 2021
@xumepadismal xumepadismal force-pushed the commands-types branch 4 times, most recently from 953fc80 to 863e725 Compare November 19, 2021 18:37
@emilyrohrbough
Copy link
Member

emilyrohrbough commented Nov 24, 2021

@xumepadismal FYI - #18967 issued by @remcohaszing has similar types changes to Cypress.Commands as well. It looks like it takes prevSubject into account. Where you aware?

@remcohaszing Did you by chance come across this PR?

I'm curious what you both think of each other's proposed changes.

Edit- it actually looks like this draft PR was also opened to tackle similar issues: #18880.

@xumepadismal
Copy link
Contributor Author

@emilyrohrbough oh I missed these PRs indeed...

Here's my thoughts about the difference.

Regarding the preSubject

PR #18880 seems to propose more future-proof approach to prevSubject typings because it supports other hypothetical options in CommandOptions besides prevSubject.

But on the other hand it doesn't support array of prevSubjects which IS supportred by this PR. You can refer to cypress-tests.ts in Changes of this PR to see the examples of how types are inferred.

Regarding the void

In #18967 return type of fn is just void which is not accurate. We should allow void but not require it.
In my PR it is ReturnType<ChainableMethods[T]> | void which is better correspond the docs.

ChainableMethods vs ConditionalKeys

I'm uncertain about what approach is better but it seems that ConditionalKeys proposed in #18967 will filter off more unapplicable keys from Chainable. I don't think there is a much difference for the consumer though.

@xumepadismal
Copy link
Contributor Author

I can modify my PR to take into account all the goodies from #18967 and #18880 if this is acceptable and nobody minds :)

@remcohaszing
Copy link
Contributor

I think this PR does everything #18967 does plus more (prevSubject), so I recommend to close that one and merge this instead.

Regarding the void

In #18967 return type of fn is just void which is not accurate. We should allow void but not require it. In my PR it is ReturnType<ChainableMethods[T]> | void which is better correspond the docs.

I used void because it turned out a return type of void | Whatever is equal to a return type of void for callback functions. Technically the unwrapped value of the chainable is also a valid return type.

@xumepadismal
Copy link
Contributor Author

@remcohaszing

I used void because it turned out a return type of void | Whatever is equal to a return type of void for callback functions.

Hmm I played around this and it seems that TS checks the return type correctly, no? You can return either correct type or nothing. Error if you try to return something inconsistent to interface.

image

…dOptions if added in future

bonus: fix {prevSubject: 'document'} so that it refer to Document instead of JQuery
@xumepadismal
Copy link
Contributor Author

Now this PR should also cover everything in #18880. Hopefully I didn't miss anything, review will be welcome :)

SimeonC
SimeonC previously approved these changes Nov 30, 2021
Copy link

@SimeonC SimeonC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this covers all the cases from my PR #18880 , thanks 👍

Could you add that this closes #18879 as well?

name: T, options: CommandOptions & { prevSubject: S | Array<Exclude<S, 'optional'>> }, fn: CommandFnWithSubject<T, PrevSubjectMap[S]>,
): void
add<T extends keyof Chainable, S extends PrevSubject>(
name: T, options: CommandOptions & { prevSubject: 'optional' | ['optional', ...S[]] }, fn: CommandFnWithSubject<T, void | PrevSubjectMap[S]>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this section work fine even if 'optional' isn't in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed a commit to support this case. I'm little bit surprised the code looks even cleaner with this fix :)

@mrkjdy
Copy link

mrkjdy commented Dec 1, 2021

I'm not sure if this is the right place to post this, but I got a chance to checkout the changes in the PR, and when overwriting commands like 'select' it's not obvious that you need to add type parameters to overwrite like this in order for the correct types to be picked up:

Cypress.Commands.overwrite<'select', 'element'>('select', (originalFn, prevSubject, valueOrTextOrIndex, options) => {
  // ...
});

After this PR is merged can the docs be updated to mention this?

@chrisbreiding
Copy link
Contributor

I agree with @mrkjdy that the docs should be updated to reflect these changes. We prefer to do docs updates in tandem with the code updates and then release them together.

@xumepadismal Would you mind opening a PR in the cypress-documentation updating the typescript support doc?

@xumepadismal
Copy link
Contributor Author

I agree with @mrkjdy that the docs should be updated to reflect these changes. We prefer to do docs updates in tandem with the code updates and then release them together.

@xumepadismal Would you mind opening a PR in the cypress-documentation updating the typescript support doc?

Sorry very busy these days but I'll try to allocate some time till the end of the week to create a PR in the docs

m7kvqbe1 added a commit to Royal-Navy/design-system-docs that referenced this pull request Dec 7, 2021
This is currently breaking the build.

The following PR upstream will make this redundant once shipped: cypress-io/cypress#19003
@chrisbreiding chrisbreiding merged commit 32d5902 into cypress-io:develop Dec 13, 2021
tgriesser added a commit that referenced this pull request Dec 15, 2021
* develop:
  chore(deps): update dependency ssri to 6.0.2 [security] (#19351)
  chore: Fix server unit tests running on mac by using actual tmp dir (#19350)
  fix: Add more precise types to Cypress.Commands (#19003)
  fix: Do not screenshot or trigger the failed event when tests are skipped (#19331)
  fix (#19262)
  fix: throw when writing to 'read only' properties of `config` (#18896)
  fix: close chrome when closing electron (#19322)
  fix: disable automatic request retries (#19161)
  chore: refactor cy funcs (#19080)
  chore(deps): update dependency @ffmpeg-installer/ffmpeg to v1.1.0 🌟 (#19300)
tgriesser added a commit that referenced this pull request Dec 16, 2021
…cycle

* 10.0-release:
  build: remove syncRemoteGraphQL from codegen
  chore: fix incorrect type from merge
  build: allow work with local dashboard (#19376)
  chore: Test example recipes against chrome (#19362)
  test(unify): Settings e2e tests (#19324)
  chore(deps): update dependency ssri to 6.0.2 [security] (#19351)
  fix: spec from story generation, add deps for install (#19352)
  chore: Fix server unit tests running on mac by using actual tmp dir (#19350)
  fix: Add more precise types to Cypress.Commands (#19003)
  fix: Do not screenshot or trigger the failed event when tests are skipped (#19331)
  fix (#19262)
  fix: throw when writing to 'read only' properties of `config` (#18896)
  fix: close chrome when closing electron (#19322)
  fix: disable automatic request retries (#19161)
  chore: refactor cy funcs (#19080)
  chore(deps): update dependency @ffmpeg-installer/ffmpeg to v1.1.0 🌟 (#19300)
@nitzanashi
Copy link

may I ask why the Subject is set to be unknown now?

@NicholasBoll
Copy link
Contributor

This PR introduces issues with Cypress.Commands.overwrite on commands with overloads. There is no way for Typescript to know which overload should be used, so the last overload is resolved. Overall, the PS is nice for Cypress.Commands.add to remove duplicate typing, but it fails for Cypress.Commands.overwrite. If there is no overloads for the command, it works fine and is nice to have originalFn properly typed, but there is no way to choose the overload. I've had to be clever with a visit override to do something on a beforeload event:

Example:

Cypress.Commands.overwrite('visit', (originalFn, urlOrOptions, inputOptions = {}) => {
  let options: typeof urlOrOptions; // weird. Typescript doesn't think `urlOrOptions` could be a `string`

  // usually these types of guards are type guards, but now this is only a runtime guard  
  if (typeof urlOrOptions === 'object') {
    options = urlOrOptions;
  } else {
    // wait, `urlOrOptions` is of type `never` here...
    options = {url: urlOrOptions, ...inputOptions};
  }

  return originalFn({
    ...options,
    onBeforeLoad(win) {
      options.onBeforeLoad?.(win);
      supports(); // prime the ally.js supports cache so it doesn't mess with the cypress-plugin-tab
    },
  });
});

In this example, urlOrOptions is string | Partial<Cypress.VisitOptions> & { url: string } because of the overload, but since the last overload is taken, Typescript now thinks the type is never a string. I can no longer override parameters to type them correctly for internal use unless I cast (Cypress.Commands.overwrite as any) first.

@NicholasBoll
Copy link
Contributor

Also slightly nuanced is the return type of the original function:

Cypress.Commands.overwrite('visit', (originalFn, options) => {
  const foo = originalFn(options);

  return foo
});

In this example, foo is a Bluebird promise, but the types say it is Cypress.Chainable<Cypress.AUTWindow>. I ran into this issue with some overrides:

return Cypress.Promise.try(() => {
  return originalFn(subject, options)
})
.then(value => {
  // value should be `JQuery<HTMLElement>` since the original fn returns a jQuery element
  // but `value` is typed as `Cypress.Chainable` since Bluebird cannot unwrap the subject because the way this is typed
  // says `originalFn` returns `Cypress.Chainable<Subject>` instead of `Subject` or `Bluebird.Promise<Subject>`
})

The problem is Cypress commands normalized around a Cypress.Chainable return type, but the original functions do not have any such normalization.

@elio-maggini
Copy link

elio-maggini commented Apr 28, 2022

.

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