Skip to content

Commit

Permalink
Provide strong types for 'invoke' command (cypress-io#4022) (cypress-…
Browse files Browse the repository at this point in the history
…io#4907)

* add strong return type to 'invoke' command

* refactor invoke for clarity. use dtslint tests

* add strong args types using typescript 3.0+ features

* handle invoke's jquery wrapping of its input

* move invoke tests to namespace

* add example to jsdocs of 'invoke'
  • Loading branch information
mayfieldiv authored and bahmutov committed Aug 6, 2019
1 parent a038e7f commit 15685db
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 8 deletions.
23 changes: 15 additions & 8 deletions cli/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Mike Woudenberg <https://github.com/mikewoudenberg>
// Robbert van Markus <https://github.com/rvanmarkus>
// Nicholas Boll <https://github.com/nicholasboll>
// TypeScript Version: 2.8
// TypeScript Version: 3.0
// Updated by the Cypress team: https://www.cypress.io/about/

/// <reference path="./cy-blob-util.d.ts" />
Expand Down Expand Up @@ -810,15 +810,22 @@ declare namespace Cypress {
hash(options?: Partial<Loggable & Timeoutable>): Chainable<string>

/**
* Invoke a function on the previously yielded subject.
* This isn't possible to strongly type without generic override yet.
* If called on an object you can do this instead: `.then(s => s.show())`.
* If called on an array you can do this instead: `.each(s => s.show())`.
* From there the subject will be properly typed.
* Invoke a function on the previously yielded subject, yielding the result.
*
* @see https://on.cypress.io/invoke
*/
invoke(functionName: keyof Subject, ...args: any[]): Chainable<Subject> // don't have a way to express return types yet
* @example
* // Invoke the 'add' function, passing in parameters
* cy.wrap({ add: (a, b) => a + b }).invoke('add', 1, 2).should('eq', 3)
* // DOM elements are automatically wrapped with jQuery,
* // so jQuery functions (including 3rd party plugins) can be invoked
* cy.get('.modal').invoke('show').should('be.visible') // jQuery's 'show' functions
*/
invoke<
TActualSubject extends (Subject extends Node | Node[] ? JQuery<Subject> : Subject),
TName extends ({ [K in keyof TActualSubject]: TActualSubject[K] extends (...args: any[]) => any ? K : never }[keyof TActualSubject]),
TArgs extends (TActualSubject[TName] extends (...args: infer A) => any ? A : never),
TReturn extends (TActualSubject[TName] extends (...args: any[]) => infer R ? R : never),
>(functionName: TName, ...args: TArgs): Chainable<TReturn>

/**
* Get a property’s value on the previously yielded subject.
Expand Down
15 changes: 15 additions & 0 deletions cli/types/tests/cypress-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,18 @@ namespace CypressContainsTests {
cy.contains('#app', 'my text to find', {log: false, timeout: 100})
cy.contains('my text to find', {log: false, timeout: 100})
}

namespace CypressInvokeTests {
const obj = {
foo: (value: number, name = 'n') => `${ name } = ${ value + 1 }`,
bar: { baz: (a: number) => a + 1 }
}
cy.wrap(obj).invoke('foo', 1).should('equal', 'n = 2') // $ExpectType Chainable<string>
cy.wrap(obj).invoke('foo', 5, 'b').should('equal', 'b = 6') // $ExpectType Chainable<string>
cy.wrap(obj).its('bar').invoke('baz', 2).should('equal', 3) // $ExpectType Chainable<number>
cy.wrap(obj).invoke('bar', 2) // $ExpectError
cy.wrap(obj).invoke('bar.baz', 2).should('equal', 3) // $ExpectError
cy.wrap(obj).invoke('foo', 5, 1) // $ExpectError
cy.wrap(obj).invoke('foo', 5, 'b', 1) // $ExpectError
cy.get('input').then(it => it[0]).invoke('checkValidity') // $ExpectError
}

0 comments on commit 15685db

Please sign in to comment.