Lesson 12

Preparing for Delivery

Tidying up loose ends from the sprint

PRO

Lesson Outline

Now E2E tests are great, but it's still worthwhile to go through the application ourselves as well just to get a sense of the overall UX and to spot any glaring issues. When thinking about how things should work it's easy to forget some obvious things.

NOTE: Remember to serve with npm start not ionic serve as we need to make sure our emulators are started.

After serving the application and playing around with the existing functionality you can see there is obviously a lot missing and a long way to go in terms of styling. However, everything that we have set out to do is working. There is just one main thing I came across that strikes me as something that has been blatantly missed and that is that there is no back button present on the ClientDetail page or the ClientAdd page. This is certainly something we want before sending it to the client.

Another issue is that although we have added the ability to edit a client, we haven't actually provided a way to delete a client. There is also currently no "loading" UI state for the clients page, and there is not an option to log out.

We are not going to address all of these now, but we will add new issues to be addressed later (possibly in the next sprint). We generally want to keep work confined to our planned sprints, but a little polish can go a long way when delivering an iteration to a client - so, we are going to address some things now.


Project management

We will create issues for everything first, because we will need them even for the stuff that we are working on now. Make sure to create all of the following issues - we are also going to follow the user story process for each new feature:

As a massage therapist, I want the ability to log out, so that I can protect sensitive client information and interact with the application as a non-admin user

As a massage therapist, I want the ability to delete a client, so that I can remove clients entries mistakingly added or remove clients that I no longer provide services for

As a massage therapist, I want an indication that data is loading, to provide a more pleasant experience when using the app

We will not follow the user story process for the back button issue - this is more of an oversight/bug fix than a "feature". We will simply create an issue with no body for these ones:

bug: can not navigate back from client detail page

bug: can not navigate back from the client add page

Once you have created all these issues, make sure to create a new task brach for the issue we will be working on first: the back button.


The issues we are going to address right now are:

  • Adding the back button
  • Adding loading state to the template

The back button is critical to add as it breaks the usability of the application otherwise. The loading state isn't so necessary - the data loads so fast that it is barely noticeable - but it's easy to add and these tiny details really can really make the application feel a lot better.

Adding the back button

Let's add the back button functionality. This will be fairly simple to add, but remember that we aren't just going to add a back button. We are using TDD, so of course we are going to add a test for it first.


Project management

We created two separate issues for the back buttons, but let's just deal with them both at the same time. Move both cards in the Kanban, and just pick one issue number to use for your task branch. When we create a pull request to merge the branch, we can just add an extra closes #x message to the body of the pull request to close the other associated issue automatically (or you can just close the issue manually).


Let's start with an E2E test to get back from the ClientDetail page:

Add the following test to clients.cy.ts:

it('can navigate back to the clients list after viewing a specific client', () => {
  const testClient = {
    name: {
      first: 'Josh',
      last: 'Morony',
    },
    phone: '333',
    email: '[email protected]',
    notes: '',
  };

  cy.callFirestore('set', 'clients/abc123', testClient);

  getItemsInList().first().click();

  getClientDetailBackButton().click();

  getItemsInList().should('contain.text', 'Josh Morony');
});

and we will need to add a new utility method:

export const getClientDetailBackButton = () =>
  cy.get('[data-test="client-detail-back-button"]');

NOTE: We are adding specific test attributes to each button rather than just grabbing the back buttons by the ion-back-button selector. This is because in some cases Ionic will actually keep views cached in the DOM, and we don't want to accidentally grab the back button of a page that is not active.

Now let's test that it fails:

Timed out retrying after 4000ms: Expected to find element: [data-test="client-detail-back-button"], but never found it.

Add the back button to our template:

<ion-header>
  <ion-toolbar>
    <ion-title>ClientDetail</ion-title>
    <ion-buttons slot="start">
      <ion-back-button data-test="client-detail-back-button"></ion-back-button>
    </ion-buttons>
    <ion-buttons slot="end">
      <ion-button
        *ngIf="client$ | async as client"
        routerLink="/clients/{{client.id}}/edit"
        routerDirection="forward"
        data-test="edit-button"
      >
        <ion-icon slot="icon-only" name="create-outline"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

and test that it passes...

and it does!

Now for our ClientAdd page we are actually going to add two tests, because there are two different scenarios in which it is used. In one case, we want to go from the client-dashboard to the client-add page to add a new client and then go back to the dashboard. In the other case, we want to go from the client-detail page to the client-add page and then go back to the client-detail page.

We will add the first scenario to the same clients.cy.ts file, but since our second scenario starts from a different page we are going to create a separate client-detail.cy.ts file.

Let's start with the first scenario:

  it('can navigate back to the clients list after going to the client-add page', () => {
    getAddClientButton().first().click();

    getAddClientBackButton().click();

    getTitle().should('contain.text', 'Clients');
  });

and add our new utility method:

export const getAddClientBackButton = () =>
  cy.get('[data-test="add-client-back-button"]');

check that it fails:

Timed out retrying after 4000ms: Expected to find element: [data-test="add-client-back-button"], but never found it.

and then add the implementation:

<ion-header>
  <ion-toolbar>
    <ion-title>
      <ng-container *ngIf="{ client: client$ | async } as vm">
        {{ vm.client ? 'Edit Client' : 'Add Client' }}
      </ng-container>
    </ion-title>
    <ion-buttons slot="start">
      <ion-back-button data-test="add-client-back-button"></ion-back-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

and check that it passes...

and it does!

Now let's add the other test for this. We will create a new file at cypress/e2e/client-detail.cy.ts:

import {
  getAddClientBackButton,
  getEditButton,
  getNameDisplay,
} from '../support/utils';

describe('Client Detail', () => {
  const testId = 'abc123';

  const testClient = {
    name: {
      first: 'Josh',
      last: 'Morony',
    },
    phone: '333',
    email: '[email protected]',
    notes: '',
  };

  beforeEach(() => {
    cy.login();
    cy.callFirestore('delete', 'clients');

    cy.callFirestore('set', `clients/${testId}`, testClient);

    cy.visit(`clients/${testId}`);
  });

  it('can navigate back to the clients detail page after visiting the client add page', () => {
    getEditButton().click();
    getAddClientBackButton().click();
    getNameDisplay().should('contain.text', testClient.name.first);
  });
});

We're taking a slightly different approach here. We want to start each test on the ClientDetail page but we need some data in order to do that. In the beforeEach we add a test client, and then navigate directly to the detail page for that client.

Now if we check to see if this test fails...

it will actually pass. This is because we have already added the back button to the ClientAdd page. Since we're responsible TDD testers, let's purposely break the back button to check that it does indeed fail:

<ion-header>
  <ion-toolbar>
    <ion-title>
      <ng-container *ngIf="{ client: client$ | async } as vm">
        {{ vm.client ? 'Edit Client' : 'Add Client' }}
      </ng-container>
    </ion-title>
    <ion-buttons slot="start"> </ion-buttons>
  </ion-toolbar>
</ion-header>
PRO

Thanks for checking out the preview of this lesson!

The full version of this lesson is only available to pro members. If you would like full access to this module and all of the other pro modules on Elite Ionic you can become a pro member (or log in if you are already a member).