تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Starting TDD Workflow0:00

Okay, let's jump into the controller and... Luke! Luke! Write the tests, Luke! Write the tests! You know what? He's right. Let's use TDD, Test Driven Development, to build the post index out. Instead of creating the actual implementation, we'll write the tests first and then slowly work to build the implementation up, ensuring that we have tests backing us, allowing us to refactor going forwards. Let's dive in. We'll need to create a new directory under Feature, so let's call this directory Controllers,

Writing Inertia Component Test0:34

We'll need to create a new directory under Feature, so let's call this directory Controllers, and inside Controllers, we'll create a directory called PostController, and inside there, a file called IndexTest.php. Now, this is how I like to structure my tests, but you might have a different way. That's absolutely fine, because it's easy to maintain going forwards, and you know very quickly where the relevant tests are related to your code base. So, we'll open with PHP tags, and what will our first test be? Well, I like to check that the correct Inertia component is actually being returned,

So, we'll open with PHP tags, and what will our first test be? Well, I like to check that the correct Inertia component is actually being returned, so let's say it should return the correct component. Alright, we'll create a closure, and inside this closure, we probably want to go ahead and get the Posts.index root, and we can use the AssertInertia method in order to check the actual response that Inertia gives us. So, AssertableInertia, and we'll call that $variableInertia, and I believe there's a method on this AssertableInertia class called component where we can check for Posts/Index, and a Boolean that we can pass in order to ensure that the file actually exists.

Adding Posts Index Route1:42

where we can check for Posts forward slash Index, and a boolean that we can pass in order to ensure that the file actually exists. Not only are we returning the correct string, but it actually also exists as a view file in our code base. Let's run the test. Of course, it fails, and we would expect that. This is test-driven development. So, it's saying that the root Posts.index is not defined. Let's go ahead and open up our web.php file, and we have this Auth Sanctum group here,

Let's go ahead and open up our web.php file, and we have this Auth Sanctum group here, but anybody should be able to access the Posts.index, including guests. So, I'm actually going to come down below that, and as a separate route, we'll create the forward slash Posts endpoint, which points to the PostController and, of course, the index method, and we'll give that a name of Posts.index. Let's go back to our test and rerun. Okay, now it's saying not a valid Inertia response, so we have a different error message, which is great news.

Okay, now it's saying not a valid Inertia response, so we have a different error message, which is great news. That means we can go into our PostController, and we can start implementing it here. We could say return Inertia, and specifically the Posts/Index page, and we can pass properties, but this is TDD, so I'm not going to do that just yet. Let's rerun the test. Okay, still failing with not a valid Inertia response.

Let's rerun the test. Okay, still failing with not a valid Inertia response. That's interesting. Let's go ahead and dump and die here. Okay, so it's reaching the controller okay. Ah, hold it. Let's go into the terminal, and let's run npm run dev in order to boot up the Vite development server, and let's rerun this once more, and now we get a different error message.

Disabling Vite in Tests3:13

and let's rerun this once more, and now we get a different error message. So this issue is being caused because currently, Vite has to be booted for Inertia to be able to complete the request. We definitely don't want that. I don't want to have to build my assets every time I run the test suite, so I think there's a way around this if we jump into the test case,

so I think there's a way around this if we jump into the test case, and this will work for phpunit or pest. We'll override the setUp method, and I'm going to keep parent::setUp because that actually performs some very important setup work for Laravel, and then I'm going to call this without Vite. When I call this without Vite, if I now go back to my test and run it again,

When I call this without Vite, if I now go back to my test and run it again, yeah, sure enough, we have the page component does not exist error instead of the invalid Inertia response error that we received before. Moving forward, we need to create that view file, so we'll go to resources/pages. Let's go and create a new directory called posts, and in there, a new file called index.view.

Let's go and create a new directory called posts, and in there, a new file called index.view. Of course, that will need a template. Rerun the test, and we have our first passing test, which means we successfully TDD'd the very early workings of the post index. I would actually expect if we run our dev server again and then jump into the browser and go to /posts that we receive a 200 response, and if I reload the page, take a look at all,

Refactoring Guest-Safe Layout4:34

that we receive a 200 response, and if I reload the page, take a look at all, yeah, sure enough, we are receiving a 200 response here, but of course, there's nothing on the page because it's just a blank piece of HTML at the moment. So let's go ahead and fill this out. We need some form of app layout, and if we go to layouts here, Jetstream actually ships with a nice app layout that will need some tweaking, but it's a good start.

Jetstream actually ships with a nice app layout that will need some tweaking, but it's a good start. So let's go and fill this with app layout, and let's just say hello world here in order to make sure things are working correctly. Okay, so this fails because it's trying to read profilePhotoUrl from the logged in User, but we want this layout to work if you're a User or a guest, so we need to handle that inside the app layout.

