در حال بارگذاری ...

Setup Countdown Props0:37

Okay, let's get started. To begin, let's create our wrapper, as we always do, and mount it. Next, what does it do? Well, at the very least, it renders a countdown timer. Now, as we saw, here's the shape it takes. Zero days, zero hours, zero minutes, and then however many seconds, right? So why don't we set the clock using a prop, and we'll do that like this. We'll say, countdown until December 5th, 2017. Something like that. So what we can say is wrapper.setPropsUntil should be equal to anything we want. Why don't we stick with 10 seconds in the future? Now, we can do this with the date helpers, but actually, Moment.js makes this incredibly easy. So let's pull that in, install it, and then we can do something like this. Moment.add(10, 'seconds'). Nice and readable.

Build Template Output1:22

Moment.js makes this incredibly easy. So let's pull that in, install it, and then we can do something like this. Moment.add 10 seconds. Nice and readable. Okay, so now, ideally, it will tick down from 10 seconds to 9 to 8 to 7. So what can we expect to see on the first render? Well, on the first render, it should say 10 seconds. So 10 seconds is the least of which the template should display. Okay, let's give this a run. npm test, and we know it's going to fail because right now, we just have an empty div. Okay, let's switch over to countdown.view. So it sounds like to start, we're going to accept an until property, and then within here, yeah, we'll put each of these in spans so that the user can style them if they want to. And we'll have something like a remaining object so that we can say remaining.days,

Compute Remaining Time2:10

yeah, we'll put each of these in spans so that the user can style them if they want to. And we'll have something like a remaining object so that we can say remainingDays, and then a few more times, then remainingHours, then remainingMinutes and remainingSeconds. Finally, that much left. Okay, so it sounds like we need a computed property called remaining here. So again, notice we didn't write a test that says it calculates how much is remaining. We don't really care about that. We just care about what we see on the page. How we come to that calculation doesn't really matter as long as it's right. So we'll say here computed remaining, and here's how we'll do this. It turns out, speaking of that moment.js library, there is a moment.duration method that will do most of the work for us. It's great. So let's pull it in and then say moment.duration, and if the user gives us 10 seconds into the

there is a moment.duration method that will do most of the work for us. It's great. So let's pull it in and then say moment.duration, and if the user gives us 10 seconds into the future, we're going to parse that into milliseconds. We can do that with date.parse, this.until, and then we want to find the difference. So if we're counting down to November 20th, and it is currently November 10th, then we have 10 days, right? We subtract. The same thing is true here, just in milliseconds. So let's subtract the current date, and that will give us how much time is remaining. Now, this will give us a special duration object where we can then call methods like days, hours, minutes, seconds to figure out how much time is left. It's perfect for our needs. So days, and then hours, minutes, seconds. So hours, minutes, and seconds. All right. So now, in our template, we reference that object and we defer to the underlying

Test Per-Second Updates6:19

We want to reset it. If we give it a run, I bet we get green now. And we do. One passing test. So let's flesh this out, because ideally I want it'll say zero days, zero hours, zero minutes, and 10 seconds. So hours, minutes, but 10 seconds. So we give that another run, and it passes, but it's not clearing out. afterEach, clock, dot, sorry, restore. Okay, one more time. Perfect. Okay, what else does it do? Well, we expect a countdown to reduce every second, right? So how about it reduces the countdown every second. All right, so one more time. We'll put 10 seconds on the clock. And yes, I expect to see 10 seconds, like that. So if we run it, of course, we're still going to get green, because we already tested that functionality. However, why don't we now say, let's advance the clock one second. I don't actually want to wait a second, but I want to act as if it has been a second.

that functionality. However, why don't we now say, let's advance the clock one second. I don't actually want to wait a second, but I want to act as if it has been a second. We can say clock.tick(1000). Now, I would expect it to show nine seconds, right? So let's give that a run, but this won't be exactly the structure we need. Nonetheless, let's run it. It's going to fail. Okay, so it shows 10 seconds, but we expected nine. Well, at the moment, there's nothing that tells Vue to update the DOM here. So maybe what we could do is set an interval. And now you'll remember when we pulled in that sign-in library right up here. Use fake timers. One of the things this method does is it replaces setInterval with a custom version. So that means I could say create, and then we'll say something like setInterval. And how about every single second, we basically want to re-evaluate and update the DOM. Now, in

a custom version. So that means I could say create, and then we'll say something like setInterval. And how about every single second, we basically want to re-evaluate and update the DOM. Now, in Vue, your computed properties are cached. So to the best of my knowledge, there's no easy way to say re-render, just completely refresh. At least not to my knowledge. Instead, we could use a more data-driven, a more reactive approach. So Vue is going to keep an eye on any of the dependencies. And when one of those dependencies changes, well, it will respond to that by re-evaluating and updating the DOM. So what if we did something like this, where we said data, and let's just make now a data property, new Date. Then, down here, we will reference that. We'll still get the same thing, but now every single second, I'm going to update now to the latest date. So when we do that, this will change, Vue will detect that one of these dependencies is different, and in response,

same thing, but now every single second, I'm going to update now to the latest date. So when we do that, this will change, Vue will detect that one of these dependencies is different, and in response, it will re-evaluate and update the DOM to reflect that it is now nine seconds instead of ten. Okay, let's give that a run, but I bet it still doesn't pass. And it doesn't. So notice, yeah, it still says ten seconds, not nine. So here's the issue. We have updated the clock, but Vue hasn't updated the DOM just yet. So this is one of the scenarios during our tests when we'll have to say wrapper.vm on the next tick, on your next batch update for the DOM, only at that point will I assert that it now is nine seconds, like this. So let's bring that up, and I'll let you take a look at that. We put ten seconds on the clock, and we expect to see ten seconds. But then, we're going to fast forward a second, wait for Vue to update the DOM, and at that point, our template

