Skip to the code
cf.

Password strength meter

EasyForms

Build App: a password box for a tea shop's account page that shows how strong the password is as you type.

The box is labelled Password. Under it is a list of four rules, in this order: At least 8 characters, Contains a number, Contains a symbol, and Contains an uppercase letter. Each rule is a list item whose text is just the rule. It has data-met="true" while the password meets the rule and data-met="false" while it doesn't, updated on every keystroke.

A number is a digit from 0 to 9. An uppercase letter is A to Z. A symbol is any character that isn't a letter from A to Z (either case), a digit, or a space: #, !, and - count, and spaces don't.

Below the list, a line reads Strength: Weak, Strength: Fair, or Strength: Strong. It's Weak when 0 or 1 rules are met, Fair for 2 or 3, and Strong for all 4. An empty box is Weak. You can add a <meter> as well; the tests only read the text.

<label for="password">Password</label>
<input id="password" type="password" />
<ul>
  <li data-met="true">At least 8 characters</li>
  <li data-met="true">Contains a number</li>
  <li data-met="false">Contains a symbol</li>
  <li data-met="false">Contains an uppercase letter</li>
</ul>
<p>Strength: Fair</p>

What the tests check

  1. starts Weak, with no rule met
    render(<App />);
    for (const rule of ["At least 8 characters", "Contains a number", "Contains a symbol", "Contains an uppercase letter"]) {
      expect(screen.getByText(rule)).toHaveAttribute("data-met", "false");
    }
    expect(screen.getByText("Strength: Weak")).toBeInTheDocument();
    
  2. the length rule is met at 8 characters, not 7
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "abcdefg");
    expect(screen.getByText("At least 8 characters")).toHaveAttribute("data-met", "false");
    await user.type(box, "h");
    expect(screen.getByText("At least 8 characters")).toHaveAttribute("data-met", "true");
    
  3. a digit meets the number rule
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "tea");
    expect(screen.getByText("Contains a number")).toHaveAttribute("data-met", "false");
    await user.type(box, "5");
    expect(screen.getByText("Contains a number")).toHaveAttribute("data-met", "true");
    
  4. a symbol meets the symbol rule, and a space doesn't
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "green tea");
    expect(screen.getByText("Contains a symbol")).toHaveAttribute("data-met", "false");
    await user.type(box, "!");
    expect(screen.getByText("Contains a symbol")).toHaveAttribute("data-met", "true");
    
  5. only an uppercase letter meets the uppercase rule
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "chai");
    expect(screen.getByText("Contains an uppercase letter")).toHaveAttribute("data-met", "false");
    await user.type(box, "C");
    expect(screen.getByText("Contains an uppercase letter")).toHaveAttribute("data-met", "true");
    
  6. one rule met is still Weak
    render(<App />);
    await user.type(screen.getByLabelText("Password"), "Chai");
    expect(screen.getByText("Strength: Weak")).toBeInTheDocument();
    
  7. two or three rules met is Fair
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "chai1234");
    expect(screen.getByText("Strength: Fair")).toBeInTheDocument();
    await user.type(box, "!");
    expect(screen.getByText("Strength: Fair")).toBeInTheDocument();
    
  8. all four rules met is Strong
    render(<App />);
    await user.type(screen.getByLabelText("Password"), "Oolong#12");
    expect(screen.getByText("Strength: Strong")).toBeInTheDocument();
    expect(screen.queryByText("Strength: Fair")).not.toBeInTheDocument();
    
  9. rules stop being met as you delete
    render(<App />);
    const box = screen.getByLabelText("Password");
    await user.type(box, "Oolong#12");
    await user.type(box, "{Backspace}{Backspace}");
    expect(screen.getByText("Contains a number")).toHaveAttribute("data-met", "false");
    expect(screen.getByText("At least 8 characters")).toHaveAttribute("data-met", "false");
    expect(screen.getByText("Strength: Fair")).toBeInTheDocument();
    

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