The tea shop's cart has a Clear cart button that asks before it empties the cart. Build Modal, the dialog it opens, and export it by name. The default export, App, is a small demo that uses it; the starter has it ready.
Modal takes open, title, onClose, and children. While open is false it renders nothing. While it's true, render into document.body with createPortal, not inside the component's parent: a <div role="dialog" aria-modal="true"> holding an <h2> with the title, then the children, then a close button with aria-label="Close" (its text can be ×). The dialog is labelled by its heading through aria-labelledby.
When the dialog opens, move the focus to the first thing in it that can take focus (a button, link, or form field), in page order. With nothing focusable in children, that's the Close button.
Pressing Escape while the focus is in the dialog calls onClose, and so does the Close button. The parent decides what closing means: it sets open back to false.
When the dialog goes away, put the focus back on whatever had it before the dialog opened, usually the button that opened it.
While it's open, Tab and Shift+Tab stay inside the dialog: Tab on its last focusable element goes to its first, and Shift+Tab on its first goes to its last.
<button onClick={() => setOpen(true)}>Clear cart</button>
<Modal open={open} title="Clear the cart?" onClose={() => setOpen(false)}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
// renders, at the end of document.body:
<div role="dialog" aria-modal="true" aria-labelledby=":r1:">
<h2 id=":r1:">Clear the cart?</h2>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
<button aria-label="Close">×</button>
</div>
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
const dialog = screen.getByRole("dialog", { name: "Clear the cart?" });
expect(dialog).toHaveAttribute("aria-modal", "true");
expect(within(dialog).getByRole("heading", { level: 2, name: "Clear the cart?" })).toBeInTheDocument();
expect(within(dialog).getByRole("button", { name: "Close" })).toBeInTheDocument();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
const { container } = render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
const dialog = screen.getByRole("dialog");
expect(container.contains(dialog)).toBe(false);
expect(document.body.contains(dialog)).toBe(true);
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
expect(screen.getByRole("button", { name: "Keep teas" })).toHaveFocus();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Saved" onClose={() => setOpen(false)}>
<p>Your order is saved.</p>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
expect(screen.getByRole("button", { name: "Close" })).toHaveFocus();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
await user.keyboard("{Escape}");
expect(onClose).toHaveBeenCalledTimes(1);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Open" })).toHaveFocus();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
await user.click(screen.getByRole("button", { name: "Close" }));
expect(onClose).toHaveBeenCalledTimes(1);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Open" })).toHaveFocus();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
await user.tab();
expect(screen.getByRole("button", { name: "Clear" })).toHaveFocus();
await user.tab();
expect(screen.getByRole("button", { name: "Close" })).toHaveFocus();
await user.tab();
expect(screen.getByRole("button", { name: "Keep teas" })).toHaveFocus();
const onClose = spy();
function Page() {
const [open, setOpen] = React.useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>Open</button>
<Modal open={open} title="Clear the cart?" onClose={() => { onClose(); setOpen(false); }}>
<p>This removes every tea from your cart.</p>
<button>Keep teas</button>
<button>Clear</button>
</Modal>
</div>
);
}
render(<Page />);
await user.click(screen.getByRole("button", { name: "Open" }));
await user.tab({ shift: true });
expect(screen.getByRole("button", { name: "Close" })).toHaveFocus();
render(<App />);
await user.click(screen.getByRole("button", { name: "Clear cart" }));
await user.click(within(screen.getByRole("dialog")).getByRole("button", { name: "Clear" }));
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
expect(screen.getByText("0 teas in your cart")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Clear cart" })).toHaveFocus();
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 Modal dialog. 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.