diff --git a/.changeset/tidy-baboons-tie.md b/.changeset/tidy-baboons-tie.md
new file mode 100644
index 0000000000..20d14a409b
--- /dev/null
+++ b/.changeset/tidy-baboons-tie.md
@@ -0,0 +1,5 @@
+---
+"@khanacademy/perseus": patch
+---
+
+[SR] Linear System - add screen reader support for Linear System interactive graph
diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts
index 725bdaa858..0723bdec92 100644
--- a/packages/perseus/src/strings.ts
+++ b/packages/perseus/src/strings.ts
@@ -258,6 +258,31 @@ export type PerseusStrings = {
endingSideX: string;
endingSideY: string;
}) => string;
+ srLinearSystemGraph: string;
+ srLinearSystemPoints: ({
+ lineNumber,
+ point1X,
+ point1Y,
+ point2X,
+ point2Y,
+ }: {
+ lineNumber: number;
+ point1X: string;
+ point1Y: string;
+ point2X: string;
+ point2Y: string;
+ }) => string;
+ srLinearSystemPoint({
+ lineNumber,
+ pointSequence,
+ x,
+ y,
+ }: {
+ lineNumber: number;
+ pointSequence: number;
+ x: string;
+ y: string;
+ }): string;
// The above strings are used for interactive graph SR descriptions.
};
@@ -478,6 +503,11 @@ export const strings: {
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
srAngleGraphAriaDescription:
"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",
+ 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.",
+ srLinearSystemPoint:
+ "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
// The above strings are used for interactive graph SR descriptions.
};
@@ -695,5 +725,10 @@ export const mockStrings: PerseusStrings = {
endingSideY,
}) =>
`The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
+ 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}.`,
+ srLinearSystemPoint: ({lineNumber, pointSequence, x, y}) =>
+ `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`,
// The above strings are used for interactive graph SR descriptions.
};
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
index 0ecb3f8e88..62afcaca5f 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
@@ -2,6 +2,7 @@ import {vec} from "mafs";
import * as React from "react";
import {usePerseusI18n} from "../../../components/i18n-context";
+import a11y from "../../../util/a11y";
import {X, Y, calculateAngleInDegrees, getClockwiseAngle, polar} from "../math";
import {findIntersectionOfRays} from "../math/geometry";
import {actions} from "../reducer/interactive-graph-action";
@@ -215,7 +216,7 @@ function AngleGraph(props: AngleGraphProps) {
}
ariaLabel={initialSideAriaLabel}
/>
-
+
{wholeAngleDescription}
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/circle.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/circle.tsx
index 4fbef30be6..805d14a148 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/circle.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/circle.tsx
@@ -3,6 +3,7 @@ import * as React from "react";
import {useRef} from "react";
import {usePerseusI18n} from "../../../components/i18n-context";
+import a11y from "../../../util/a11y";
import {snap, X, Y} from "../math";
import {actions} from "../reducer/interactive-graph-action";
import {getRadius} from "../reducer/interactive-graph-state";
@@ -103,10 +104,10 @@ function CircleGraph(props: CircleGraphProps) {
/>
{/* Hidden elements to provide the descriptions for the
circle and radius point's `aria-describedby` properties. */}
-
+
{srCircleRadius}
-
+
{srCircleOuterPoints}
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.test.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.test.tsx
new file mode 100644
index 0000000000..2bda019d36
--- /dev/null
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.test.tsx
@@ -0,0 +1,301 @@
+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 {describeLinearSystemGraph} from "./linear-system";
+
+import type {InteractiveGraphState} from "../types";
+import type {UserEvent} from "@testing-library/user-event";
+
+const baseMafsGraphProps = getBaseMafsGraphPropsForTests();
+const baseLinearSystemState: InteractiveGraphState = {
+ type: "linear-system",
+ coords: [
+ [
+ [-5, 5],
+ [5, 5],
+ ],
+ [
+ [-5, -5],
+ [5, -5],
+ ],
+ ],
+ hasBeenInteractedWith: false,
+ range: [
+ [-10, 10],
+ [-10, 10],
+ ],
+ snapStep: [1, 1],
+};
+
+const overallGraphLabel = "Two lines on a coordinate plane.";
+
+describe("Linear System 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 linear system graph", () => {
+ // Arrange
+ render(
+ ,
+ );
+
+ // Act
+ const linearSystemGraph = screen.getByLabelText(
+ "Two lines on a coordinate plane.",
+ );
+
+ // Assert
+ expect(linearSystemGraph).toBeInTheDocument();
+ expect(linearSystemGraph).toHaveAccessibleDescription(
+ "Line 1 has two points, point 1 at -5 comma 5 and point 2 at 5 comma 5. The line crosses the Y-axis at 0 comma 5. Its slope is zero. Line 2 has two points, point 1 at -5 comma -5 and point 2 at 5 comma -5. The line crosses the Y-axis at 0 comma -5. Its slope is zero.",
+ );
+ });
+
+ // Test each line in the linear system graph separately.
+ describe.each`
+ lineNumber
+ ${1}
+ ${2}
+ `(`Line $lineNumber`, ({lineNumber}) => {
+ test.each`
+ case | coords | interceptDescription
+ ${"origin intercept"} | ${[[1, 1], [2, 2]]} | ${"The line crosses the x and y axes at the graph's origin."}
+ ${"both x and y intercepts"} | ${[[4, 4], [7, 1]]} | ${"The line crosses the X-axis at 8 comma 0 and the Y-axis at 0 comma 8."}
+ ${"x intercept only"} | ${[[5, 5], [5, 2]]} | ${"The line crosses the X-axis at 5 comma 0."}
+ ${"y intercept only"} | ${[[5, 5], [2, 5]]} | ${"The line crosses the Y-axis at 0 comma 5."}
+ ${"overlaps y-axis"} | ${[[0, 5], [0, 2]]} | ${"The line crosses the X-axis at 0 comma 0."}
+ ${"overlaps x-axis"} | ${[[5, 0], [2, 0]]} | ${"The line crosses the Y-axis at 0 comma 0."}
+ `(
+ "slope description should include slope info for $case",
+ ({coords, interceptDescription}) => {
+ // Arrange
+ const newCoords = [...baseLinearSystemState.coords];
+ newCoords[lineNumber - 1] = coords;
+
+ render(
+ ,
+ );
+
+ // Act
+ const linearSystemGraph =
+ screen.getByLabelText(overallGraphLabel);
+
+ // Assert
+ expect(linearSystemGraph).toHaveTextContent(
+ interceptDescription,
+ );
+ },
+ );
+
+ test.each`
+ case | coords | slopeDescription
+ ${"positive slope"} | ${[[1, 1], [3, 3]]} | ${`Its slope increases from left to right.`}
+ ${"negative slope"} | ${[[3, 3], [1, 6]]} | ${`Its slope decreases from left to right.`}
+ ${"horizontal line"} | ${[[1, 1], [3, 1]]} | ${`Its slope is zero.`}
+ ${"vertical line"} | ${[[1, 1], [1, 3]]} | ${`Its slope is undefined.`}
+ ${"overlaps x-axis"} | ${[[1, 0], [3, 0]]} | ${`Its slope is zero.`}
+ ${"overlaps y-axis"} | ${[[0, 1], [0, 3]]} | ${`Its slope is undefined.`}
+ `(
+ "slope description should include slope info for $case",
+ ({coords, slopeDescription}) => {
+ // Arrange
+ const newCoords = [...baseLinearSystemState.coords];
+ newCoords[lineNumber - 1] = coords;
+
+ render(
+ ,
+ );
+
+ // Act
+ const linearSystemGraph =
+ screen.getByLabelText(overallGraphLabel);
+
+ // Assert
+ expect(linearSystemGraph).toHaveTextContent(slopeDescription);
+ },
+ );
+
+ test("aria label reflects updated values", async () => {
+ // Arrange
+ const newCoords = [...baseLinearSystemState.coords];
+ newCoords[lineNumber - 1] = [
+ [-2, 3],
+ [3, 3],
+ ];
+
+ // Act
+ render(
+ ,
+ );
+
+ const interactiveElements = screen.getAllByRole("button");
+
+ // Get interactive elements for this line.
+ const point1 = interactiveElements[0 + (lineNumber - 1) * 3];
+ const grabHandle = interactiveElements[1 + (lineNumber - 1) * 3];
+ const point2 = interactiveElements[2 + (lineNumber - 1) * 3];
+
+ // Assert
+ // Check updated aria-label for the linear graph.
+ expect(point1).toHaveAttribute(
+ "aria-label",
+ `Point 1 on line ${lineNumber} at -2 comma 3.`,
+ );
+ expect(grabHandle).toHaveAttribute(
+ "aria-label",
+ `The line crosses the Y-axis at 0 comma 3. Its slope is zero.`,
+ );
+ expect(point2).toHaveAttribute(
+ "aria-label",
+ `Point 2 on line ${lineNumber} at 3 comma 3.`,
+ );
+ });
+
+ test.each`
+ element | index
+ ${"point1"} | ${0}
+ ${"grabHandle"} | ${1}
+ ${"point2"} | ${2}
+ `("should have describedby on all interactive elements", ({index}) => {
+ // Arrange
+ render(
+ ,
+ );
+
+ // Act
+ const interactiveElements = screen.getAllByRole("button");
+ const element = interactiveElements[index + (lineNumber - 1) * 3];
+
+ // Assert
+ expect(element.getAttribute("aria-describedby")).toContain(
+ "-slope",
+ );
+ expect(element.getAttribute("aria-describedby")).toContain(
+ "-intercept",
+ );
+ });
+
+ 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(describeLinearSystemGraph, () => {
+ test("describes a default linear system graph", () => {
+ // Arrange
+
+ // Act
+ const linearSystemGraphDescription = describeLinearSystemGraph(
+ baseLinearSystemState,
+ mockPerseusI18nContext,
+ );
+
+ // Assert
+ expect(linearSystemGraphDescription).toEqual(
+ "Interactive elements: Two lines on a coordinate plane. Line 1 has two points, point 1 at -5 comma 5 and point 2 at 5 comma 5. Line 2 has two points, point 1 at -5 comma -5 and point 2 at 5 comma -5.",
+ );
+ });
+
+ test("describes a linear system graph with updated points", () => {
+ // Arrange
+
+ // Act
+ const linearSystemGraphDescription = describeLinearSystemGraph(
+ {
+ ...baseLinearSystemState,
+ coords: [
+ [
+ [-2, 3],
+ [3, 3],
+ ],
+ [
+ [-2, -3],
+ [3, -3],
+ ],
+ ],
+ },
+ mockPerseusI18nContext,
+ );
+
+ // Assert
+ expect(linearSystemGraphDescription).toEqual(
+ "Interactive elements: Two lines on a coordinate plane. Line 1 has two points, point 1 at -2 comma 3 and point 2 at 3 comma 3. Line 2 has two points, point 1 at -2 comma -3 and point 2 at 3 comma -3.",
+ );
+ });
+});
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.tsx
index 05ca19d704..d6a38cf885 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.tsx
@@ -1,9 +1,14 @@
import * as React from "react";
+import {usePerseusI18n} from "../../../components/i18n-context";
+import a11y from "../../../util/a11y";
import {actions} from "../reducer/interactive-graph-action";
import {MovableLine} from "./components/movable-line";
+import {srFormatNumber} from "./screenreader-text";
+import {getInterceptStringForLine, getSlopeStringForLine} from "./utils";
+import type {I18nContextType} from "../../../components/i18n-context";
import type {
MafsGraphProps,
LinearSystemGraphState,
@@ -18,7 +23,9 @@ export function renderLinearSystemGraph(
): InteractiveGraphElementSuite {
return {
graph: ,
- interactiveElementsDescription: null,
+ interactiveElementsDescription: (
+
+ ),
};
}
@@ -28,12 +35,64 @@ const LinearSystemGraph = (props: LinearSystemGraphProps) => {
const {dispatch} = props;
const {coords: lines} = props.graphState;
+ const {strings, locale} = usePerseusI18n();
+ const id = React.useId();
+
+ const linesAriaInfo = lines.map((line, i) => {
+ return {
+ pointsDescriptionId: `${id}-line${i + 1}-points`,
+ interceptDescriptionId: `${id}-line${i + 1}-intercept`,
+ slopeDescriptionId: `${id}-line${i + 1}-slope`,
+ pointsDescription: strings.srLinearSystemPoints({
+ lineNumber: i + 1,
+ point1X: srFormatNumber(line[0][0], locale),
+ point1Y: srFormatNumber(line[0][1], locale),
+ point2X: srFormatNumber(line[1][0], locale),
+ point2Y: srFormatNumber(line[1][1], locale),
+ }),
+ interceptDescription: getInterceptStringForLine(
+ line,
+ strings,
+ locale,
+ ),
+ slopeDescription: getSlopeStringForLine(line, strings),
+ };
+ });
+
return (
- <>
+
+ `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`,
+ )
+ .join(" ")}
+ >
{lines?.map((line, i) => (
{
dispatch(actions.linearSystem.moveLine(i, delta));
}}
@@ -56,7 +115,82 @@ const LinearSystemGraph = (props: LinearSystemGraphProps) => {
color="var(--movable-line-stroke-color)"
/>
))}
- ;
- >
+ {linesAriaInfo.map(
+ ({
+ pointsDescriptionId,
+ interceptDescriptionId,
+ slopeDescriptionId,
+ pointsDescription,
+ interceptDescription,
+ slopeDescription,
+ }) => (
+ <>
+
+ {pointsDescription}
+
+
+ {interceptDescription}
+
+
+ {slopeDescription}
+
+ >
+ ),
+ )}
+
);
};
+
+function LinearSystemGraphDescription({
+ state,
+}: {
+ state: LinearSystemGraphState;
+}) {
+ // The reason that LinearSystemGraphDescription is a component (rather
+ // than a function that returns a string) is because it needs to use a
+ // hook: `usePerseusI18n`.
+ const i18n = usePerseusI18n();
+
+ return describeLinearSystemGraph(state, i18n);
+}
+
+// Exported for testing
+export function describeLinearSystemGraph(
+ state: LinearSystemGraphState,
+ i18n: I18nContextType,
+): string {
+ const {strings, locale} = i18n;
+ const {coords: lines} = state;
+
+ const graphDescription = strings.srLinearSystemGraph;
+
+ const lineDescriptions = lines.map((line, i) => {
+ const point1 = line[0];
+ const point2 = line[1];
+ return strings.srLinearSystemPoints({
+ lineNumber: i + 1,
+ point1X: srFormatNumber(point1[0], locale),
+ point1Y: srFormatNumber(point1[1], locale),
+ point2X: srFormatNumber(point2[0], locale),
+ point2Y: srFormatNumber(point2[1], locale),
+ });
+ });
+
+ const allDescriptions = [graphDescription, ...lineDescriptions];
+
+ return strings.srInteractiveElements({
+ elements: allDescriptions.join(" "),
+ });
+}
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/linear.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/linear.tsx
index 915084532d..f0b0f429df 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/linear.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/linear.tsx
@@ -1,10 +1,12 @@
import * as React from "react";
import {usePerseusI18n} from "../../../components/i18n-context";
+import a11y from "../../../util/a11y";
import {actions} from "../reducer/interactive-graph-action";
import {MovableLine} from "./components/movable-line";
import {srFormatNumber} from "./screenreader-text";
+import {getInterceptStringForLine, getSlopeStringForLine} from "./utils";
import type {
MafsGraphProps,
@@ -49,57 +51,17 @@ const LinearGraph = (props: LinearGraphProps, key: number) => {
point2X: srFormatNumber(line[1][0], locale),
point2Y: srFormatNumber(line[1][1], locale),
});
-
- // Slope description
- const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
- let slopeString = "";
- if (slope === Infinity || slope === -Infinity) {
- slopeString = strings.srLinearGraphSlopeVertical;
- } else if (slope === 0) {
- slopeString = strings.srLinearGraphSlopeHorizontal;
- } else {
- slopeString =
- slope > 0
- ? strings.srLinearGraphSlopeIncreasing
- : strings.srLinearGraphSlopeDecreasing;
- }
-
- // Intersection description
- const xIntercept = (0 - line[0][1]) / slope + line[0][0];
- const yIntercept = line[0][1] - slope * line[0][0];
- const hasXIntercept = xIntercept !== Infinity && xIntercept !== -Infinity;
- const hasYIntercept = yIntercept !== Infinity && yIntercept !== -Infinity;
- let interceptString;
- if (hasXIntercept && hasYIntercept) {
- // Describe both intercepts in the same sentence.
- interceptString =
- xIntercept === 0 && yIntercept === 0
- ? strings.srLinearGraphOriginIntercept
- : strings.srLinearGraphBothIntercepts({
- xIntercept: srFormatNumber(xIntercept, locale),
- yIntercept: srFormatNumber(yIntercept, locale),
- });
- } else {
- // Describe only one intercept.
- interceptString = hasXIntercept
- ? strings.srLinearGraphXOnlyIntercept({
- xIntercept: srFormatNumber(xIntercept, locale),
- })
- : strings.srLinearGraphYOnlyIntercept({
- yIntercept: srFormatNumber(yIntercept, locale),
- });
- }
+ const slopeString = getSlopeStringForLine(line, strings);
+ const interceptString = getInterceptStringForLine(line, strings, locale);
// Linear graphs only have one line
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
// and another action for moving multiple lines
return (
{
/>
{/* Hidden elements to provide the descriptions for the
circle and radius point's `aria-describedby` properties. */}
-
+
{linearGraphPointsDescription}
-
+
{interceptString}
-
+
{slopeString}
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/utils.ts b/packages/perseus/src/widgets/interactive-graphs/graphs/utils.ts
index d42852ec91..c023e5d7f6 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/utils.ts
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/utils.ts
@@ -1,3 +1,7 @@
+import {srFormatNumber} from "./screenreader-text";
+
+import type {PerseusStrings} from "../../../strings";
+import type {PairOfPoints} from "../types";
import type {Coord} from "@khanacademy/perseus";
import type {Interval, vec} from "mafs";
@@ -62,3 +66,57 @@ export function getArrayWithoutDuplicates(array: Array): Array {
return returnArray;
}
+
+export function getSlopeStringForLine(
+ line: PairOfPoints,
+ strings: PerseusStrings,
+): string {
+ const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
+ if (!Number.isFinite(slope)) {
+ return strings.srLinearGraphSlopeVertical;
+ }
+
+ if (slope === 0) {
+ return strings.srLinearGraphSlopeHorizontal;
+ }
+
+ return slope > 0
+ ? strings.srLinearGraphSlopeIncreasing
+ : strings.srLinearGraphSlopeDecreasing;
+}
+
+export function getInterceptStringForLine(
+ line: PairOfPoints,
+ strings: PerseusStrings,
+ locale: string,
+): string {
+ const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
+ const xIntercept = (0 - line[0][1]) / slope + line[0][0];
+ const yIntercept = line[0][1] - slope * line[0][0];
+
+ // Check if the line fully overlaps with an axis.
+ const overlapsXAxis = line[0][1] === 0 && line[1][1] === 0;
+ const overlapsYAxis = line[0][0] === 0 && line[1][0] === 0;
+
+ const hasXIntercept = Number.isFinite(xIntercept) && !overlapsXAxis;
+ const hasYIntercept = Number.isFinite(yIntercept) && !overlapsYAxis;
+
+ if (hasXIntercept && hasYIntercept) {
+ // Describe both intercepts in the same sentence.
+ return xIntercept === 0 && yIntercept === 0
+ ? strings.srLinearGraphOriginIntercept
+ : strings.srLinearGraphBothIntercepts({
+ xIntercept: srFormatNumber(xIntercept, locale),
+ yIntercept: srFormatNumber(yIntercept, locale),
+ });
+ }
+
+ // Describe only one intercept.
+ return hasXIntercept
+ ? strings.srLinearGraphXOnlyIntercept({
+ xIntercept: srFormatNumber(xIntercept, locale),
+ })
+ : strings.srLinearGraphYOnlyIntercept({
+ yIntercept: srFormatNumber(yIntercept, locale),
+ });
+}
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 6acf462f4b..a7774a8c12 100644
--- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx
@@ -249,10 +249,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("Point 1 on line 1 at 0 comma 0.");
+ expectLabelInDoc("Point 2 on line 1 at -7 comma 0.5.");
+ expectLabelInDoc("Point 1 on line 2 at 1 comma 1.");
+ expectLabelInDoc("Point 2 on line 2 at 7 comma 0.5.");
});
it("renders ARIA labels for each point (ray)", () => {