From 776c38b99fbf205877b4c1c8ad42681329445b7e Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Wed, 18 Dec 2024 14:29:40 -0800 Subject: [PATCH 01/20] docs(changeset): adds aria labels to line segment --- .changeset/fuzzy-cheetahs-develop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-cheetahs-develop.md diff --git a/.changeset/fuzzy-cheetahs-develop.md b/.changeset/fuzzy-cheetahs-develop.md new file mode 100644 index 0000000000..392b529878 --- /dev/null +++ b/.changeset/fuzzy-cheetahs-develop.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +adds aria labels to line segment From bf27d160f6ca695948c3523dd06dbc13c283d669 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Wed, 18 Dec 2024 14:30:03 -0800 Subject: [PATCH 02/20] add aria labels to line segment --- packages/perseus/src/strings.ts | 56 ++++++++++++++++++- .../interactive-graphs/graphs/segment.tsx | 54 ++++++++++++++++-- .../interactive-graphs/mafs-graph.test.tsx | 12 ++-- 3 files changed, 110 insertions(+), 12 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 82ffa14133..4f075b097f 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -247,6 +247,29 @@ export type PerseusStrings = { tsX: string; tsY: string; }) => string; + srSegmentGraphAriaLabel: string; + srSegmentGraphAriaDescription: ({ + point1X, + point1Y, + point2X, + point2Y, + length, + }: { + point1X: string; + point1Y: string; + point2X: string; + point2Y: string; + length: string; + }) => string; + srSegmentGraphEndpointAriaLabel: ({ + endpointNumber, + x, + y, + }: { + endpointNumber: number; + x: string; + y: string; + }) => string; // The above strings are used for interactive graph SR descriptions. }; @@ -526,13 +549,33 @@ export const strings: { message: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees", }, - srAngleGraphAriaLabel: "An angle on a coordinate plane.", + srAngleGraphAriaLabel: { + context: + "Screenreader-accessible label for an angle on a coordinate plane.", + message: "An angle on a coordinate plane", + }, srAngleGraphAriaDescription: { context: "Screenreader-only description of an angle on a coordinate plane.", message: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the initial side at %(isX)s comma %(isY)s and a point on the terminal side at %(tsX)s comma %(tsY)s", }, + srSegmentGraphAriaLabel: { + context: + "Screenreader-accessible description of a line segment on a coordinate plane.", + message: "A line segment on a coordinate plane", + }, + srSegmentGraphAriaDescription: { + context: + "Screenreader-only description of a line segment on a coordinate plane.", + message: + "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s", + }, + srSegmentGraphEndpointAriaLabel: { + context: + "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", + message: "Endpoint $(endpointNumber)s at $(x)s comma $(y)s", + }, // The above strings are used for interactive graph SR descriptions. }; @@ -748,5 +791,16 @@ export const mockStrings: PerseusStrings = { tsY, }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the initial side at ${isX} comma ${isY} and a point on the terminal side at ${tsX} comma ${tsY}.`, + srSegmentGraphAriaLabel: "A line segment on a coordinate plane.", + srSegmentGraphAriaDescription: ({ + point1X, + point1Y, + point2X, + point2Y, + length, + }) => + `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length}`, + srSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => + `Endpoint ${endpointNumber} at ${x} comma ${y}`, // The above strings are used for interactive graph SR descriptions. }; diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index b8e5b964d1..282d8ea932 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -1,8 +1,12 @@ +import {point as kpoint} from "@khanacademy/kmath"; import * as React from "react"; +import {usePerseusI18n} from "../../../components/i18n-context"; +import {X, Y} from "../math"; import {actions} from "../reducer/interactive-graph-action"; import {MovableLine} from "./components/movable-line"; +import {srFormatNumber} from "./screenreader-text"; import type { Dispatch, @@ -24,12 +28,37 @@ export function renderSegmentGraph( type SegmentProps = MafsGraphProps; -const SegmentGraph = (props: SegmentProps) => { - const {dispatch} = props; - const {coords: segments} = props.graphState; +const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { + const {coords: segments} = graphState; + const {strings, locale} = usePerseusI18n(); + const lengthOfSegment = + segments[0] && kpoint.distanceToPoint(...segments[0]); + + const wholeSegmentAriaLabel = strings.srAngleGraphAriaLabel; + const wholeSegmentAriaDescription = + segments[0] && + strings.srSegmentGraphAriaDescription({ + point1X: srFormatNumber(segments[0][0][X], locale), + point1Y: srFormatNumber(segments[0][0][Y], locale), + point2X: srFormatNumber(segments[0][1][X], locale), + point2Y: srFormatNumber(segments[0][1][Y], locale), + length: srFormatNumber(lengthOfSegment, locale), + }); + + function formatSegment(endpointNumber: number, x: number, y: number) { + return strings.srSegmentGraphEndpointAriaLabel({ + endpointNumber: endpointNumber, + x: srFormatNumber(x, locale), + y: srFormatNumber(y, locale), + }); + } return ( - <> + {segments?.map((segment, i) => ( { ), ); }} + ariaLabels={{ + point1AriaLabel: formatSegment( + 1, + segment[0][X], + segment[0][Y], + ), + point2AriaLabel: formatSegment( + 2, + segment[1][X], + segment[1][Y], + ), + }} /> ))} - + + {wholeSegmentAriaDescription} + + ); }; diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx index d9b4f0d03f..82847af9ad 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx @@ -154,8 +154,8 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Point 1 at 0 comma 0"); - expectLabelInDoc("Point 2 at -7 comma 0.5"); + expectLabelInDoc("Endpoint 1 at 0 comma 0"); + expectLabelInDoc("Endpoint 2 at -7 comma 0.5"); }); it("renders ARIA labels for each point (multiple segments)", () => { @@ -187,10 +187,10 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Point 1 at 0 comma 0"); - expectLabelInDoc("Point 2 at -7 comma 0.5"); - expectLabelInDoc("Point 1 at 1 comma 1"); - expectLabelInDoc("Point 2 at 7 comma 0.5"); + expectLabelInDoc("Endpoint 1 at 0 comma 0"); + expectLabelInDoc("Endpoint 2 at -7 comma 0.5"); + expectLabelInDoc("Endpoint 1 at 1 comma 1"); + expectLabelInDoc("Endpoint 2 at 7 comma 0.5"); }); it("renders ARIA labels for each point (linear)", () => { From 76391ecd3f6cdde7b8e43629667fcad6efcc1096 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Wed, 18 Dec 2024 15:17:22 -0800 Subject: [PATCH 03/20] use line segment label --- .../perseus/src/widgets/interactive-graphs/graphs/segment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 282d8ea932..892de83415 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -34,7 +34,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const lengthOfSegment = segments[0] && kpoint.distanceToPoint(...segments[0]); - const wholeSegmentAriaLabel = strings.srAngleGraphAriaLabel; + const wholeSegmentAriaLabel = strings.srSegmentGraphAriaLabel; const wholeSegmentAriaDescription = segments[0] && strings.srSegmentGraphAriaDescription({ From de688011e479fdb24556456aae6c52222ee5338a Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Wed, 18 Dec 2024 16:33:27 -0800 Subject: [PATCH 04/20] remove role from segment --- .../perseus/src/widgets/interactive-graphs/graphs/segment.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 892de83415..0c7bdbc2dc 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -57,7 +57,6 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { {segments?.map((segment, i) => ( Date: Fri, 20 Dec 2024 14:56:37 -0800 Subject: [PATCH 05/20] add description for multiple segments --- packages/perseus/src/strings.ts | 7 +- .../interactive-graphs/graphs/segment.tsx | 107 ++++++++++-------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index a123b5a736..2e554ed307 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -265,12 +265,14 @@ export type PerseusStrings = { point2X, point2Y, length, + indexOfSegment }: { point1X: string; point1Y: string; point2X: string; point2Y: string; length: string; + indexOfSegment: number; }) => string; srSegmentGraphEndpointAriaLabel: ({ endpointNumber, @@ -586,7 +588,7 @@ export const strings: { context: "Screenreader-only description of a line segment on a coordinate plane.", message: - "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s", + "Segment %(indexOfSegment)s. Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s", }, srSegmentGraphEndpointAriaLabel: { context: @@ -817,8 +819,9 @@ export const mockStrings: PerseusStrings = { point2X, point2Y, length, + indexOfSegment }) => - `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length}`, + `Segment ${indexOfSegment}. Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length}`, srSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => `Endpoint ${endpointNumber} at ${x} comma ${y}`, // The above strings are used for interactive graph SR descriptions. diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 0c7bdbc2dc..72a7bc556b 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -12,6 +12,7 @@ import type { Dispatch, InteractiveGraphElementSuite, MafsGraphProps, + PairOfPoints, SegmentGraphState, } from "../types"; import type {vec} from "mafs"; @@ -31,19 +32,23 @@ type SegmentProps = MafsGraphProps; const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const {coords: segments} = graphState; const {strings, locale} = usePerseusI18n(); - const lengthOfSegment = - segments[0] && kpoint.distanceToPoint(...segments[0]); - const wholeSegmentAriaLabel = strings.srSegmentGraphAriaLabel; - const wholeSegmentAriaDescription = - segments[0] && - strings.srSegmentGraphAriaDescription({ - point1X: srFormatNumber(segments[0][0][X], locale), - point1Y: srFormatNumber(segments[0][0][Y], locale), - point2X: srFormatNumber(segments[0][1][X], locale), - point2Y: srFormatNumber(segments[0][1][Y], locale), - length: srFormatNumber(lengthOfSegment, locale), + + function getLengthOfSegment(segment: PairOfPoints) { + return kpoint.distanceToPoint(...segment); + } + + function getWholeSegmentAriaDescription(segment: PairOfPoints, index: number) { + const indexOfSegment = index + 1; + return strings.srSegmentGraphAriaDescription({ + point1X: srFormatNumber(segment[0][X], locale), + point1Y: srFormatNumber(segment[0][Y], locale), + point2X: srFormatNumber(segment[1][X], locale), + point2Y: srFormatNumber(segment[1][Y], locale), + length: srFormatNumber(getLengthOfSegment(segment), locale), + indexOfSegment: indexOfSegment, }); + } function formatSegment(endpointNumber: number, x: number, y: number) { return strings.srSegmentGraphEndpointAriaLabel({ @@ -54,46 +59,52 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { } return ( - + <> {segments?.map((segment, i) => ( - { - dispatch(actions.segment.moveLine(i, delta)); - }} - onMovePoint={( - endpointIndex: number, - destination: vec.Vector2, - ) => { - dispatch( - actions.segment.movePointInFigure( - i, - endpointIndex, - destination, + > + { + dispatch(actions.segment.moveLine(i, delta)); + }} + onMovePoint={( + endpointIndex: number, + destination: vec.Vector2, + ) => { + dispatch( + actions.segment.movePointInFigure( + i, + endpointIndex, + destination, + ), + ); + }} + ariaLabels={{ + point1AriaLabel: formatSegment( + 1, + segment[0][X], + segment[0][Y], + ), + point2AriaLabel: formatSegment( + 2, + segment[1][X], + segment[1][Y], ), - ); - }} - ariaLabels={{ - point1AriaLabel: formatSegment( - 1, - segment[0][X], - segment[0][Y], - ), - point2AriaLabel: formatSegment( - 2, - segment[1][X], - segment[1][Y], - ), - }} - /> + }} + /> + + {getWholeSegmentAriaDescription(segment, i)} + + ))} - - {wholeSegmentAriaDescription} - - + ); }; From a4f033a7e674a6095f2fd1f609c9b6801256275b Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Fri, 20 Dec 2024 14:58:27 -0800 Subject: [PATCH 06/20] update strings --- packages/perseus/src/strings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 2e554ed307..e0c6c284d9 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -588,7 +588,7 @@ export const strings: { context: "Screenreader-only description of a line segment on a coordinate plane.", message: - "Segment %(indexOfSegment)s. Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s", + "Segment %(indexOfSegment)s. Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", }, srSegmentGraphEndpointAriaLabel: { context: @@ -821,7 +821,7 @@ export const mockStrings: PerseusStrings = { length, indexOfSegment }) => - `Segment ${indexOfSegment}. Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length}`, + `Segment ${indexOfSegment}. Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, srSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => `Endpoint ${endpointNumber} at ${x} comma ${y}`, // The above strings are used for interactive graph SR descriptions. From 67b3bd75a16c93a62248aa1346ef698317844619 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Mon, 23 Dec 2024 10:47:22 -0800 Subject: [PATCH 07/20] add periods at the end of sentences --- packages/perseus/src/strings.ts | 4 ++-- .../widgets/interactive-graphs/mafs-graph.test.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index e0c6c284d9..f23a1ec810 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -593,7 +593,7 @@ export const strings: { srSegmentGraphEndpointAriaLabel: { context: "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", - message: "Endpoint $(endpointNumber)s at $(x)s comma $(y)s", + message: "Endpoint $(endpointNumber)s at $(x)s comma $(y)s.", }, // The above strings are used for interactive graph SR descriptions. }; @@ -823,6 +823,6 @@ export const mockStrings: PerseusStrings = { }) => `Segment ${indexOfSegment}. Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, srSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => - `Endpoint ${endpointNumber} at ${x} comma ${y}`, + `Endpoint ${endpointNumber} at ${x} comma ${y}.`, // The above strings are used for interactive graph SR descriptions. }; diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx index 82847af9ad..d3fa319ba1 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx @@ -154,8 +154,8 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Endpoint 1 at 0 comma 0"); - expectLabelInDoc("Endpoint 2 at -7 comma 0.5"); + expectLabelInDoc("Endpoint 1 at 0 comma 0."); + expectLabelInDoc("Endpoint 2 at -7 comma 0.5."); }); it("renders ARIA labels for each point (multiple segments)", () => { @@ -187,10 +187,10 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Endpoint 1 at 0 comma 0"); - expectLabelInDoc("Endpoint 2 at -7 comma 0.5"); - expectLabelInDoc("Endpoint 1 at 1 comma 1"); - expectLabelInDoc("Endpoint 2 at 7 comma 0.5"); + expectLabelInDoc("Endpoint 1 at 0 comma 0."); + expectLabelInDoc("Endpoint 2 at -7 comma 0.5."); + expectLabelInDoc("Endpoint 1 at 1 comma 1."); + expectLabelInDoc("Endpoint 2 at 7 comma 0.5."); }); it("renders ARIA labels for each point (linear)", () => { From 06114d8def7a4212ed6abfdf038bc4bf7a77eb8b Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Mon, 23 Dec 2024 14:53:36 -0800 Subject: [PATCH 08/20] update strings to include multiples and add desc for whole graph --- packages/perseus/src/strings.ts | 66 +++++++++++++---- .../interactive-graphs/graphs/segment.tsx | 73 +++++++++++++++---- .../interactive-graphs/mafs-graph.test.tsx | 8 +- 3 files changed, 112 insertions(+), 35 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index f23a1ec810..f35ed53233 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -258,14 +258,19 @@ export type PerseusStrings = { tsX: string; tsY: string; }) => string; - srSegmentGraphAriaLabel: string; - srSegmentGraphAriaDescription: ({ + srSingleSegmentGraphAriaLabel: string; + srMultipleSegmentGraphAriaLabel: ({ + countOfSegments, + }: { + countOfSegments: number; + }) => string; + srIndividualSegmentAriaDescription: ({ point1X, point1Y, point2X, point2Y, length, - indexOfSegment + indexOfSegment, }: { point1X: string; point1Y: string; @@ -274,7 +279,7 @@ export type PerseusStrings = { length: string; indexOfSegment: number; }) => string; - srSegmentGraphEndpointAriaLabel: ({ + srSingleSegmentGraphEndpointAriaLabel: ({ endpointNumber, x, y, @@ -283,6 +288,17 @@ export type PerseusStrings = { x: string; y: string; }) => string; + srMultipleSegmentGraphEndpointAriaLabel: ({ + endpointNumber, + x, + y, + indexOfSegment, + }: { + endpointNumber: number; + x: string; + y: string; + indexOfSegment: number; + }) => string; // The above strings are used for interactive graph SR descriptions. }; @@ -579,21 +595,32 @@ export const strings: { message: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the initial side at %(isX)s comma %(isY)s and a point on the terminal side at %(tsX)s comma %(tsY)s", }, - srSegmentGraphAriaLabel: { + srSingleSegmentGraphAriaLabel: { context: "Screenreader-accessible description of a line segment on a coordinate plane.", - message: "A line segment on a coordinate plane", + message: "A line segment on a coordinate plane.", }, - srSegmentGraphAriaDescription: { + srMultipleSegmentGraphAriaLabel: { + context: + "Screenreader accessible description of multiple line segments on a coordinate plane.", + message: "%(countOfSegments)s segments on a coordinate plane.", + }, + srIndividualSegmentAriaDescription: { context: "Screenreader-only description of a line segment on a coordinate plane.", message: - "Segment %(indexOfSegment)s. Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", + "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", + }, + srSingleSegmentGraphEndpointAriaLabel: { + context: + "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", + message: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", }, - srSegmentGraphEndpointAriaLabel: { + srMultipleSegmentGraphEndpointAriaLabel: { context: "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", - message: "Endpoint $(endpointNumber)s at $(x)s comma $(y)s.", + message: + "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", }, // The above strings are used for interactive graph SR descriptions. }; @@ -812,17 +839,26 @@ export const mockStrings: PerseusStrings = { tsY, }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the initial side at ${isX} comma ${isY} and a point on the terminal side at ${tsX} comma ${tsY}.`, - srSegmentGraphAriaLabel: "A line segment on a coordinate plane.", - srSegmentGraphAriaDescription: ({ + srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", + srMultipleSegmentGraphAriaLabel: ({countOfSegments}) => + `${countOfSegments} segments on a coordinate plane.`, + srIndividualSegmentAriaDescription: ({ point1X, point1Y, point2X, point2Y, length, - indexOfSegment + indexOfSegment, }) => - `Segment ${indexOfSegment}. Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, - srSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => + `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, + srSingleSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`, + srMultipleSegmentGraphEndpointAriaLabel: ({ + endpointNumber, + x, + y, + indexOfSegment, + }) => + `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`, // The above strings are used for interactive graph SR descriptions. }; diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 72a7bc556b..6509ce4a67 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -32,40 +32,73 @@ type SegmentProps = MafsGraphProps; const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const {coords: segments} = graphState; const {strings, locale} = usePerseusI18n(); - const wholeSegmentAriaLabel = strings.srSegmentGraphAriaLabel; function getLengthOfSegment(segment: PairOfPoints) { return kpoint.distanceToPoint(...segment); } - function getWholeSegmentAriaDescription(segment: PairOfPoints, index: number) { - const indexOfSegment = index + 1; - return strings.srSegmentGraphAriaDescription({ + function getWholeSegmentGraphAriaLabel(): string { + return segments?.length > 1 + ? strings.srMultipleSegmentGraphAriaLabel({ + countOfSegments: segments.length, + }) + : strings.srSingleSegmentGraphAriaLabel; + } + + const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel(); + + function getIndividualSegmentAriaDescription( + segment: PairOfPoints, + index: number, + ) { + return strings.srIndividualSegmentAriaDescription({ point1X: srFormatNumber(segment[0][X], locale), point1Y: srFormatNumber(segment[0][Y], locale), point2X: srFormatNumber(segment[1][X], locale), point2Y: srFormatNumber(segment[1][Y], locale), length: srFormatNumber(getLengthOfSegment(segment), locale), - indexOfSegment: indexOfSegment, + indexOfSegment: index + 1, + }); + } + + function getWholeSegmentGraphAriaDescription() { + let description = `${wholeSegmentGraphAriaLabel} `; + + segments.forEach((segment, index) => { + description += + getIndividualSegmentAriaDescription(segment, index) + " "; }); + + return description; } - function formatSegment(endpointNumber: number, x: number, y: number) { - return strings.srSegmentGraphEndpointAriaLabel({ + function formatSegment( + endpointNumber: number, + x: number, + y: number, + index: number, + ) { + const segObj = { endpointNumber: endpointNumber, x: srFormatNumber(x, locale), y: srFormatNumber(y, locale), - }); + }; + + return segments.length > 1 + ? strings.srMultipleSegmentGraphEndpointAriaLabel({ + ...segObj, + indexOfSegment: index, + }) + : strings.srSingleSegmentGraphEndpointAriaLabel(segObj); } return ( - <> + {segments?.map((segment, i) => ( - + { 1, segment[0][X], segment[0][Y], + i + 1, ), point2AriaLabel: formatSegment( 2, segment[1][X], segment[1][Y], + i + 1, ), }} /> @@ -101,10 +136,16 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { id={`segment-description-${i}`} style={{display: "hidden"}} > - {getWholeSegmentAriaDescription(segment, i)} + {getIndividualSegmentAriaDescription(segment, i)} ))} - + + {getWholeSegmentGraphAriaDescription()} + + ); }; diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx index d3fa319ba1..eb625f93ef 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx @@ -187,10 +187,10 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Endpoint 1 at 0 comma 0."); - expectLabelInDoc("Endpoint 2 at -7 comma 0.5."); - expectLabelInDoc("Endpoint 1 at 1 comma 1."); - expectLabelInDoc("Endpoint 2 at 7 comma 0.5."); + expectLabelInDoc("Endpoint 1 on segment 1 at 0 comma 0."); + expectLabelInDoc("Endpoint 2 on segment 1 at -7 comma 0.5."); + expectLabelInDoc("Endpoint 1 on segment 2 at 1 comma 1."); + expectLabelInDoc("Endpoint 2 on segment 2 at 7 comma 0.5."); }); it("renders ARIA labels for each point (linear)", () => { From 1f6b8b4591f677511f5178c891d9ca2800ffd581 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Fri, 27 Dec 2024 09:47:30 -0800 Subject: [PATCH 09/20] move function outside of component --- packages/perseus/src/strings.ts | 2 +- .../src/widgets/interactive-graphs/graphs/segment.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index f35ed53233..b44351defb 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -587,7 +587,7 @@ export const strings: { srAngleGraphAriaLabel: { context: "Screenreader-accessible label for an angle on a coordinate plane.", - message: "An angle on a coordinate plane", + message: "An angle on a coordinate plane.", }, srAngleGraphAriaDescription: { context: diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 6509ce4a67..1d09c53ba5 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -33,10 +33,6 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const {coords: segments} = graphState; const {strings, locale} = usePerseusI18n(); - function getLengthOfSegment(segment: PairOfPoints) { - return kpoint.distanceToPoint(...segment); - } - function getWholeSegmentGraphAriaLabel(): string { return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({ @@ -149,3 +145,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { ); }; + +function getLengthOfSegment(segment: PairOfPoints) { + return kpoint.distanceToPoint(...segment); +} From 822097d6b6b8a64046ce674710e595bbb940b941 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Fri, 27 Dec 2024 10:17:14 -0800 Subject: [PATCH 10/20] add unique id to the descriptions --- .../src/widgets/interactive-graphs/graphs/segment.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 1d09c53ba5..e807d3d655 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -32,7 +32,7 @@ type SegmentProps = MafsGraphProps; const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const {coords: segments} = graphState; const {strings, locale} = usePerseusI18n(); - + const segmentUniqueId = React.useId(); function getWholeSegmentGraphAriaLabel(): string { return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({ @@ -91,10 +91,10 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { return ( {segments?.map((segment, i) => ( - + { ))} {getWholeSegmentGraphAriaDescription()} From 4981fdc31325768debefb57c03e274a297d072fa Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Fri, 27 Dec 2024 10:18:20 -0800 Subject: [PATCH 11/20] remove unique id from aria described by key --- .../perseus/src/widgets/interactive-graphs/graphs/segment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index e807d3d655..6cfea91954 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -94,7 +94,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { aria-describedby={`wholeSegmentGraphAriaDescription-${segmentUniqueId}`} > {segments?.map((segment, i) => ( - + Date: Fri, 27 Dec 2024 10:23:27 -0800 Subject: [PATCH 12/20] add missing id --- .../perseus/src/widgets/interactive-graphs/graphs/segment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 6cfea91954..2def0c7f6e 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -129,7 +129,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { }} /> {getIndividualSegmentAriaDescription(segment, i)} From 717bd34ca637526960642c5bb8c077e3bbbf5a04 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Fri, 27 Dec 2024 10:30:20 -0800 Subject: [PATCH 13/20] readd key --- .../src/widgets/interactive-graphs/graphs/segment.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 2def0c7f6e..ae89933b0b 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -94,7 +94,10 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { aria-describedby={`wholeSegmentGraphAriaDescription-${segmentUniqueId}`} > {segments?.map((segment, i) => ( - + Date: Tue, 7 Jan 2025 14:05:33 -0800 Subject: [PATCH 14/20] remove context and message objects --- packages/perseus/src/strings.ts | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index b44351defb..66a4ee9672 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -595,33 +595,12 @@ export const strings: { message: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the initial side at %(isX)s comma %(isY)s and a point on the terminal side at %(tsX)s comma %(tsY)s", }, - srSingleSegmentGraphAriaLabel: { - context: - "Screenreader-accessible description of a line segment on a coordinate plane.", - message: "A line segment on a coordinate plane.", - }, - srMultipleSegmentGraphAriaLabel: { - context: - "Screenreader accessible description of multiple line segments on a coordinate plane.", - message: "%(countOfSegments)s segments on a coordinate plane.", - }, - srIndividualSegmentAriaDescription: { - context: - "Screenreader-only description of a line segment on a coordinate plane.", - message: - "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", - }, - srSingleSegmentGraphEndpointAriaLabel: { - context: - "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", - message: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", - }, - srMultipleSegmentGraphEndpointAriaLabel: { - context: - "Screenreader-accessible label for an endpoint of a line segment on a coordinate plane.", - message: - "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", - }, + srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", + srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s segments on a coordinate plane.", + srIndividualSegmentAriaDescription: + "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", + srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", + srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", // The above strings are used for interactive graph SR descriptions. }; From 072225a0c9ec60bca97b4ac4e382e868e07ce2e8 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Tue, 7 Jan 2025 14:15:07 -0800 Subject: [PATCH 15/20] lint fix --- packages/perseus/src/strings.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 2b6aa53d79..7b90c66ebf 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -598,11 +598,14 @@ export const strings: { "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s", }, srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", - srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s segments on a coordinate plane.", + srMultipleSegmentGraphAriaLabel: + "%(countOfSegments)s segments on a coordinate plane.", srIndividualSegmentAriaDescription: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", - srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", - srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", + srSingleSegmentGraphEndpointAriaLabel: + "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", + srMultipleSegmentGraphEndpointAriaLabel: + "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", // The above strings are used for interactive graph SR descriptions. }; From d070c098d39e70d9f451ce1a814ca21213ce0a46 Mon Sep 17 00:00:00 2001 From: Anakaren Rojas Date: Wed, 15 Jan 2025 10:56:04 -0800 Subject: [PATCH 16/20] add placeholder for grab handle aria label --- packages/perseus/src/strings.ts | 3 +++ .../perseus/src/widgets/interactive-graphs/graphs/segment.tsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index d7dcd0e206..f0e1d2e774 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -264,6 +264,7 @@ export type PerseusStrings = { }: { countOfSegments: number; }) => string; + srIndividualSegmentAriaLabel: string; srIndividualSegmentAriaDescription: ({ point1X, point1Y, @@ -522,6 +523,7 @@ export const strings: { srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s segments on a coordinate plane.", + srIndividualSegmentAriaLabel: "PLACEHOLDER: PLEASE UPDATE ME", srIndividualSegmentAriaDescription: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", srSingleSegmentGraphEndpointAriaLabel: @@ -748,6 +750,7 @@ export const mockStrings: PerseusStrings = { srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: ({countOfSegments}) => `${countOfSegments} segments on a coordinate plane.`, + srIndividualSegmentAriaLabel: "PLACEHOLDER: PLEASE UPDATE ME", srIndividualSegmentAriaDescription: ({ point1X, point1Y, diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index ae89933b0b..3bfb9a86d2 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -42,6 +42,8 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { } const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel(); + // STRING FOR THIS SHOULD BE UPDATED PRIOR TO BEING TRANSLATED + const individualSegmentAriaLabel = strings.srIndividualSegmentAriaLabel; function getIndividualSegmentAriaDescription( segment: PairOfPoints, @@ -95,6 +97,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { > {segments?.map((segment, i) => ( From 2adacae08f139f36c42ec7bca0a2f67762184f06 Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Tue, 21 Jan 2025 12:52:37 -0800 Subject: [PATCH 17/20] Remove unused string. Make the description the label. --- packages/perseus/src/strings.ts | 9 +++------ .../interactive-graphs/graphs/segment.tsx | 18 ++++-------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index aafe2a37e2..94e9a0efad 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -266,8 +266,7 @@ export type PerseusStrings = { }: { countOfSegments: number; }) => string; - srIndividualSegmentAriaLabel: string; - srIndividualSegmentAriaDescription: ({ + srIndividualSegmentAriaLabel: ({ point1X, point1Y, point2X, @@ -570,8 +569,7 @@ export const strings = { srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s segments on a coordinate plane.", - srIndividualSegmentAriaLabel: "PLACEHOLDER: PLEASE UPDATE ME", - srIndividualSegmentAriaDescription: + srIndividualSegmentAriaLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", @@ -814,8 +812,7 @@ export const mockStrings: PerseusStrings = { srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: ({countOfSegments}) => `${countOfSegments} segments on a coordinate plane.`, - srIndividualSegmentAriaLabel: "PLACEHOLDER: PLEASE UPDATE ME", - srIndividualSegmentAriaDescription: ({ + srIndividualSegmentAriaLabel: ({ point1X, point1Y, point2X, diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 3bfb9a86d2..7b69161871 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -42,14 +42,12 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { } const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel(); - // STRING FOR THIS SHOULD BE UPDATED PRIOR TO BEING TRANSLATED - const individualSegmentAriaLabel = strings.srIndividualSegmentAriaLabel; - function getIndividualSegmentAriaDescription( + function getIndividualSegmentAriaLabel( segment: PairOfPoints, index: number, ) { - return strings.srIndividualSegmentAriaDescription({ + return strings.srIndividualSegmentAriaLabel({ point1X: srFormatNumber(segment[0][X], locale), point1Y: srFormatNumber(segment[0][Y], locale), point2X: srFormatNumber(segment[1][X], locale), @@ -63,8 +61,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { let description = `${wholeSegmentGraphAriaLabel} `; segments.forEach((segment, index) => { - description += - getIndividualSegmentAriaDescription(segment, index) + " "; + description += getIndividualSegmentAriaLabel(segment, index) + " "; }); return description; @@ -97,8 +94,7 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { > {segments?.map((segment, i) => ( { ), }} /> - - {getIndividualSegmentAriaDescription(segment, i)} - ))} Date: Tue, 21 Jan 2025 13:00:02 -0800 Subject: [PATCH 18/20] Add grab handle string --- packages/perseus/src/strings.ts | 15 +++++++++++++++ .../widgets/interactive-graphs/graphs/segment.tsx | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 94e9a0efad..7d05866a23 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -301,6 +301,17 @@ export type PerseusStrings = { y: string; indexOfSegment: number; }) => string; + srSegmentGrabHandle: ({ + point1X, + point1Y, + point2X, + point2Y, + }: { + point1X: string; + point1Y: string; + point2X: string; + point2Y: string; + }) => string; srLinearSystemGraph: string; srLinearSystemPoints: ({ lineNumber, @@ -575,6 +586,8 @@ export const strings = { "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.", + srSegmentGrabHandle: + "Segment from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.", srLinearSystemGraph: "Two lines on a coordinate plane.", srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.", @@ -830,6 +843,8 @@ export const mockStrings: PerseusStrings = { indexOfSegment, }) => `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`, + srSegmentGrabHandle: ({point1X, point1Y, point2X, point2Y}) => + `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`, srLinearSystemGraph: "Two lines on a coordinate plane.", srLinearSystemPoints: ({lineNumber, point1X, point1Y, point2X, point2Y}) => `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`, diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 7b69161871..fb2a553bec 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -128,6 +128,12 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { segment[1][Y], i + 1, ), + grabHandleAriaLabel: strings.srSegmentGrabHandle({ + point1X: srFormatNumber(segment[0][X], locale), + point1Y: srFormatNumber(segment[0][Y], locale), + point2X: srFormatNumber(segment[1][X], locale), + point2Y: srFormatNumber(segment[1][Y], locale), + }), }} /> From 4235cd736f29e6c109053fd9b4e6c3ba59fdc302 Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Tue, 21 Jan 2025 13:10:40 -0800 Subject: [PATCH 19/20] Separate length into description --- packages/perseus/src/strings.ts | 8 +++---- .../interactive-graphs/graphs/segment.tsx | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 7d05866a23..af5928740b 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -271,16 +271,15 @@ export type PerseusStrings = { point1Y, point2X, point2Y, - length, indexOfSegment, }: { point1X: string; point1Y: string; point2X: string; point2Y: string; - length: string; indexOfSegment: number; }) => string; + srSegmentLength: ({length}: {length: string}) => string; srSingleSegmentGraphEndpointAriaLabel: ({ endpointNumber, x, @@ -581,7 +580,8 @@ export const strings = { srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s segments on a coordinate plane.", srIndividualSegmentAriaLabel: - "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s. Segment length %(length)s units.", + "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.", + srSegmentLength: "Segment length %(length)s units.", srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", srMultipleSegmentGraphEndpointAriaLabel: @@ -830,10 +830,10 @@ export const mockStrings: PerseusStrings = { point1Y, point2X, point2Y, - length, indexOfSegment, }) => `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, + srSegmentLength: ({length}) => `Segment length ${length} units.`, srSingleSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`, srMultipleSegmentGraphEndpointAriaLabel: ({ diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index fb2a553bec..213529f927 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -2,6 +2,7 @@ import {point as kpoint} from "@khanacademy/kmath"; import * as React from "react"; import {usePerseusI18n} from "../../../components/i18n-context"; +import a11y from "../../../util/a11y"; import {X, Y} from "../math"; import {actions} from "../reducer/interactive-graph-action"; @@ -33,6 +34,9 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { const {coords: segments} = graphState; const {strings, locale} = usePerseusI18n(); const segmentUniqueId = React.useId(); + const lengthDescriptionId = segmentUniqueId + "-length"; + const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph"; + function getWholeSegmentGraphAriaLabel(): string { return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({ @@ -52,7 +56,6 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { point1Y: srFormatNumber(segment[0][Y], locale), point2X: srFormatNumber(segment[1][X], locale), point2Y: srFormatNumber(segment[1][Y], locale), - length: srFormatNumber(getLengthOfSegment(segment), locale), indexOfSegment: index + 1, }); } @@ -90,11 +93,12 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { return ( {segments?.map((segment, i) => ( { }), }} /> + + {strings.srSegmentLength({ + length: srFormatNumber( + getLengthOfSegment(segment), + locale, + ), + })} + ))} - + {getWholeSegmentGraphAriaDescription()} From 30061d3dcc96d6ff99bcae0b4701aad932cb72d8 Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Tue, 21 Jan 2025 14:07:13 -0800 Subject: [PATCH 20/20] Describe interactive elements in outer graph --- packages/perseus/src/strings.ts | 25 +- .../graphs/segment.test.tsx | 421 ++++++++++++++++++ .../interactive-graphs/graphs/segment.tsx | 58 ++- .../interactive-graph.test.tsx | 3 +- 4 files changed, 492 insertions(+), 15 deletions(-) create mode 100644 packages/perseus/src/widgets/interactive-graphs/graphs/segment.test.tsx diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index af5928740b..eba783c769 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -266,7 +266,7 @@ export type PerseusStrings = { }: { countOfSegments: number; }) => string; - srIndividualSegmentAriaLabel: ({ + srMultipleSegmentIndividualLabel: ({ point1X, point1Y, point2X, @@ -279,6 +279,17 @@ export type PerseusStrings = { point2Y: string; indexOfSegment: number; }) => string; + srSingleSegmentLabel: ({ + point1X, + point1Y, + point2X, + point2Y, + }: { + point1X: string; + point1Y: string; + point2X: string; + point2Y: string; + }) => string; srSegmentLength: ({length}: {length: string}) => string; srSingleSegmentGraphEndpointAriaLabel: ({ endpointNumber, @@ -578,9 +589,11 @@ export const strings = { "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s", srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: - "%(countOfSegments)s segments on a coordinate plane.", - srIndividualSegmentAriaLabel: + "%(countOfSegments)s line segments on a coordinate plane.", + srMultipleSegmentIndividualLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.", + srSingleSegmentLabel: + "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.", srSegmentLength: "Segment length %(length)s units.", srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.", @@ -825,14 +838,16 @@ export const mockStrings: PerseusStrings = { srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.", srMultipleSegmentGraphAriaLabel: ({countOfSegments}) => `${countOfSegments} segments on a coordinate plane.`, - srIndividualSegmentAriaLabel: ({ + srMultipleSegmentIndividualLabel: ({ point1X, point1Y, point2X, point2Y, indexOfSegment, }) => - `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}. Segment length ${length} units.`, + `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`, + srSingleSegmentLabel: ({point1X, point1Y, point2X, point2Y}) => + `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`, srSegmentLength: ({length}) => `Segment length ${length} units.`, srSingleSegmentGraphEndpointAriaLabel: ({endpointNumber, x, y}) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`, diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.test.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.test.tsx new file mode 100644 index 0000000000..58b2b93a13 --- /dev/null +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.test.tsx @@ -0,0 +1,421 @@ +import {render, screen} from "@testing-library/react"; +import {userEvent as userEventLib} from "@testing-library/user-event"; +import * as React from "react"; + +import {Dependencies} from "@khanacademy/perseus"; + +import {testDependencies} from "../../../../../../testing/test-dependencies"; +import {mockPerseusI18nContext} from "../../../components/i18n-context"; +import {MafsGraph} from "../mafs-graph"; +import {getBaseMafsGraphPropsForTests} from "../utils"; + +import {describeSegmentGraph} from "./segment"; + +import type {InteractiveGraphState} from "../types"; +import type {UserEvent} from "@testing-library/user-event"; + +const baseMafsGraphProps = getBaseMafsGraphPropsForTests(); +const baseSingleSegmentState: InteractiveGraphState = { + type: "segment", + coords: [ + [ + [-5, 5], + [5, 5], + ], + ], + hasBeenInteractedWith: false, + range: [ + [-10, 10], + [-10, 10], + ], + snapStep: [1, 1], +}; + +const baseMultipleSegmentState: InteractiveGraphState = { + type: "segment", + coords: [ + [ + [-5, 5], + [5, 5], + ], + [ + [-5, -5], + [5, -5], + ], + ], + hasBeenInteractedWith: false, + range: [ + [-10, 10], + [-10, 10], + ], + snapStep: [1, 1], +}; + +const singleGraphOverallLabel = "A line segment on a coordinate plane."; +const multipleGraphOverallLabel = "2 segments on a coordinate plane."; + +describe("Segment graph screen reader", () => { + let userEvent: UserEvent; + beforeEach(() => { + userEvent = userEventLib.setup({ + advanceTimers: jest.advanceTimersByTime, + }); + jest.spyOn(Dependencies, "getDependencies").mockReturnValue( + testDependencies, + ); + }); + + test("should have aria label and describedby for overall single segment graph", () => { + // Arrange + render( + , + ); + + // Act + const segmentGraph = screen.getByLabelText(singleGraphOverallLabel); + + // Assert + expect(segmentGraph).toBeInTheDocument(); + expect(segmentGraph).toHaveAccessibleName( + "A line segment on a coordinate plane.", + ); + expect(segmentGraph).toHaveAccessibleDescription( + "Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5.", + ); + }); + + test("should have aria label and describedby for overall multiple segments graph", () => { + // Arrange + render( + , + ); + + // Act + const segmentGraph = screen.getByLabelText(multipleGraphOverallLabel); + + // Assert + expect(segmentGraph).toBeInTheDocument(); + expect(segmentGraph).toHaveAccessibleName( + "2 segments on a coordinate plane.", + ); + expect(segmentGraph).toHaveAccessibleDescription( + "Segment 1: Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5. Segment 2: Endpoint 1 at -5 comma -5. Endpoint 2 at 5 comma -5.", + ); + }); + + test.each` + element | index | expectedValue + ${"point1"} | ${0} | ${"Endpoint 1 at -5 comma 5."} + ${"grabHandle"} | ${1} | ${"Segment from -5 comma 5 to 5 comma 5."} + ${"point2"} | ${2} | ${"Endpoint 2 at 5 comma 5."} + `( + "should have aria label for $element on a single segment", + ({index, expectedValue}) => { + // Arrange + render( + , + ); + + // Act + // Moveable elements: point 1, grab handle, point 2 + const movableElements = screen.getAllByRole("button"); + const element = movableElements[index]; + + // Assert + expect(element).toHaveAttribute("aria-label", expectedValue); + }, + ); + + test.each` + element | index | expectedValue + ${"segment 1 point1"} | ${0} | ${"Endpoint 1 on segment 1 at -5 comma 5."} + ${"segment 1 grabHandle"} | ${1} | ${"Segment from -5 comma 5 to 5 comma 5."} + ${"segment 1 point2"} | ${2} | ${"Endpoint 2 on segment 1 at 5 comma 5."} + ${"segment 2 point1"} | ${3} | ${"Endpoint 1 on segment 2 at -5 comma -5."} + ${"segment 2 grabHandle"} | ${4} | ${"Segment from -5 comma -5 to 5 comma -5."} + ${"segment 2 point2"} | ${5} | ${"Endpoint 2 on segment 2 at 5 comma -5."} + `( + "should have aria label for $element on multiple segments", + ({index, expectedValue}) => { + // Arrange + render( + , + ); + + // Act + // Moveable elements: point 1, grab handle, point 2 + const movableElements = screen.getAllByRole("button"); + const element = movableElements[index]; + + // Assert + expect(element).toHaveAttribute("aria-label", expectedValue); + }, + ); + + test("Single segment points description should include points info", () => { + // Arrange + render( + , + ); + + // Act + const linearGraph = screen.getByLabelText(singleGraphOverallLabel); + + // Assert + expect(linearGraph).toHaveTextContent( + "Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5.", + ); + }); + + test("Multiple segments points description should include points info", () => { + // Arrange + render( + , + ); + + // Act + const linearGraph = screen.getByLabelText(multipleGraphOverallLabel); + + // Assert + expect(linearGraph).toHaveTextContent( + "Segment 1: Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5. Segment 2: Endpoint 1 at -5 comma -5. Endpoint 2 at 5 comma -5.", + ); + }); + + test("Single segment aria label reflects updated values", async () => { + // Arrange + + // Act + render( + , + ); + + const interactiveElements = screen.getAllByRole("button"); + const [point1, grabHandle, point2] = interactiveElements; + + // Assert + // Check updated aria-label for the segment graph. + expect(point1).toHaveAttribute( + "aria-label", + "Endpoint 1 at -2 comma 3.", + ); + expect(grabHandle).toHaveAttribute( + "aria-label", + "Segment from -2 comma 3 to 3 comma 3.", + ); + expect(point2).toHaveAttribute( + "aria-label", + "Endpoint 2 at 3 comma 3.", + ); + }); + + test("Multiple segment aria label reflects updated values", async () => { + // Arrange + + // Act + render( + , + ); + + const interactiveElements = screen.getAllByRole("button"); + const [ + seg1Point1, + seg1GrabHandle, + seg1Point2, + seg2Point1, + seg2GrabHandle, + seg2Point2, + ] = interactiveElements; + + // Assert + // Check updated aria-label for the segment graph. + expect(seg1Point1).toHaveAttribute( + "aria-label", + "Endpoint 1 on segment 1 at -2 comma 3.", + ); + expect(seg1GrabHandle).toHaveAttribute( + "aria-label", + "Segment from -2 comma 3 to 3 comma 3.", + ); + expect(seg1Point2).toHaveAttribute( + "aria-label", + "Endpoint 2 on segment 1 at 3 comma 3.", + ); + expect(seg2Point1).toHaveAttribute( + "aria-label", + "Endpoint 1 on segment 2 at -2 comma -3.", + ); + expect(seg2GrabHandle).toHaveAttribute( + "aria-label", + "Segment from -2 comma -3 to 3 comma -3.", + ); + expect(seg2Point2).toHaveAttribute( + "aria-label", + "Endpoint 2 on segment 2 at 3 comma -3.", + ); + }); + + test.each` + elementName | index + ${"point1"} | ${0} + ${"grabHandle"} | ${1} + ${"point2"} | ${2} + `( + "Should update the aria-live when $elementName is moved", + async ({index}) => { + // Arrange + render( + , + ); + const interactiveElements = screen.getAllByRole("button"); + const [point1, grabHandle, point2] = interactiveElements; + const movingElement = interactiveElements[index]; + + // Act - Move the element + movingElement.focus(); + await userEvent.keyboard("{ArrowRight}"); + + const expectedAriaLive = ["off", "off", "off"]; + expectedAriaLive[index] = "polite"; + + // Assert + expect(point1).toHaveAttribute("aria-live", expectedAriaLive[0]); + expect(grabHandle).toHaveAttribute( + "aria-live", + expectedAriaLive[1], + ); + expect(point2).toHaveAttribute("aria-live", expectedAriaLive[2]); + }, + ); +}); + +describe("describeSegmentGraph", () => { + test("describes a single segment", () => { + // Arrange + + // Act + const interactiveElementsString = describeSegmentGraph( + baseSingleSegmentState, + mockPerseusI18nContext, + ); + + // Assert + expect(interactiveElementsString).toBe( + "Interactive elements: Segment 1: Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5.", + ); + }); + + test("describes multiple segments", () => { + // Arrange + + // Act + const interactiveElementsString = describeSegmentGraph( + baseMultipleSegmentState, + mockPerseusI18nContext, + ); + + // Assert + expect(interactiveElementsString).toBe( + "Interactive elements: Segment 1: Endpoint 1 at -5 comma 5. Endpoint 2 at 5 comma 5. Segment 2: Endpoint 1 at -5 comma -5. Endpoint 2 at 5 comma -5.", + ); + }); + + test("describes a segment graph with updated points", () => { + // Arrange + + // Act + const interactiveElementsString = describeSegmentGraph( + { + ...baseSingleSegmentState, + coords: [ + [ + [-1, 2], + [3, 4], + ], + ], + }, + mockPerseusI18nContext, + ); + + // Assert + expect(interactiveElementsString).toBe( + "Interactive elements: Segment 1: Endpoint 1 at -1 comma 2. Endpoint 2 at 3 comma 4.", + ); + }); + + test("describes a segment graph with multiple segments and updated points", () => { + // Arrange + + // Act + const interactiveElementsString = describeSegmentGraph( + { + ...baseMultipleSegmentState, + coords: [ + [ + [-1, 2], + [3, 4], + ], + [ + [-1, -2], + [3, -4], + ], + ], + }, + mockPerseusI18nContext, + ); + + // Assert + expect(interactiveElementsString).toBe( + "Interactive elements: Segment 1: Endpoint 1 at -1 comma 2. Endpoint 2 at 3 comma 4. Segment 2: Endpoint 1 at -1 comma -2. Endpoint 2 at 3 comma -4.", + ); + }); +}); diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx index 213529f927..9fd7faa2d6 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/segment.tsx @@ -9,6 +9,7 @@ import {actions} from "../reducer/interactive-graph-action"; import {MovableLine} from "./components/movable-line"; import {srFormatNumber} from "./screenreader-text"; +import type {I18nContextType} from "../../../components/i18n-context"; import type { Dispatch, InteractiveGraphElementSuite, @@ -24,7 +25,9 @@ export function renderSegmentGraph( ): InteractiveGraphElementSuite { return { graph: , - interactiveElementsDescription: null, + interactiveElementsDescription: ( + + ), }; } @@ -51,7 +54,16 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { segment: PairOfPoints, index: number, ) { - return strings.srIndividualSegmentAriaLabel({ + if (segments.length === 1) { + return strings.srSingleSegmentLabel({ + point1X: srFormatNumber(segments[0][0][X], locale), + point1Y: srFormatNumber(segments[0][0][Y], locale), + point2X: srFormatNumber(segments[0][1][X], locale), + point2Y: srFormatNumber(segments[0][1][Y], locale), + }); + } + + return strings.srMultipleSegmentIndividualLabel({ point1X: srFormatNumber(segment[0][X], locale), point1Y: srFormatNumber(segment[0][Y], locale), point2X: srFormatNumber(segment[1][X], locale), @@ -61,13 +73,11 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { } function getWholeSegmentGraphAriaDescription() { - let description = `${wholeSegmentGraphAriaLabel} `; - - segments.forEach((segment, index) => { - description += getIndividualSegmentAriaLabel(segment, index) + " "; - }); - - return description; + return segments + .map((segment, index) => + getIndividualSegmentAriaLabel(segment, index), + ) + .join(" "); } function formatSegment( @@ -160,3 +170,33 @@ const SegmentGraph = ({dispatch, graphState}: SegmentProps) => { function getLengthOfSegment(segment: PairOfPoints) { return kpoint.distanceToPoint(...segment); } + +function SegmentGraphDescription({state}: {state: SegmentGraphState}) { + // The reason that SegmentGraphDescription is a component (rather than a + // function that returns a string) is because it needs to use a + // hook: `usePerseusI18n`. + const i18n = usePerseusI18n(); + return describeSegmentGraph(state, i18n); +} + +// Exported for testing +export function describeSegmentGraph( + state: SegmentGraphState, + i18n: I18nContextType, +): string { + const {strings, locale} = i18n; + + const segmentDescriptions = state.coords.map(([point1, point2], index) => + strings.srMultipleSegmentIndividualLabel({ + point1X: srFormatNumber(point1[X], locale), + point1Y: srFormatNumber(point1[Y], locale), + point2X: srFormatNumber(point2[X], locale), + point2Y: srFormatNumber(point2[Y], locale), + indexOfSegment: index + 1, + }), + ); + + return strings.srInteractiveElements({ + elements: segmentDescriptions.join(" "), + }); +} 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 1ce049aa1d..3ab059b049 100644 --- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx @@ -34,6 +34,7 @@ import { linearQuestionWithDefaultCorrect, linearSystemQuestion, linearSystemQuestionWithDefaultCorrect, + noneQuestion, pointQuestion, pointQuestionWithDefaultCorrect, polygonQuestion, @@ -1382,7 +1383,7 @@ describe("Interactive Graph", function () { it("should not have an aria-label or description if they are not provided", async () => { // Arrange - const {container} = renderQuestion(segmentQuestion, apiOptions); + const {container} = renderQuestion(noneQuestion, apiOptions); // Act // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access