Overriding Cypress typescript using declaration file

2.1k views Asked by At

I was reading this article about Cypress. On the last part, the author overrode the types to be inferred by typescript when calling Cypress.env.

Here's the snippet that i want to understand.

export { };

declare global {
  namespace Cypress {

    export interface Cypress {

      /**
       * Returns all environment variables set with CYPRESS_ prefix or in "env" object in "cypress.json"
       *
       * @see https://on.cypress.io/env
       */
      env(): Partial<EnvKeys>;
      /**
       * Returns specific environment variable or undefined
       * @see https://on.cypress.io/env
       * @example
       *    // cypress.json
       *    { "env": { "foo": "bar" } }
       *    Cypress.env("foo") // => bar
       */
      env<T extends keyof EnvKeys>(key: T): EnvKeys[T];
      /**
       * Set value for a variable.
       * Any value you change will be permanently changed for the remainder of your tests.
       * @see https://on.cypress.io/env
       * @example
       *    Cypress.env("host", "http://server.dev.local")
       */
      env<T extends keyof EnvKeys>(key: T, value: EnvKeys[T]): void;

      /**
       * Set values for multiple variables at once. Values are merged with existing values.
       * @see https://on.cypress.io/env
       * @example
       *    Cypress.env({ host: "http://server.dev.local", foo: "foo" })
       */
      env(object: Partial<EnvKeys>): void;

    }

  }
}

interface EnvKeys {
  'boards': Array<{
    created: string;
    id: number;
    name: string;
    starred: boolean;
    user: number;
  }>;
  'lists': Array<{
    boardId: number
    title: string
    id: number
    created: string
  }>;
}

Few questions that I have in this snippet:

  1. How does typescript know about this file when it is named as env.d.ts? I know that typescript automatically know the types if the file is named the same with the file (e.g command.ts -> command.d.ts
  2. What's the use of export {} here. If i comment this one out, Vscode complains.
  3. Why do we have to add declare global here when the original Cypress typescript starts with declare namespace Cypress?
1

There are 1 answers

1
Fody On BEST ANSWER

I'm not an expert in typescript, but here's my take

1. How does typescript know about this file when it is named as env.d.ts

In tsconfig.json you have

include": [
  "**/*.ts"
]

which means env.d.ts is included in the compile.

Run tsc --listFiles --noEmit to see what get's included.


2. What's the use of export {} here

You are merging namespaces, and the docs Merging Namespaces say

To merge the namespaces, type definitions from exported interfaces declared in each namespace are themselves merged.

Further

Non-exported members are only visible in the original (un-merged) namespace. This means that after merging, merged members that came from other declarations cannot see non-exported members.

So, it's information hiding - like public vs private variables.

Oops wrong anwser

I just realized you want to know about the first line.

Please see this answer
Augmentations for the global scope can only be directly nested in external modules or ambient module declarations(2669)

I think the trick is just that an "external module" is a file containing an import or export statement, so this makes it an "external module".


3. Why do we have to add declare global

Ref Global augmentation

You can also add declarations to the global scope from inside a module

When you write a test, you can use Cypress.Commands.add(), cy.get(), it(), etc without needing to import anything because they are declared globally.

To merge with the global Cypress interface, your enhancement must also be declared as global.

To see the effect, change env.d.ts

declare module "env" {  
  namespace Cypress {

and addTaskApi.ts

Cypress.Commands.add('addTaskApi', ({ title, boardIndex = 0, listIndex = 0 }) => {
  cy.request('POST', '/api/tasks', {
      title,
      boardId: Cypress.env('boards')[boardIndex].id,
      listId: Cypress.env('lists')[listIndex].id
    })
    .then(({ body }) => {

      const tasks = Cypress.env('tasks')  // tasks typed as any 
                                          // instead of  'tasks': Array<{ boardId:...number;
      tasks.push(body);
    })
})