Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR] Linear - Add the interactive elements linear description to the whole graph container #2110

Merged
merged 47 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
30333ff
[SR] Linear graph - add grab handle description and aria lives
nishasy Dec 18, 2024
586c962
docs(changeset): [SR] Linear graph - add grab handle description and …
nishasy Dec 18, 2024
ee8b9b2
Merge branch 'main' into sr-linear-body
nishasy Dec 18, 2024
a5ef0f8
Linear System SR
nishasy Dec 18, 2024
0fbbbfa
docs(changeset): [SR] Linear System - add screen reader support for L…
nishasy Dec 18, 2024
48f17c5
Ray SR
nishasy Dec 18, 2024
625c9a4
docs(changeset): [SR] Ray graph - Add screen reader support for Ray i…
nishasy Dec 18, 2024
951fc96
Merge branch 'main' into sr-linear-system
nishasy Dec 19, 2024
193ee2a
fix misunderstanding from linear PR. update tests
nishasy Dec 19, 2024
1ffe7fa
Merge branch 'sr-linear-system' into sr-ray
nishasy Dec 19, 2024
f754610
update test
nishasy Dec 19, 2024
c01b4f6
Merge branch 'main' into sr-linear-system
nishasy Jan 8, 2025
b315fbf
remove contexts from strings
nishasy Jan 8, 2025
7d96885
Add full graph description of all interactive elements
nishasy Jan 8, 2025
8c8cbf5
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 9, 2025
33e209a
remove context from strings
nishasy Jan 9, 2025
914ea5a
Add full graph description of interactive element
nishasy Jan 9, 2025
9ac9782
Rename lineSequence --> lineNumber
nishasy Jan 9, 2025
c242323
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 9, 2025
5b48e40
Create custom matcher for aria descriptions
nishasy Jan 9, 2025
24f71a1
Include ID in template string
nishasy Jan 9, 2025
8669b75
Use srOnly style instead of invalid display hidden
nishasy Jan 9, 2025
ed8ccbd
use isFinite
nishasy Jan 9, 2025
4523ca0
Merge branch 'main' into sr-linear-system
nishasy Jan 9, 2025
07b60bf
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 9, 2025
e625ea6
use srOnly style
nishasy Jan 9, 2025
c56b3c3
use new matcher for aria descriptions in test
nishasy Jan 9, 2025
f8c8c11
handle SR for lines overlapping axes
nishasy Jan 15, 2025
1dcf3fd
Use existing aria description matcher rather than creating a new one
nishasy Jan 15, 2025
d64ac0d
correct string in test
nishasy Jan 15, 2025
a2210a5
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 15, 2025
1728c68
[SR] Linear - Add the interactive elements linear description to the …
nishasy Jan 15, 2025
efb68a8
docs(changeset): [SR] Linear - Add the interactive elements linear de…
nishasy Jan 15, 2025
91f3166
Fix comments
nishasy Jan 15, 2025
98ac47c
Merge branch 'sr-ray' into sr-linear-interactive-elements
nishasy Jan 15, 2025
4468c3e
Add types
nishasy Jan 15, 2025
cbd82f6
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 15, 2025
1bd1d6e
Merge branch 'sr-ray' into sr-linear-interactive-elements
nishasy Jan 15, 2025
2a45174
fix import
nishasy Jan 15, 2025
e4a7322
fix import
nishasy Jan 15, 2025
11f2a4a
Update grab handle description to match SRUX doc
nishasy Jan 15, 2025
336ac2f
Merge branch 'sr-linear-system' into sr-ray
nishasy Jan 15, 2025
a164397
Update copy to match SRUX doc
nishasy Jan 15, 2025
5bd5903
Merge branch 'sr-ray' into sr-linear-interactive-elements
nishasy Jan 15, 2025
8e9201d
Merge branch 'main' into sr-linear-interactive-elements
nishasy Jan 16, 2025
f20fb25
Merge branch 'main' into sr-linear-interactive-elements
nishasy Jan 21, 2025
05e1c6b
[sr-linear-interactive-elements] Merge branch 'main' into sr-linear-i…
nishasy Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lemon-apricots-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

[SR] Linear - Add the interactive elements linear description to the whole graph container
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ 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 {describeLinearGraph} from "./linear";

import type {InteractiveGraphState} from "../types";
import type {UserEvent} from "@testing-library/user-event";

Expand Down Expand Up @@ -50,9 +53,8 @@ describe("Linear graph screen reader", () => {

// Assert
expect(linearGraph).toBeInTheDocument();
expect(linearGraph).toHaveAttribute(
"aria-describedby",
":r1:-points :r1:-intercept :r1:-slope",
expect(linearGraph).toHaveAccessibleDescription(
"The line 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.",
);
});

Expand Down Expand Up @@ -239,3 +241,65 @@ describe("Linear graph screen reader", () => {
},
);
});

