Skip to the code
cf.

Fix the impure price summary

The checkout page shows an order summary: each line, then the subtotal, the tax, and the total. PriceSummary (a named export of App.jsx) looks right the first time it draws, but in the Preview, which renders in StrictMode, every total is doubled, and it changes the array it's given. Fix it.

It takes items, an array of { name, price, quantity }. Keep what it shows: a <section> with aria-label="Order summary", holding a <ul> with one <li> per item, sorted by name, that reads like Sencha x 2: $8.00 (the price times the quantity); then three paragraphs, Subtotal: $20.50, Tax: $1.64, and Total: $22.14. Tax is 8% of the subtotal, and every amount has a dollar sign and two decimals.

Make it pure. It must show the same totals however many times React calls it, in StrictMode or not, and with more than one summary on the page. It must not change items: the parent's array keeps its order, and a frozen array works.

<PriceSummary
  items={[
    { name: "Sencha", price: 4, quantity: 2 },
    { name: "Oolong", price: 6.5, quantity: 1 },
    { name: "Earl Grey", price: 3, quantity: 2 },
  ]}
/>
 
// shows:
// Earl Grey x 2: $6.00
// Oolong x 1: $6.50
// Sencha x 2: $8.00
// Subtotal: $20.50
// Tax: $1.64
// Total: $22.14

What the tests check

  1. lists the items by name, each with its line total
    const items = [
      { name: "Sencha", price: 4, quantity: 2 },
      { name: "Oolong", price: 6.5, quantity: 1 },
      { name: "Earl Grey", price: 3, quantity: 2 },
    ];
    render(<PriceSummary items={items} />);
    const lines = within(screen.getByRole("region", { name: "Order summary" })).getAllByRole("listitem");
    expect(lines.map((line) => line.textContent)).toEqual(["Earl Grey x 2: $6.00", "Oolong x 1: $6.50", "Sencha x 2: $8.00"]);
    
  2. shows the subtotal, 8% tax, and total
    const items = [
      { name: "Sencha", price: 4, quantity: 2 },
      { name: "Oolong", price: 6.5, quantity: 1 },
      { name: "Earl Grey", price: 3, quantity: 2 },
    ];
    render(<PriceSummary items={items} />);
    expect(screen.getByText("Subtotal: $20.50")).toBeInTheDocument();
    expect(screen.getByText("Tax: $1.64")).toBeInTheDocument();
    expect(screen.getByText("Total: $22.14")).toBeInTheDocument();
    
  3. shows the right totals in StrictMode
    const items = [
      { name: "Sencha", price: 4, quantity: 2 },
      { name: "Oolong", price: 6.5, quantity: 1 },
    ];
    render(
      <React.StrictMode>
        <PriceSummary items={items} />
      </React.StrictMode>,
    );
    expect(screen.getByText("Subtotal: $14.50")).toBeInTheDocument();
    expect(screen.getByText("Tax: $1.16")).toBeInTheDocument();
    expect(screen.getByText("Total: $15.66")).toBeInTheDocument();
    
  4. shows the same totals when it renders again
    const order = () => [
      { name: "Sencha", price: 4, quantity: 2 },
      { name: "Oolong", price: 6.5, quantity: 1 },
    ];
    const { rerender } = render(<PriceSummary items={order()} />);
    rerender(<PriceSummary items={order()} />);
    rerender(<PriceSummary items={order()} />);
    expect(screen.getByText("Subtotal: $14.50")).toBeInTheDocument();
    expect(screen.getByText("Total: $15.66")).toBeInTheDocument();
    
  5. two summaries on one page each show their own totals
    render(
      <div>
        <PriceSummary items={[{ name: "Sencha", price: 4, quantity: 2 }]} />
        <PriceSummary items={[{ name: "Oolong", price: 6.5, quantity: 1 }]} />
      </div>,
    );
    const [first, second] = screen.getAllByRole("region", { name: "Order summary" });
    expect(within(first).getByText("Subtotal: $8.00")).toBeInTheDocument();
    expect(within(second).getByText("Subtotal: $6.50")).toBeInTheDocument();
    expect(within(second).getByText("Total: $7.02")).toBeInTheDocument();
    
  6. doesn't reorder the array it's given
    const items = [
      { name: "Sencha", price: 4, quantity: 2 },
      { name: "Oolong", price: 6.5, quantity: 1 },
      { name: "Earl Grey", price: 3, quantity: 2 },
    ];
    render(<PriceSummary items={items} />);
    expect(items.map((item) => item.name)).toEqual(["Sencha", "Oolong", "Earl Grey"]);
    
  7. works with a frozen array
    const items = Object.freeze([
      Object.freeze({ name: "Sencha", price: 4, quantity: 2 }),
      Object.freeze({ name: "Earl Grey", price: 3, quantity: 2 }),
    ]);
    render(<PriceSummary items={items} />);
    const lines = screen.getAllByRole("listitem");
    expect(lines.map((line) => line.textContent)).toEqual(["Earl Grey x 2: $6.00", "Sencha x 2: $8.00"]);
    expect(screen.getByText("Subtotal: $14.00")).toBeInTheDocument();
    
  8. shows $0.00 everywhere for an empty order
    render(<PriceSummary items={[]} />);
    expect(screen.queryAllByRole("listitem")).toHaveLength(0);
    expect(screen.getByText("Subtotal: $0.00")).toBeInTheDocument();
    expect(screen.getByText("Tax: $0.00")).toBeInTheDocument();
    expect(screen.getByText("Total: $0.00")).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).