From 17ebfc0de9a89df44c65dfbc49fb74c6c7895ec2 Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Mon, 30 Sep 2024 15:04:40 -0700 Subject: [PATCH] [Locked Figure Aria] Locked line aria label editor UI (#1684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: - Use `locked-figure-aria.tsx` within LockedLineSettings. - Write the function to auto-generate the locked line aria label with its coordinates and visible labels. Issue: https://khanacademy.atlassian.net/browse/LEMS-2376 ## Test plan: `yarn jest packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.test.tsx` Storybook - http://localhost:6006/?path=/story/perseuseditor-widgets-interactive-graph--mafs-with-locked-figure-labels-all-flags - Confirm that the locked line aria label field is already populated with "Line PQ" (passed in via builder) - Confirm that pressing "Auto-generate" works with no labels, one label, and multiple labels - Confirm that updating the aria label in the editor also updates the aria label on the line - Check the web inspector to confirm the aria-label text is updated - Use a screen reader to confirm the new label is read out image https://github.com/user-attachments/assets/36738b4f-6524-4034-adbb-52f6cc53cb94 Author: nishasy Reviewers: benchristel, nishasy, catandthemachines, anakaren-rojas Required Reviewers: Approved By: benchristel Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ gerald Pull Request URL: https://github.com/Khan/perseus/pull/1684 --- .changeset/itchy-pears-march.md | 6 + .../locked-line-settings.test.tsx | 157 ++++++++++++++++++ .../locked-figures/locked-line-settings.tsx | 41 ++++- 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 .changeset/itchy-pears-march.md diff --git a/.changeset/itchy-pears-march.md b/.changeset/itchy-pears-march.md new file mode 100644 index 0000000000..3b7781655a --- /dev/null +++ b/.changeset/itchy-pears-march.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/perseus": minor +"@khanacademy/perseus-editor": minor +--- + +[Locked Figure Aria] Locked line aria label editor UI diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.test.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.test.tsx index 92ed774f76..d5b5e345cc 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.test.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.test.tsx @@ -550,4 +550,161 @@ describe("LockedLineSettings", () => { }); }); }); + + describe("Aria label", () => { + test("Renders with aria label", () => { + // Arrange + + // Act + render( + , + {wrapper: RenderStateRoot}, + ); + + const input = screen.getByRole("textbox", {name: "Aria label"}); + + // Assert + expect(input).toHaveValue("Line at (x, y)"); + }); + + test("calls onChangeProps when the aria label is updated", async () => { + // Arrange + const onChangeProps = jest.fn(); + render( + , + {wrapper: RenderStateRoot}, + ); + + // Act + const input = screen.getByRole("textbox", {name: "Aria label"}); + await userEvent.clear(input); + await userEvent.type(input, "A"); + + // Assert + expect(onChangeProps).toHaveBeenCalledWith({ + ariaLabel: "A", + }); + }); + + test("aria label auto-generates with different kind", async () => { + // Arrange + const onChangeProps = jest.fn(); + render( + , + {wrapper: RenderStateRoot}, + ); + + // Act + const autoGenButton = screen.getByRole("button", { + name: "Auto-generate", + }); + await userEvent.click(autoGenButton); + + // Assert + expect(onChangeProps).toHaveBeenCalledWith({ + ariaLabel: "Segment from (0, 0) to (2, 2)", + }); + }); + + test("aria label auto-generates (no labels)", async () => { + // Arrange + const onChangeProps = jest.fn(); + + // Act + render( + , + {wrapper: RenderStateRoot}, + ); + + const autoGenButton = screen.getByRole("button", { + name: "Auto-generate", + }); + await userEvent.click(autoGenButton); + + // Assert + expect(onChangeProps).toHaveBeenCalledWith({ + ariaLabel: "Line from (0, 0) to (2, 2)", + }); + }); + + test("aria label auto-generates (one label)", async () => { + // Arrange + const onChangeProps = jest.fn(); + render( + , + {wrapper: RenderStateRoot}, + ); + + // Act + const autoGenButton = screen.getByRole("button", { + name: "Auto-generate", + }); + await userEvent.click(autoGenButton); + + // Assert + expect(onChangeProps).toHaveBeenCalledWith({ + ariaLabel: "Line from (0, 0) to (2, 2) with label A", + }); + }); + + test("aria label auto-generates (multiple labels)", async () => { + // Arrange + const onChangeProps = jest.fn(); + render( + , + {wrapper: RenderStateRoot}, + ); + + // Act + const autoGenButton = screen.getByRole("button", { + name: "Auto-generate", + }); + await userEvent.click(autoGenButton); + + // Assert + expect(onChangeProps).toHaveBeenCalledWith({ + ariaLabel: "Line from (0, 0) to (2, 2) with labels A, B", + }); + }); + }); }); diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.tsx index 563e82be4f..5bf69ad6ac 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-line-settings.tsx @@ -21,6 +21,7 @@ import PerseusEditorAccordion from "../../../components/perseus-editor-accordion import ColorSelect from "./color-select"; import LineStrokeSelect from "./line-stroke-select"; import LineSwatch from "./line-swatch"; +import LockedFigureAria from "./locked-figure-aria"; import LockedFigureSettingsActions from "./locked-figure-settings-actions"; import LockedLabelSettings from "./locked-label-settings"; import LockedPointSettings from "./locked-point-settings"; @@ -56,6 +57,7 @@ const LockedLineSettings = (props: Props) => { showPoint1, showPoint2, labels, + ariaLabel, onChangeProps, onMove, onRemove, @@ -69,6 +71,24 @@ const LockedLineSettings = (props: Props) => { // Check if the line has length 0. const isInvalid = kvector.equal(point1.coord, point2.coord); + function getPrepopulatedAriaLabel() { + let str = `${capitalizeKind} from (${point1.coord[0]}, ${point1.coord[1]}) to (${point2.coord[0]}, ${point2.coord[1]})`; + + if (labels && labels.length > 0) { + str += " with label"; + // Make it "with labels" instead of "with label" if there are + // multiple labels. + if (labels.length > 1) { + str += "s"; + } + + // Separate additional labels with commas. + str += ` ${labels.map((l) => l.text).join(", ")}`; + } + + return str; + } + function handleChangePoint( newPointProps: Partial, index: 0 | 1, @@ -243,9 +263,28 @@ const LockedLineSettings = (props: Props) => { onChangeProps={(newProps) => handleChangePoint(newProps, 1)} /> + {flags?.["mafs"]?.["locked-figures-aria"] && ( + <> + + + + { + onChangeProps(newProps); + }} + /> + + )} + {flags?.["mafs"]?.["locked-line-labels"] && ( <> + + + + Visible labels {labels?.map((label, labelIndex) => (