{
beforeEach(() => {
@@ -38,4 +41,13 @@ describe("interaction widget", () => {
// what state its in.
expect(score).toHaveBeenAnsweredIncorrectly();
});
+
+ it("renders movable point elements with blank constraintXMin, constraintXMax, etc.", async () => {
+ const {container} = renderQuestion(
+ questionWithMovablePointMissingConstraints,
+ );
+ await waitForInitialGraphieRender();
+
+ expect(container).toMatchSnapshot();
+ });
});
diff --git a/packages/perseus/src/widgets/interaction/interaction.testdata.ts b/packages/perseus/src/widgets/interaction/interaction.testdata.ts
index 03e8edbacc..9ff4f9c8b4 100644
--- a/packages/perseus/src/widgets/interaction/interaction.testdata.ts
+++ b/packages/perseus/src/widgets/interaction/interaction.testdata.ts
@@ -259,3 +259,60 @@ export const question1: PerseusRenderer = {
},
},
};
+
+export const questionWithMovablePointMissingConstraints: PerseusRenderer = {
+ content: "[[☃ interaction 1]]",
+ images: {},
+ widgets: {
+ "interaction 1": {
+ graded: true,
+ options: {
+ static: false,
+ elements: [
+ {
+ key: "movable-point-64f8ec",
+ options: {
+ constraint: "none",
+ constraintFn: "0",
+ constraintXMin: "",
+ constraintXMax: "",
+ constraintYMin: "",
+ constraintYMax: "",
+ snap: 0.5,
+ startX: "0",
+ startY: "10",
+ varSubscript: 0,
+ },
+ type: "movable-point",
+ },
+ ],
+ graph: {
+ backgroundImage: {
+ bottom: 0,
+ left: 0,
+ scale: 1,
+ url: null,
+ },
+ box: [400, 400],
+ editableSettings: ["canvas", "graph"],
+ gridStep: [2, 2],
+ labels: ["x", "y"],
+ markings: "graph",
+ range: [
+ [-5, 50],
+ [-5, 50],
+ ],
+ rulerLabel: "",
+ rulerTicks: 10,
+ showProtractor: false,
+ showRuler: false,
+ snapStep: [1, 1],
+ tickStep: [5, 5],
+ valid: true,
+ },
+ },
+ type: "interaction",
+ version: {major: 0, minor: 0},
+ },
+ },
+};
diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
index ecc93225dd..0ecb3f8e88 100644
--- a/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/graphs/angle.tsx
@@ -112,10 +112,10 @@ function AngleGraph(props: AngleGraphProps) {
angleMeasure,
vertexX: srFormatNumber(coords[1][X], locale),
vertexY: srFormatNumber(coords[1][Y], locale),
- isX: srFormatNumber(coords[2][X], locale),
- isY: srFormatNumber(coords[2][Y], locale),
- tsX: srFormatNumber(coords[0][X], locale),
- tsY: srFormatNumber(coords[0][Y], locale),
+ startingSideX: srFormatNumber(coords[2][X], locale),
+ startingSideY: srFormatNumber(coords[2][Y], locale),
+ endingSideX: srFormatNumber(coords[0][X], locale),
+ endingSideY: srFormatNumber(coords[0][Y], locale),
});
const formatCoordinates = (x: number, y: number) => ({
diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx
index d9b4f0d03f..6acf462f4b 100644
--- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.test.tsx
@@ -569,7 +569,7 @@ describe("MafsGraph", () => {
"angle-description",
);
expect(angleGraph).toHaveTextContent(
- "The angle measure is 270 degrees with a vertex at 0 comma 0, a point on the initial side at 1 comma 1 and a point on the terminal side at -1 comma 1.",
+ "The angle measure is 270 degrees with a vertex at 0 comma 0, a point on the starting side at 1 comma 1 and a point on the ending side at -1 comma 1.",
);
});
diff --git a/packages/perseus/src/widgets/numeric-input/score-numeric-input.test.ts b/packages/perseus/src/widgets/numeric-input/score-numeric-input.test.ts
index ddf569c325..b6a989eacf 100644
--- a/packages/perseus/src/widgets/numeric-input/score-numeric-input.test.ts
+++ b/packages/perseus/src/widgets/numeric-input/score-numeric-input.test.ts
@@ -315,6 +315,10 @@ describe("scoreNumericInput", () => {
});
it("rejects responses formatted as a percentage when any answer has no value field", () => {
+ // NOTE(benchristel): this is a characterization test for existing
+ // behavior. Ideally, answers should always have a value field, but
+ // some don't, so this test documents how we handle that.
+ // TODO(benchristel): Fix the data so we can remove this test.
const scoringData: PerseusNumericInputScoringData = {
answers: [
{
@@ -347,6 +351,43 @@ describe("scoreNumericInput", () => {
expect(score).toHaveBeenAnsweredIncorrectly();
});
+ it("rejects responses formatted as a percentage when any answer has a null value", () => {
+ // NOTE(benchristel): this is a characterization test for existing
+ // behavior. Ideally, answers should always have a value field, but
+ // some don't, so this test documents how we handle that.
+ // TODO(benchristel): Fix the data so we can remove this test.
+ const rubric: PerseusNumericInputScoringData = {
+ answers: [
+ {
+ value: null,
+ status: "correct",
+ maxError: 0,
+ simplify: "",
+ strict: false,
+ message: "",
+ },
+ {
+ // This is the actual correct answer
+ value: 0.5,
+ status: "correct",
+ maxError: 0,
+ simplify: "",
+ strict: false,
+ message: "",
+ },
+ ],
+ coefficient: true,
+ };
+
+ const score = scoreNumericInput(
+ {currentValue: "50%"},
+ rubric,
+ mockStrings,
+ );
+
+ expect(score).toHaveBeenAnsweredIncorrectly();
+ });
+
it("converts a percentage input value to a decimal", () => {
const scoringData: PerseusNumericInputScoringData = {
answers: [
diff --git a/packages/perseus/src/widgets/numeric-input/score-numeric-input.ts b/packages/perseus/src/widgets/numeric-input/score-numeric-input.ts
index df12f20ef3..f8d26f72b8 100644
--- a/packages/perseus/src/widgets/numeric-input/score-numeric-input.ts
+++ b/packages/perseus/src/widgets/numeric-input/score-numeric-input.ts
@@ -111,10 +111,7 @@ function scoreNumericInput(
const normalizedAnswerExpected = scoringData.answers
.filter((answer) => answer.status === "correct")
- .every(
- (answer) =>
- answer.value !== undefined && Math.abs(answer.value) <= 1,
- );
+ .every((answer) => answer.value != null && Math.abs(answer.value) <= 1);
// The coefficient is an attribute of the widget
let localValue: string | number = currentValue;
diff --git a/packages/perseus/src/widgets/passage-ref/passage-ref.tsx b/packages/perseus/src/widgets/passage-ref/passage-ref.tsx
index f6f1007621..ed91993a02 100644
--- a/packages/perseus/src/widgets/passage-ref/passage-ref.tsx
+++ b/packages/perseus/src/widgets/passage-ref/passage-ref.tsx
@@ -11,13 +11,14 @@ import {isPassageWidget} from "../passage/utils";
import type {PerseusPassageRefWidgetOptions} from "../../perseus-types";
import type {ChangeFn, Widget, WidgetExports, WidgetProps} from "../../types";
import type {PassageRefPromptJSON} from "../../widget-ai-utils/passage-ref/passage-ref-ai-utils";
+import type {PropsFor} from "@khanacademy/wonder-blocks-core";
const EN_DASH = "\u2013";
type RenderProps = {
passageNumber: PerseusPassageRefWidgetOptions["passageNumber"];
referenceNumber: PerseusPassageRefWidgetOptions["referenceNumber"];
- summaryText: PerseusPassageRefWidgetOptions["summaryText"];
+ summaryText: string;
};
type Props = WidgetProps
;
@@ -25,7 +26,7 @@ type Props = WidgetProps;
type DefaultProps = {
passageNumber: Props["passageNumber"];
referenceNumber: Props["referenceNumber"];
- summaryText: Props["summaryText"];
+ summaryText: string;
};
type State = {
@@ -33,6 +34,10 @@ type State = {
content: string | null | undefined;
};
+0 as any as WidgetProps satisfies PropsFor<
+ typeof PassageRef
+>;
+
class PassageRef extends React.Component implements Widget {
static contextType = PerseusI18nContext;
declare context: React.ContextType;
@@ -175,9 +180,7 @@ export default {
hidden: true,
defaultAlignment: "inline",
widget: PassageRef,
- transform: (
- widgetOptions: PerseusPassageRefWidgetOptions,
- ): RenderProps => ({
+ transform: (widgetOptions: PerseusPassageRefWidgetOptions) => ({
passageNumber: widgetOptions.passageNumber,
referenceNumber: widgetOptions.referenceNumber,
summaryText: widgetOptions.summaryText,
diff --git a/packages/perseus/src/zoom.ts b/packages/perseus/src/zoom.ts
index daca6f9c56..747c4d4c54 100644
--- a/packages/perseus/src/zoom.ts
+++ b/packages/perseus/src/zoom.ts
@@ -265,7 +265,9 @@ ZoomServiceClass.prototype._scrollHandler = function (e: any) {
};
ZoomServiceClass.prototype._keyHandler = function (e: any) {
- if (e.keyCode === 27) {
+ // 27: Esc, 13: Enter, 32: Space
+ const keyCodes = [27, 13, 32];
+ if (keyCodes.includes(e.keyCode)) {
this._activeZoomClose();
}
};
@@ -367,12 +369,14 @@ Zoom.prototype.zoomImage = function () {
img.src = this._targetImage.src;
img.alt = this._targetImage.alt;
+ img.tabIndex = 0;
this.$zoomedImage = $zoomedImage;
};
Zoom.prototype._zoomOriginal = function () {
this.$zoomedImage.addClass("zoom-img").attr("data-action", "zoom-out");
+
$(this._targetImage).css("visibility", "hidden");
this._backdrop = document.createElement("div");