Rebuilding my invoicing application in Laravel, Final - Clients Create

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.

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

/**
 * 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

/**
 * @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.

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

<template>
    <thead>
        <tr>
            <th>Title</th>
            <th><a href="#" class="create-client">Create</a></th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="client in clients">
            <td v-text="client.title"></td>
            <td> </td>
        </tr>
    </tbody>
</template>
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

<div id="modal-create-client" uk-modal>
    <div class="uk-modal-dialog uk-modal-body">
        <h2 class="uk-modal-title">Create Client</h2>
        <button class="uk-modal-close" type="button"></button>
    </div>
</div>

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

<th><a href="#" class="create-client" @click.prevent="showModal">Create</a></th>

Then create the method to display the modal.

resources/assets/js/components/Clients.vue

methods: {
    showModal: function() {
        UIkit.modal('#modal-create-client').toggle();
    }
}

Now the test.

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

<div id="modal-create-client" uk-modal>
    <div class="uk-modal-dialog uk-modal-body">
        <h2 class="uk-modal-title">Create Client</h2>
        <button class="uk-modal-close" type="button"></button>

        <form>
            <input type="text" name="title">
            <input type="email" name="email">
            <input type="text" name="address_1">
            <input type="text" name="address_2">
            <input type="text" name="city">
            <input type="text" name="state">
            <input type="text" name="zip">
            <input type="text" name="phone">
            <input type="submit" value="Save">
        </form>
    </div>
</div>
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

<form @submit.prevent="processForm(event)">
processForm: function(event) {
    let form = $(event.target);

    axios.post('api/clients', form.serialize())
        .then(response => {
            this.clients.push(response.data);
        })
}
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

[
    {
        "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!

2023 Phil Mareu - Coder, Traveler & Disc Thrower