Skip to content

Commit

Permalink
fix(toast): ensure closed toasts do not trigger axe warnings
Browse files Browse the repository at this point in the history
When a Toast's content is only defined on open, its accessible name cannot be computed and is
"empty" while closed. This causes an axe issue when more than one Toast is rendered on a page with
undefined content as their accessible name is not "unique". This fix ensures that the region is
aria-hidden while closed and aria-labelledby is only set when the Toast is opened.
  • Loading branch information
nuria1110 committed Sep 12, 2024
1 parent 6005559 commit 6183351
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 4 deletions.
20 changes: 20 additions & 0 deletions src/components/toast/components.test-pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ export const ToastWhenOtherModalRenders = ({
);
};

export const ToastWithConditionalContent = ({
...props
}: Partial<ToastProps>) => {
const [isOpen1, setIsOpen1] = useState(false);
const [isOpen2, setIsOpen2] = useState(false);
return (
<>
<Button onClick={() => setIsOpen1(!isOpen1)}>Open Toast 1</Button>
<Button onClick={() => setIsOpen2(!isOpen2)}>Open Toast 2</Button>

<Toast open={isOpen1} {...props}>
{isOpen1 && "Toast 1"}
</Toast>
<Toast open={isOpen2} {...props}>
{isOpen2 && "Toast 2"}
</Toast>
</>
);
};

export const ToastAllAlign = ({ ...props }: Partial<ToastProps>) => {
return (
<>
Expand Down
22 changes: 21 additions & 1 deletion src/components/toast/toast-test.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Dialog from "../dialog";

export default {
title: "Toast/Test",
includeStories: ["Default", "Visual", "ToastWhenOtherModalRenders"],
excludeStories: ["TopAndBottom"],
parameters: {
info: { disable: true },
chromatic: {
Expand Down Expand Up @@ -368,3 +368,23 @@ export const ToastWhenOtherModalRenders = () => {
</>
);
};

export const ToastWithConditionalContent = ({
...props
}: Partial<ToastProps>) => {
const [isOpen1, setIsOpen1] = useState(false);
const [isOpen2, setIsOpen2] = useState(false);
return (
<>
<Button onClick={() => setIsOpen1(!isOpen1)}>Open Toast 1</Button>
<Button onClick={() => setIsOpen2(!isOpen2)}>Open Toast 2</Button>

<Toast open={isOpen1} {...props}>
{isOpen1 && "Toast 1"}
</Toast>
<Toast open={isOpen2} {...props}>
{isOpen2 && "Toast 2"}
</Toast>
</>
);
};
8 changes: 5 additions & 3 deletions src/components/toast/toast.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
const focusedElementBeforeOpening = useRef<HTMLElement | null>(null);

const [tabIndex, setTabIndex] = useState<number | undefined>(0);
const ariaLabelledBy = `${!isNotice && toastIconId.current} ${
toastContentId.current
}`;

let refToPass = toastRef;
if (ref && typeof ref === "object" && "current" in ref) {
Expand Down Expand Up @@ -266,9 +269,8 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
isNotice={isNotice}
data-role="toast-wrapper"
role="region"
aria-labelledby={`${!isNotice && toastIconId.current} ${
toastContentId.current
}`}
aria-hidden={!open}
aria-labelledby={open ? ariaLabelledBy : undefined}
>
<TransitionGroup>{renderToastContent()}</TransitionGroup>
</ToastWrapper>
Expand Down
16 changes: 16 additions & 0 deletions src/components/toast/toast.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ToastProps } from ".";
import {
ToastComponent,
ToastWhenOtherModalRenders,
ToastWithConditionalContent,
ToastAllAlign,
} from "./components.test-pw";
import {
Expand Down Expand Up @@ -320,4 +321,19 @@ test.describe("Accessibility tests for Toast component", () => {

await checkAccessibility(page);
});

test("passes accessibility checks when multiple Toasts only have content when opened", async ({
mount,
page,
}) => {
await mount(<ToastWithConditionalContent />);

// check accessibility when both toasts are closed and have undefined content
await checkAccessibility(page);

await button(page).nth(0).click();

// check accessibility when first toast is open and has defined content
await checkAccessibility(page, toastComponent(page));
});
});
16 changes: 16 additions & 0 deletions src/components/toast/toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,22 @@ test("should render with provided `data-` attributes", () => {
expect(toast).toHaveAttribute("data-element", "toast-element");
});

test("should render with accessible name when Toast is open", () => {
render(<Toast open>foobar</Toast>);

const toast = screen.getByRole("region");

expect(toast).toHaveAccessibleName("Success foobar");
});

test("should not render an accessible region when Toast is closed", () => {
render(<Toast open={false}>foobar</Toast>);

const toast = screen.queryByRole("region");

expect(toast).not.toBeInTheDocument();
});

test("should allow custom data props to be passed to close button to be passed via `closeButtonDataProps`", () => {
render(
<Toast
Expand Down

0 comments on commit 6183351

Please sign in to comment.