diff --git a/.changeset/weak-pandas-watch.md b/.changeset/weak-pandas-watch.md new file mode 100644 index 0000000000..1bba8547a6 --- /dev/null +++ b/.changeset/weak-pandas-watch.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/perseus": patch +"@khanacademy/perseus-editor": patch +--- + +Fixing open polygon scoring issues within exercises and editors. diff --git a/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx b/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx index 51137cfd45..81594258f5 100644 --- a/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx +++ b/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx @@ -22,6 +22,7 @@ import { segmentWithStartingCoordsQuestion, segmentsWithStartingCoordsQuestion, sinusoidWithStartingCoordsQuestion, + unlimitedPolygonWithStartingCoordsQuestion, } from "../../../perseus/src/widgets/interactive-graphs/interactive-graph.testdata"; import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing"; @@ -125,6 +126,14 @@ export const InteractiveGraphPolygon = (): React.ReactElement => { ); }; +export const InteractiveGraphUnlimitedPolygon = (): React.ReactElement => { + return ( + + ); +}; + export const InteractiveGraphAngle = (): React.ReactElement => { return ( { } } + // Do not save a unlimited polygon that is open (coords is null). + if ( + this.props.graph?.type === "polygon" && + this.props.graph.numSides === "unlimited" && + this.props.graph.coords === null + ) { + issues.push("Polygon must be closed."); + } + return issues; }; diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/polygon.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/polygon.tsx index 18ec8c1ff6..04b663de0b 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/polygon.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/polygon.tsx @@ -101,6 +101,15 @@ const PolygonGraph = (props: Props) => { } }, [props.graphState.focusedPointIndex, pointsRef]); + // If the unlimited polygon is rendered with 3 or more coordinates + // Close the polygon, but only on first render. + React.useEffect(() => { + if (numSides === "unlimited" && props.graphState.coords.length > 2) { + dispatch(actions.polygon.closePolygon()); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const statefulProps: StatefulProps = { ...props, graphConfig, diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts index a2f5e97e07..264e69e048 100644 --- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts +++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts @@ -193,6 +193,19 @@ export const polygonWithStartingCoordsQuestion: PerseusRenderer = }) .build(); +export const unlimitedPolygonWithStartingCoordsQuestion: PerseusRenderer = + interactiveGraphQuestionBuilder() + .withPolygon("grid", { + numSides: "unlimited", + coords: [ + [-4.5, -6], + [4.5, -5], + [3.5, 0.5], + [-4.5, 0], + ], + }) + .build(); + export const polygonWithAnglesQuestion: PerseusRenderer = interactiveGraphQuestionBuilder() .withContent( diff --git a/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.test.ts b/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.test.ts index f446a3cb39..25b16a928d 100644 --- a/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.test.ts +++ b/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.test.ts @@ -22,6 +22,26 @@ const defaultAngleState: InteractiveGraphState = { allowReflexAngles: false, }; +const defaultUnlimitedPolygonState: InteractiveGraphState = { + type: "polygon", + closedPolygon: false, + focusedPointIndex: 0, + coords: [[5, 0]], + numSides: "unlimited", + hasBeenInteractedWith: true, + showRemovePointButton: false, + showKeyboardInteractionInvitation: false, + interactionMode: "mouse", + range: [ + [-10, 10], + [-10, 10], + ], + snapStep: [1, 1], + snapTo: "grid", + showSides: true, + showAngles: true, +}; + describe("getGradableGraph", () => { /** * Originally `getGradableGraph` was returning a PerseusGraphType with just a @@ -117,4 +137,30 @@ describe("getGradableGraph", () => { [-5, -5], ]); }); + + it("returns null coordinates if the unlimited polygon graph is open", () => { + const state: InteractiveGraphState = { + ...defaultUnlimitedPolygonState, + closedPolygon: false, + }; + const initialGraph: PerseusGraphType = { + type: "polygon", + }; + const result = getGradableGraph(state, initialGraph); + invariant(result.type === "polygon"); + expect(result.coords).toEqual(null); + }); + + it("returns coordinates if the unlimited polygon graph is closed", () => { + const state: InteractiveGraphState = { + ...defaultUnlimitedPolygonState, + closedPolygon: true, + }; + const initialGraph: PerseusGraphType = { + type: "polygon", + }; + const result = getGradableGraph(state, initialGraph); + invariant(result.type === "polygon"); + expect(result.coords).toEqual([[5, 0]]); + }); }); diff --git a/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.ts b/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.ts index 8433cbd8b1..0abae73773 100644 --- a/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.ts +++ b/packages/perseus/src/widgets/interactive-graphs/reducer/interactive-graph-state.ts @@ -44,6 +44,14 @@ export function getGradableGraph( } if (state.type === "polygon" && initialGraph.type === "polygon") { + // Unless the polygon is closed it is not considered score-able. + if (state.numSides === "unlimited" && !state.closedPolygon) { + return { + ...initialGraph, + coords: null, + }; + } + return { ...initialGraph, coords: state.coords,