Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/wb-announcer-tangent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

[Interactive Graph] Use WB Announcer in Tangent graph.
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,61 @@ describe("getAnnouncementText", () => {
});
});

describe("move-tangent-point", () => {
// Coord layout: [inflection(0), second/control point(1)]. The
// inflection point uses the inflection label; the second point uses
// the control-point label.
it("uses the inflection-point label for index 0", () => {
const result = getAnnouncementText(
{
type: "move-tangent-point",
pointIndex: 0,
pointLabel: 1,
x: -3,
y: 1,
},
mockStrings,
"en",
);

expect(result).toBe("Inflection point at -3 comma 1.");
});

it("uses the control-point label for index 1", () => {
const result = getAnnouncementText(
{
type: "move-tangent-point",
pointIndex: 1,
pointLabel: 2,
x: 4,
y: -2,
},
mockStrings,
"en",
);

expect(result).toBe("Control point at 4 comma -2.");
});

// TODO(LEMS-4206): allow custom labels for tangent points so we can
// keep the inflection/control-point wording.
it("uses the custom label, overriding the inflection/control-point wording, when one is set", () => {
const result = getAnnouncementText(
{
type: "move-tangent-point",
pointIndex: 0,
pointLabel: "I",
x: -3,
y: 1,
},
mockStrings,
"en",
);

expect(result).toBe("Point I at -3 comma 1.");
});
});

describe("move-angle-point", () => {
// Coord layout: [endingSide(0), vertex(1), startingSide(2)]. The
// side labels include their coords; the vertex also includes the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export function getAnnouncementText(
);
case "move-sinusoid-point":
return srSinusoidPointLabel(state, strings, locale);
case "move-tangent-point":
return srTangentPointLabel(state, strings, locale);
case "move-absolute-value-point":
return srAbsoluteValuePointLabel(state, strings, locale);
case "move-angle-point":
Expand Down Expand Up @@ -166,6 +168,31 @@ function srSinusoidPointLabel(
: strings.srSinusoidMinPoint(formatted);
}

function srTangentPointLabel(
state: {
pointIndex: number;
pointLabel: string | number;
x: number;
y: number;
},
strings: PerseusStrings,
locale: string,
): string {
const x = srFormatNumber(state.x, locale);
const y = srFormatNumber(state.y, locale);
// A custom author label overrides the inflection/control-point semantics,
// matching the static aria-label behavior in tangent.tsx.
// TODO(LEMS-4206): Once we update the translation keys to allow custom labels
// we can remove this block in favor of using the index logic below.
if (typeof state.pointLabel === "string") {
return strings.srPointAtCoordinates({num: state.pointLabel, x, y});
}
// Coord layout in tangent graphs: [inflection(0), second/control point(1)].
return state.pointIndex === 0
? strings.srTangentInflectionPoint({x, y})
: strings.srTangentSecondPoint({x, y});
}

function srAbsoluteValuePointLabel(
state: {
pointIndex: number;
Expand All @@ -185,6 +212,7 @@ function srAbsoluteValuePointLabel(
if (typeof state.pointLabel === "string") {
return strings.srPointAtCoordinates({num: state.pointLabel, x, y});
}

// Coord layout in absolute-value graphs: [vertex(0), arm point(1)].
return state.pointIndex === 0
? strings.srAbsoluteValueVertexPoint({x, y})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ function TangentGraph(props: TangentGraphProps) {
onMove={(destination) =>
dispatch(actions.tangent.movePoint(i, destination))
}
// Move announcements come from the WB Announcer via
// stateAnnouncement; disable aria-live here to avoid
// the focusable handle double-announcing.
// TODO(LEMS-4189): Remove ariaLive once aria-live is
// dropped from useControlPoint.
ariaLive="off"
/>
))}
<SRDescInSVG id={descriptionId}>{srTangentDescription}</SRDescInSVG>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,102 @@ describe("movePoint on an absolute-value graph", () => {
});
});

