Build App: the box for posting a short note on the tea shop's page. A textarea labelled Message takes notes of up to 140 characters, counted with text.length.
Under it, a counter says how many characters are left: 140 characters left at first, then 1 character left and 0 characters left near the end. Past the limit it says how many to cut instead: 1 character too many, 5 characters too many.
The counter has a data-status the stylesheet uses to color it: "ok" normally, "warning" when fewer than 20 characters are left (19 down to 0), and "over" past the limit. The textarea points at the counter with aria-describedby, so a screen reader reads the count with the box.
A Post button calls the onPost prop with the text as typed, then empties the box. It's disabled when the note is past the limit, and when it's empty or only spaces. onPost may be missing (the Preview doesn't pass it).
<App onPost={(text) => publish(text)} />
// renders, with 128 characters typed:
<label>Message <textarea aria-describedby="note-count" /></label>
<p id="note-count" data-status="warning">12 characters left</p>
<button>Post</button>
render(<App />);
expect(screen.getByText("140 characters left")).toHaveAttribute("data-status", "ok");
expect(screen.getByRole("button", { name: "Post" })).toBeDisabled();
render(<App />);
await user.type(screen.getByLabelText("Message"), "Fresh oolong");
expect(screen.getByText("128 characters left")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Post" })).toBeEnabled();
render(<App />);
await user.click(screen.getByLabelText("Message"));
await user.paste("a".repeat(139));
expect(screen.getByText("1 character left")).toBeInTheDocument();
render(<App />);
await user.click(screen.getByLabelText("Message"));
await user.paste("a".repeat(120));
expect(screen.getByText("20 characters left")).toHaveAttribute("data-status", "ok");
await user.type(screen.getByLabelText("Message"), "a");
expect(screen.getByText("19 characters left")).toHaveAttribute("data-status", "warning");
render(<App />);
await user.click(screen.getByLabelText("Message"));
await user.paste("a".repeat(140));
expect(screen.getByText("0 characters left")).toHaveAttribute("data-status", "warning");
expect(screen.getByRole("button", { name: "Post" })).toBeEnabled();
render(<App />);
await user.click(screen.getByLabelText("Message"));
await user.paste("a".repeat(141));
expect(screen.getByText("1 character too many")).toHaveAttribute("data-status", "over");
expect(screen.getByRole("button", { name: "Post" })).toBeDisabled();
await user.paste("aaaa");
expect(screen.getByText("5 characters too many")).toHaveAttribute("data-status", "over");
render(<App />);
const box = screen.getByLabelText("Message");
const id = box.getAttribute("aria-describedby");
expect(id).toBeTruthy();
expect(document.getElementById(id)).toHaveTextContent("140 characters left");
await user.type(box, "Chai");
expect(document.getElementById(id)).toHaveTextContent("136 characters left");
const onPost = spy();
render(<App onPost={onPost} />);
await user.type(screen.getByLabelText("Message"), "Fresh oolong today");
await user.click(screen.getByRole("button", { name: "Post" }));
expect(onPost).toHaveBeenCalledTimes(1);
expect(onPost).toHaveBeenCalledWith("Fresh oolong today");
expect(screen.getByLabelText("Message")).toHaveValue("");
expect(screen.getByText("140 characters left")).toBeInTheDocument();
render(<App />);
await user.type(screen.getByLabelText("Message"), " ");
expect(screen.getByText("137 characters left")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Post" })).toBeDisabled();
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).
I'm here to help with Character counter. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.