diff --git a/packages/perseus-editor/src/components/__tests__/interactive-graph-description.test.tsx b/packages/perseus-editor/src/components/__tests__/interactive-graph-description.test.tsx new file mode 100644 index 0000000000..5195146d49 --- /dev/null +++ b/packages/perseus-editor/src/components/__tests__/interactive-graph-description.test.tsx @@ -0,0 +1,102 @@ +import {Dependencies} from "@khanacademy/perseus"; +import {render, screen} from "@testing-library/react"; +import {userEvent as userEventLib} from "@testing-library/user-event"; +import * as React from "react"; + +import {testDependencies} from "../../../../../testing/test-dependencies"; +import InteractiveGraphDescription from "../interactive-graph-description"; + +import type {UserEvent} from "@testing-library/user-event"; + +import "@testing-library/jest-dom"; // Imports custom matchers + +function userEventForFakeTimers() { + return userEventLib.setup({ + advanceTimers: jest.advanceTimersByTime, + }); +} + +describe("InteractiveGraphSettings", () => { + let userEvent: UserEvent; + beforeEach(() => { + userEvent = userEventForFakeTimers(); + jest.spyOn(Dependencies, "getDependencies").mockReturnValue( + testDependencies, + ); + }); + + test("renders", () => { + // Arrange + render( + , + ); + + // Act + const titleInput = screen.getByRole("textbox", {name: "Title"}); + const descriptionInput = screen.getByRole("textbox", { + name: "Description", + }); + + // Assert + expect(titleInput).toBeInTheDocument(); + expect(titleInput).toHaveValue("Graph Title"); + expect(descriptionInput).toBeInTheDocument(); + expect(descriptionInput).toHaveValue("Graph Description"); + }); + + test("calls onChange when the title is changed", async () => { + // Arrange + const onChange = jest.fn(); + render( + , + ); + + // Act + const titleInput = screen.getByRole("textbox", {name: "Title"}); + await userEvent.clear(titleInput); + await userEvent.type(titleInput, "Zot"); + + // Assert + // Calls are not being accumulated because they're mocked. + expect(onChange.mock.calls).toEqual([ + [{fullGraphAriaLabel: "Z"}], + [{fullGraphAriaLabel: "o"}], + [{fullGraphAriaLabel: "t"}], + ]); + }); + + test("calls onChange when the description is changed", async () => { + // Arrange + const onChange = jest.fn(); + render( + , + ); + + // Act + const descriptionInput = screen.getByRole("textbox", { + name: "Description", + }); + await userEvent.clear(descriptionInput); + await userEvent.type(descriptionInput, "Zot"); + + // Assert + // Calls are not being accumulated because they're mocked. + expect(onChange.mock.calls).toEqual([ + [{fullGraphAriaDescription: "Z"}], + [{fullGraphAriaDescription: "o"}], + [{fullGraphAriaDescription: "t"}], + ]); + }); +}); diff --git a/packages/perseus-editor/src/components/interactive-graph-description.tsx b/packages/perseus-editor/src/components/interactive-graph-description.tsx index 257fedbdaa..86700f3b32 100644 --- a/packages/perseus-editor/src/components/interactive-graph-description.tsx +++ b/packages/perseus-editor/src/components/interactive-graph-description.tsx @@ -36,7 +36,7 @@ export default function InteractiveGraphDescription(props: Props) { are used by screen readers to describe content to users who are visually impaired. - + Title - + Description {/* TODO: Change this to a WB TextArea */} { }), ).toBeNull(); }); + + test("shows description section", () => { + // Arrange + + // Act + render( + , + { + wrapper: RenderStateRoot, + }, + ); + + // Assert + expect( + screen.getByRole("textbox", {name: "Title"}), + ).toBeInTheDocument(); + expect( + screen.getByRole("textbox", {name: "Description"}), + ).toBeInTheDocument(); + }); }); diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx index 1e2c9c90dc..8ff861a1db 100644 --- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx @@ -19,6 +19,7 @@ import { angleQuestionWithDefaultCorrect, circleQuestion, circleQuestionWithDefaultCorrect, + interactiveGraphWithAriaLabel, linearQuestion, linearQuestionWithDefaultCorrect, linearSystemQuestion, @@ -911,4 +912,47 @@ describe("locked layer", () => { top: "240px", }); }); + + it("should have an aria-label and description if they are provided", async () => { + // Arrange + const {container} = renderQuestion(interactiveGraphWithAriaLabel, { + flags: { + mafs: { + segment: true, + "interactive-graph-locked-features-labels": true, + }, + }, + }); + + // Act + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const graph = container.querySelector(".mafs-graph"); + + // Assert + expect(graph).toHaveAttribute("aria-label", "Segment Graph Title"); + // The aria-describedby attribute is set to the description + // element's ID. This ID is unique to the graph instance, so + // we can't predict it in this test. + expect(graph).toHaveAttribute("aria-describedby"); + }); + + it("should not have an aria-label or description if they are not provided", async () => { + // Arrange + const {container} = renderQuestion(segmentQuestion, { + flags: { + mafs: { + segment: true, + "interactive-graph-locked-features-labels": true, + }, + }, + }); + + // Act + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const graph = container.querySelector(".mafs-graph"); + + // Assert + expect(graph).not.toHaveAttribute("aria-label"); + expect(graph).not.toHaveAttribute("aria-describedby"); + }); }); diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx index 397fa2969a..e42191b636 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx @@ -108,21 +108,25 @@ export const MafsGraph = (props: MafsGraphProps) => { } }} aria-label={fullGraphAriaLabel} - aria-describedby={descriptionId} + aria-describedby={ + fullGraphAriaDescription ? descriptionId : undefined + } ref={graphRef} tabIndex={0} > - - {fullGraphAriaDescription} - + {fullGraphAriaDescription && ( + + {fullGraphAriaDescription} + + )}