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

[Locked Figure Labels] View locked function labels #1658

Merged
merged 11 commits into from
Sep 24, 2024
6 changes: 6 additions & 0 deletions .changeset/spotty-days-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": minor
---

[Locked Figure Labels] View locked function labels
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const flags = {
"locked-line-labels": true,
"locked-vector-labels": true,
"locked-ellipse-labels": true,
"locked-function-labels": true,
},
} satisfies APIOptions["flags"];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const MafsWithLockedFiguresCurrent = (): React.ReactElement => {
"locked-line-labels": false,
"locked-vector-labels": false,
"locked-ellipse-labels": false,
"locked-function-labels": false,
},
},
}}
Expand Down Expand Up @@ -185,6 +186,7 @@ export const MafsWithLockedLabelsFlag = (): React.ReactElement => {
"locked-line-labels": false,
"locked-vector-labels": false,
"locked-ellipse-labels": false,
"locked-function-labels": false,
},
},
}}
Expand All @@ -205,6 +207,7 @@ export const MafsWithLockedPointLabelsFlag = (): React.ReactElement => {
"locked-line-labels": false,
"locked-vector-labels": false,
"locked-ellipse-labels": false,
"locked-function-labels": false,
},
},
}}
Expand All @@ -225,6 +228,7 @@ export const MafsWithLockedLineLabelsFlag = (): React.ReactElement => {
"locked-line-labels": true,
"locked-vector-labels": false,
"locked-ellipse-labels": false,
"locked-function-labels": false,
},
},
}}
Expand All @@ -245,6 +249,7 @@ export const MafsWithLockedVectorLabelsFlag = (): React.ReactElement => {
"locked-line-labels": false,
"locked-vector-labels": true,
"locked-ellipse-labels": false,
"locked-function-labels": false,
},
},
}}
Expand All @@ -265,6 +270,7 @@ export const MafsWithLockedEllipseLabelsFlag = (): React.ReactElement => {
"locked-line-labels": false,
"locked-vector-labels": false,
"locked-ellipse-labels": true,
"locked-function-labels": false,
},
},
}}
Expand All @@ -273,6 +279,33 @@ export const MafsWithLockedEllipseLabelsFlag = (): React.ReactElement => {
);
};

export const MafsWithLockedFunctionLabelsFlag = (): React.ReactElement => {
return (
<EditorPageWithStorybookPreview
apiOptions={{
flags: {
mafs: {
...flags.mafs,
"interactive-graph-locked-features-labels": true,
"locked-point-labels": false,
"locked-line-labels": false,
"locked-vector-labels": false,
"locked-ellipse-labels": false,
"locked-function-labels": true,
},
},
}}
question={segmentWithLockedFigures}
/>
);
};

export const MafsWithLockedFigureLabelsAllFlags = (): React.ReactElement => {
return (
<EditorPageWithStorybookPreview question={segmentWithLockedFigures} />
);
};

