Search code examples
javascriptjsdoc

Get rid of repetitive JSDoc type declarations in object literal


While my question is more general and not directly related to automation tests architecture, POM, and any framework I will use some real-world examples.

In my test framework I use Cypress and POM architecture. Please see example of page object:

export const homePage = getPageObjectProxy({
  /** @type {Cypress.Chainable<JQuery<HTMLElement>>} */ 
  searchField: 'input',
  /** @type {Cypress.Chainable<JQuery<HTMLElement>>} */ 
  logOutButton: '[data-testid=logout-button]',
  /** @type {Cypress.Chainable<JQuery<HTMLElement>>} */ 
  modeDropdown: '[data-testid=mode-dropdown]',

  setMode(name) {
    this.modeDropdown.click();
    this.modeDropdown.children().contains(name).click();
  }
});

Proxy function (as you can see I change type of string properties to Cypress.Chainable<JQuery<HTMLElement>>):

/**
 * @template T
 * @param {T} pageObject a regular page object
 * @returns {T} proxied page object
 */
export const getPageObjectProxy = pageObject => new Proxy(pageObject, {
  get(target, prop) {
    if (typeof target[prop] === 'string') {
      return cy.get(target[prop]);
    }
    return target[prop];
  }
});

As a result after applying proxy I get an object with DOM elements (instead of selectors) and methods. These works perfectly (IDE intellisense also works).

With this approach I see 1 drawback. I must explicitly set type for each object property using JSDoc:

/** @type {Cypress.Chainable<JQuery<HTMLElement>>} */

I am curious is it possible using JSDoc somehow avoid this repetitive @type definitions for each property?


Solution

  • Proxy (used conditional mapped types in JSDoc):

    /**
     * @template T
     * @param {T} pageObject a page object with string selectors
     * @returns {{[key in keyof T]: T[key] extends string ? Cypress.Chainable<JQuery<HTMLElement>> : T[key]}} page object with DOM elements
     */
    export const getPageObjectProxy = pageObject => new Proxy(pageObject, {
      get(target, prop) {
        if (typeof target[prop] === 'string') {
          return cy.get(target[prop]);
        }
        return target[prop];
      }
    });
    

    Page object (used proxied object instead of this in the methods):

    export const homePage = getPageObjectProxy({
      searchField: 'input',
      logOutButton: '[data-testid=logout-button]',
      modeDropdown: '[data-testid=mode-dropdown]',
    
      setMode(name) {
        homePage.modeDropdown.click();
        homePage.modeDropdown.children().contains(name).click();
      }
    });