Check out a free preview of the full Enterprise UI Development: Testing & Code Quality course
The "Interacting with the DOM" Lesson is part of the full, Enterprise UI Development: Testing & Code Quality course featured in this preview video. Here's what you'd learn in this lesson:
Steve explores the screen and fireEvent methods from Testing Library. The screen method provides APIs for querying the DOM and selecting elements based on different conditions. The fireEvent method dispatches common DOM events like click or change.
Transcript from the "Interacting with the DOM" Lesson
[00:00:00]
>> So then we've got this idea of should increment when the increment button is pressed, all right? We're gonna need to know some things here. So, yeah, we should, let's render the counter. Now, if we were when we debugged it before, I actually had an idea, let's do something.
[00:00:24]
Let's cancel this and let's do dash dash UI. And we should [SOUND] I got to do the dash dash, dash dash UI, boom, okay? And so there you can see we have not run this test yet. We have the passing one, we have the one that's to do.
[00:00:48]
And let's go ahead, and we'll type in screen.debug, and you'll see why I like this. In the console, okay? It's a little on the big side, and we can the syntax highlighting leaves something to be desired. But generally speaking, you can kind of see the shape of the DOM.
[00:01:15]
We've got this increment button here, as well. Yeah, the syntax highlighting leaves a little bit to be desired. But generally speaking, we get a general shape of our DOM. And I'm wondering if this is just my dark terminal theme or something along those lines. I'm not totally clear, and we can get a sense of it.
[00:01:39]
Now, one thing you'll notice is that I decorated some of these things with, like, current count as a test ID. These are useful for like finding stuff in the test. Could you use classes? Sure. Could you use IDs? Absolutely. Do those maybe mean things to your design buddies?
[00:01:58]
Yes. Can they change? Do some modern tooling, do everything in their power to obfuscate the class names cuz something CSS and JS? Also, yes. Do you ever have to worry about somebody thinking current count is just a useless class and deleting it versus test I dated, that test I did?
[00:02:18]
I think everyone knows what that one was for. So sometimes it's about, like, protecting you from other people. Cool, so I've got that in place. The other thing you can always do is also spin up this app and just use the Chrome tools to inspect it, right? That's the thing.
[00:02:34]
>> What can I answer this, again, I've been trying to.
>> Dash dash UI, now if you use NPM tests, and you want a dash dash flag is to be npm test dash dash and then the dash dash flags.
>> Those extra dash dash.
>> Yeah, let me show that cuz those words seemed fine when I was saying them, and then it occurred to me that doesn't make any sense if you weren't literally the one saying it.
[00:02:53]
So yeah, there's an extra, and I think you'll avoid that if using pnpm. And if you just did npx vitest, you don't need that. But with the NPM test you do need to say like, and I'm gonna give you some flags now. Cool. So that will run in that place.
[00:03:10]
So we knew that current count was the current count. So let's, I should increment the button, but let's start with like maybe I could have written this in the other one but here we are. You can see what I meant to do in the solution if you want, so we'll say now one of the changes you'll see is that like theoretically this will give you a container, but we have this screen cuz container was always the document body anyway.
[00:03:36]
Screen is actually not scoped to anything. It's scoped to document body. And so it's like from the user's perspective, staring at the screen, what do they see? That way, you're not accidentally querying from some child node. So we go, we can say const current count equals screen get by test ID, current count, right?
[00:04:10]
And we'll delete that for a second. The interesting part is, this is where some of the stuff we were saying in the beginning, somewhat behooves us, right? This test passes, this test passes because it doesn't fail. But before that could have been a bad thing. Here is kind of a good thing cuz like, let's throw an extra R in current count.
[00:04:33]
It can't find that node and so it blows up. So the act that you could find it at all, this is a valid test case. We render a counter, cool, that didn't blow up. And then we went to go look for something with the test ID current count with one R, which was totally on the page, right?
[00:04:50]
So we have validated two things in this test as zero expectations, right? Cuz those do blow up if they're not present. So it's not test, it's not doing what the test says in the tin, but it is like definitely exercising and testing our application. So we could say, we could do something like expect(currentCount.textContent).toBe.
[00:05:27]
I had a good test earlier today, it should be zero by default and that passes. The problem is, that if it doesn't pass, because that's not super helpful. It's like zero, is not equal to double zero, okay? One of the things that you can do, and you'll see that I have the auto complete cuz this is a giant file.
[00:05:53]
But I will show it to you kind of separately first and like how we got there, because it won't work otherwise. Is Jest DOM from testing library has a whole bunch of additional matchers like are all DOM related because the regular Jest assumes that you don't have JS DOM.
[00:06:12]
Assume, you don't have happy DOM, assume you don't have anything, so it equals stuff that, Jest DOM basically says all the very DOM related stuff, right? To have text.Content closest, a whole bunch of various stuff unique to the DOM. You can either, if you're pulling it in, you can expand or extend matchers.
[00:06:36]
Also, if you literally, I'll show you each is in this other file over here, which should actually be the one that matters, that's a different one. This is why you don't put seven apps in the same folder, cool in this counter. I have nothing right now. I could actually do this if I just import.
[00:07:03]
I could do this in every individual file that I wanted to as well. But all I have to do is in any file, like, I'm gonna set this up basically to, we'll look to say, like, hey, run this file before we run the test. So it's, like, stuff that you can make available and do stuff if there's any kind of setup that you need to do.
[00:07:23]
One of the things I do is lock the timezone into UTC, cuz it turns out GitHub's machines are in a different timezone than my machine, and you know what's not fun? Having tests that fail because the computer's in a different timezone, and then it is expect extend or extend expect.
[00:07:49]
Expect extend, and we'll find out if I'm wrong. It's one of those things you do once in a code base, so you never actually memorize it. And then in vitestConfig, we will also say setupFiles, and we'll say ./test. I can't find that. Did I spell it right?
>> Don't think it found your expect extend.
[00:08:23]
>> Yep, it could have been cuz it could have been. Let's find out together, extend, expect. Hey, there we go. And now we can go in here, and we can have a much healthier to, and like, that's cool, right? That's actually a different extender that's also installed. IntelliSense is good, but it was not set up for this ridiculous repo that I have.
[00:08:59]
To have attribute, that's one coming from Jest you can see in testing library matchers to have class, right? To have focused very DOM specific stuff, right? So now, I can say to have text content and if you look, we at least get a slightly better error message. I was expecting it to show me the DOM, but it didn't, but we at least get the expected element to have the text content.
[00:09:34]
And you could actually write your own matchers that have, if there's something really unique, I have never had to do that before in my life, but I appreciate that I can. And even the regular to equal, you can put in a custom error message. Again, never done it, I just like that I can.
[00:09:49]
Cool, and we'll save that, and we can see that it passes, all right? So now, our next trick for my next trick. [LAUGH] We would like to do what this test actually says, right? Which is, should increment when the increment button is pressed now press isn't a thing, but click is, and so we could.
[00:10:14]
We need to go find that increment button, and then we need to press it, and then we should expect that it has one. Should I move this to a different test maybe, but I don't want to I kind of liked the idea that hey it's got zero then we go ahead, and we figure out the next thing.
[00:10:34]
So, in testing library, there is a very helpful function called fireEvent, and we will say fireEvent., look, change. No, we don't want change, we want click. Yeah, and there was double click. There's a whole bunch of other ones. Click, and then if we look at the IntelliSense, it gives me an element.
[00:10:58]
So with this one, we'll say screen, and so we've got a bunch of these find alls, that is kinda document query selector all. There's findBy, there's getAll, we'll talk about why that's different in a little bit. There's getByAltText, getByDisplayValue. For a lot of the cases, we do getByRole.
[00:11:24]
What does that mean? GetByRole is basically, again we're thinking about this from the perspective of our users, they don't really know what different DOM elements are. And even the fact of the matter is, we've all been guilty of making an A tag link look like a button before, right?
[00:11:43]
Who amongst us hasn't today, [LAUGH] or this week? But like a lot of those cases, if you care about screen readers, that's cool, because links are cool, that's kind of how the web works, but we would give it the role of a button. So it's like this idea of a spiritually a button, and so I can say button, and then we got to give it something, right?
[00:12:08]
And these are all like things that the browser does by itself, which is if you have a button with this label, its role as a button with this name as terms as a screen reader in this case. And one-
>> It's a DOM's aria-role effectively.
>> Exactly, and to be clear, you do not have to define all these things.
[00:12:29]
If you use semantic HTML, you will get a certain number of these for free. You do not have to be like button, role button, aria-role button. It's a button, the browser knows, relax. So let's go ahead and we'll say const button cool, let's go click that button.
[00:12:52]
And then we should expect that current count has a text content of one and the world is good, and that all happens.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops