Skip to the code
cf.

Like button

Build App: the like button under a tea on the shop's page. It takes one prop, likes, the number of likes the tea already has (0 when it isn't given), and shows the count under the button, like 12 likes.

The button reads Like. Clicking it likes the tea: the count goes up by one and the button is pressed, aria-pressed="true". Clicking it again takes your like back: the count goes back down and aria-pressed is "false". You can only like a tea once, however many times you click.

The word after the count follows the number: 1 like, but 0 likes and 2 likes.

The button's name stays Like whether it's pressed or not. aria-pressed already tells a screen reader that it's on, so changing the name as well would make it sound like a different button.

If the page passes a new likes (someone else liked the tea), the count shows it, plus one if you've liked it.

<App likes={12} />
 
// renders, before you click:
<button aria-pressed="false">Like</button>
<p>12 likes</p>

What the tests check

  1. shows the likes it's given, with the button not pressed
    render(<App likes={12} />);
    expect(screen.getByText("12 likes")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Like" })).toHaveAttribute("aria-pressed", "false");
    
  2. starts at 0 likes without a likes prop
    render(<App />);
    expect(screen.getByText("0 likes")).toBeInTheDocument();
    
  3. a click adds one like and presses the button
    render(<App likes={12} />);
    await user.click(screen.getByRole("button", { name: "Like" }));
    expect(screen.getByText("13 likes")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Like" })).toHaveAttribute("aria-pressed", "true");
    
  4. a second click takes the like back
    render(<App likes={12} />);
    const button = screen.getByRole("button", { name: "Like" });
    await user.click(button);
    await user.click(button);
    expect(screen.getByText("12 likes")).toBeInTheDocument();
    expect(button).toHaveAttribute("aria-pressed", "false");
    
  5. adds at most one like, however many clicks
    render(<App likes={12} />);
    const button = screen.getByRole("button", { name: "Like" });
    await user.click(button);
    await user.click(button);
    await user.click(button);
    expect(screen.getByText("13 likes")).toBeInTheDocument();
    expect(button).toHaveAttribute("aria-pressed", "true");
    
  6. says 1 like, not 1 likes
    render(<App likes={0} />);
    await user.click(screen.getByRole("button", { name: "Like" }));
    expect(screen.getByText("1 like")).toBeInTheDocument();
    
  7. works from the keyboard
    render(<App likes={12} />);
    await user.tab();
    expect(screen.getByRole("button", { name: "Like" })).toHaveFocus();
    await user.keyboard("{Enter}");
    expect(screen.getByText("13 likes")).toBeInTheDocument();
    await user.keyboard(" ");
    expect(screen.getByText("12 likes")).toBeInTheDocument();
    
  8. shows a new likes prop, plus your like
    const { rerender } = render(<App likes={12} />);
    await user.click(screen.getByRole("button", { name: "Like" }));
    rerender(<App likes={20} />);
    expect(screen.getByText("21 likes")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Like" })).toHaveAttribute("aria-pressed", "true");
    

Learn more

App.jsxSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).