Reader small image

You're reading from  Mastering React Test-Driven Development - Second Edition

Product typeBook
Published inSep 2022
Reading LevelIntermediate
PublisherPackt
ISBN-139781803247120
Edition2nd Edition
Languages
Tools
Right arrow
Author (1)
Daniel Irvine
Daniel Irvine
author image
Daniel Irvine

Daniel Irvine is a UK-based software consultant. He helps businesses simplify their existing codebases and assists dev teams in improving the quality of their software using eXtreme programming (XP) practices. He has been coaching developers for many years and co-founded the Queer Code London meetup.
Read more about Daniel Irvine

Right arrow

Adding Complex Form Interactions

It’s time to apply what you’ve learned to a more complicated HTML setup. In this chapter, we’ll test-drive a new component: AppointmentForm. It contains a select box, for selecting the service required, and a grid of radio buttons that form a calendar view for selecting the appointment time.

Combining both layout and form input, the code in this chapter shows how TDD gives you a structure for your work that makes even complicated scenarios straightforward: you will use your tests to grow the component into a component hierarchy, splitting out functionality from the main component as it begins to grow.

In this chapter, we will cover the following topics:

  • Choosing a value from a select box
  • Constructing a calendar view
  • Test-driving radio button groups
  • Reducing effort when constructing components

By the end of the chapter, you’ll have learned how to apply test-driven development to complex user...

Technical requirements

Choosing a value from a select box

Let’s start by creating a component for booking new appointments, named AppointmentForm.

The first field is a select box for choosing which service the customer requires: cut, color, blow-dry, and so on. Let’s create that now:

  1. Create a new file, test/AppointmentForm.test.js, with the following test and setup:
    import React from "react";
    import {
      initializeReactContainer,
      render,
      field,
      form,
    } from "./reactTestExtensions";
    import { AppointmentForm } from "../src/AppointmentForm";
    describe("AppointmentForm", () => {
      beforeEach(() => {
        initializeReactContainer();
      });
      it("renders a form", () => {
        render(<AppointmentForm />);
        expect(form()).not.toBeNull();
      });
    });
  2. Make this test pass by implementing...

Constructing a calendar view

In this section, we’ll learn how to use our existing helpers, such as element and elements, mixed with CSS selectors, to select specific elements we’re interested in within our HTML layout.

But first, let’s start with some planning.

We’d like AppointmentForm to display available time slots over the next 7 days as a grid, with columns representing days and rows representing 30-minute time slots, just like a standard calendar view. The user will be able to quickly find a time slot that works for them and then select the right radio button before submitting the form:

Figure 5.1 – The visual design of our calendar view

Here’s an example of the HTML structure that we’re aiming to build. We can use this as a guide as we write out our React component:

<table id="time-slots">
  <thead>
    <tr>
     ...

Test-driving radio button groups

Now that we have our table with headings in place, it’s time to add radio buttons to each of the table cells. Not all cells will have radio buttons – only those that represent an available time slot will have a radio button.

This means we’ll need to pass in another new prop to AppointmentForm that will help us determine which time slots to show. This prop is availableTimeSlots, which is an array of objects that list times that are still available. Follow these steps:

  1. Add the following test, which establishes a value for the availableTimeSlots prop and then checks that radio buttons have been rendered for each of those slots:
    it("renders radio buttons in the correct table cell positions", () => {
      const oneDayInMs = 24 * 60 * 60 * 1000;
      const today = new Date();
      const tomorrow = new Date(
        today.getTime() + oneDayInMs
      );
      const...

Reducing effort when constructing components

Let’s look at a couple of simple ways to reduce the amount of time and code needed for test suites like the one we’ve just built: first, extracting builder functions, and second, extracting objects to store sensible defaults for our component props.

Extracting test data builders for time and date functions

You’ve already seen how we can extract reusable functions into namespaces of their own, such as the render, click, and element DOM functions. A special case of this is the builder function, which constructs objects that you’ll use in the Arrange and Act phases of your test.

The purpose of these functions is not just to remove duplication but also for simplification and to aid with comprehension.

We already have one candidate in our test suite, which is the following code:

const today = new Date();
today.setHours(9, 0, 0, 0);

We’ll update our test suite so that it uses a builder function...

Summary

In this chapter, you learned how to use two types of HTML form elements: select boxes and radio buttons.

The component we’ve built has a decent amount of complexity, mainly due to the component hierarchy that’s used to display a calendar view, but also because of the date and time functions we’ve needed to help display that view.

That is about as complex as it gets: writing React component tests shouldn’t feel any more difficult than it has in this chapter.

Taking a moment to review our tests, the biggest issue we have is the use of expect.hasAssertions and the unusual Arrange-Assert-Act order. In Chapter 6, Exploring Test Doubles, we’ll discover how we can simplify these tests and get them back into Arrange-Act-Assert order.

Exercises

The following are some exercises for you to try out:

  1. Add a toBeElementWithTag matcher that replaces the two expectations in the renders as a select box test. It should be used like so:
    expect(field("service")).toBeElementWithTag("select");
  2. Complete the remaining tests for the AppointmentForm select box:
    • Renders a label
    • Assigns an ID that matches the label ID
    • Saves an existing value when submitted
    • Saves a new value when submitted

These tests are practically the same as they were for CustomerForm, including the use of the change helper. If you want a challenge, you can try extracting these form test helpers into a module of their own that is shared between CustomerForm and AppointmentForm.

  1. Update the time slot table tests so that they use the testProps object.
  2. Update the AppointmentsDayView component so that it uses the todayAt builder, where appropriate.
  3. Add the ability to choose a stylist before choosing a time slot. This...

Further reading

The useCallback hook is useful when you’re passing event handlers through a hierarchy of components. Take a look at the React documentation for tips on how to ensure correct usage: https://reactjs.org/docs/hooks-reference.html#usecallback.

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Mastering React Test-Driven Development - Second Edition
Published in: Sep 2022Publisher: PacktISBN-13: 9781803247120
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Daniel Irvine

Daniel Irvine is a UK-based software consultant. He helps businesses simplify their existing codebases and assists dev teams in improving the quality of their software using eXtreme programming (XP) practices. He has been coaching developers for many years and co-founded the Queer Code London meetup.
Read more about Daniel Irvine