From d96821e08b3f80eb0a277882f4a8a40330b27adc Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Wed, 15 Jan 2025 15:55:39 -0800 Subject: [PATCH 01/11] [SR] Linear System - add screen reader support for Linear System interactive graph (#2030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: Add the aria label and descriptions for the full graph and the interactive elements in the Linear System graph, based on the [SRUX doc](https://khanacademy.atlassian.net/wiki/spaces/LC/pages/3460366359/Linear+Systems). Issue: https://khanacademy.atlassian.net/browse/LEMS-1727 ## Test plan: `yarn jest packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.test.tsx` Storybook - http://localhost:6006/iframe.html?globals=&args=&id=perseuseditor-widgets-interactive-graph--interactive-graph-linear-system&viewMode=story View Storybook publish in the checks below to try it yourself. https://github.com/user-attachments/assets/007a3418-5dcb-470f-a3fd-45479cc3c4d2 Author: nishasy Reviewers: catandthemachines, benchristel, nishasy, anakaren-rojas Required Reviewers: Approved By: anakaren-rojas, catandthemachines Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x) Pull Request URL: https://github.com/Khan/perseus/pull/2030 --- .changeset/tidy-baboons-tie.md | 5 + packages/perseus/src/strings.ts | 35 ++ .../interactive-graphs/graphs/angle.tsx | 3 +- .../interactive-graphs/graphs/circle.tsx | 5 +- .../graphs/linear-system.test.tsx | 301 ++++++++++++++++++ .../graphs/linear-system.tsx | 142 ++++++++- .../interactive-graphs/graphs/linear.tsx | 52 +-- .../interactive-graphs/graphs/utils.ts | 58 ++++ .../interactive-graphs/mafs-graph.test.tsx | 8 +- 9 files changed, 553 insertions(+), 56 deletions(-) create mode 100644 .changeset/tidy-baboons-tie.md create mode 100644 packages/perseus/src/widgets/interactive-graphs/graphs/linear-system.test.tsx 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 a40ce32b07..635556ecaf 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -260,6 +260,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. }; @@ -480,6 +505,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. }; @@ -697,6 +727,11 @@ 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 9960185e23..eda9dce47d 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 30e7eb67d2..538d65ad13 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)", () => { From 0f8d11c0b8c00a10eb49f2d84b664803c5c83f3f Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Wed, 15 Jan 2025 16:04:09 -0800 Subject: [PATCH 02/11] [SR] Ray graph - Add screen reader support for Ray interactive graph (#2036) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: Add the aria label and descriptions for the full graph and the interactive elements in the Linear System graph, based on the [SRUX doc](https://khanacademy.atlassian.net/wiki/spaces/LC/pages/3460366337/Ray). Issue: https://khanacademy.atlassian.net/browse/LEMS-1734 ## Test plan: `yarn jest packages/perseus/src/widgets/interactive-graphs/graphs/ray.test.tsx` Storybook http://localhost:6006/iframe.html?globals=&args=&id=perseuseditor-widgets-interactive-graph--interactive-graph-ray&viewMode=story https://github.com/user-attachments/assets/fd00be9c-a8a6-42ca-af44-6f4f2bd1a0d3 Author: nishasy Reviewers: catandthemachines, anakaren-rojas, nishasy Required Reviewers: Approved By: catandthemachines, anakaren-rojas Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x) Pull Request URL: https://github.com/Khan/perseus/pull/2036 --- .changeset/wild-keys-sit.md | 5 + packages/perseus/src/strings.ts | 39 ++++ .../interactive-graphs/graphs/ray.test.tsx | 212 ++++++++++++++++++ .../widgets/interactive-graphs/graphs/ray.tsx | 108 ++++++++- .../interactive-graphs/mafs-graph.test.tsx | 4 +- 5 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 .changeset/wild-keys-sit.md create mode 100644 packages/perseus/src/widgets/interactive-graphs/graphs/ray.test.tsx diff --git a/.changeset/wild-keys-sit.md b/.changeset/wild-keys-sit.md new file mode 100644 index 0000000000..6e9702456f --- /dev/null +++ b/.changeset/wild-keys-sit.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +[SR] Ray graph - Add screen reader support for Ray interactive graph diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 635556ecaf..8a7f81e329 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -285,6 +285,31 @@ export type PerseusStrings = { x: string; y: string; }): string; + srRayGraph: string; + srRayPoints: ({ + point1X, + point1Y, + point2X, + point2Y, + }: { + point1X: string; + point1Y: string; + point2X: string; + point2Y: string; + }) => string; + srRayEndpoint: ({x, y}: {x: string; y: string}) => string; + srRayTerminalPoint: ({x, y}: {x: string; y: string}) => string; + srRayGrabHandle: ({ + point1X, + point1Y, + point2X, + point2Y, + }: { + point1X: string; + point1Y: string; + point2X: string; + point2Y: string; + }) => string; // The above strings are used for interactive graph SR descriptions. }; @@ -510,6 +535,13 @@ export const strings: { "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.", + srRayGraph: "A ray on a coordinate plane.", + srRayPoints: + "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.", + srRayGrabHandle: + "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.", + srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.", + srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s.", // The above strings are used for interactive graph SR descriptions. }; @@ -732,6 +764,13 @@ export const mockStrings: PerseusStrings = { `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}.`, + srRayGraph: "A ray on a coordinate plane.", + srRayPoints: ({point1X, point1Y, point2X, point2Y}) => + `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`, + srRayGrabHandle: ({point1X, point1Y, point2X, point2Y}) => + `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`, + srRayEndpoint: ({x, y}) => `Endpoint at ${x} comma ${y}.`, + srRayTerminalPoint: ({x, y}) => `Through point at ${x} comma ${y}.`, // The above strings are used for interactive graph SR descriptions. }; diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/ray.test.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/ray.test.tsx new file mode 100644 index 0000000000..f87795306f --- /dev/null +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/ray.test.tsx @@ -0,0 +1,212 @@ +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 {describeRayGraph} from "./ray"; + +import type {InteractiveGraphState} from "../types"; +import type {UserEvent} from "@testing-library/user-event"; + +const baseMafsGraphProps = getBaseMafsGraphPropsForTests(); +const baseRayState: InteractiveGraphState = { + type: "ray", + coords: [ + [-5, 5], + [5, 5], + ], + hasBeenInteractedWith: false, + range: [ + [-10, 10], + [-10, 10], + ], + snapStep: [1, 1], +}; + +const overallGraphLabel = "A ray on a coordinate plane."; + +describe("Linear 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 graph", () => { + // Arrange + render(); + + // Act + const linearGraph = screen.getByLabelText( + "A ray on a coordinate plane.", + ); + + // Assert + expect(linearGraph).toBeInTheDocument(); + expect(linearGraph).toHaveAccessibleDescription( + "The endpoint is at -5 comma 5 and the ray goes through point 5 comma 5.", + ); + }); + + test.each` + element | index | expectedValue + ${"point1"} | ${0} | ${"Endpoint at -5 comma 5."} + ${"grabHandle"} | ${1} | ${"Ray with endpoint -5 comma 5 going through point 5 comma 5."} + ${"point2"} | ${2} | ${"Through point at 5 comma 5."} + `( + "should have aria label for $element on the line", + ({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("points description should include points info", () => { + // Arrange + render(); + + // Act + const linearGraph = screen.getByLabelText(overallGraphLabel); + + // Assert + expect(linearGraph).toHaveTextContent( + "The endpoint is at -5 comma 5 and the ray goes through point 5 comma 5.", + ); + }); + + test("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 linear graph. + expect(point1).toHaveAttribute("aria-label", "Endpoint at -2 comma 3."); + expect(grabHandle).toHaveAttribute( + "aria-label", + "Ray with endpoint -2 comma 3 going through point 3 comma 3.", + ); + expect(point2).toHaveAttribute( + "aria-label", + "Through point 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("describeRayGraph", () => { + test("describes a default ray", () => { + // Arrange + + // Act + const strings = describeRayGraph(baseRayState, mockPerseusI18nContext); + + // Assert + expect(strings.srRayGraph).toBe("A ray on a coordinate plane."); + expect(strings.srRayPoints).toBe( + "The endpoint is at -5 comma 5 and the ray goes through point 5 comma 5.", + ); + expect(strings.srRayEndpoint).toBe("Endpoint at -5 comma 5."); + expect(strings.srRayTerminalPoint).toBe("Through point at 5 comma 5."); + expect(strings.srRayGrabHandle).toBe( + "Ray with endpoint -5 comma 5 going through point 5 comma 5.", + ); + expect(strings.srRayInteractiveElement).toBe( + "Interactive elements: A ray on a coordinate plane. The endpoint is at -5 comma 5 and the ray goes through point 5 comma 5.", + ); + }); + + test("describes a ray with updated points", () => { + // Arrange + + // Act + const strings = describeRayGraph( + { + ...baseRayState, + coords: [ + [-1, 2], + [3, 4], + ], + }, + mockPerseusI18nContext, + ); + + // Assert + expect(strings.srRayGraph).toBe("A ray on a coordinate plane."); + expect(strings.srRayPoints).toBe( + "The endpoint is at -1 comma 2 and the ray goes through point 3 comma 4.", + ); + expect(strings.srRayEndpoint).toBe("Endpoint at -1 comma 2."); + expect(strings.srRayTerminalPoint).toBe("Through point at 3 comma 4."); + expect(strings.srRayGrabHandle).toBe( + "Ray with endpoint -1 comma 2 going through point 3 comma 4.", + ); + expect(strings.srRayInteractiveElement).toBe( + "Interactive elements: A ray on a coordinate plane. The endpoint is at -1 comma 2 and the ray goes through point 3 comma 4.", + ); + }); +}); diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/ray.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/ray.tsx index 2880e7c5b0..fc9aa3e48d 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/ray.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/ray.tsx @@ -1,9 +1,13 @@ 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 type {I18nContextType} from "../../../components/i18n-context"; import type { Dispatch, InteractiveGraphElementSuite, @@ -18,7 +22,7 @@ export function renderRayGraph( ): InteractiveGraphElementSuite { return { graph: , - interactiveElementsDescription: null, + interactiveElementsDescription: , }; } @@ -33,16 +37,100 @@ const RayGraph = (props: Props) => { const handleMovePoint = (pointIndex: number, newPoint: vec.Vector2) => dispatch(actions.ray.movePoint(pointIndex, newPoint)); + const {strings, locale} = usePerseusI18n(); + const id = React.useId(); + const pointsDescriptionId = id + "-points"; + + // Aria label strings + const { + srRayGraph, + srRayPoints, + srRayEndpoint, + srRayTerminalPoint, + srRayGrabHandle, + } = describeRayGraph(props.graphState, {strings, locale}); + // Ray graphs only have one line return ( - + + + {/* Hidden elements to provide the descriptions for the + `aria-describedby` properties. */} + + {srRayPoints} + + ); }; + +function RayGraphDescription({state}: {state: RayGraphState}) { + // The reason that RayGraphDescription is a component (rather than a + // function that returns a string) is because it needs to use a + // hook: `usePerseusI18n`. + const i18n = usePerseusI18n(); + const strings = describeRayGraph(state, i18n); + + return strings.srRayInteractiveElement; +} + +// Exported for testing +export function describeRayGraph( + state: RayGraphState, + i18n: I18nContextType, +): Record { + const {coords: line} = state; + const {strings, locale} = i18n; + + // Aria label strings + const srRayGraph = strings.srRayGraph; + const srRayPoints = strings.srRayPoints({ + 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 srRayEndpoint = strings.srRayEndpoint({ + x: srFormatNumber(line[0][0], locale), + y: srFormatNumber(line[0][1], locale), + }); + const srRayTerminalPoint = strings.srRayTerminalPoint({ + x: srFormatNumber(line[1][0], locale), + y: srFormatNumber(line[1][1], locale), + }); + const srRayGrabHandle = strings.srRayGrabHandle({ + 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 srRayInteractiveElement = strings.srInteractiveElements({ + elements: [srRayGraph, srRayPoints].join(" "), + }); + + return { + srRayGraph, + srRayPoints, + srRayEndpoint, + srRayTerminalPoint, + srRayGrabHandle, + srRayInteractiveElement, + }; +} 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 538d65ad13..0f3ecfbe69 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx @@ -278,8 +278,8 @@ describe("MafsGraph", () => { />, ); - expectLabelInDoc("Point 1 at 0 comma 0"); - expectLabelInDoc("Point 2 at -7 comma 0.5"); + expectLabelInDoc("Endpoint at 0 comma 0."); + expectLabelInDoc("Through point at -7 comma 0.5."); }); it("renders ARIA labels for each point (circle)", () => { From faccc2d5959a4a7051720f7a3dfe4a4875b6ace9 Mon Sep 17 00:00:00 2001 From: Ben Christel Date: Thu, 16 Jan 2025 10:31:56 -0800 Subject: [PATCH 03/11] Enable the exhaustive test tool for parsePerseusItem to test articles. (#2077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will help us find data issues where article content contains invalid or strangely-shaped Perseus data. Issue: LEMS-2582 ## Test plan: Run the exhaustive test tool on a content snapshot that includes articles. You should get 14 errors: ``` (root).question.widgets["interactive-graph N"].options.lockedFigures[N].size -- expected "small", "medium", "large"; got undefined (root).question.widgets["grapher N"].options.correct.coords -- expected array; got null (root).question.widgets["explanation N"][N] -- expected numeric string; got string (root).question.widgets["image N"][N] -- expected numeric string; got string (root).question.widgets["interactive-graph N"].options.lockedFigures[N].text -- expected string; got undefined (root).question.widgets["interaction N"].options.elements[N].key -- expected string; got undefined (root).question.widgets["cs-program N"].options.width -- expected number; got null (root).question.widgets["iframe N"].options.allowFullScreen -- expected boolean; got undefined (root).question.widgets["example-graphie-widget N"] -- expected a valid widget type; got string (root).question.widgets["interactive-graph N"].options.lockedFigures[N].coord -- expected array; got undefined (root).question.widgets["interactive-graph N"].options.lockedFigures[N].type -- expected "label"; got string (root).question.widgets["explanation N"].options.widgets["image N"][N] -- expected numeric string; got string (root).question.widgets["iframe N"].options.settings -- expected array; got undefined (root).question.widgets["graded-group N"].options.widgets["numeric-input N"].options.answers[N].answerForms[N] -- expected "integer", "mixed", "improper", "proper", "decimal", "percent", "pi"; got string ``` Author: benchristel Reviewers: jeremywiebe, benchristel Required Reviewers: Approved By: jeremywiebe Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x) Pull Request URL: https://github.com/Khan/perseus/pull/2077 --- .changeset/new-tomatoes-fly.md | 5 + .../exhaustive-test-tool/index.ts | 106 +++++++++++++----- 2 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 .changeset/new-tomatoes-fly.md diff --git a/.changeset/new-tomatoes-fly.md b/.changeset/new-tomatoes-fly.md new file mode 100644 index 0000000000..eacbb409e2 --- /dev/null +++ b/.changeset/new-tomatoes-fly.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +Internal: Enable the exhaustive test tool for parsePerseusItem to test articles. diff --git a/packages/perseus/src/util/parse-perseus-json/exhaustive-test-tool/index.ts b/packages/perseus/src/util/parse-perseus-json/exhaustive-test-tool/index.ts index 439fd32989..4414eb322f 100755 --- a/packages/perseus/src/util/parse-perseus-json/exhaustive-test-tool/index.ts +++ b/packages/perseus/src/util/parse-perseus-json/exhaustive-test-tool/index.ts @@ -26,8 +26,10 @@ import * as fs from "fs/promises"; import {join} from "path"; import {ErrorTrackingParseContext} from "../error-tracking-parse-context"; +import {isObject} from "../general-purpose-parsers"; import {formatPath} from "../object-path"; import {parsePerseusItem} from "../perseus-parsers/perseus-item"; +import {parsePerseusRenderer} from "../perseus-parsers/perseus-renderer"; import {isSuccess} from "../result"; import type {Mismatch} from "../parser-types"; @@ -61,20 +63,8 @@ async function testFile(path: string, outputDir: string) { return; } const json = await fs.readFile(path, "utf-8"); - let assessmentItems: null | unknown[] = null; - try { - assessmentItems = JSON.parse(json); - } catch { - // eslint-disable-next-line no-console - console.warn("Failed to parse JSON file: " + path); - return; - } - if (!Array.isArray(assessmentItems)) { - return; - } - - for (const rawItem of assessmentItems.map(getAssessmentItemData)) { - for (const mismatch of getMismatches(rawItem)) { + for (const tester of createContentItemTesters(json, path)) { + for (const mismatch of tester.getMismatches()) { const desc = describeMismatch(mismatch); const hash = sha256(desc); await fs.mkdir(join(outputDir, hash), {recursive: true}); @@ -89,27 +79,93 @@ async function testFile(path: string, outputDir: string) { // current item is shorter than the one already on disk. await writeFileIfShorterThanExisting( join(outputDir, hash, "item.json"), - String(JSON.stringify(rawItem)), + String(JSON.stringify(tester.rawData)), "utf-8", ); } } } -function getAssessmentItemData(raw: unknown): unknown { - if (raw && typeof raw === "object" && "item_data" in raw) { - return raw.item_data; - } else { - return raw; - } +interface ContentItemTester { + readonly rawData: unknown; + getMismatches(): Mismatch[]; } -function getMismatches(rawItem: unknown): Mismatch[] { - const result = parsePerseusItem(rawItem, new ErrorTrackingParseContext([])); - if (isSuccess(result)) { +function createContentItemTesters( + json: string, + path: string, +): ContentItemTester[] { + let contentItems: unknown; + try { + contentItems = JSON.parse(json); + } catch { + // eslint-disable-next-line no-console + console.warn("Failed to parse JSON file: " + path); return []; } - return result.detail; + if (!Array.isArray(contentItems)) { + return []; + } + + const testers: ContentItemTester[] = []; + for (const item of contentItems) { + const tester = createContentItemTester(item, path); + if (tester != null) { + testers.push(tester); + } + } + + return testers; +} + +function createContentItemTester( + item: unknown, + path: string, +): ContentItemTester | undefined { + if (isObject(item)) { + if ("item_data" in item) { + // We're looking at an exercise. + return new AssessmentItemTester(item.item_data); + } + + if ("content" in item) { + // We're looking at an article. + return new ArticleTester(item); + } + } + + // eslint-disable-next-line no-console + console.warn("Cannot classify content as an article or exercise: ", path); +} + +class AssessmentItemTester implements ContentItemTester { + constructor(readonly rawData: unknown) {} + + getMismatches(): Mismatch[] { + const result = parsePerseusItem( + this.rawData, + new ErrorTrackingParseContext([]), + ); + if (isSuccess(result)) { + return []; + } + return result.detail; + } +} + +class ArticleTester implements ContentItemTester { + constructor(readonly rawData: unknown) {} + + getMismatches(): Mismatch[] { + const result = parsePerseusRenderer( + this.rawData, + new ErrorTrackingParseContext([]), + ); + if (isSuccess(result)) { + return []; + } + return result.detail; + } } function describeMismatch(mismatch: Mismatch): string { From 41ffd4a71673399657d7024c206af4fa4e0be267 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:53:49 -0800 Subject: [PATCH 04/11] Bump the wonder-blocks group with 22 updates (#2109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the wonder-blocks group with 22 updates: | Package | From | To | | --- | --- | --- | | [@khanacademy/wonder-blocks-button](https://github.com/khan/wonder-blocks) | `7.0.3` | `7.0.5` | | @khanacademy/wonder-blocks-layout | `3.0.3` | `3.0.5` | | @khanacademy/wonder-blocks-banner | `4.0.3` | `4.0.5` | | @khanacademy/wonder-blocks-icon | `5.0.3` | `5.0.5` | | @khanacademy/wonder-blocks-icon-button | `6.0.3` | `6.0.5` | | @khanacademy/wonder-blocks-link | `7.0.3` | `7.0.5` | | @khanacademy/wonder-blocks-search-field | `4.0.1` | `4.0.5` | | @khanacademy/wonder-blocks-timing | `6.0.0` | `6.0.1` | | @khanacademy/wonder-blocks-tokens | `3.0.0` | `3.0.1` | | @khanacademy/wonder-blocks-toolbar | `5.0.3` | `5.0.5` | | @khanacademy/wonder-blocks-tooltip | `4.0.1` | `4.0.3` | | @khanacademy/wonder-blocks-clickable | `5.0.3` | `5.0.5` | | [@khanacademy/wonder-blocks-core](https://github.com/khan/wonder-blocks) | `11.0.0` | `11.1.0` | | @khanacademy/wonder-blocks-popover | `5.0.1` | `5.0.3` | | @khanacademy/wonder-blocks-data | `14.0.3` | `14.0.6` | | @khanacademy/wonder-blocks-dropdown | `7.0.1` | `7.0.5` | | @khanacademy/wonder-blocks-form | `6.0.1` | `6.0.5` | | @khanacademy/wonder-blocks-pill | `3.0.3` | `3.0.5` | | @khanacademy/wonder-blocks-progress-spinner | `3.0.3` | `3.0.5` | | @khanacademy/wonder-blocks-switch | `3.0.1` | `3.0.3` | | [@khanacademy/wonder-blocks-typography](https://github.com/khan/wonder-blocks) | `3.0.3` | `3.0.5` | | @khanacademy/wonder-blocks-accordion | `3.0.1` | `3.0.3` | Updates `@khanacademy/wonder-blocks-button` from 7.0.3 to 7.0.5
Release notes

Sourced from @​khanacademy/wonder-blocks-button's releases.

@​khanacademy/wonder-blocks-button@​7.0.5

Patch Changes

  • 0cffa81f: Use pseudo-classes for styling states (:hover, :focus-visible). Keep some clickable states for programmatic focus and preserve active/pressed overrides.
  • Updated dependencies [7516b239]
    • @​khanacademy/wonder-blocks-core@​11.1.0
    • @​khanacademy/wonder-blocks-clickable@​5.0.5
    • @​khanacademy/wonder-blocks-icon@​5.0.5
    • @​khanacademy/wonder-blocks-progress-spinner@​3.0.5
    • @​khanacademy/wonder-blocks-typography@​3.0.5

@​khanacademy/wonder-blocks-button@​7.0.4

Patch Changes

  • 11a0f5c6: No functional changes. Adding prepublishOnly script.
  • Updated dependencies [11a0f5c6]
    • @​khanacademy/wonder-blocks-progress-spinner@​3.0.4
    • @​khanacademy/wonder-blocks-typography@​3.0.4
    • @​khanacademy/wonder-blocks-clickable@​5.0.4
    • @​khanacademy/wonder-blocks-theming@​3.0.1
    • @​khanacademy/wonder-blocks-tokens@​3.0.1
    • @​khanacademy/wonder-blocks-core@​11.0.1
    • @​khanacademy/wonder-blocks-icon@​5.0.4
Commits
  • 2ecdfb5 RELEASING: Releasing 27 package(s) (#2419)
  • 0cffa81 WB-1808: Button - Use CSS pseudo-classes for styling states (hover, focus, et...
  • 7516b23 Update useOnMountEffect to pass isMountedRef to callback (#2413)
  • 703d793 Version Packages (#2418)
  • faf7bd2 [fei6062.3.releasing] Catch the RELEASING commit issueprotections (#2417)
  • bf75d60 RELEASING: Releasing 31 package(s) (#2415)
  • f7877ad [fei6062.releaseprotections.2] Fail snapshot runs if a release is happening (...
  • 11a0f5c [fei6062.releaseprotections] Update prepublish checks to add SNAPSHOT_RELEASE...
  • c58e3fa Version Packages (#2411)
  • 53b4197 Changesets: Bump form package to test release process (#2410)
  • Additional commits viewable in compare view

Updates `@khanacademy/wonder-blocks-layout` from 3.0.3 to 3.0.5 Updates `@khanacademy/wonder-blocks-banner` from 4.0.3 to 4.0.5 Updates `@khanacademy/wonder-blocks-icon` from 5.0.3 to 5.0.5 Updates `@khanacademy/wonder-blocks-icon-button` from 6.0.3 to 6.0.5 Updates `@khanacademy/wonder-blocks-link` from 7.0.3 to 7.0.5 Updates `@khanacademy/wonder-blocks-search-field` from 4.0.1 to 4.0.5 Updates `@khanacademy/wonder-blocks-timing` from 6.0.0 to 6.0.1 Updates `@khanacademy/wonder-blocks-tokens` from 3.0.0 to 3.0.1 Updates `@khanacademy/wonder-blocks-toolbar` from 5.0.3 to 5.0.5 Updates `@khanacademy/wonder-blocks-tooltip` from 4.0.1 to 4.0.3 Updates `@khanacademy/wonder-blocks-clickable` from 5.0.3 to 5.0.5 Updates `@khanacademy/wonder-blocks-core` from 11.0.0 to 11.1.0
Release notes

Sourced from @​khanacademy/wonder-blocks-core's releases.

@​khanacademy/wonder-blocks-core@​11.1.0

Minor Changes

  • 7516b239: Update useOnMountEffect to pass isMountedRef to callback

@​khanacademy/wonder-blocks-core@​11.0.1

Patch Changes

  • 11a0f5c6: No functional changes. Adding prepublishOnly script.
Commits
  • 2ecdfb5 RELEASING: Releasing 27 package(s) (#2419)
  • 0cffa81 WB-1808: Button - Use CSS pseudo-classes for styling states (hover, focus, et...
  • 7516b23 Update useOnMountEffect to pass isMountedRef to callback (#2413)
  • 703d793 Version Packages (#2418)
  • faf7bd2 [fei6062.3.releasing] Catch the RELEASING commit issueprotections (#2417)
  • bf75d60 RELEASING: Releasing 31 package(s) (#2415)
  • f7877ad [fei6062.releaseprotections.2] Fail snapshot runs if a release is happening (...
  • 11a0f5c [fei6062.releaseprotections] Update prepublish checks to add SNAPSHOT_RELEASE...
  • c58e3fa Version Packages (#2411)
  • 53b4197 Changesets: Bump form package to test release process (#2410)
  • Additional commits viewable in compare view

Updates `@khanacademy/wonder-blocks-popover` from 5.0.1 to 5.0.3 Updates `@khanacademy/wonder-blocks-data` from 14.0.3 to 14.0.6 Updates `@khanacademy/wonder-blocks-dropdown` from 7.0.1 to 7.0.5 Updates `@khanacademy/wonder-blocks-form` from 6.0.1 to 6.0.5 Updates `@khanacademy/wonder-blocks-pill` from 3.0.3 to 3.0.5 Updates `@khanacademy/wonder-blocks-progress-spinner` from 3.0.3 to 3.0.5 Updates `@khanacademy/wonder-blocks-switch` from 3.0.1 to 3.0.3 Updates `@khanacademy/wonder-blocks-typography` from 3.0.3 to 3.0.5
Release notes

Sourced from @​khanacademy/wonder-blocks-typography's releases.

@​khanacademy/wonder-blocks-typography@​3.0.5

Patch Changes

  • Updated dependencies [7516b239]
    • @​khanacademy/wonder-blocks-core@​11.1.0

@​khanacademy/wonder-blocks-typography@​3.0.4

Patch Changes

  • 11a0f5c6: No functional changes. Adding prepublishOnly script.
  • Updated dependencies [11a0f5c6]
    • @​khanacademy/wonder-blocks-core@​11.0.1
Commits
  • 2ecdfb5 RELEASING: Releasing 27 package(s) (#2419)
  • 0cffa81 WB-1808: Button - Use CSS pseudo-classes for styling states (hover, focus, et...
  • 7516b23 Update useOnMountEffect to pass isMountedRef to callback (#2413)
  • 703d793 Version Packages (#2418)
  • faf7bd2 [fei6062.3.releasing] Catch the RELEASING commit issueprotections (#2417)
  • bf75d60 RELEASING: Releasing 31 package(s) (#2415)
  • f7877ad [fei6062.releaseprotections.2] Fail snapshot runs if a release is happening (...
  • 11a0f5c [fei6062.releaseprotections] Update prepublish checks to add SNAPSHOT_RELEASE...
  • c58e3fa Version Packages (#2411)
  • 53b4197 Changesets: Bump form package to test release process (#2410)
  • Additional commits viewable in compare view

Updates `@khanacademy/wonder-blocks-accordion` from 3.0.1 to 3.0.3 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Author: dependabot[bot] Reviewers: catandthemachines, #wonder-blocks Required Reviewers: Approved By: catandthemachines Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x) Pull Request URL: https://github.com/Khan/perseus/pull/2109 --- .changeset/young-beers-wave.md | 8 + dev/package.json | 18 +- package.json | 4 +- packages/math-input/package.json | 20 +- packages/perseus-editor/package.json | 60 ++-- packages/perseus/package.json | 72 ++-- .../__snapshots__/explanation.test.ts.snap | 8 +- .../graded-group-set-jipt.test.ts.snap | 12 +- .../graded-group-set.test.ts.snap | 4 +- .../__snapshots__/graded-group.test.ts.snap | 4 +- yarn.lock | 314 +++++------------- 11 files changed, 193 insertions(+), 331 deletions(-) create mode 100644 .changeset/young-beers-wave.md diff --git a/.changeset/young-beers-wave.md b/.changeset/young-beers-wave.md new file mode 100644 index 0000000000..7c45fc6d91 --- /dev/null +++ b/.changeset/young-beers-wave.md @@ -0,0 +1,8 @@ +--- +"@khanacademy/perseus-dev-ui": patch +"@khanacademy/math-input": patch +"@khanacademy/perseus": patch +"@khanacademy/perseus-editor": patch +--- + +Updating our wonder-blocks packages with the latest versions. diff --git a/dev/package.json b/dev/package.json index 854b4bdaee..676b1a523c 100644 --- a/dev/package.json +++ b/dev/package.json @@ -21,15 +21,15 @@ "@khanacademy/perseus-linter": "^1.2.12", "@khanacademy/pure-markdown": "^0.3.21", "@khanacademy/simple-markdown": "^0.13.14", - "@khanacademy/wonder-blocks-banner": "4.0.3", - "@khanacademy/wonder-blocks-icon": "5.0.3", - "@khanacademy/wonder-blocks-icon-button": "6.0.3", - "@khanacademy/wonder-blocks-link": "7.0.3", - "@khanacademy/wonder-blocks-search-field": "4.0.1", - "@khanacademy/wonder-blocks-timing": "6.0.0", - "@khanacademy/wonder-blocks-tokens": "3.0.0", - "@khanacademy/wonder-blocks-toolbar": "5.0.3", - "@khanacademy/wonder-blocks-tooltip": "4.0.1", + "@khanacademy/wonder-blocks-banner": "4.0.5", + "@khanacademy/wonder-blocks-icon": "5.0.5", + "@khanacademy/wonder-blocks-icon-button": "6.0.5", + "@khanacademy/wonder-blocks-link": "7.0.5", + "@khanacademy/wonder-blocks-search-field": "4.0.5", + "@khanacademy/wonder-blocks-timing": "6.0.1", + "@khanacademy/wonder-blocks-tokens": "3.0.1", + "@khanacademy/wonder-blocks-toolbar": "5.0.5", + "@khanacademy/wonder-blocks-tooltip": "4.0.3", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2" }, diff --git a/package.json b/package.json index c8378cc1c2..14805440b2 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "@khanacademy/eslint-config": "^5.0.1", "@khanacademy/eslint-plugin": "^3.1.1", "@khanacademy/mathjax-renderer": "^2.1.1", - "@khanacademy/wonder-blocks-button": "7.0.3", - "@khanacademy/wonder-blocks-layout": "3.0.3", + "@khanacademy/wonder-blocks-button": "7.0.5", + "@khanacademy/wonder-blocks-layout": "3.0.5", "@khanacademy/wonder-blocks-spacing": "^4.0.1", "@popperjs/core": "^2.10.2", "@rollup/plugin-alias": "^3.1.9", diff --git a/packages/math-input/package.json b/packages/math-input/package.json index 7d880032df..cb344aea8c 100644 --- a/packages/math-input/package.json +++ b/packages/math-input/package.json @@ -46,11 +46,11 @@ }, "devDependencies": { "@khanacademy/mathjax-renderer": "^2.1.1", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-popover": "5.0.1", - "@khanacademy/wonder-blocks-timing": "6.0.0", - "@khanacademy/wonder-blocks-tokens": "3.0.0", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-popover": "5.0.3", + "@khanacademy/wonder-blocks-timing": "6.0.1", + "@khanacademy/wonder-blocks-tokens": "3.0.1", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "aphrodite": "^1.2.5", @@ -64,11 +64,11 @@ }, "peerDependencies": { "@khanacademy/mathjax-renderer": "^2.1.1", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-popover": "5.0.1", - "@khanacademy/wonder-blocks-timing": "6.0.0", - "@khanacademy/wonder-blocks-tokens": "3.0.0", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-popover": "5.0.3", + "@khanacademy/wonder-blocks-timing": "6.0.1", + "@khanacademy/wonder-blocks-tokens": "3.0.1", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "aphrodite": "^1.2.5", diff --git a/packages/perseus-editor/package.json b/packages/perseus-editor/package.json index 5206ee39fc..4bdfc90859 100644 --- a/packages/perseus-editor/package.json +++ b/packages/perseus-editor/package.json @@ -46,21 +46,21 @@ }, "devDependencies": { "@khanacademy/perseus-linter": "^1.2.12", - "@khanacademy/wonder-blocks-accordion": "3.0.1", - "@khanacademy/wonder-blocks-banner": "4.0.3", - "@khanacademy/wonder-blocks-button": "7.0.3", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-dropdown": "7.0.1", - "@khanacademy/wonder-blocks-form": "6.0.1", - "@khanacademy/wonder-blocks-icon": "5.0.3", - "@khanacademy/wonder-blocks-icon-button": "6.0.3", - "@khanacademy/wonder-blocks-pill": "3.0.3", - "@khanacademy/wonder-blocks-switch": "3.0.1", - "@khanacademy/wonder-blocks-timing": "6.0.0", - "@khanacademy/wonder-blocks-tokens": "3.0.0", - "@khanacademy/wonder-blocks-tooltip": "4.0.1", - "@khanacademy/wonder-blocks-typography": "3.0.3", + "@khanacademy/wonder-blocks-accordion": "3.0.3", + "@khanacademy/wonder-blocks-banner": "4.0.5", + "@khanacademy/wonder-blocks-button": "7.0.5", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-dropdown": "7.0.5", + "@khanacademy/wonder-blocks-form": "6.0.5", + "@khanacademy/wonder-blocks-icon": "5.0.5", + "@khanacademy/wonder-blocks-icon-button": "6.0.5", + "@khanacademy/wonder-blocks-pill": "3.0.5", + "@khanacademy/wonder-blocks-switch": "3.0.3", + "@khanacademy/wonder-blocks-timing": "6.0.1", + "@khanacademy/wonder-blocks-tokens": "3.0.1", + "@khanacademy/wonder-blocks-tooltip": "4.0.3", + "@khanacademy/wonder-blocks-typography": "3.0.5", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "aphrodite": "^1.2.5", @@ -74,21 +74,21 @@ "underscore": "^1.4.4" }, "peerDependencies": { - "@khanacademy/wonder-blocks-accordion": "3.0.1", - "@khanacademy/wonder-blocks-banner": "4.0.3", - "@khanacademy/wonder-blocks-button": "7.0.3", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-dropdown": "7.0.1", - "@khanacademy/wonder-blocks-form": "6.0.1", - "@khanacademy/wonder-blocks-icon": "5.0.3", - "@khanacademy/wonder-blocks-icon-button": "6.0.3", - "@khanacademy/wonder-blocks-pill": "3.0.3", - "@khanacademy/wonder-blocks-switch": "3.0.1", - "@khanacademy/wonder-blocks-timing": "6.0.0", - "@khanacademy/wonder-blocks-tokens": "3.0.0", - "@khanacademy/wonder-blocks-tooltip": "4.0.1", - "@khanacademy/wonder-blocks-typography": "3.0.3", + "@khanacademy/wonder-blocks-accordion": "3.0.3", + "@khanacademy/wonder-blocks-banner": "4.0.5", + "@khanacademy/wonder-blocks-button": "7.0.5", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-dropdown": "7.0.5", + "@khanacademy/wonder-blocks-form": "6.0.5", + "@khanacademy/wonder-blocks-icon": "5.0.5", + "@khanacademy/wonder-blocks-icon-button": "6.0.5", + "@khanacademy/wonder-blocks-pill": "3.0.5", + "@khanacademy/wonder-blocks-switch": "3.0.3", + "@khanacademy/wonder-blocks-timing": "6.0.1", + "@khanacademy/wonder-blocks-tokens": "3.0.1", + "@khanacademy/wonder-blocks-tooltip": "4.0.3", + "@khanacademy/wonder-blocks-typography": "3.0.5", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "aphrodite": "^1.2.5", diff --git a/packages/perseus/package.json b/packages/perseus/package.json index 0cfe211182..bd63970ba6 100644 --- a/packages/perseus/package.json +++ b/packages/perseus/package.json @@ -56,24 +56,24 @@ "uuid": "^10.0.0" }, "devDependencies": { - "@khanacademy/wonder-blocks-banner": "4.0.3", - "@khanacademy/wonder-blocks-button": "7.0.3", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-data": "14.0.3", - "@khanacademy/wonder-blocks-dropdown": "7.0.1", - "@khanacademy/wonder-blocks-form": "6.0.1", - "@khanacademy/wonder-blocks-icon": "5.0.3", - "@khanacademy/wonder-blocks-icon-button": "6.0.3", - "@khanacademy/wonder-blocks-layout": "3.0.3", - "@khanacademy/wonder-blocks-link": "7.0.3", - "@khanacademy/wonder-blocks-pill": "3.0.3", - "@khanacademy/wonder-blocks-popover": "5.0.1", - "@khanacademy/wonder-blocks-progress-spinner": "3.0.3", - "@khanacademy/wonder-blocks-switch": "3.0.1", - "@khanacademy/wonder-blocks-tokens": "3.0.0", - "@khanacademy/wonder-blocks-tooltip": "4.0.1", - "@khanacademy/wonder-blocks-typography": "3.0.3", + "@khanacademy/wonder-blocks-banner": "4.0.5", + "@khanacademy/wonder-blocks-button": "7.0.5", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-data": "14.0.6", + "@khanacademy/wonder-blocks-dropdown": "7.0.5", + "@khanacademy/wonder-blocks-form": "6.0.5", + "@khanacademy/wonder-blocks-icon": "5.0.5", + "@khanacademy/wonder-blocks-icon-button": "6.0.5", + "@khanacademy/wonder-blocks-layout": "3.0.5", + "@khanacademy/wonder-blocks-link": "7.0.5", + "@khanacademy/wonder-blocks-pill": "3.0.5", + "@khanacademy/wonder-blocks-popover": "5.0.3", + "@khanacademy/wonder-blocks-progress-spinner": "3.0.5", + "@khanacademy/wonder-blocks-switch": "3.0.3", + "@khanacademy/wonder-blocks-tokens": "3.0.1", + "@khanacademy/wonder-blocks-tooltip": "4.0.3", + "@khanacademy/wonder-blocks-typography": "3.0.5", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "@popperjs/core": "^2.10.2", @@ -91,24 +91,24 @@ "underscore": "^1.4.4" }, "peerDependencies": { - "@khanacademy/wonder-blocks-banner": "4.0.3", - "@khanacademy/wonder-blocks-button": "7.0.3", - "@khanacademy/wonder-blocks-clickable": "5.0.3", - "@khanacademy/wonder-blocks-core": "11.0.0", - "@khanacademy/wonder-blocks-data": "14.0.3", - "@khanacademy/wonder-blocks-dropdown": "7.0.1", - "@khanacademy/wonder-blocks-form": "6.0.1", - "@khanacademy/wonder-blocks-icon": "5.0.3", - "@khanacademy/wonder-blocks-icon-button": "6.0.3", - "@khanacademy/wonder-blocks-layout": "3.0.3", - "@khanacademy/wonder-blocks-link": "7.0.3", - "@khanacademy/wonder-blocks-pill": "3.0.3", - "@khanacademy/wonder-blocks-popover": "5.0.1", - "@khanacademy/wonder-blocks-progress-spinner": "3.0.3", - "@khanacademy/wonder-blocks-switch": "3.0.1", - "@khanacademy/wonder-blocks-tokens": "3.0.0", - "@khanacademy/wonder-blocks-tooltip": "4.0.1", - "@khanacademy/wonder-blocks-typography": "3.0.3", + "@khanacademy/wonder-blocks-banner": "4.0.5", + "@khanacademy/wonder-blocks-button": "7.0.5", + "@khanacademy/wonder-blocks-clickable": "5.0.5", + "@khanacademy/wonder-blocks-core": "11.1.0", + "@khanacademy/wonder-blocks-data": "14.0.6", + "@khanacademy/wonder-blocks-dropdown": "7.0.5", + "@khanacademy/wonder-blocks-form": "6.0.5", + "@khanacademy/wonder-blocks-icon": "5.0.5", + "@khanacademy/wonder-blocks-icon-button": "6.0.5", + "@khanacademy/wonder-blocks-layout": "3.0.5", + "@khanacademy/wonder-blocks-link": "7.0.5", + "@khanacademy/wonder-blocks-pill": "3.0.5", + "@khanacademy/wonder-blocks-popover": "5.0.3", + "@khanacademy/wonder-blocks-progress-spinner": "3.0.5", + "@khanacademy/wonder-blocks-switch": "3.0.3", + "@khanacademy/wonder-blocks-tokens": "3.0.1", + "@khanacademy/wonder-blocks-tooltip": "4.0.3", + "@khanacademy/wonder-blocks-typography": "3.0.5", "@khanacademy/wonder-stuff-core": "1.5.4", "@phosphor-icons/core": "^2.0.2", "@popperjs/core": "^2.10.2", diff --git a/packages/perseus/src/widgets/explanation/__snapshots__/explanation.test.ts.snap b/packages/perseus/src/widgets/explanation/__snapshots__/explanation.test.ts.snap index 3383f7cfa0..e96e0672b6 100644 --- a/packages/perseus/src/widgets/explanation/__snapshots__/explanation.test.ts.snap +++ b/packages/perseus/src/widgets/explanation/__snapshots__/explanation.test.ts.snap @@ -21,12 +21,12 @@ exports[`Explanation should snapshot when expanded: expanded 1`] = ` aria-controls=":r1:" aria-disabled="false" aria-expanded="true" - class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8-o_O-small_14crccx-o_O-inlineStyles_1s8anjv" + class="button_vr44p2-o_O-shared_lwskrm-o_O-default_qjb97o-o_O-small_14crccx-o_O-inlineStyles_1s8anjv" role="button" type="button" > Hide explanation! @@ -94,12 +94,12 @@ exports[`Explanation should snapshot: initial render 1`] = ` aria-controls=":r0:" aria-disabled="false" aria-expanded="false" - class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8-o_O-small_14crccx-o_O-inlineStyles_1s8anjv" + class="button_vr44p2-o_O-shared_lwskrm-o_O-default_qjb97o-o_O-small_14crccx-o_O-inlineStyles_1s8anjv" role="button" type="button" > Explanation diff --git a/packages/perseus/src/widgets/graded-group-set/__snapshots__/graded-group-set-jipt.test.ts.snap b/packages/perseus/src/widgets/graded-group-set/__snapshots__/graded-group-set-jipt.test.ts.snap index 6d4b738751..fcf253b7dd 100644 --- a/packages/perseus/src/widgets/graded-group-set/__snapshots__/graded-group-set-jipt.test.ts.snap +++ b/packages/perseus/src/widgets/graded-group-set/__snapshots__/graded-group-set-jipt.test.ts.snap @@ -251,12 +251,12 @@ exports[`graded-group-set should render all graded groups 1`] = ` />