Build App: the color picker for a tea tin's label. It takes colors, a list of { name, hex } with unique names, and shows them as a group of radio buttons: a <fieldset> with the legend Color, and one radio per color, labelled by its name, with a small swatch of the color beside it. When colors isn't given, as in the Preview, use a few colors of your own.
The first color is chosen at first. Clicking a color, or its name, chooses it, and only one color is ever chosen.
A preview, <section aria-label="Preview">, shows a large box filled with the chosen color and, under it, the color's name and hex code, like Matcha #7A9A3A.
The keyboard works as in any radio group: Tab lands on the chosen color, and the arrow keys move the choice to the next or previous color, wrapping round at the ends. Real <input type="radio"> elements do this for you when the radios in one group share a name.
A page can show more than one picker, so each picker's radios need a name of their own. Otherwise the arrow keys in one picker would walk into the other.
<fieldset>
<legend>Color</legend>
<label><input type="radio" name="tin-color" checked /> <span style="background: #7A9A3A"></span> Matcha</label>
<label><input type="radio" name="tin-color" /> <span style="background: #B5651D"></span> Oolong</label>
</fieldset>
<section aria-label="Preview">
<div style="background: #7A9A3A"></div>
<p>Matcha <code>#7A9A3A</code></p>
</section>
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
const radios = within(screen.getByRole("group", { name: "Color" })).getAllByRole("radio");
expect(radios).toHaveLength(4);
colors.forEach((color) => expect(screen.getByRole("radio", { name: color.name })).toBeInTheDocument());
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
expect(screen.getByRole("radio", { name: "Matcha" })).toBeChecked();
expect(screen.getByRole("radio", { name: "Oolong" })).not.toBeChecked();
const preview = screen.getByRole("region", { name: "Preview" });
expect(preview).toHaveTextContent("Matcha");
expect(preview).toHaveTextContent("#7A9A3A");
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
await user.click(screen.getByRole("radio", { name: "Oolong" }));
expect(screen.getByRole("radio", { name: "Oolong" })).toBeChecked();
expect(screen.getByRole("radio", { name: "Matcha" })).not.toBeChecked();
const preview = screen.getByRole("region", { name: "Preview" });
expect(preview).toHaveTextContent("Oolong");
expect(preview).toHaveTextContent("#B5651D");
expect(preview).not.toHaveTextContent("Matcha");
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
await user.click(within(screen.getByRole("group", { name: "Color" })).getByText("Rooibos"));
expect(screen.getByRole("radio", { name: "Rooibos" })).toBeChecked();
expect(screen.getByRole("region", { name: "Preview" })).toHaveTextContent("#A0522D");
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
await user.tab();
expect(screen.getByRole("radio", { name: "Matcha" })).toHaveFocus();
await user.keyboard("{ArrowRight}");
expect(screen.getByRole("radio", { name: "Oolong" })).toBeChecked();
expect(screen.getByRole("radio", { name: "Oolong" })).toHaveFocus();
await user.keyboard("{ArrowDown}");
expect(screen.getByRole("radio", { name: "Butterfly pea" })).toBeChecked();
expect(screen.getByRole("region", { name: "Preview" })).toHaveTextContent("Butterfly pea");
await user.keyboard("{ArrowUp}");
expect(screen.getByRole("radio", { name: "Oolong" })).toBeChecked();
expect(screen.getByRole("region", { name: "Preview" })).toHaveTextContent("Oolong");
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(<App colors={colors} />);
await user.tab();
await user.keyboard("{ArrowLeft}");
expect(screen.getByRole("radio", { name: "Rooibos" })).toBeChecked();
expect(screen.getByRole("region", { name: "Preview" })).toHaveTextContent("Rooibos");
await user.keyboard("{ArrowRight}");
expect(screen.getByRole("radio", { name: "Matcha" })).toBeChecked();
expect(screen.getByRole("region", { name: "Preview" })).toHaveTextContent("Matcha");
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Butterfly pea", hex: "#3F51B5" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(
<>
<button>Before</button>
<App colors={colors} />
<button>After</button>
</>
);
await user.click(screen.getByRole("radio", { name: "Butterfly pea" }));
await user.click(screen.getByRole("button", { name: "Before" }));
await user.tab();
expect(screen.getByRole("radio", { name: "Butterfly pea" })).toHaveFocus();
await user.tab();
expect(screen.getByRole("button", { name: "After" })).toHaveFocus();
const colors = [
{ name: "Matcha", hex: "#7A9A3A" },
{ name: "Oolong", hex: "#B5651D" },
{ name: "Rooibos", hex: "#A0522D" },
];
render(
<>
<App colors={colors} />
<App colors={colors} />
</>
);
const [first, second] = screen.getAllByRole("group", { name: "Color" }).map((group) => within(group));
await user.click(second.getByRole("radio", { name: "Oolong" }));
await user.click(first.getByRole("radio", { name: "Matcha" }));
await user.keyboard("{ArrowLeft}");
expect(first.getByRole("radio", { name: "Rooibos" })).toBeChecked();
expect(first.getByRole("radio", { name: "Rooibos" })).toHaveFocus();
expect(second.getByRole("radio", { name: "Oolong" })).toBeChecked();
expect(second.getByRole("radio", { name: "Rooibos" })).not.toBeChecked();
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 Color swatches. 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.