In the Angular version of the app, we can see that the component will be a modal with lots of css, header, section and finally a footer with 2 buttons. For a walking skeleton, we can start with a div wrapping header, section, footer and the two buttons under the footer.
Create a branch feat/modalYesNo. Create 2 files under src/components/ folder; ModalYesNo.cy.tsx, ModalYesNo.tsx. As usual, start minimal with a component rendering; copy the below to the files and execute the test after opening the runner with yarn cy:open-ct.
To keep things simple we will use the original recipe from React TypeScript Cheatsheet, modal portal example. Create src/components/Modal.tsx and paste-in the following code.
For the moment we will assume that the modal is always open, and write a failing tests the ensures that an element with an id modal-root exists in the DOM (Red 1).
That is looking a bit bare. Let us copy the styles from the Angular version of the app, and add a few more tags (Refactor 2). Similar to the previous chapter, we are able to do a RedGreenRefactor cycle with visual aids for refactor increments.
The visuals are looking like the real thing. Now what is remaining are some text, and onClick handlers for the buttons.
There are 4 pieces of text in the modal; the title, the message, and the buttons. Let's write a failing test checking for these strings. We will use hard-coded values, and decide later what can be parameterized (Red 3).
With the visuals, we can make a better judgement on what needs to be parameterized. Confirm, No and Yes are most likely to stay constants. The message, if anything, should be parameterized as a prop. It is significant here that the tool is aiding us in the refactoring of the component as well as the design.
We will tweak the test to accept a prop for the message. The test passes, but the TS compiler is warning us against the newly added prop (Red 4). It is significant here that TS also aids us in the RedGreenRefactor cycles.
Let's add click handlers for the Yes and No buttons. We are going to need to pass in props and ensure that they are called. Write a failing test (Red 5).
The final feature to ponder about is the usage of the modal. It can either be open or closed. Usually this is handled by a useState hook, but we can replicate the usage of it with a test. We need a parent component that includes a boolean conditional render. We add a test, with a helper function that allows us to check for this edge case. The only thing we have to do make it work is to add a data-cy attribute to the top div of the component (Refactor 6).
// src/components/ModalYesNo.cy.tsximport ModalYesNo from"./ModalYesNo";import"../styles.scss";describe("ModalYesNo", () => {it("should render the modal and call onClick handlers", () => {constmessage="Are you sure?";cy.mount( <ModalYesNomessage={message}onYes={cy.stub().as("onYes")}onNo={cy.stub().as("onNo")} /> );cy.get("#modal-root").should("exist");cy.get("div").last().within(() => {cy.get("header").contains("Confirm");cy.get("section").contains(message);cy.get("footer");cy.getByCy("button-yes").contains("Yes");cy.getByCy("button-no").contains("No"); });cy.getByCy("button-yes").click();cy.get("@onYes").should("be.called");cy.getByCy("button-no").click();cy.get("@onNo").should("be.called"); });it("should not render the modal with if conditional render is false", () => {functionParentComponent():JSX.Element|boolean {return (false&& ( <ModalYesNomessage={"yo"}onYes={cy.stub().as("onYes")}onNo={cy.stub().as("onNo")} /> ) ); }// @ts-expect-error: replicating useStatecy.mount(<ParentComponent />);cy.getByCy("modal-yes-no").should("not.exist"); });});
// src/components/ModalYesNo.test.tsximport ModalYesNo from"./ModalYesNo";import { render, screen } from"@testing-library/react";import userEvent from"@testing-library/user-event";import"@testing-library/jest-dom";describe("ModalYesNo", () => {constmessage="Are you sure?";constonYes=jest.fn();constonNo=jest.fn();it("should render the modal and call onClick handlers",async () => {render(<ModalYesNomessage={message} onYes={onYes} onNo={onNo} />);awaitscreen.findByText("Confirm");awaitscreen.findByText(message);constbuttonYes=awaitscreen.findByTestId("button-yes");constbuttonNo=awaitscreen.findByTestId("button-no");expect(buttonYes).toBeVisible();expect(buttonNo).toBeVisible();awaituserEvent.click(buttonYes);expect(onYes).toHaveBeenCalled();awaituserEvent.click(buttonNo);expect(onNo).toHaveBeenCalled(); });it("should not render the modal with if conditional render is false",async () => {functionParentComponent():JSX.Element|boolean {returnfalse&& <ModalYesNomessage={"yo"} onYes={onYes} onNo={onNo} />; }// @ts-expect-error: replicating useStaterender(<ParentComponent />);expect(screen.queryByTestId("modal-yes-no")).not.toBeInTheDocument(); });});
Summary
We started with a simple test that checks that the modal-root is rendered (Red 1).
We used a modal recipe and imported it to our component (Green 1).
We decided on the skeleton of the component and wrote a test for it (Red 2).
We refactored the component by adding styles. As in the previous chapters, we used visual aids for refactor increments (Refactor 3).
We wrote a failing test with the 4 pieces of text in the modal, using hard coded values (Red 3)
We added the hard-coded strings into respective tags to pass the test (Green 3).
Aided by the visuals of the component, we made a design choice to parameterize one of the strings, and leave the rest unchanged. It was of significance to be aided by the tool in the refactoring of the component as well as the design.
We added a prop fo the parameterized string "message" and wrote a failing test for it (Red 4).
We followed the pattern of adding a prop type, an argument to the component and using the value in the function return / render.
We added click handler tests for the Yes and No buttons (Red 5, 6), and added the props to the component with types, args and onClick attributes (Green 5, 6).
To ensure that the component can be used with conditional rendering, we created a helper function, a parent component that includes a boolean conditional render, in the component test (Refactor 6).
Takeaway
The visual results of the component test can aid with refactoring the component as well as designing it.
As we also saw in chapter one, TypeScript and ESlint can also serve as "tests" that give us a Red.