Start Reminders TDD Setup0:00
Alright, let's move on to a new example. We'll use TDD to drive out a little Reminders component, where you can add any number of reminders to a collection. So what I have at the moment is the basic skin, where we have our webpack.config.js file, we have all the dependencies that we set up in lesson 1, we have our npm script to trigger our test, but at the moment we don't have any components or any tests. Okay, let's create a new file here, and we'll call it reminders.spec.js, and as always we're going to pull in mount from vue-test-utils. We should also review the shallow option as well, which can be useful for stepping out child components.
We should also review the shallow option as well, which can be useful for stepping out child components. Anyways, next we're going to pull in expect, and now we will describe our Reminders component. Okay, so when you're at this point, generally I'd recommend describe the entire flow. So rather than asserting against the makeup or the implementation details or the structure, instead just think, how do Reminders work? To start we might say, well, if there are no reminders, it should hide the reminders list entirely. So how about this, it hides the reminders list if there are none. Okay, so how would we assert this?
Test Hidden Empty List1:08
So how about this, it hides the reminders list if there are none. Okay, so how would we assert this? Well basically we want to say, if there are no reminders, then I don't expect to see the markup for one. So let's say wrapper equals, and we're going to mount, yeah, this reminders component that we'll need to create. So this doesn't exist yet. So we will put this in the source/components directory, and we'll call it reminders.vue. Okay, so mount that component, and then we'll say, well, we could actually do this in a number of ways.
Okay, so mount that component, and then we'll say, well, we could actually do this in a number of ways. We could use a ref, a reference. We could also just say, wrapper contains the reminders list. I'm going to use a generic tag here, but of course you can be as specific as you need to. So yeah, if we wanted to, we could say, expect the wrapper contains UL, I expect that to be false. It should not contain that. Okay, so let's give this a run.
It should not contain that. Okay, so let's give this a run. Now it should fail immediately because the reminders component doesn't exist. So right here, yeah, it can't resolve that. Okay, let's create that. And it should run again. And here we can see, of course, the template and render function have not been defined. Okay, so this is going to be some kind of div. And then we're going to have our script. And we'll say, export default.
Test Adding Reminder2:44
You can test this at the data level. So you could basically say, well, by default, I expect the reminders list to be empty. But then if I call this method, then the underlying items property should now have a count of one. So that's sort of like testing the underlying implementation, which you can do in some cases, but I would be careful about it. Another option is test the input versus the output. Okay, so we're going to test the presentation, the UI. So from that perspective, we would say, well, if we fill out the input, we type in a reminder, we click the button, now we expect to see that reminder in the list.
So from that perspective, we would say, well, if we fill out the input, we type in a reminder, we click the button, now we expect to see that reminder in the list. So let's try that. We'll build up another wrapper here. And we're going to look for an input called newReminder. And let's set that to a variable called newReminder. And yeah, here we could say, well, newReminder.element.value. So this is new. So newReminder at the moment is a wrapper, right? However, if we want to get access to the underlying element, so the input itself, we can say .element,
So new reminder at the moment is a wrapper, right? However, if we want to get access to the underlying element, so the input itself, we can say .element, and then at that point, you can save on any input with JavaScript. input.value will give you the text associated with the input. So we could set that to go to the store. And then we've changed the value, but Vue doesn't really know about it yet. So we could say new reminder.trigger the input event. And again, this is just a standard DOM event. All right. So now that we've entered text, we could say wrapper.findbutton.trigger the click event.
All right. So now that we've entered text, we could say wrapper.find.trigger the click event. All right. So now that we've filled out the input, we've clicked on the button, what do we expect to happen? Well, how about wrapper.find the unordered list? Again, we're being very generic here. Later, we'll go back and be more specific with a class name or an ID. So what we're going to do is find the unordered list, get its text, so not the HTML itself but just the text portion, and I'm going to expect that, at the very least, to contain
So what we're going to do is find the unordered list, get its text, so not the HTML itself but just the text portion, and I'm going to expect that, at the very least, to contain go to the store. Okay. So that should run behind the scenes. Cannot set property value of undefined. So here's the issue. We're trying to find this input called newReminder, but it doesn't exist. Okay. Let's set that up.
Okay. Let's set that up. So we need an input here with a class of newReminder. So that should run again. Did not return button. So we're trying to click the button, but it doesn't exist yet. All right. And Reminder. Now it should run again. So now it didn't find the ul.
Now it should run again. So now it didn't find the UL. So here's our verification step where we're trying to see that goToStore was added to this reminders list, but we don't see it. Okay. Now we can add it below, like so, and we'll say filter through the reminders. And for each one, we want to set the text of this list item equal to that reminder. Now remember, this doesn't even exist yet, and it's telling me that. So let's go ahead and build that up. And to start, reminders will be set to an array, an empty array.
So let's go ahead and build that up. And to start, reminders will be set to an array, an empty array. But now we're still going to get an issue here. Elements in iteration expect to have v-bind key. So yeah, any time in view that you are filtering through a collection, you need to make sure that you set a key. Now usually you'll set that to the primary key, something like that, the ID. In this case, we'll just set it to the reminder itself. And later, yeah, maybe we'll change that to an ID because this won't always be unique. Anyways, it's okay for now.
And later, yeah, maybe we'll change that to an ID because this won't always be unique. Anyways, it's okay for now. So at this point, our test is still failing in two different places. So hides the reminders list if there are none. Well now you'll see that we display the ul no matter what. What we could say is only display this ul if we have any reminders. So we could check reminders.link. So now if we run it again, only the second test is failing. Vine did not return ul, so we can't call text on an empty wrapper. And that's because, well think about it, right now we filled out the new reminder input.
Vine did not return ul, so we can't call text on an empty wrapper. And that's because, well think about it, right now we filled out the new reminder input. So we filled out this. Then we clicked on the button, so we clicked on that, but nothing happened. So because of that, reminders is still an empty array, so we don't display the items below it. Why don't we say here, click, and we're going to add a reminder. So now here we'll say methods, add. So notice in this case I didn't write a test for how add should behave. In some cases you may want to test a method like that, but in our case it's an implementation.
So notice in this case I didn't write a test for how add should behave. In some cases you may want to test a method like that, but in our case it's an implementation detail how the reminder is added. So I'm not going to specifically test the add method. I'm just going to test what should happen when you click on this button and what should be the output of that. Okay, so here we could say this.reminders, and we're going to push to it the new reminder. But how do I get access to that? Well, this is probably a good use case for v-model. So we'll call it newReminder, and then we'll add that here.
Well, this is probably a good use case for v-model. So we'll call it newReminder, and then we'll add that here. So newReminder, whatever their user types into this input to the newReminder property, and then when they click on the button, we will add that newReminder to the list, and then we can update this. So this.newReminder, and then I will reset that input. Okay, so if we take a look at this, yeah, now we have two passing tests. So what's nice about this is if later I change the implementation, I do something like, we'll leave that blank, and of course it's not going to fail. But if later I say reminders.push newReminder, I take an approach like that, you'll see that
So we have a new Reminder, we set the value, we trigger the input event, so view picks up on it, and then we click the button to add the Reminder. If we want, and if we're going to be using that in multiple places for our tests, we could always add a helper function called addReminder, and then you accept the body of the Reminder. So then you could take all of this, bring it down, and then update the value to be whatever is passed in. And now we have a little wrapper around this operation, which means I could say addReminder, go to the store, and if we come back we should still, whoops, wrapper is not defined. Okay.
go to the store, and if we come back we should still, whoops, wrapper is not defined. Okay. So in this case, we're trying to use the wrapper, but we defined it here. This is probably a good place to do beforeEach, because we're going to need a wrapper for every single test. So why don't we say wrapper equals mountReminders, and that way we'll have access to it. All right, come back, and yeah, now we get two passing tests. And this is just a little bit easier to take in. Anyways, let's continue. So yes, it can add a reminder, but also it should be able to remove a reminder.
Test Removing Reminder9:38
Anyways, let's continue. So yes, it can add a reminder, but also it should be able to remove a reminder. So it can remove any reminder. So we could say something like given we add a reminder called goToTheStore, add another reminder, finishScreencast. If we then remove or delete this goToTheStore reminder, and how could we do that? Well, we'll need a delete button, right? So how about wrapper.find the, let's just say, the very first list item, and then we need a button, and we'll give it a class name of delete.
need a button, and we'll give it a class name of delete. Okay. Well, let's say delete button. We're going to trigger a click on it. So yeah, presumably every single list item is going to have some kind of trashcan icon next to it, and we're going to represent that with a button with a class of delete. So we're going to simulate that we click on that delete button, and when we do, we should no longer see goToTheStore in the output. So I could say expect wrapper.find ul to not contain. So with the expect library, we can proceed any assertion with not.
So I could say expect wrapper.find ul to not contain. So with the expect library, we can proceed any assertion with not. So not to contain goToTheStore. All right. So if we run this, it's going to fail, of course, because there is no delete button. All right. Let's add that. So now, right here, I'm no longer going to set the text. We'll echo it out here. And then we'll have a button.
We'll echo it out here. And then we'll have a button. So that will be a class of delete. How about remove? And again, when you get to the silent stage, this can become a nice icon or a trash icon or something like that. So now that we've added the button, we can see right here, we track that down, we click on it, and now we would expect that to remove goToTheStore from the collection. However, you know what? We called it delete.
This is just a simple explanation with no code.
But notice it does contain goToTheStore when we didn't expect that. And of course it does. That's because right now we have a button called remove, but that doesn't do anything. So now I could say when you click on it, we're going to remove the reminder. So we'll set that here. Now how would we do this? Well, let's send through the current reminder that we want to remove. And then we'll accept that. And now we can use a simple splice operation for this. I could say this.reminders.splice.
And now we can use a simple splice operation for this. I could say this.reminders.splice. So we're going to remove an item from it. And we'll grab the index here, the reminder, and we're going to splice or strip out exactly one item. So if we give this another run, yep, now we have three passing tests. So notice I haven't even yet opened a browser, but I already know this basic functionality works. So when we do view it in the browser, we're just going to be focusing on the visuals there. And we have proof that everything else should work as we expect.
Review Assertions and Helpers12:32
So when we do view it in the browser, we're just going to be focusing on the visuals there. And we have proof that everything else should work as we expect. All right, so that should wrap up our review for this episode. So you learned a handful of new things. You learned how to assert that a given piece of HTML contains anything whatsoever. Now I will tell you, though, be careful that you don't confuse toContain with wrapper.contains. So in this case, wrapper.contains and expect.toContain, those are two different things, and that can be a little tricky. wrapper.contains is going to expect an element name or an element tag name, like ul. In this case, expect whatever you give it, any string you give it, toContain, that's
Wrapper.contains is going to expect an element name or an element tag name, like ul. In this case, expect whatever you give it, any string you give it, to-contain, that's just checking to see does this text occur in that text. It's a different thing here. Now on that note, though, you will see in a couple of different places we are hard-coding how to fetch the RemindersList text. Once again, if it helps, you can extract that out to a helper function. It just depends on how often you will reference it. So you could have a function called RemindersList, and there you could return wrapper.findul and give me the text of it, or leave it off if you don't need that.
So you could have a function called RemindersList, and there you could return wrapper.findul and give me the text of it, or leave it off if you don't need that. But anyways, then we could just say RemindersList not-to-contain-this, and then up here the same thing. And it also makes your test a little bit more readable. So if we come back, we're still getting green. But yeah, notice now it can add Reminders, alright, we'll add a Reminder, and then expect the RemindersList to contain that Reminder. Next, it can remove any Reminder. Alright, add two Reminders, click on the delete button for the first one, and then we'll assert
Next, it can remove any Reminder. Alright, add two Reminders, click on the delete button for the first one, and then we'll assert that we no longer see that Reminder. Now if you want to be careful and assert that it doesn't remove all of them, you could have another one. Expect RemindersList to contain finishScreencast, and we should still get green, and we do. And finally, we talked about the value in writing our assertions and preparing these tests against the generated output versus the implementation details. Because remember, sometimes you have to, but the closer you get to testing those implementation details, the more brittle your code is.
