Skip to content

Instantly share code, notes, and snippets.

@danyg
Last active February 7, 2023 13:43
Show Gist options
  • Save danyg/a1c37e80a1872618c9dd7fdfc292290e to your computer and use it in GitHub Desktop.
Save danyg/a1c37e80a1872618c9dd7fdfc292290e to your computer and use it in GitHub Desktop.
How to use an interface with type literals as methods names effectively?

Hi, thanks for check in this in before hand.

So what I would like to achieve, is to define an interface, that specify how a certain type of object can be defined.

In this case PageObject, objects that will work as adaptor between the tests and the ui.

export interface PageObject {
// eslint-disable-next-line prettier/prettier
[key: `get${string}`]: (options?: CypressGetOptions, ...args: unknown[]) => Cypress.Chainable<JQuery<HTMLElement>>

// eslint-disable-next-line prettier/prettier
[key: `type${string}`]: (value: string, options?: CypressTypeOptions) => Cypress.Chainable<JQuery<HTMLElement>>
[key: string]: CallableFunction
};

So in this way, if you write a getElementName function it must comply with the interface of getters, if you write a typeFieldName must comply with the interface of the Typers.

Great, so when you write a new PageObject you can just do this image and then, here you'll see that the type returned is incorrect, you don't need to type all the implementations as is already given by PageObject, etc, etc

PERFECT.

The problem comes when you try to use this object, as all the specific implementation gets lost, when you define that the object is of type PageObject.

export const myPageObject: PageObject = {
  getElement: (options?) => cy.get('.Element', options);
};

In this example, myPageObject.get{cmd+space} won't type hint getElement as getElement is a specific method of myPageObject but not of PageObject and TS at this point doesn't get that myPageObject has its own properties apart of implementing with PageObject interface.

so in this case what's happening when you try to use myPageObject is that it doesn't know that there is an implementation for getElement. It will accept that getElement can be called, because is validated by the literal type, but if using VSCode I try to go to the implementation, it show me the PageObject type definition.

So I've tried several ideas. Like this:

export type MyPageObject = PageObject & {
  getElement: PageObject['get'];
};
const _myPageObject: PageObject = {
  getElement: (options?) => cy.get('.Element', options);
};

export const myPageObject: MyPageObject = _myPageObject;

and it works, when ask Vscode to go to implementations it goes to the type definition of getElement (line 2 in this example) at least is the right file. But is a bit cumbersome and error prone, as you might forget to type one of the methods.

Trying to use a Class (which I think is the right way for this scenario) doesn't work as typescript keeps complaining about something is not implemented.

using a Generic, would work, but you lack validation while writing the implementation, as you can not annotate the object as implementing PageObject...

So basically, there is a way to inform typescript that my object will IMPLEMENT PageObject, and force it to create a compose type between my definition and the PageObject itself?

Hope is clear what I'm trying to achieve here.

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