describe("describeLinearGraph", () => {
test("describes a default linear graph", () => {
// Arrange

// Act
const strings = describeLinearGraph(
baseLinearState,
mockPerseusI18nContext,
);

// Assert
expect(strings.srLinearGraph).toBe("A line on a coordinate plane.");
expect(strings.srLinearGraphPoints).toBe(
"The line has two points, point 1 at -5 comma 5 and point 2 at 5 comma 5.",
);
expect(strings.srLinearGrabHandle).toBe(
"Line from -5 comma 5 to 5 comma 5.",
);
expect(strings.slopeString).toBe("Its slope is zero.");
expect(strings.interceptString).toBe(
"The line crosses the Y-axis at 0 comma 5.",
);
expect(strings.srLinearInteractiveElement).toBe(
"Interactive elements: A line on a coordinate plane. The line has two points, point 1 at -5 comma 5 and point 2 at 5 comma 5.",
);
});

test("describes a linear graph with updated points", () => {
// Arrange

// Act
const strings = describeLinearGraph(
{
...baseLinearState,
coords: [
[-1, 2],
[3, 4],
],
},
mockPerseusI18nContext,
);

// Assert
expect(strings.srLinearGraph).toBe("A line on a coordinate plane.");
expect(strings.srLinearGraphPoints).toBe(
"The line has two points, point 1 at -1 comma 2 and point 2 at 3 comma 4.",
);
expect(strings.srLinearGrabHandle).toBe(
"Line from -1 comma 2 to 3 comma 4.",
);
expect(strings.slopeString).toBe(
"Its slope increases from left to right.",
);
expect(strings.interceptString).toBe(
"The line crosses the X-axis at -5 comma 0 and the Y-axis at 0 comma 2.5.",
);
expect(strings.srLinearInteractiveElement).toBe(
"Interactive elements: A line on a coordinate plane. The line has two points, point 1 at -1 comma 2 and point 2 at 3 comma 4.",
);
});
});
83 changes: 64 additions & 19 deletions packages/perseus/src/widgets/interactive-graphs/graphs/linear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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,
LinearGraphState,
Expand All @@ -22,7 +23,9 @@ export function renderLinearGraph(
): InteractiveGraphElementSuite {
return {
graph: <LinearGraph graphState={state} dispatch={dispatch} />,
interactiveElementsDescription: null,
interactiveElementsDescription: (
<LinearGraphDescription state={state} />
),
};
}

Expand All @@ -38,34 +41,27 @@ const LinearGraph = (props: LinearGraphProps, key: number) => {
const interceptDescriptionId = id + "-intercept";
const slopeDescriptionId = id + "-slope";

// Aria label strings
const linearGraphPointsDescription = strings.srLinearGraphPoints({
point1X: srFormatNumber(line[0][0], locale),
point1Y: srFormatNumber(line[0][1], locale),
point2X: srFormatNumber(line[1][0], locale),
point2Y: srFormatNumber(line[1][1], locale),
});
const grabHandleAriaLabel = strings.srLinearGrabHandle({
point1X: srFormatNumber(line[0][0], locale),
point1Y: srFormatNumber(line[0][1], locale),
point2X: srFormatNumber(line[1][0], locale),
point2Y: srFormatNumber(line[1][1], locale),
});
const slopeString = getSlopeStringForLine(line, strings);
const interceptString = getInterceptStringForLine(line, strings, locale);
// Aria strings
const {
srLinearGraph,
srLinearGraphPoints,
srLinearGrabHandle,
slopeString,
interceptString,
} = describeLinearGraph(props.graphState, {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 (
<g
// Outer line minimal description
aria-label={strings.srLinearGraph}
aria-label={srLinearGraph}
aria-describedby={`${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`}
>
<MovableLine
key={0}
ariaLabels={{grabHandleAriaLabel: grabHandleAriaLabel}}
ariaLabels={{grabHandleAriaLabel: srLinearGrabHandle}}
ariaDescribedBy={`${interceptDescriptionId} ${slopeDescriptionId}`}
points={line}
onMoveLine={(delta: vec.Vector2) => {
Expand All @@ -88,7 +84,7 @@ const LinearGraph = (props: LinearGraphProps, key: number) => {
{/* Hidden elements to provide the descriptions for the
circle and radius point's `aria-describedby` properties. */}
<g id={pointsDescriptionId} style={a11y.srOnly}>
{linearGraphPointsDescription}
{srLinearGraphPoints}
</g>
<g id={interceptDescriptionId} style={a11y.srOnly}>
{interceptString}
Expand All @@ -99,3 +95,52 @@ const LinearGraph = (props: LinearGraphProps, key: number) => {
</g>
);
};

function LinearGraphDescription({state}: {state: LinearGraphState}) {
// The reason that LinearGraphDescription is a component (rather than a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pass in the i18n content into this function? Typically if you have a function that relies on the content or result of a hook you can avoid turning it into a component by simply passing the content through a parameter.

// function that returns a string) is because it needs to use a
// hook: `usePerseusI18n`.
const i18n = usePerseusI18n();
const strings = describeLinearGraph(state, i18n);

return strings.srLinearInteractiveElement;
}

// Exported for testing
export function describeLinearGraph(
state: LinearGraphState,
i18n: I18nContextType,
): Record<string, string> {
const {coords: line} = state;
const {strings, locale} = i18n;

// Aria label strings
const srLinearGraph = strings.srLinearGraph;
const srLinearGraphPoints = strings.srLinearGraphPoints({
point1X: srFormatNumber(line[0][0], locale),
point1Y: srFormatNumber(line[0][1], locale),
point2X: srFormatNumber(line[1][0], locale),
point2Y: srFormatNumber(line[1][1], locale),
});
const srLinearGrabHandle = strings.srLinearGrabHandle({
point1X: srFormatNumber(line[0][0], locale),
point1Y: srFormatNumber(line[0][1], locale),
point2X: srFormatNumber(line[1][0], locale),
point2Y: srFormatNumber(line[1][1], locale),
});
const slopeString = getSlopeStringForLine(line, strings);
const interceptString = getInterceptStringForLine(line, strings, locale);

const srLinearInteractiveElement = strings.srInteractiveElements({
elements: [srLinearGraph, srLinearGraphPoints].join(" "),
});

return {
srLinearGraph,
srLinearGraphPoints,
srLinearGrabHandle,
slopeString,
interceptString,
srLinearInteractiveElement,
};
}
Loading