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) => (