but we want this layout to work if you're a User or a guest, so we need to handle that inside the app layout. Now, if you're not interested in the front-end portion of this series, just skip ahead five minutes or so, and we'll get back to normal coding, but if you want to stick around and see how I tackle this, then it could be a good learning experience for you, especially if you're using Jetstream. Let's jump into the app layout and begin to tear this to pieces.

especially if you're using Jetstream. Let's jump into the app layout and begin to tear this to pieces. We have title defined. We'll keep that in place, and we want all of these things in place, and they're closures, so they're not being executed immediately anyway. We can skip the head and the banner. We have the primary navigation menu. We'll come back to this in a moment,

We have the primary navigation menu. We'll come back to this in a moment, especially the nav links. I want to refactor how that works. We have a teams dropdown here. Well, we're not using teams, so I'll just remove that completely. Then we have the settings dropdown, and this is where it's calling the profilePhotoUrl property. So I'll tell you what.

and this is where it's calling the profile photo URL property. So I'll tell you what. Let's introduce a v-if, and inside this v-if, we can just say, only show this if page.props.auth.user is not equal to null. Okay. We're still getting an error here, and I imagine that's because there's also a hamburger menu for mobile. Sure enough, there is.

for mobile. Sure enough, there is. Here's the hamburger menu. Here's the navigation menu for that, and, yeah, it's this little bit here, responsive setting options. So let's go ahead and wrap this in v-if as well. v-if page.props.auth.user. Come back. Cool.

Come back. Cool. We now have valid Chrome. We have a dashboard here, which obviously takes us to login because that's behind authentication, and we don't have the profile dropdown because we are a guest. It would be good if there was a little login link over here that we can click, so I'm going to come back to the top.

that we can click, so I'm going to come back to the top. I'm generally going to avoid worrying too much about mobile responsive for now. We'll sort that out at a later date, and down here, I'm going to wrap a div with v-ls and drop in a link with a href to the root of login. If you're wondering where the root helper comes from, by the way, that's another nifty little gesturing feature. It allows you to use the root syntax that you're used to in php.

that's another nifty little gesturing feature. It allows you to use the root syntax that you're used to in php from the front end. So much nicer than manually entering URLs and then having to find and replace them all if you happen to change them down the line. Let's enter login here. Sure enough, here is our login link, and, yeah, we can now go straight to the login page from anywhere if we are a guest.

and, yeah, we can now go straight to the login page from anywhere if we are a guest. Just to check that that's the case, let's log in as test at example.com with a password of password, and now we have our settings drop down. Awesome. I'll log out again. We'll go straight back to posts, and let's now think about the menu items that we show at the top.

We'll go straight back to posts, and let's now think about the menu items that we show at the top. Currently, we show dashboard, but it makes a lot of sense that the post index is also a top-level menu item, and, of course, it doesn't make sense that we would show dashboard for a guest user. Now, we could do all of this inside the HTML over here, but I think it makes much more sense to extract this to its own array in JavaScript.

but I think it makes much more sense to extract this to its own array in JavaScript and handle it using a loop. So let's come to the top and define our menu items, const menu equals an array, and we'll create an array of objects. So let's handle dashboard first. We'll have a name of dashboard. We need a URL. Well, the URL can use the root helper to grab dashboard,

We need a URL. Well, the URL can use the root helper to grab dashboard, and in order to check if we're on the current URL, maybe we could also output the root, which is just dashboard on its own, and finally, we only want to show this if you are logged in, so why don't we add a little when property, which is a closure that simply says use the current page, grab the props, and I need to check if auth.user actually exists.

use the current page, grab the props, and I need to check if auth.user actually exists. By the way, because we're not in HTML, we have to use this usePage item, but if you're in the HTML, you can reference the $page variable instead, and it will work just the same, just in case you're wondering why I'm interchanging between the two. Okay, there is our first menu item.

why I'm interchanging between the two. Okay, there is our first menu item. Let's drop in a second menu item. This one is instead going to take you to all posts, which means we're actually pointing to posts.index, and we don't need when because we're always going to show this item. So with our array in place, we should now be able to refactor what we have down here. So we could do a v-for.

we should now be able to refactor what we have down here. So we could do a v4. Of course, we want item in menu, and let's set a key up to be item.name. I'm going to refactor this to put the attributes on separate lines, and we can use a v-if statement where we can check, well, does the item have a property called when? If it does, let's go ahead and call it,

does the item have a property called when? If it does, let's go ahead and call it, so item.when. Otherwise, we're always going to return true. Let's change the hardcoded name here with item.name. For the href, well, we'll change that to item.url, and for active, we want root.current, but then we're going to check item.root instead of item.url or hardcoding. What does that look like?

instead of item.url or hardcoding. What does that look like? Okay, we have an error. So this is an issue with using v4 and v-if together. I think we need to extract a template here where we do the v4 on the template instead, and we'll place the key on the template as well. We'll wrap the nav-link in that template, and then on the nav-link, we can still use v-if like so.

