Skip to the code
cf.

Show password

Build App: the password part of the tea shop's sign-in form. It has a password box labelled Password, a button next to it that shows or hides what you've typed, and a Sign in button that submits the form.

The password starts hidden (type="password") and the toggle reads Show password. Clicking the toggle shows the password (type="text") and the toggle reads Hide password. Clicking it again hides it. What you've typed stays in the box either way.

The toggle is inside the form, so make sure clicking it doesn't submit the form. Submitting, by clicking Sign in or pressing Enter in the box, calls the onSignIn prop with the password, shown or hidden. onSignIn may be missing (the Preview doesn't pass it), so don't fail without it.

The tab order is the box, then the toggle, then Sign in. Pressing the toggle from the keyboard leaves the focus on it, so you can press it again to hide the password.

<App onSignIn={(password) => signIn(password)} />
 
// renders, before you click:
<form>
  <label>Password <input type="password" /></label>
  <button type="button">Show password</button>
  <button type="submit">Sign in</button>
</form>

What the tests check

  1. starts hidden, with a Show password button
    render(<App />);
    expect(screen.getByLabelText("Password")).toHaveAttribute("type", "password");
    expect(screen.getByRole("button", { name: "Show password" })).toBeInTheDocument();
    
  2. Show password shows what you typed
    render(<App />);
    await user.type(screen.getByLabelText("Password"), "oolong42");
    await user.click(screen.getByRole("button", { name: "Show password" }));
    expect(screen.getByLabelText("Password")).toHaveAttribute("type", "text");
    expect(screen.getByRole("button", { name: "Hide password" })).toBeInTheDocument();
    
  3. Hide password hides it again
    render(<App />);
    await user.click(screen.getByRole("button", { name: "Show password" }));
    await user.click(screen.getByRole("button", { name: "Hide password" }));
    expect(screen.getByLabelText("Password")).toHaveAttribute("type", "password");
    expect(screen.getByRole("button", { name: "Show password" })).toBeInTheDocument();
    
  4. keeps what you typed through every toggle
    render(<App />);
    await user.type(screen.getByLabelText("Password"), "oolong42");
    await user.click(screen.getByRole("button", { name: "Show password" }));
    expect(screen.getByLabelText("Password")).toHaveValue("oolong42");
    await user.type(screen.getByLabelText("Password"), "!");
    await user.click(screen.getByRole("button", { name: "Hide password" }));
    expect(screen.getByLabelText("Password")).toHaveValue("oolong42!");
    
  5. the toggle doesn't submit the form
    const onSignIn = spy();
    render(<App onSignIn={onSignIn} />);
    await user.type(screen.getByLabelText("Password"), "oolong42");
    await user.click(screen.getByRole("button", { name: "Show password" }));
    expect(onSignIn).not.toHaveBeenCalled();
    
  6. Sign in sends the password, even while it's shown
    const onSignIn = spy();
    render(<App onSignIn={onSignIn} />);
    await user.type(screen.getByLabelText("Password"), "oolong42");
    await user.click(screen.getByRole("button", { name: "Show password" }));
    await user.click(screen.getByRole("button", { name: "Sign in" }));
    expect(onSignIn).toHaveBeenCalledTimes(1);
    expect(onSignIn).toHaveBeenCalledWith("oolong42");
    
  7. pressing Enter in the box signs in
    const onSignIn = spy();
    render(<App onSignIn={onSignIn} />);
    await user.type(screen.getByLabelText("Password"), "oolong42{Enter}");
    expect(onSignIn).toHaveBeenCalledWith("oolong42");
    
  8. tabs from the box to the toggle to Sign in
    render(<App />);
    await user.tab();
    expect(screen.getByLabelText("Password")).toHaveFocus();
    await user.tab();
    expect(screen.getByRole("button", { name: "Show password" })).toHaveFocus();
    await user.tab();
    expect(screen.getByRole("button", { name: "Sign in" })).toHaveFocus();
    
  9. the toggle keeps the focus when pressed from the keyboard
    render(<App />);
    await user.tab();
    await user.tab();
    await user.keyboard("{Enter}");
    expect(screen.getByLabelText("Password")).toHaveAttribute("type", "text");
    expect(screen.getByRole("button", { name: "Hide password" })).toHaveFocus();
    await user.keyboard(" ");
    expect(screen.getByLabelText("Password")).toHaveAttribute("type", "password");
    expect(screen.getByRole("button", { name: "Show password" })).toHaveFocus();
    

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).