Handle Expired State10:37

Well, once it gets down to two seconds, one second, zero seconds, what should display? Well, maybe in that case, we expect to see something like nowExpired. Now, this will be the exact same thing. So if I say we'll tick forward, advance the clock exactly ten seconds, the countdown timer should run out. But if we give this a run, it's going to fail, because once again, we have to wait for the DOM to update. So let's grab that, paste that in there. All right, give it another run, and yeah, you'll see everything has been reset to zero, but we're not showing nowExpired. Of course not. We didn't show it. So let's say right here, we'll say something like, if we're not finished or not completed, then show the countdown timer. Otherwise, we can say nowExpired. Okay, but completed, we don't have that anywhere. Why don't we put that here? How can we determine if the countdown is finished? Well, we can fetch the remaining time, this.remaining, but all we can

completed, we don't have that anywhere. Why don't we put that here? How can we determine if the countdown is finished? Well, we can fetch the remaining time, this.remaining, but all we can access at the moment is days and hours. What if we had one like total? Now, if we cast the duration to a number, it's going to be the number of milliseconds. So that means I could say, is the total remaining milliseconds less than or equal to zero? If so, it's done. We're complete. So if we scroll up, if we're not complete, show all of this. Otherwise, display now expired. And you know what? Why don't we change this to finished. If we're not finished, then continue showing the countdown. If we are finished, show now expired. So I'm going to give that another run, and do we get green? We do. Now, what else? If we come back, one thing we'd like to do is when we use this component, it would be helpful to hook into when it's completed or when it's finished.

Emit Finished Event12:23

and do we get green? We do. Now, what else? If we come back, one thing we'd like to do is when we use this component, it would be helpful to hook into when it's completed or when it's finished. That way, we could say, call this method on my parent controller in response. So this is, you can think of this sort of as your API to the outside world. So I do want to test for that. How about something like it broadcasts when the countdown is finished. Okay, so how would we test that? Well, a couple episodes ago, we learned how to test against emitted events. So let's do this. Let's imagine that the clock has ticked the full 10 seconds. We'll grab that. Well, if that's the case, it should fire an event called finished. So we'll say right here, expect wrapper.emitted, and the event will be called finished, and that should be truthy. Now, actually, I would say this is a good use case for extracting another helper.

say right here, expect wrapper.emit, and the event will be called finished, and that should be truthy. Now, actually, I would say this is a good use case for extracting another helper function, because it's a little blurry. I kind of want to just say expect event, or expect event fired finished. So this might be a good use case for a helper if you want. Anyways, let's give it a run, and it fails, because right now we don't fire that event. Okay, let's see. Maybe right here is a good place. I could say if the remaining milliseconds is less than or equal to zero again, remember I can't call this because then I'd have a circular method call. So we'll reproduce that, and we'll say this .emit finished. Now, if I give it a run, it should fire that event, and we get green. What else now? Well, if we come back here, it shows an expired message when the countdown has completed. Yes, I want that to default to now expired, but I want that to be

Customize Expired Message14:05

and we get green. What else now? Well, if we come back here, it shows an expired message when the countdown has completed. Yes, I want that to default to now expired, but I want that to be configurable. So let's make one more here, and we'll say it shows a custom expired message when the countdown has finished. Okay, so now we could say setProps and then expiredText. Maybe that's what it will be, because we'll have something like countdown, and the expiredText should be anything you want. Okay, so we'll make that equal to contest is over, something like that. Well, if we do that, we should see this. All right, let's run it, and of course it's going to fail. Right now we're hard coding now expired, so we need to make that configurable. Right here, expiredText, and if we come up here, well, yes, we could spit that out like this, but at no point are we setting a default. So one will pass and the other will fail. Yeah,

write a test to confirm that we do clear that out? Right down here, it clears the interval once completed. This gives us a little more awareness of the underlying code. So it's not ideal, but I still think it's a useful test to write just to make sure we always clear that out. So consider this. We will put 10 seconds on the clock. We will then advance it 10 seconds, so that will finish the countdown. Well, take a look. console.log wrapper.vm.now. So that will be a DateTime instance, right? Let's see. Yeah, we can see there's exactly 10 seconds on the clock, and we could even say getSeconds, and that will tell us exactly 10, which is what we expect. So what we could say is expect the number of seconds to be 10, and that will give a screen. We're still not testing what we want, but that's our setup. Yeah, that's green. Next, we'll say on the next update, we will advance the clock further. So let's do it another, how about, 5 seconds at this point. Well, now I don't expect

Refactor With Test Safety24:22

So now remaining will return that moment duration object. So let's see what that would look like. Get rid of that. Return remaining here. And yeah, I mean, we have tests to back us up, so we'll know if there's any issue. Ah, and it fails. Okay, it looks like the problem is right here. We're assuming a total value, but it's gone now. Let's just cast it directly. Give that another run. And what do you know? Everything works. So that was a good refactor. So that means if we come back, yeah, this all works great. Maybe you even decide, eh, maybe we don't need a finished computed property. But without tests, you sometimes don't do those refactors, right? Because you think, ah, I don't want to screw anything up. For example, maybe you think, oh, I could just do this and then come down here and get rid of this. Yeah, sometimes you won't even do something as simple as that because you're thinking, oh, I don't know if this breaks anything.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