and then on the nav link, we can still use v-if like so. Okay, does that work? Yeah, sure enough, it does. Happy days. So now we show posts, but if we go and log in, so test.example.com and password, we see dashboard, and we see posts. So what we're left with is a nice layout,

we see dashboard, and we see posts. So what we're left with is a nice layout, a nice structure that we can use on any page. It provides us with all the Chrome we need, and it works for both guest and authenticated users. So a great starting place, and it's important you just take the time to build up these components so that you're not repeating yourself over and over as you build the application out over time.

Testing Posts Data Prop11:29

so that you're not repeating yourself over and over as you build the application out over time. With our page loading, why don't we expand on our tests, and we'll add a second one that checks that the correct Post objects are actually passed to the view. So it passes posts to the view. And again, we'll open with the closure body. We want to make a get request to the post index,

And again, we'll open with the closure body. We want to make a get request to the post index, and we'll be using assertInertia again. So I'll basically copy and paste the existing test, but I'll remove this component call here. This time, I'm going to check for the existence of a property called posts. Let's run it. It doesn't exist at the moment. Jump into the PostController,

It doesn't exist at the moment. Jump into the PostController, and in this array here, I'm going to add a property called posts. Of course, I can make this pass simply by setting it to null, and it should work, which it does. Normally, I would now expand on this, and I might dive in and start picking the posts property apart to check for the right keys.

and start picking the post property apart to check for the right keys. But in the back of my mind, I'm thinking that we're going to change this very, very shortly anyway. So I'm going to hold off on that until I finish my prototyping. And once I'm happy with it, I'll head back and update this test here. So all we have to do is jump into the PostController,

Rendering Posts List12:38

I'll head back and update this test here. So all we have to do is jump into the PostController, and for now, now again, for now, this is not staying. I'm going to pull in posts, and I'm going to pass all the posts down into the view. So back in our view component, let's go ahead and define the props. So define props, and for now, I'm just going to use a simple erasing text to grab the posts property that we've just created.

and for now, I'm just going to use a simple erasing text to grab the posts property that we've just created. Up here, we can now use a ul, so an unordered list. We'll create a li (list item), and let's loop over each post in posts. And what do we want to output? For now, I'm going to keep it super simple. I'm just going to output the post title. Of course, we'll probably want to add a key to this as well. Let's do that now.

Of course, we'll probably want to add a key to this as well. Let's do that now. So the key is equal to post.id. Let's come back and refresh. And as we'd expect, we now have all of the post titles from every single Post in our database being displayed on this page. Why don't we style this a bit so it's nicer to look at, and then we'll talk about what our next steps are.

Creating Reusable Container13:37

Why don't we style this a bit so it's nicer to look at, and then we'll talk about what our next steps are. I think I want to pull this in from the sides a little bit to add some form of container. Now, JetStream already is doing this itself, so let's see how it does it. Profile show, and it's this line here. This is what they're using. Now, I want to be able to easily reuse this container everywhere, so I'm going to create a new component, a new file,

Now, I want to be able to easily reuse this container everywhere, so I'm going to create a new component, a new file, and let's call this container.view, and we'll add the template. And then inside there, we'll simply drop this div, and we'll enter a slot in the middle like so. Easily reusable container. So back in our index, let's pull in the container component, and we'll wrap it around the unordered list here. That should pull everything in from the sides,

and we'll wrap it around the unordered list here. That should pull everything in from the sides, which it does very nice. Now we can go and add classes to the unordered list. So why don't we divide y to add a little bit of spacing there, and then on each list item, we could add a little bit of padding on the sides, maybe px-2 and py-4. What does that look like? Okay, that's already looking much better.

What does that look like? Okay, that's already looking much better. Of course, the list item itself, the title, probably needs to be a little bolder so that you know that it is a title. So why don't we wrap that in a span, and I'll add our tailwind classes directly to this. So font-bold, maybe text-large, and there we go. We have an ugly index of forum post titles. There are a few things I want to add,

We have an ugly index of forum post titles. There are a few things I want to add, and obviously we'll style this further. For example, the excerpt of each post, the author's name, the publish date. However, I don't want to get ahead of ourselves because there are a couple of glaring issues we need to resolve first. Otherwise, we're going to be spending ages refactoring this down the line.

Planning Resources and Pagination15:24

Otherwise, we're going to be spending ages refactoring this down the line. So the first thing I want to think about is the structure of our data. Currently, we're allowing Laravel to handle everything for us. And the problem is, we could be leaking sensitive information to the view. So we need to resolve that, and I'm going to turn to Eloquent's API resources.

So we need to resolve that, and I'm going to turn to Eloquent's API resources in order to control how our data looks and feels. The second thing we need to do is handle pagination. Currently, we're passing every single forum post down to the view. If we have tens of thousands, hundreds of thousands of posts, this page is going to come crashing to a halt. So there we go.

this page is going to come crashing to a halt. So there we go. That's the topic of our next two episodes, API resources and pagination. See you there.

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