Blog header image

Rebuilding my invoicing application in Laravel, Final - Clients Create

Posted on Dec 7th, 2017

The last post I'll make in this series is about adding clients. What I want is to have a "Add/Create" link that shows a modal with a form to add a new client. Then update the list with a new client. First, I'll add a "Create Client" element to our test page class and add a method for testing the functionality. `tests/Browser/Pages/ClientsPage.php` ```php /** * Get the element shortcuts for the page. * * @return array */ public function elements() { return [ '@create-client' => '.create-client' ]; } /** * Create a new client * * @param Browser $browser * @param $attributes */ public function createClient(Browser $browser, $attributes) { $browser->click('@create-client') ->whenAvailable('#modal-create-client', function ($modal) use ($attributes) { $modal->type('title', $attributes['title']) ->type('email', $attributes['email']) ->type('address_1', $attributes['address_1']) ->type('address_2', $attributes['address_2']) ->type('city', $attributes['city']) ->type('state', $attributes['state']) ->type('zip', $attributes['zip']) ->type('phone', $attributes['phone']) ->press('Save'); }); } ``` Next, I'll create the test. `tests/Browser/ClientsPageTest.php` ```php /** * @test */ public function user_can_create_a_client() { $attributes = factory(Client::class)->make()->toArray(); $this->browse(function(Browser $browser) use ($attributes) { $browser->loginAs($this->createUser()) ->visit(new ClientsPage) ->createClient($attributes) ->waitFor('td.title'); $this->assertEquals($attributes['title'], $browser->text('td.title')); }); } ``` I want the test to visit the page, create a new client and then verify it updated the list on the page. Now, I'll run the test and look for a "missing" selector error. ```console php artisan dusk --filter user_can_create_a_client Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body .create-client"} ``` Yep. Ok, I'll create this element in the Vue component and rerun the test. `resources/assets/js/components/Clients.vue` ```html ``` ```console php artisan dusk --filter user_can_create_a_client Facebook\WebDriver\Exception\TimeOutException: Waited 5 seconds for selector [.modal-create-client]. ``` It looks like the test timed out since the modal never made an apperance. To set this up, I'll use UIkit's default modal and add it to the component's template. `resources/assets/js/components/Clients.vue` ```html ``` I could use UIkit's default JS to open the modal, but my preference is to trigger it manually with the component's method. This way I can handle a pre or post modal process such as loading data or building elements. Vue makes this super easy. I'll update the link with the `v-on` directive shorthand plus the "prevent" event modifier. `resources/assets/js/components/Clients.vue` ```html Create ``` Then create the method to display the modal. `resources/assets/js/components/Clients.vue` ```js methods: { showModal: function() { UIkit.modal('#modal-create-client').toggle(); } } ``` Now the test. ```console php artisan dusk --filter user_can_create_a_client Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body #modal-create-client textarea[name='title']"} ``` Ok, this is great (although it's error). It appears the tests are working correctly and guiding me in the right direction. I don't have any fields in the modal so the test is not able to type the title. I'll go ahead and add the form with the input elements. `resources/assets/js/components/Clients.vue` ```html ``` ```console php artisan dusk --filter user_can_create_a_client Failed asserting that two arrays are equal. ``` It looks like the form is now gettings filled out and submitted but nothing happens. Well, I'm not doing anything with the data. Here I will use the "create" endpoint to create a new client and then repopulate the list of clients. I'll handle the submit event in the Vue component and pass the data to a method for interacting with the endpoint. `resources/assets/js/components/Clients.vue` ```html
``` ```js processForm: function(event) { let form = $(event.target); axios.post('api/clients', form.serialize()) .then(response => { this.clients.push(response.data); }) } ``` ```console php artisan dusk --filter user_can_create_a_client 1) Tests\Browser\ClientsPageTest::user_can_create_a_client Failed asserting that a row in the table [clients] matches the attributes { "title": "Abbott-Corwin", "address_1": "33631 Christopher Forks Apt. 313", "address_2": "16005 Beier Run", "city": "East Fernando", "state": "NE", "zip": "56419", "phone": "+1.936.417.3566", "email": "homenick.reyes@hotmail.com" }. The table is empty. ``` This feedback is not that helpful and there is nothing showing up in `laravel.log`. So now what? Fortunetly, Dusk also captures the console log for possible JS errors. `tests/Browser/console/user_can_create_a_client-0.log` ```text [ { "level": "SEVERE", "message": "http:\/\/invoicing.dev\/js\/app.js 38942:14 \"[Vue warn]: Property or method \\\"event\\\" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.\\n\\nfound in\\n\\n---> \\u003CClients> at resources\/assets\/js\/components\/Clients.vue\\n \\u003CRoot>\"", "source": "console-api", "timestamp": 1508179206733 }, { "level": "SEVERE", "message": "http:\/\/invoicing.dev\/js\/app.js 48870:31 Uncaught TypeError: Cannot read property 'target' of undefined", "source": "javascript", "timestamp": 1508179206735 } ] ``` It appears that I used the wrong variable when sending the event. I used "event" instead of "$event". After making that fix it passes the test!