export const WithSaveWarnings = (): React.ReactElement => {
const [previewDevice, setPreviewDevice] =
React.useState<DeviceType>("phone");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe("getDefaultFigureForType", () => {
strokeStyle: "solid",
equation: "x^2",
directionalAxis: "x",
labels: [],
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function getDefaultFigureForType(type: LockedFigureType): LockedFigure {
strokeStyle: "solid",
equation: "x^2",
directionalAxis: "x",
labels: [],
};
case "label":
return {
Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/perseus-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ export type LockedFunctionType = {
equation: string; // This is the user-defined equation (as it was typed)
directionalAxis: "x" | "y";
domain?: Interval;
labels: LockedLabelType[];
};

// Not associated with a specific figure
Expand Down
5 changes: 5 additions & 0 deletions packages/perseus/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ export const InteractiveGraphLockedFeaturesFlags = [
* updated Interactive Graph widget.
*/
"locked-ellipse-labels",
/**
* Enables/disables the labels associated with locked functions in the
* updated Interactive Graph widget.
*/
"locked-function-labels",
] as const;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export default function GraphLockedLabelsLayer(props: Props) {
figure.type === "vector") ||
// Ellipse flag + ellipse type
(flags?.["mafs"]?.["locked-ellipse-labels"] &&
figure.type === "ellipse")
figure.type === "ellipse") ||
// Function flag + function type
(flags?.["mafs"]?.["locked-function-labels"] &&
figure.type === "function")
) {
return (
<React.Fragment key={i}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1199,17 +1199,19 @@ describe("InteractiveGraphQuestionBuilder", () => {
color: "grayH",
strokeStyle: "solid",
directionalAxis: "x",
labels: [],
},
]);
});

it("adds a locked function with options", () => {
it("adds a locked function with options and minimal label", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
.addLockedFunction("x^2", {
color: "green",
strokeStyle: "dashed",
directionalAxis: "y",
domain: [-5, 5],
labels: [{text: "a label"}],
})
.build();
const graph = question.widgets["interactive-graph 1"];
Expand All @@ -1222,6 +1224,43 @@ describe("InteractiveGraphQuestionBuilder", () => {
strokeStyle: "dashed",
directionalAxis: "y",
domain: [-5, 5],
labels: [
{
type: "label",
text: "a label",
coord: [0, 0],
color: "green",
size: "medium",
},
],
},
]);
});

it("adds a locked function with a specific label", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
.addLockedFunction("x^2", {
labels: [{text: "a label", coord: [9, 9], size: "small"}],
})
.build();
const graph = question.widgets["interactive-graph 1"];

expect(graph.options.lockedFigures).toEqual([
{
type: "function",
equation: "x^2",
color: "grayH",
strokeStyle: "solid",
directionalAxis: "x",
labels: [
{
type: "label",
text: "a label",
coord: [9, 9],
color: "grayH",
size: "small",
},
],
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type LockedFunctionOptions = {
strokeStyle?: LockedLineStyle;
directionalAxis?: "x" | "y";
domain?: Interval;
labels?: LockedFigureLabelOptions[];
};

type LockedFigureLabelOptions = {
Expand Down Expand Up @@ -437,6 +438,17 @@ class InteractiveGraphQuestionBuilder {
strokeStyle: "solid",
directionalAxis: "x",
...options,
labels:
options?.labels?.map(
(label) =>
({
type: "label",
coord: label.coord ?? [0, 0],
text: label.text,
color: options?.color ?? "grayH",
size: label.size ?? "medium",
}) satisfies LockedLabelType,
) ?? [],
};

this.addLockedFigure(lockedFunction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
circleQuestion,
circleQuestionWithDefaultCorrect,
graphWithLabeledEllipse,
graphWithLabeledFunction,
graphWithLabeledLine,
graphWithLabeledPoint,
graphWithLabeledVector,
Expand Down Expand Up @@ -1098,6 +1099,34 @@ describe("locked layer", () => {
});
});

it("should render a locked label within a locked function", async () => {
// Arrange
const {container} = renderQuestion(graphWithLabeledFunction, {
flags: {
mafs: {
segment: true,
"interactive-graph-locked-features-labels": true,
"locked-function-labels": true,
},
},
});

// Act
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const labels = container.querySelectorAll(".locked-label");
const label = labels[0];

// Assert
expect(labels).toHaveLength(1);
expect(label).toHaveTextContent("E");
expect(label).toHaveStyle({
color: lockedFigureColors["grayH"],
fontSize: "16px",
left: "200px",
top: "200px",
});
});

it("should have an aria-label and description if they are provided", async () => {
// Arrange
const {container} = renderQuestion(interactiveGraphWithAriaLabel, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ export const segmentWithLockedFigures: PerseusRenderer =
)
.addLockedFunction("sin(x)", {
color: "red",
labels: [{text: "E"}],
})
.addLockedLabel("\\sqrt{\\frac{1}{2}}", [6, -5])
.build();
Expand Down Expand Up @@ -989,3 +990,10 @@ export const graphWithLabeledEllipse: PerseusRenderer =
labels: [{text: "D"}],
})
.build();

export const graphWithLabeledFunction: PerseusRenderer =
interactiveGraphQuestionBuilder()
.addLockedFunction("sin(x)", {
labels: [{text: "E"}],
})
.build();