Scaffold Question Spec0:00
Alright, let's move on to a new example. We're going to use this concept of a question. Think of like a form thread or something like that, where you can view the user's question, but if you have permission, you can click an edit button, and that will then display a form to update the fields. So let's see how we might drive out a component like that. Alright, if I go to question.spec, it's blank. So this time I'm going to use a little snippet that I have here. We'll call it question. It'll just scaffold out some basic stuff that we've been using quite a bit. Alright, so very first thing, what does it do? Well, at the very least, it should show the title and the body of the question. So we'll say it presents the title and the body. This will get us started. So why don't we say we have our wrapper, our snippet mounted that for us. We could also use a shallowMount there. We're going to talk
and the body. This will get us started. So why don't we say we have our wrapper, our snippet mounted that for us. We could also use a shallowMount there. We're going to talk about that in a later episode though. Anyways, we'll say wrapper.html, and I'm going to expect that to contain, how about, the title. Alright, so we're going to have to set up some kind of question and pass in the props. Alright, so at the moment, if we give npm run watch to compile this down, and here we go. So right now we just have an empty div, but we expected to see the title. Of course we did. Alright, so we'll go right here. And yeah, it sounds like we're going to have an h1 or something where we will bind maybe question.title. A question could be, you know, something maybe coming from your database that has a handful of columns. So let's say we're going to pass that in as props, and that will accept a question. So let's give
Render Title and Body1:27
you know, something maybe coming from your database that has a handful of columns. So let's say we're going to pass that in as props, and that will accept a question. So let's give that another run, and it's still going to fail, because now we're expecting this question prop, but we didn't pass anything in. So the way we can do that is right here. We can use props.data to pass that in. So we want a question, and we'll just hard code it. We'll say title is the title, and body is the body. So we'll give that a save, and now we have a passing test. Does that all make sense? So we passed in this data as a prop, we accepted it, and then we echoed the title within an h1. Next, we need another one here. So we'll say expect the wrapper to contain the body. So all we're doing is saying I expect to see this on the page or in the component. So now that one fails. So we'll do that within maybe a div with a class of
Add Custom Test Helpers2:12
the wrapper to contain the body. So all we're doing is saying I expect to see this on the page or in the component. So now that one fails. So we'll do that within maybe a div with a class of body, and then we'll bind that to question.body. Give it a run, and we're back to green. But now let's talk about helpers real quick. You're going to see we do this quite a bit, and it's kind of bloated in my mind. Expect wrapper.html to contain text. What are we really saying there? We're really just saying I want to see the title. That's all we're doing here. So it'd be nice if I could just do that. All right. Well, if we run that, of course it fails. So what we could do is set up some global helpers within setup.js, or to start, let's just add it right here. So we're going to have a little helper function here called see, and that's going to give us the text we want to see, and then maybe a nested selector if we want. So that way I could say
So we're going to have a little helper function here called see, and that's going to give us the text we want to see, and then maybe a nested selector if we want. So that way I could say see foobar within this sub div, or if I leave it off, it'll just look for foobar within the main wrapper, the root wrapper. So let's say wrap will be, do we have a selector? If so, it will be wrapper.find, the selector. Otherwise, it'll be wrapper. Then we could have our assertion here. expect wrap.html to contain the given text. So if I save that, we're back to green. So yeah, this ends up reading so much better to me. Okay, let's continue on. What else should it do? Well, what about for the question, it can be edited? So it sounds like we need to click a button. So how about wrapper.find a button called edit, and we will trigger a click on that. So if I just save that as is, it's immediately going to fail. So let's go ahead and add that now.
Toggle Edit Mode Form3:55
button. So how about wrapper.find a button called edit, and we will trigger a click on that. So if I just save that as is, it's immediately going to fail. So let's go ahead and add that now right down here. So we'll say button with an id of edit and text of edit. Okay, now that's back to green. Anyways, if we find that button and we click on it, now we expect to be presented with a form. So let's say wrapper.find an input with a name of title, and we're going to expect that.element.value. We learned about that an episode or two ago, and that should be the title of the thread or the question. So if we run that, now it's going to fail. All right, so we need an input. So it sounds like we're going to have two different presentations, one for viewing the question and then one for editing the question. So right now you can see VS Code is blowing up, and that's because it expects one root element. So let's wrap this whole thing like so,
question and then one for editing the question. So right now you can see VS Code is blowing up, and that's because it expects one root element. So let's wrap this whole thing like so, and then indent. Okay, so we'll say here viewing the question, and then we'll have another one here for editing the question. So we said here we're going to need an input with a name of title, and we're going to bind that. Well, to start, to question.title, we're binding to a prop, which I'd prefer and we shouldn't do. But to get us to green, that seems to work. However, let's continue back. It can be edited. So we're going to find that edit button, and when we trigger click, we expect to see this. But you know what? Even if we didn't trigger a click, we're still going to see it, as you see there. So maybe we could have two sections here. How about expect wrapper.contains this input to be false. Okay, now it's failing. So by default,
we're still going to see it, as you see there. So maybe we could have two sections here. How about expect wrapper.contains this input to be false. Okay, now it's failing. So by default, we don't expect to see it, and that should fail with that line alone. So how about this? We'll say v-if. If you're not editing the question, then we want to show you the question. You know, we want to present the general question. But if you are editing the question, then we should see the form to modify it. Now, here you might be thinking, well, you can use v-if, or you could also just say v-else, like so. I think that's fine. Sometimes, though, just be a little careful, because if you have huge blocks of HTML, you're going to see v-else, and you're not going to know what else refers to, and you're going to have to scroll up 50 lines to figure out, oh, we're saying otherwise if we are editing. Sometimes, if that's the case, just do v-if.
So now let's say, when you click on me, editing will now equal true. And I bet we get green here. Yeah, so does that make sense? We didn't see any of this. But then we clicked on the edit button, and we updated this editing property to true. So now, this evaluates to true, at which point this renders, and we can see the input. So now this is passing. And let's also make sure we have a text area with a name of body, and that should be the body. And I bet that will fail. We'll make this guy pass real quick. Name is body. Let's hide that. And we'll set v-text to question.body. And now we're back to green. Okay, great. Let's move on. How about it hides the edit button during edit mode? Does that make sense? So we're saying, yes, I want to show an edit button. However, when you click on that, we shouldn't see it anymore. It's superfluous. There's no reason for them to see that. So we expect that to be hidden. And now, at this point, the way we have
However, when you click on that, we shouldn't see it anymore. It's superfluous. There's no reason for them to see that. So we expect that to be hidden. And now, at this point, the way we have everything set up, that will already work. But just to make sure, even if we change our HTML structure, this still works the way we expect. Yeah, let's try to test for that. So we'll say wrapper.findEdit, trigger click, and then we'll say wrapper.findEdit. Well, I'm going to expect a wrapper, I'm sorry, that contains edit to be false. Because I don't expect to see an edit button at this point. Yeah, and now it works. Let's do another one. How about it updates the question after being edited? Okay, so you see the question, you click the edit button, you change the text area, you change the input. Well, now once you update, you click a button, well, yes, in real life, you would expect an AJAX request to be sent, which we will review.
Test Updating Question9:00
you change the text area, you change the input. Well, now once you update, you click a button, well, yes, in real life, you would expect an AJAX request to be sent, which we will review in a future episode. But until then, at the very least, those changes should be reflected in the main question that we present. So why don't we say, well, let's edit the question. And then, well, we're going to find the input. And you're going to see that this is a little painful to do. So we're going to feel that pain and then extract a helper function to make it easier since the API doesn't provide that for us natively. So we're basically going to say find the input with a name of title. And then we're going to set the element.value equal to changeTitle anything. We just want to say find that input and update its value to changeTitle. But now we want Vue to recognize the change. At the moment, we've just updated the property. So the way you have to
anything. We just want to say find that input and update its value to change title. But now we want Vue to recognize the change. At the moment, we've just updated the property. So the way you have to do this is say, well, find that input and trigger the input event so that Vue can pick up on it. So what you're going to see is this is kind of painful to do for every single input. And then you can imagine for a big form, it's even more annoying. So here, like we could say, find the textarea with a name of body and set its value to change body. And then once again, we have to trigger the input event. So this feels rough to me and it's not very clear. Instead, I kind of just want to say type change title into the input with a name of title. It'd be nice if I had a little abstraction like that. So let's make it a thing. We'll say let type is selector and text. And now let's just steal
one for the text area. And that will be change body. And now I can get rid of that. And immediately, look how much more clear this is. Okay, so type into that and then click the button. So wrapper.find update.trigger click. And then once again, even this is a little painful. And I'm starting to see that we click buttons all over the place. So here's yet another use case. And remember, we can reuse these functions across all of our files if we move them into setup. So we'll say let click the selector. And all we have to do there is say wrapper.find the selector.trigger click. Yeah, these are little wrappers, but they really do help. So now this can just become click the update button. So now once we click the update button, we expect everything to remain in sync. We should no longer see the form. We should just see the original title and body. So now we can once again make use of these C calls like so. Okay,
Refactor Props Into Form12:58
that one up. Of course, this should now say changedTitle and changedBody. And there we go. We're back to green. However, since we're at green, I want to do a little bit of a refactor. At the moment, we are using v-model on a prop. And generally, you want to think of props as being immutable. So in these situations, what I will often do is initialize it, but then make its default value equal to what was passed in from the prop. And often, I will call that data and then the name. So something like dataQuestion. So basically, you'd say dataQuestion equals your data. And then we will assign that to this questionData property. All right. But now everything's failing. And that's because the prop is now called dataQuestion, which means we need to come up here and update this. And I think that'll bring us back to green. And yeah, that's just a better practice there. Now, I will tell you, in this case, when you click update, that would be the point
form. That's basic view reactivity. So it sounds like the form. Yeah. If we need to switch back to the initial data. Now, I can think of a couple of ways to do this. One, though, maybe we can make the form inputs its own thing. So let's see. We're going to create a dedicated cancel method. Like so. And yes, as part of it, we need to turn off editing modes. There we go. But next, we want to reset the title and the body. So, yeah, what we could maybe do here is have our form and the default title will be this data.question.title and the default body. So we'll just read those off. Then in our form right here, we can bind to those instead. So I could say bind to form.body and form.title. Yeah, now we're back to green because this is bound to a form and this is bound to the presentation. Okay. So let's bring this back. And other should fail at this point. Updates the question after being edited. Yeah,
bound to a form and this is bound to the presentation. Okay. So let's bring this back. And other should fail at this point. Updates the question after being edited. Yeah, now that's a new thing. So you clicked on update, but now these fields are no longer bound directly to the form. So let's say here we'll call an update method. And once again, editing will be set to false. But it sounds like we also need to say, okay, well now this question.title will be what you typed into the form. So this is like our way of committing it. And now we're back to green. Everything's passing. Now, this will end up being a good way to go because in reality, when you submit your AJAX request using something like Axios, yeah, you can say Axios.post to your endpoint, and then you would just pass through the form's data. So you have a nice container for all of your form attributes. So now we'll
these cases, it's useful to have a before and after just to be explicit. Well, before this event, I expect this to be true. But after that, I expect it to be false. We still get green there. All right, it updates the question after being edited. So if I click on the edit button, and I type in a new title and a new body, and I click the update button, well now I expect to see those represented in the question. Finally, it can cancel out of edit mode. So if I click edit, I type in a new title, but then I click cancel instead of update. Well now, well yeah, I expect to see the original title and not the new title. And then you can see these little helpers that we added. All right, so that'll do it for this episode.
