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:
- 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.gcommand.ts
->command.d.ts
- What's the use of
export {}
here. If i comment this one out, Vscode complains. - Why do we have to add
declare global
here when the original Cypress typescript starts withdeclare namespace Cypress
?
CodePudding user response:
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
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);
})
})