All files / src/cypress data-cy.ts

100% Statements 9/9
100% Branches 7/7
100% Functions 3/3
100% Lines 9/9

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96                                                                                                                            113x   1665x 1665x   66x             1599x 1599x     190x                       113x 113x        
/**
 * @module cypress
 */
 
type CyOptions = Partial<
  Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow
>;
 
type DataCyWithSubject = (
  subject: Cypress.JQueryWithSelector<HTMLElement> | HTMLElement | undefined,
  selector: string,
  children?: string | null,
  options?: CyOptions
) => Cypress.Chainable<Cypress.JQueryWithSelector<HTMLElement>>;
 
type DataCy = DataCyWithSubject & {
  (
    selector: string,
    children?: string | null,
    options?: CyOptions,
    never?: never
  ): Cypress.Chainable<Cypress.JQueryWithSelector<HTMLElement>>;
  commandOptions?: {
    prevSubject: "optional";
  };
};
 
/**
 * Select an html element by its data-cy attribute.
 *
 * ```ts
 * cy.dataCy('something').click() // -> cy.get("[data-cy='something']").click()
 * ```
 *
 * Accepts children which will then become further selectors
 *
 * ```ts
 * cy.dataCy('something', '.nested').click()
 * // -> cy.get("[data-cy='something'] .nested").click()
 * ```
 *
 * And is chainable such that it will use the previous element as its scope
 *
 * ```ts
 * cy.dataCy('something').dataCy("other").click()
 * // -> cy.get("[data-cy='something']").within(() => cy.get("[data-cy='other']").click())
 * cy.get('.top').dataCy('something') // same idea as above
 * ```
 *
 * Finally, it supports taking the first signature literally and passing in
 * a subject directly. That's very likely an unnecessary step and convention/obvious errors
 * would prevent it, but going for complete here
 *
 * ```ts
 * cy.get(".first-selector").then(($first) =>
 *   cy.dataCy($first, "second-selector").should("contain", 0)
 * );
 * ```
 *
 * Note: this is not added to cypress commands by default. Pass to {@link addCommands}
 * or a custom function this should be added as a cypress command.
 */
export const dataCy: DataCy = (...args: any) => {
  // handle the unlikely scenario someone takes the first signature literally
  const unlikelyArgs = args as [undefined, ...Parameters<DataCyWithSubject>];
  if (!unlikelyArgs[0] && typeof unlikelyArgs[1] !== "string") {
    // type coerce here due to build intentionally not including the added types
    return (cy.wrap(unlikelyArgs[1]) as unknown as { dataCy: DataCy }).dataCy(
      unlikelyArgs[2],
      unlikelyArgs[3],
      unlikelyArgs[4]
    );
  }
  const [subject, selector, children, options] =
    args as Parameters<DataCyWithSubject>;
  return subject
    ? cy.wrap(subject).within(() =>
        // again, type coerce here due to build intentionally not including the added types
        (cy as unknown as { dataCy: DataCy }).dataCy(
          selector,
          children,
          options
        )
      )
    : cy.get(
        `[data-cy="${selector}"]${children ? ` ${children}` : ""}`,
        options
      );
};
// unnecessary IIFE, just avoiding typedoc thinking this is a namespace
(() => {
  dataCy.commandOptions = {
    prevSubject: "optional",
  };
})();