Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

View a locked point with mafs #1067

Merged
merged 13 commits into from
Mar 13, 2024
5 changes: 5 additions & 0 deletions .changeset/shaggy-parrots-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": minor
---

Can pass a point into `lockedFigures` into MafsGraph and a point will be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
polygonQuestion,
rayQuestion,
segmentQuestion,
segmentWithLockedPointsQuestion,
sinusoidQuestion,
} from "../__testdata__/interactive-graph.testdata";

Expand Down Expand Up @@ -56,6 +57,21 @@ export const Segment = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI question={segmentQuestion} />
);

export const SegmentWithMafsAndLockedPoints = (
args: StoryArgs,
): React.ReactElement => (
<RendererWithDebugUI
apiOptions={{
flags: {
mafs: {
segment: true,
},
},
}}
question={segmentWithLockedPointsQuestion}
/>
);

export const Sinusoid = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI question={sinusoidQuestion} />
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {color} from "@khanacademy/wonder-blocks-tokens";

import type {Coord} from "../../interactive2/types";
import type {PerseusRenderer} from "../../perseus-types";

Expand Down Expand Up @@ -480,6 +482,61 @@ export const segmentQuestion: PerseusRenderer = {
},
};

export const segmentWithLockedPointsQuestion: PerseusRenderer = {
content:
"Line segment $\\overline{OG}$ is rotated $180^\\circ$ about the point $(-2,4)$. \n\n**Draw the image of this rotation using the interactive graph.**\n\n*The direction of a rotation by a positive angle is counter-clockwise.* \n\n[[☃ interactive-graph 1]]\n\n",
images: {},
widgets: {
"interactive-graph 1": {
graded: true,
options: {
correct: {
coords: [
[
[-7, -7],
[2, -5],
],
],
type: "segment",
},
graph: {
type: "segment",
},
gridStep: [1, 1],
labels: ["x", "y"],
markings: "graph",
range: [
[-10, 10],
[-10, 10],
],
rulerLabel: "",
rulerTicks: 10,
showProtractor: false,
showRuler: false,
snapStep: [0.5, 0.5],
step: [1, 1],
lockedFigures: [
{
type: "point",
coord: [-7, -7],
style: {stroke: color.red, fill: color.red},
},
{
type: "point",
coord: [2, -5],
style: {stroke: color.red, fill: color.red},
},
],
},
type: "interactive-graph",
version: {
major: 0,
minor: 0,
},
},
},
};

export const segmentQuestionDefaultCorrect: PerseusRenderer = {
content:
"Line segment $\\overline{OG}$ is rotated $180^\\circ$ about the point $(-2,4)$. \n\n**Draw the image of this rotation using the interactive graph.**\n\n*The direction of a rotation by a positive angle is counter-clockwise.* \n\n[[☃ interactive-graph 1]]\n\n",
Expand Down
42 changes: 42 additions & 0 deletions packages/perseus/src/widgets/__tests__/interactive-graph.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {describe, beforeEach, it} from "@jest/globals";
import {color} from "@khanacademy/wonder-blocks-tokens";
import {waitFor} from "@testing-library/react";
import {userEvent as userEventLib} from "@testing-library/user-event";

Expand All @@ -9,6 +10,7 @@ import {ApiOptions} from "../../perseus-api";
import {
questionsAndAnswers,
segmentQuestion,
segmentWithLockedPointsQuestion,
segmentQuestionDefaultCorrect,
} from "../__testdata__/interactive-graph.testdata";

Expand Down Expand Up @@ -178,3 +180,43 @@ describe("segment graph", () => {
});
});
});

describe("locked layer", () => {
const apiOptions = {flags: {mafs: {segment: true}}};
it("should render locked points", async () => {
// Arrange
const {container} = renderQuestion(
segmentWithLockedPointsQuestion,
apiOptions,
);

// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const points = container.querySelectorAll(
// Filter out the interactive points' circles
"circle:not(.mafs-movable-point):not(.mafs-movable-point-hitbox):not(.mafs-movable-point-focus):not(.mafs-movable-point-ring):not(.mafs-movable-point-point)",
);
nishasy marked this conversation as resolved.
Show resolved Hide resolved

// Act

// Assert
expect(points).toHaveLength(2);
});

test("should render locked points with styles", async () => {
// Arrange
const {container} = renderQuestion(
segmentWithLockedPointsQuestion,
apiOptions,
);

// Act
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const points = container.querySelectorAll(
"circle:not(.mafs-movable-point):not(.mafs-movable-point-hitbox):not(.mafs-movable-point-focus):not(.mafs-movable-point-ring):not(.mafs-movable-point-point)",
);

// Assert
expect(points[0]).toHaveStyle({fill: color.red, stroke: color.red});
expect(points[1]).toHaveStyle({fill: color.red, stroke: color.red});
});
});
13 changes: 13 additions & 0 deletions packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Grid} from "./grid";
import {interactiveGraphReducer} from "./interactive-graph-reducer";
import {initializeGraphState} from "./interactive-graph-state";
import {getLegacyGrid} from "./legacy-grid";
import MafsLockedLayer from "./mafs-locked-layer";

import type {InteractiveGraphAction} from "./interactive-graph-action";
import type {InteractiveGraphState} from "./interactive-graph-state";
Expand Down Expand Up @@ -94,7 +95,19 @@ export const MafsGraph = React.forwardRef<
width={width}
height={height}
>
{/* Background layer */}
{!legacyGrid && <Grid {...props} />}

{/* Locked layer */}
{props.lockedFigures && (
<MafsLockedLayer
// Make a copy of lockedFigures since the prop
// type is readonly.
nishasy marked this conversation as resolved.
Show resolved Hide resolved
lockedFigures={[...props.lockedFigures]}
/>
)}

{/* Interactive layer */}
{renderGraph({
state,
dispatch,
Expand Down
nishasy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Point} from "mafs";
import * as React from "react";

import type {LockedFigure} from "../../perseus-types";

type Props = {
lockedFigures: Array<LockedFigure>;
nishasy marked this conversation as resolved.
Show resolved Hide resolved
};

const MafsLockedLayer = (props: Props) => {
const {lockedFigures} = props;
return (
<>
{lockedFigures.map((figure, index) => {
if (figure.type === "point") {
nishasy marked this conversation as resolved.
Show resolved Hide resolved
const [x, y] = figure.coord;
return (
<Point
key={`${figure.type}-${index}`}
x={x}
y={y}
svgCircleProps={{style: figure.style}}
/>
);
}

/**
* Devlopment-time future-proofing: This `never` should
* fail during type-checking if we add a new locked
* shape type and forget to handle it in any other
* switch case here.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const exhaustiveCheck: never = figure.type;
throw new Error(`Unknown locked shape type: ${figure.type}`);

Check warning on line 35 in packages/perseus/src/widgets/interactive-graphs/mafs-locked-layer.tsx

View check run for this annotation

Codecov / codecov/patch

packages/perseus/src/widgets/interactive-graphs/mafs-locked-layer.tsx#L26-L35

Added lines #L26 - L35 were not covered by tests
nishasy marked this conversation as resolved.
Show resolved Hide resolved
})}
</>
);
};

export default MafsLockedLayer;
Loading