The tea shop team tracks its jobs on a whiteboard, and wants it on screen. Build KanbanBoard, the default export: a board with three columns of cards.
It takes initialCards, an array of { id, title, column } where column is "todo", "doing", or "done". It's optional, with a few tea shop jobs as the default. It only sets the starting cards: after that, the board keeps its own. Don't change the array you're given or the objects in it.
Show the columns in this order: To do, Doing, Done. Each is a <section> labelled by its <h2> heading (with aria-labelledby), so it's a region named after the column. Under the heading, show how many cards it holds: 0 cards, 1 card, 2 cards. Then show its cards as a list (<ul> with an <li> per card), in order.
Each card shows its title and buttons to move it one column left or right. Name each button after the card and where it goes, with aria-label: Move Fix the kettle to To do, Move Fix the kettle to Done. Cards in To do have no move-left button, and cards in Done have no move-right button. A moved card goes to the end of its new column.
Above the columns, a form adds cards: a text input labelled New card and an Add card button. Submitting it (with the button or Enter) adds a card with the trimmed title to the end of To do and clears the input. A blank title adds nothing.
<KanbanBoard
initialCards={[
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Fix the kettle", column: "doing" },
]}
/>
// the Doing column:
<section aria-labelledby="doing-heading">
<h2 id="doing-heading">Doing</h2>
<p>1 card</p>
<ul>
<li>
Fix the kettle
<button aria-label="Move Fix the kettle to To do">←</button>
<button aria-label="Move Fix the kettle to Done">→</button>
</li>
</ul>
</section>
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Write the autumn menu", column: "todo" },
{ id: 3, title: "Fix the kettle", column: "doing" },
];
render(<App initialCards={cards} />);
expect(screen.getAllByRole("heading", { level: 2 }).map((heading) => heading.textContent)).toEqual(["To do", "Doing", "Done"]);
const todo = screen.getByRole("region", { name: "To do" });
expect(within(todo).getByText("2 cards")).toBeInTheDocument();
const items = within(todo).getAllByRole("listitem");
expect(items.length).toBe(2);
expect(items[0]).toHaveTextContent("Restock matcha");
expect(items[1]).toHaveTextContent("Write the autumn menu");
const doing = screen.getByRole("region", { name: "Doing" });
expect(within(doing).getByText("1 card")).toBeInTheDocument();
expect(within(doing).getByText("Fix the kettle")).toBeInTheDocument();
const done = screen.getByRole("region", { name: "Done" });
expect(within(done).getByText("0 cards")).toBeInTheDocument();
expect(within(done).queryAllByRole("listitem").length).toBe(0);
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Write the autumn menu", column: "todo" },
{ id: 3, title: "Fix the kettle", column: "doing" },
];
render(<App initialCards={cards} />);
await user.click(screen.getByRole("button", { name: "Move Restock matcha to Doing" }));
const todo = screen.getByRole("region", { name: "To do" });
const doing = screen.getByRole("region", { name: "Doing" });
expect(within(todo).queryByText("Restock matcha")).not.toBeInTheDocument();
expect(within(doing).getByText("Restock matcha")).toBeInTheDocument();
expect(within(todo).getByText("1 card")).toBeInTheDocument();
expect(within(doing).getByText("2 cards")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Move Restock matcha to Done" }));
expect(within(screen.getByRole("region", { name: "Done" })).getByText("Restock matcha")).toBeInTheDocument();
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 3, title: "Fix the kettle", column: "doing" },
{ id: 4, title: "Order paper cups", column: "done" },
];
render(<App initialCards={cards} />);
await user.click(screen.getByRole("button", { name: "Move Order paper cups to Doing" }));
const doing = screen.getByRole("region", { name: "Doing" });
expect(within(doing).getByText("Order paper cups")).toBeInTheDocument();
expect(within(doing).getByText("2 cards")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Move Fix the kettle to To do" }));
const todo = screen.getByRole("region", { name: "To do" });
expect(within(todo).getByText("Fix the kettle")).toBeInTheDocument();
expect(within(todo).getByText("2 cards")).toBeInTheDocument();
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 3, title: "Fix the kettle", column: "doing" },
{ id: 4, title: "Order paper cups", column: "done" },
];
render(<App initialCards={cards} />);
const names = (column) => within(screen.getByRole("region", { name: column })).getAllByRole("button").map((button) => button.getAttribute("aria-label"));
expect(names("To do")).toEqual(["Move Restock matcha to Doing"]);
expect(names("Doing").sort()).toEqual(["Move Fix the kettle to Done", "Move Fix the kettle to To do"]);
expect(names("Done")).toEqual(["Move Order paper cups to Doing"]);
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Fix the kettle", column: "doing" },
{ id: 3, title: "Order paper cups", column: "done" },
];
render(<App initialCards={cards} />);
await user.click(screen.getByRole("button", { name: "Move Restock matcha to Doing" }));
const items = within(screen.getByRole("region", { name: "Doing" })).getAllByRole("listitem");
expect(items.length).toBe(2);
expect(items[0]).toHaveTextContent("Fix the kettle");
expect(items[1]).toHaveTextContent("Restock matcha");
const cards = [{ id: 1, title: "Restock matcha", column: "todo" }];
render(<App initialCards={cards} />);
const input = screen.getByLabelText("New card");
await user.type(input, " Clean the teapots ");
await user.click(screen.getByRole("button", { name: "Add card" }));
expect(input).toHaveValue("");
const todo = screen.getByRole("region", { name: "To do" });
expect(within(todo).getByText("2 cards")).toBeInTheDocument();
const items = within(todo).getAllByRole("listitem");
expect(items[1]).toHaveTextContent("Clean the teapots");
expect(items[1].textContent).not.toContain(" Clean");
expect(items[1].textContent).not.toContain("teapots ");
expect(screen.getByRole("button", { name: "Move Clean the teapots to Doing" })).toBeInTheDocument();
await user.type(input, "Wipe the tables{Enter}");
expect(within(todo).getByText("3 cards")).toBeInTheDocument();
expect(within(todo).getAllByRole("listitem")[2]).toHaveTextContent("Wipe the tables");
const cards = [{ id: 1, title: "Restock matcha", column: "todo" }];
render(<App initialCards={cards} />);
await user.click(screen.getByRole("button", { name: "Add card" }));
await user.type(screen.getByLabelText("New card"), " ");
await user.click(screen.getByRole("button", { name: "Add card" }));
const todo = screen.getByRole("region", { name: "To do" });
expect(within(todo).getByText("1 card")).toBeInTheDocument();
expect(within(todo).getAllByRole("listitem").length).toBe(1);
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Fix the kettle", column: "doing" },
];
render(<App initialCards={cards} />);
await user.type(screen.getByLabelText("New card"), "Clean the teapots{Enter}");
await user.click(screen.getByRole("button", { name: "Move Clean the teapots to Doing" }));
const doing = screen.getByRole("region", { name: "Doing" });
expect(within(doing).getByText("2 cards")).toBeInTheDocument();
expect(within(doing).getAllByRole("listitem")[1]).toHaveTextContent("Clean the teapots");
expect(within(screen.getByRole("region", { name: "To do" })).getByText("1 card")).toBeInTheDocument();
const cards = [
{ id: 1, title: "Restock matcha", column: "todo" },
{ id: 2, title: "Fix the kettle", column: "doing" },
];
const before = JSON.stringify(cards);
const { unmount } = render(<App initialCards={cards} />);
await user.type(screen.getByLabelText("New card"), "Clean the teapots{Enter}");
expect(JSON.stringify(cards)).toBe(before);
unmount();
render(<App initialCards={cards} />);
await user.click(screen.getByRole("button", { name: "Move Restock matcha to Doing" }));
await user.click(screen.getByRole("button", { name: "Move Fix the kettle to Done" }));
expect(JSON.stringify(cards)).toBe(before);
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 Kanban board. 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.