The tea shop's info page has tabs, and the Menu tab's code loads slowly. Right now clicking it blanks the page with a spinner. Build Tabs, the default export, so the old tab stays on screen until the new one is ready.
It takes tabs, an array of { id, label, content }, where content is the element to show. Some of them suspend while they load (a lazy component, say). The starter's default has three sample tabs with a slow Menu, so the Preview works.
Render one button per tab with role="tab" and the tab's label, inside an element with role="tablist". Below them, an element with role="tabpanel" shows only the selected tab's content, inside a Suspense boundary whose fallback is Loading… (with the single … character). The first tab is selected at first.
Switch tabs inside a transition. While a tab's content is still loading, the old tab's content stays on screen and keeps aria-selected="true", and the clicked tab's button gets aria-busy="true". No other button is busy. When the content is ready, it shows, the clicked tab gets aria-selected="true", and nothing is busy.
A click on another tab while one is loading wins: if that tab is ready, it shows right away, and the slow tab never takes over when it finishes loading. The fallback only shows when there's nothing old to keep, like a first tab that suspends.
const Menu = lazy(() => import("./Menu.jsx"));
<Tabs
tabs={[
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
]}
/>
// Click Menu: "A small tea shop." stays, the Menu button has aria-busy="true".
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
expect(screen.getAllByRole("tab").map((tab) => tab.textContent)).toEqual(["About", "Menu", "Contact"]);
expect(screen.getByRole("tab", { name: "About" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Menu" })).toHaveAttribute("aria-selected", "false");
expect(screen.getByRole("tabpanel")).toHaveTextContent("A small tea shop.");
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
await user.click(screen.getByRole("tab", { name: "Contact" }));
expect(screen.getByRole("tabpanel")).toHaveTextContent("Call 555 0100.");
expect(screen.queryByText("A small tea shop.")).not.toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Contact" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "About" })).toHaveAttribute("aria-selected", "false");
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
await user.click(screen.getByRole("tab", { name: "Menu" }));
expect(screen.getByText("A small tea shop.")).toBeVisible();
expect(screen.queryByText("Loading…")).not.toBeInTheDocument();
expect(screen.getByRole("tab", { name: "About" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Menu" })).toHaveAttribute("aria-selected", "false");
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
await user.click(screen.getByRole("tab", { name: "Menu" }));
expect(screen.getByRole("tab", { name: "Menu" })).toHaveAttribute("aria-busy", "true");
expect(screen.getByRole("tab", { name: "About" })).not.toHaveAttribute("aria-busy", "true");
expect(screen.getByRole("tab", { name: "Contact" })).not.toHaveAttribute("aria-busy", "true");
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
await user.click(screen.getByRole("tab", { name: "Menu" }));
loadMenu();
expect(await screen.findByText("Today: Sencha and Assam.")).toBeInTheDocument();
expect(screen.queryByText("A small tea shop.")).not.toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Menu" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Menu" })).not.toHaveAttribute("aria-busy", "true");
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={tabs} />);
await user.click(screen.getByRole("tab", { name: "Menu" }));
await user.click(screen.getByRole("tab", { name: "Contact" }));
expect(await screen.findByText("Call 555 0100.")).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Contact" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Menu" })).not.toHaveAttribute("aria-busy", "true");
loadMenu();
await flush();
await sleep(400);
expect(screen.getByRole("tabpanel")).toHaveTextContent("Call 555 0100.");
expect(screen.queryByText("Today: Sencha and Assam.")).not.toBeInTheDocument();
const menuModule = deferred();
const Menu = React.lazy(() => menuModule.promise);
const loadMenu = () => menuModule.resolve({ default: () => <p>Today: Sencha and Assam.</p> });
const tabs = [
{ id: "about", label: "About", content: <p>A small tea shop.</p> },
{ id: "menu", label: "Menu", content: <Menu /> },
{ id: "contact", label: "Contact", content: <p>Call 555 0100.</p> },
];
render(<App tabs={[tabs[1], tabs[0]]} />);
expect(screen.getByText("Loading…")).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "About" })).toBeInTheDocument();
loadMenu();
expect(await screen.findByText("Today: Sencha and Assam.")).toBeInTheDocument();
expect(screen.queryByText("Loading…")).not.toBeInTheDocument();
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 Tabs that wait with transitions. 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.