Skip to content

Commit

Permalink
View a locked point with mafs (#1067)
Browse files Browse the repository at this point in the history
## Summary:
The ability to view a locked points was added as part of
[LEMS-1702](https://khanacademy.atlassian.net/browse/LEMS-1702), but now that we've
switched to mafs, we need to implement viewing a point again with mafs.

- Add a layer for locked figures to the mafs graph
- Createa a new component for said locked feature
- Add an interactive graph story to confirm that locked points can be viewed

Issue: https://khanacademy.atlassian.net/browse/LEMS-1702

## Test plan:
- Open http://localhost:6006/?path=/story/perseus-widgets-interactive-graph--segment-with-mafs-and-locked-points
- Confirm that red points are there at (-7, -7) and (2, -5)
- Drag the points of the line segment over the locked points and confirm that
  the blue points sit on top of the red points. (Check the interactive layer
  is on top of the locked layer.)

[LEMS-1702]: https://khanacademy.atlassian.net/browse/LEMS-1702?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

### Story

<img width="917" alt="Screenshot 2024-03-12 at 3 17 13 PM" src="https://github.com/Khan/perseus/assets/13231763/3045a9fe-8b17-4190-b73c-68d72159ae8e">

Author: nishasy

Reviewers: benchristel, nishasy, mark-fitzgerald, jeremywiebe, nedredmond

Required Reviewers:

Approved By: jeremywiebe, nedredmond

Checks: ✅ Upload Coverage, ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Extract i18n strings (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), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1067
  • Loading branch information
nishasy authored Mar 13, 2024
1 parent 881da72 commit 6196375
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 3 deletions.
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 @@ -55,3 +55,36 @@ export const Controlled: StoryComponentType = {
return <InteractiveGraphEditor {...state} onChange={dispatch} />;
},
};

/**
* Example of what the InteractiveGraphEditor experience is when using
* a Mafs-based InteractiveGraph.
*/
export const WithMafs: StoryComponentType = {
render: function Render() {
const reducer = (state, newState) => {
return {
...state,
...newState,
};
};

const [state, dispatch] = React.useReducer(reducer, {
apiOptions: {
flags: {
mafs: {
segment: true,
},
},
},
graph: {
type: "segment",
},
correct: {
type: "segment",
},
});

return <InteractiveGraphEditor {...state} onChange={dispatch} />;
},
};
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([class*='movable-point'])",
);

// 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([class*='movable-point'])",
);

// Assert
expect(points[0]).toHaveStyle({fill: color.red, stroke: color.red});
expect(points[1]).toHaveStyle({fill: color.red, stroke: color.red});
});
});
2 changes: 1 addition & 1 deletion packages/perseus/src/widgets/interactive-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ class InteractiveGraph extends React.Component<Props, State> {
this.sinusoid = null;
// eslint-disable-next-line react/no-string-refs
// @ts-expect-error - TS2339 - Property 'reset' does not exist on type 'ReactInstance'.
this.refs.graph.reset();
this.refs.graph?.reset();
};

setupGraphie: () => void = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {UnreachableCaseError} from "@khanacademy/wonder-stuff-core";
import {Point} from "mafs";
import * as React from "react";

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

type Props = {
lockedFigures: ReadonlyArray<LockedFigure>;
};

const GraphLockedLayer = (props: Props) => {
const {lockedFigures} = props;
return (
<>
{lockedFigures.map((figure, index) => {
switch (figure.type) {
case "point":
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
throw new UnreachableCaseError(figure.type);
})}
</>
);
};

export default GraphLockedLayer;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {View} from "@khanacademy/wonder-blocks-core";
import {Mafs} from "mafs";
import * as React from "react";

import GraphLockedLayer from "./graph-locked-layer";
import {SegmentGraph} from "./graphs";
import {Grid} from "./grid";
import {interactiveGraphReducer} from "./interactive-graph-reducer";
Expand Down Expand Up @@ -94,7 +95,15 @@ export const MafsGraph = React.forwardRef<
width={width}
height={height}
>
{/* Background layer */}
{!legacyGrid && <Grid {...props} />}

{/* Locked layer */}
{props.lockedFigures && (
<GraphLockedLayer lockedFigures={props.lockedFigures} />
)}

{/* Interactive layer */}
{renderGraph({
state,
dispatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
--mafs-bg: transparent;
--mafs-fg: rgb(33, 36, 44); /* WB color.offBlack */

--mafs-line-color: rgb(33, 36, 44, 0.64); /* WB color.offBlack64 */
--grid-line-subdivision-color: rgba(33, 36, 44, 0.16);
--mafs-line-color: rgba(33, 36, 44, 0.16); /* WB color.offBlack16*/

--mafs-blue: #1865f2; /* WB color.blue */
--mafs-red: #d92916; /* WB color.red */
Expand Down

0 comments on commit 6196375

Please sign in to comment.