Transcripted Summary

I have created a new custom command for myself editBoard.

This one, as the name states, is going to edit a board.

I haven't passed anything into this command, and it's already saying that I should be passing at least 1 argument, but I got 0.



Let's go ahead and take a look into our custom command.



Right now, it looks very similar to our addBoard command that we talked about in the previous chapter, but I want to make it type safe, and actually use types from my Board interface that I already have in my application.

Since I want the body to be of type Board, I can just go ahead and pass it.



Then when I take a look into my spec file, I can start typing an object, and I have all of these auto suggestions of what I want to type, but my API actually does not allow me to use everything.



For example, I cannot change the user or I cannot change the created date when that board was created.

This means that I cannot use my interface as it is currently defined.

What can I do? There are multiple options that I can choose.

The bare minimum I can do is to use something that's called utility types.

# Utility type: Record

One of those utility types is something called Record.

This enables us to define what kind of keys we want to type, and what kind of parameters those keys should have.

If I type something like <string, any>, this will mean that inside editBoard, I can just pass any object.



Anything that will have a parameter and a value will be good enough.

I can even pass an empty object, and my editBoard function would still not complain.

Now, this is less than ideal.

We need to be more specific, but there are some cases where we don't really care what kind of object we pass, as long as it's an object.

# Utility Type: Pick

Let's take a look into another utility type, and that's called Pick.

This enables us to pick any attributes that we want from an existing interface.

I'm going to use <Board, 'id'>.



I paste it into my custom command, and when I now save my file and go to my spec file, you can see that my TypeScript compiler is complaining.



I simply cannot pass an empty object.

What I need to do is pass an id.

If I do that, the errors disappear.

Now, if I take a look into my custom command and what it actually does, I can see that it will call a PATCH request to /api/boards/ with a certain id, and then we are going to be passing the body that will contain all of the changes that we want to make.



For example, if we take a look into this interface, we might want to change the name of our board, or maybe even the starred attribute.



If we were to pass any of this into our command - for example, name: 'my board', the TypeScript compiler is going to complain about that.

The reason for that is that we simply picked only the id.

If we want to add multiple keys that we want to include in this object, we can add a pipe character | and then type the other attribute that we want to add.



As you can see, the errors have already disappeared.

But also, we might want to change the starred attributes, so let's add that one as well.

When I add this, the test is going to complain, but I'm going to add it and pass starred: false.

All of these keys are type checked, so in my interface, the starred attribute is a boolean, which means that if I change this to a string, I'm immediately getting an error.

I'll change it back and go back to my editBoard command.

This Pick utility type is quite useful, but I can choose an opposite approach, and just pick all of the keys from my board interface, but leave out some of them.

# Utility Type: Omit

To do this, I can use the utility type called Omit, and this is going to do the exact opposite.

Instead of requiring id, name, and starred, it's going to require all of the others - so that would be user and created.

If we want to keep our function working, we need to add those into our Omit utility type - so that's going to be created | user.



Now everything is looking good.

The only problem might be that whenever we call editBoard, we are required to call both name and the starred attribute, which is something we might not always want to do.

What if we just want to change the name or just want to change the starred attribute?

In that case, we can go ahead and define our attributes explicitly.

What I'm going to do is I'm going to define an object, and I'm going to say that there will be a key of id that's going to be a type of Board['id].

Then, I'm going to say that there's also an attribute of name with the type of Board['name'] and an attribute starred, which will be a type of Board['starred'].



The TypeScript compiler is now satisfied, but it still didn't solve the problem.

If I delete my starred attribute, the compiler is going to complain about the fact that I haven't included the starred attribute.

What I can do now is say that the name attribute and the starred attribute are optional.

To do that, I'm going to type this question mark ? to both of them.



This means that I am required to use the id, but I don't really have to add the name or the starred attribute.

If I were to decide that all of my attributes were optional, I could add the question mark to all of them, but also I could use another utility type that's called Partial.

# Utility Type: Partial

With this one, I can say that the argument of this editBoard function can be a Partial<Board> interface.



This means that if I start typing, I get these suggestions for different attributes, but none of them are required.

If I take a look into what editBoard actually does, it seems that it is always going to require body.id.

We basically always need to pass it if we want our API call to work.

# Combining Utility Types

What I can do is that I can actually combine these utility types.

I'm going to keep the Partial<Board>, but then I'm going to add to this type another type that's called Required<>.

In this one, I need to define which types are required, but I'm actually going to pass a Pick utility type, and from my Board, I'm going to pick the 'id'.



Now in editBoard, I am required to use the id, which will be a number.

But I also have the optional keys, so if I want to pass the name, I can pass it, or I can pass the starred say it's, for example, false.

One more thing that is kind of annoying about this is that I have two different types in my editBoard type definition and in the function itself.

Usually, I need to type everything twice to include that both in my custom command, and also in the type definition.

There is a different way of how I can do this.

Basically, I'm going to copy the whole function and define it as a function of its own.

I'm going to call it editBoard, and paste the function in here.



Now, the thing that I'm going to do here is that I'll use this function as an argument into my Cypress.Commands.add function.



Now that I have defined my custom command as a standard JavaScript function, what I can do here is say that this editBoard() function will actually be a typeof our editBoard.



What this means is that TypeScript is going to infer all of the types that are required for this function from this standard JavaScript function, and then they're going to be passed on to the Cypress API, and then the whole function is going to be passed on into the Cypress API.

Everything still works as before.

If I delete my id, it's going to complain.

I get the auto-completion, but in my custom command, I just need to write everything once.



© 2024 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR