diff --git a/.changeset/giant-donkeys-wink.md b/.changeset/giant-donkeys-wink.md new file mode 100644 index 0000000000..c8864127ee --- /dev/null +++ b/.changeset/giant-donkeys-wink.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +[Image] | (a11y) | Add aria-describedby to Explore Image modal diff --git a/packages/perseus/src/widgets/image/components/explore-image-modal-content.tsx b/packages/perseus/src/widgets/image/components/explore-image-modal-content.tsx index 9a7004b542..7011f793a2 100644 --- a/packages/perseus/src/widgets/image/components/explore-image-modal-content.tsx +++ b/packages/perseus/src/widgets/image/components/explore-image-modal-content.tsx @@ -16,7 +16,12 @@ import type {CommonImageProps, ZoomProps, GifProps} from "./image-info-area"; const MODAL_HEIGHT = 568; -type Props = CommonImageProps & ZoomProps & GifProps; +type Props = CommonImageProps & + ZoomProps & + GifProps & { + captionId: string; + longDescId: string; + }; export default function ExploreImageModalContent({ backgroundImage, @@ -30,6 +35,8 @@ export default function ExploreImageModalContent({ labels, range, zoomSize, + captionId, + longDescId, }: Props) { const [isGifPlaying, setIsGifPlaying] = React.useState(false); const context = React.useContext(PerseusI18nContext); @@ -146,7 +153,10 @@ export default function ExploreImageModalContent({ )} {caption && ( -
+
{/* Use Renderer so that the caption can support markdown and TeX. */} {/* Use Renderer so that the description can support markdown and TeX. */} - +
+ +
); diff --git a/packages/perseus/src/widgets/image/components/explore-image-modal.test.tsx b/packages/perseus/src/widgets/image/components/explore-image-modal.test.tsx index 78c4d06d98..0b1c2b4b74 100644 --- a/packages/perseus/src/widgets/image/components/explore-image-modal.test.tsx +++ b/packages/perseus/src/widgets/image/components/explore-image-modal.test.tsx @@ -234,6 +234,38 @@ describe("ExploreImageModal", () => { expect(screen.getByText("widget long description")).toBeInTheDocument(); }); + it("sets the describedby to short and long description IDs if there is a caption", () => { + // Arrange, Act + renderModal({ + ...defaultProps, + backgroundImage: earthMoonImage, + caption: "widget caption", + }); + + // Assert + const dialog = screen.getByRole("dialog"); + // Regex for "uniqueId-caption uniqueId-long-desc" + expect(dialog.getAttribute("aria-describedby")).toMatch( + /^:r\w+:-caption :r\w+:-long-desc$/, + ); + }); + + it("sets the describedby to long description ID if there is no caption", () => { + // Arrange, Act + renderModal({ + ...defaultProps, + backgroundImage: earthMoonImage, + longDescription: "widget long description", + }); + + // Assert + const dialog = screen.getByRole("dialog"); + const ariaDescribedBy = dialog.getAttribute("aria-describedby"); + // Regex for "uniqueId-long-desc" + expect(ariaDescribedBy).toMatch(/^:r\w+:-long-desc$/); + expect(ariaDescribedBy).not.toMatch(/caption/); + }); + describe("gif controls", () => { it("should render gif controls if the image is a gif", () => { // Arrange, Act diff --git a/packages/perseus/src/widgets/image/components/explore-image-modal.tsx b/packages/perseus/src/widgets/image/components/explore-image-modal.tsx index 2fc6948af0..5858e66a6b 100644 --- a/packages/perseus/src/widgets/image/components/explore-image-modal.tsx +++ b/packages/perseus/src/widgets/image/components/explore-image-modal.tsx @@ -14,6 +14,9 @@ type Props = CommonImageProps & ZoomProps & GifProps; export const ExploreImageModal = (props: Props) => { const context = React.useContext(PerseusI18nContext); + const uniqueId = React.useId(); + const captionId = `${uniqueId}-caption`; + const longDescId = `${uniqueId}-long-desc`; const titleText = props.title || context.strings.imageAlternativeTitle; const title = ( @@ -39,7 +42,16 @@ export const ExploreImageModal = (props: Props) => { > } + content={ + + } + aria-describedby={ + props.caption ? `${captionId} ${longDescId}` : longDescId + } styles={{ root: wbStyles.root, }}