The tea shop's home page shows photos of its teas in a carousel. Build App: it takes slides, a list of { src, alt }, and autoplay (default false). When slides isn't given, as in the Preview, use a few of your own (the images don't need to exist).
Render a <section aria-roledescription="carousel" aria-label="Tea gallery">. Inside, show only the current slide's <img> (with its alt), a Previous and a Next button, and one dot per slide: a button with aria-label like Go to slide 2. The current slide's dot has aria-current="true"; the others don't. The first slide shows at first.
Next shows the next slide, and on the last slide goes back to the first. Previous shows the one before, and on the first goes to the last. Clicking a dot shows its slide.
With autoplay, move to the next slide every 3 seconds, wrapping around like Next. Pause while the pointer is over the carousel or the focus is anywhere inside it. When it resumes, the next move comes a full 3 seconds later. Without autoplay, run no timer at all, and leave no timer running when the carousel is removed.
<App autoplay slides={[
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
]} />
// renders:
<section aria-roledescription="carousel" aria-label="Tea gallery">
<img src="/teas/sencha.jpg" alt="Sencha leaves" />
<button>Previous</button>
<button>Next</button>
<button aria-label="Go to slide 1" aria-current="true"></button>
<button aria-label="Go to slide 2"></button>
</section>
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
render(<App slides={slides} />);
expect(screen.getByRole("region", { name: "Tea gallery" })).toHaveAttribute("aria-roledescription", "carousel");
expect(screen.getAllByRole("img")).toHaveLength(1);
expect(shown()).toBe("Sencha leaves");
expect(screen.getByRole("button", { name: "Go to slide 1" })).toHaveAttribute("aria-current", "true");
for (const n of [2, 3, 4]) {
expect(screen.getByRole("button", { name: "Go to slide " + n })).not.toHaveAttribute("aria-current", "true");
}
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
render(<App slides={slides} />);
await user.click(screen.getByRole("button", { name: "Next" }));
await user.click(screen.getByRole("button", { name: "Next" }));
expect(shown()).toBe("Rolled oolong");
await user.click(screen.getByRole("button", { name: "Previous" }));
expect(shown()).toBe("Assam in a cup");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
render(<App slides={slides} />);
await user.click(screen.getByRole("button", { name: "Previous" }));
expect(shown()).toBe("Rooibos tea");
await user.click(screen.getByRole("button", { name: "Next" }));
expect(shown()).toBe("Sencha leaves");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
render(<App slides={slides} />);
await user.click(screen.getByRole("button", { name: "Go to slide 3" }));
expect(shown()).toBe("Rolled oolong");
expect(screen.getByRole("button", { name: "Go to slide 3" })).toHaveAttribute("aria-current", "true");
expect(screen.getByRole("button", { name: "Go to slide 1" })).not.toHaveAttribute("aria-current", "true");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
const clock = useFakeTimers();
render(<App slides={slides} autoplay />);
await clock.advance(2999);
expect(shown()).toBe("Sencha leaves");
await clock.advance(1);
expect(shown()).toBe("Assam in a cup");
await clock.advance(9000);
expect(shown()).toBe("Sencha leaves");
expect(screen.getByRole("button", { name: "Go to slide 1" })).toHaveAttribute("aria-current", "true");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
const clock = useFakeTimers();
render(<App slides={slides} />);
expect(clock.pending()).toBe(0);
await clock.advance(10000);
expect(shown()).toBe("Sencha leaves");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
const clock = useFakeTimers();
render(<App slides={slides} autoplay />);
await user.hover(screen.getByRole("img"));
await clock.advance(7000);
expect(shown()).toBe("Sencha leaves");
await user.unhover(screen.getByRole("img"));
await clock.advance(2999);
expect(shown()).toBe("Sencha leaves");
await clock.advance(1);
expect(shown()).toBe("Assam in a cup");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
const clock = useFakeTimers();
render(
<>
<App slides={slides} autoplay />
<button>Outside</button>
</>,
);
await user.tab();
await clock.advance(4000);
await user.tab();
await clock.advance(4000);
expect(shown()).toBe("Sencha leaves");
await user.click(screen.getByRole("button", { name: "Outside" }));
await clock.advance(3000);
expect(shown()).toBe("Assam in a cup");
const slides = [
{ src: "/teas/sencha.jpg", alt: "Sencha leaves" },
{ src: "/teas/assam.jpg", alt: "Assam in a cup" },
{ src: "/teas/oolong.jpg", alt: "Rolled oolong" },
{ src: "/teas/rooibos.jpg", alt: "Rooibos tea" },
];
const shown = () => screen.getByRole("img").getAttribute("alt");
const clock = useFakeTimers();
const { unmount } = render(<App slides={slides} autoplay />);
await clock.advance(1000);
unmount();
expect(clock.pending()).toBe(0);
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 Image carousel. 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.