describe("movePoint on a tangent graph", () => {
it("sets stateAnnouncement to a move-tangent-point when moving the inflection point", () => {
const state = generateTangentGraphState({
coords: [
[0, 0],
[2, 2],
],
});

const updated = interactiveGraphReducer(
state,
actions.tangent.movePoint(0, [-3, 1]),
);

invariant(updated.stateAnnouncement?.type === "move-tangent-point");
expect(updated.stateAnnouncement.pointIndex).toBe(0);
expect(updated.stateAnnouncement.x).toBe(-3);
expect(updated.stateAnnouncement.y).toBe(1);
});

it("sets stateAnnouncement to a move-tangent-point when moving the second point", () => {
const state = generateTangentGraphState({
coords: [
[0, 0],
[2, 2],
],
});

const updated = interactiveGraphReducer(
state,
actions.tangent.movePoint(1, [4, -2]),
);

invariant(updated.stateAnnouncement?.type === "move-tangent-point");
expect(updated.stateAnnouncement.pointIndex).toBe(1);
expect(updated.stateAnnouncement.x).toBe(4);
expect(updated.stateAnnouncement.y).toBe(-2);
});

it("carries the custom pointLabel when one is set", () => {
const state = generateTangentGraphState({
coords: [
[0, 0],
[2, 2],
],
pointLabels: ["I", "P"],
});

const updated = interactiveGraphReducer(
state,
actions.tangent.movePoint(0, [-3, 1]),
);

invariant(updated.stateAnnouncement?.type === "move-tangent-point");
expect(updated.stateAnnouncement.pointLabel).toBe("I");
});

it("falls back to the numeric default when the pointLabel slot is empty", () => {
const state = generateTangentGraphState({
coords: [
[0, 0],
[2, 2],
],
pointLabels: ["", "P"],
});

const updated = interactiveGraphReducer(
state,
actions.tangent.movePoint(0, [-3, 1]),
);

invariant(updated.stateAnnouncement?.type === "move-tangent-point");
expect(updated.stateAnnouncement.pointLabel).toBe(1);
});

it("rejects the move when both points would share the same x-coordinate", () => {
const state = generateTangentGraphState({
coords: [
[0, 0],
[2, 2],
],
});

// Moving the inflection point onto the second point's x (2) would make
// the coefficients undefined, so the move is rejected.
const updated = interactiveGraphReducer(
state,
actions.tangent.movePoint(0, [2, 5]),
);

invariant(updated.type === "tangent");
expect(updated.coords[0]).toEqual([0, 0]);
expect(updated.stateAnnouncement).toBeUndefined();
});
});

describe("movePoint on a quadratic graph", () => {
it("moves a point", () => {
const state: InteractiveGraphState = baseQuadraticGraphState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,16 @@ function doMovePoint(
index: action.index,
newValue: boundDestination,
}),
stateAnnouncement: {
type: "move-tangent-point",
pointIndex: action.index,
pointLabel: resolvePointLabel(
state.pointLabels,
action.index,
),
x: boundDestination[X],
y: boundDestination[Y],
},
};
}
case "quadratic": {
Expand Down
12 changes: 12 additions & 0 deletions packages/perseus/src/widgets/interactive-graphs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ type MoveSinusoidPointAnnouncement = {
otherY: number;
};

// Tangent graph: the inflection point (index 0) and the second/control
// point (index 1) use different labels, chosen by index — mirroring the
// static aria-labels in tangent.tsx.
type MoveTangentPointAnnouncement = {
type: "move-tangent-point";
pointIndex: number;
pointLabel: string | number;
x: number;
y: number;
};

// Absolute-value graph: the vertex (index 0) and the point on the arm
// (index 1) use different labels, chosen by index — mirroring the static
// aria-labels in absolute-value.tsx.
Expand Down Expand Up @@ -221,6 +232,7 @@ export type InteractiveGraphStateAnnouncement =
| MoveRayLineAnnouncement
| MoveLinearLineAnnouncement
| MoveSinusoidPointAnnouncement
| MoveTangentPointAnnouncement
| MoveAbsoluteValuePointAnnouncement
| MoveAnglePointAnnouncement
| MovePolygonAnnouncement;
Expand Down
Loading