Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lazy-donuts-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/perseus": patch
"@khanacademy/perseus-core": patch
"@khanacademy/perseus-editor": patch
---

Removing Interactive Graph Builder and refactoring tests/storybook to use more standardized generator patterns.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ module.exports = {
"*.cypress.ts",
"*.test.ts",
"*.test.tsx",
"*.testdata.ts",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It made sense to me to add a global override for our test data as it seems reasonable for these files to be exempt, but I'm also happy to remove this and re-add the file-specific override for interactive-graph.testdata.ts.

"*.stories.ts",
"*.stories.tsx",
"**/__testutils__/**",
Expand Down
3 changes: 3 additions & 0 deletions packages/perseus-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,10 @@ export {
generateIGLockedPolygon,
generateIGLockedFunction,
generateIGLockedLabel,
generateIGExponentialGraph,
generateIGAbsoluteValueGraph,
generateInteractiveGraphWidget,
generateInteractiveGraphQuestion,
} from "./utils/generators/interactive-graph-widget-generator";
export {
generateNumericInputOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
generateIGAbsoluteValueGraph,
generateIGAngleGraph,
generateIGCircleGraph,
generateIGExponentialGraph,
generateIGLinearGraph,
generateIGLinearSystemGraph,
generateIGLockedEllipse,
Expand All @@ -19,6 +21,7 @@ import {
generateIGSinusoidGraph,
generateIGTangentGraph,
generateInteractiveGraphOptions,
generateInteractiveGraphQuestion,
generateInteractiveGraphWidget,
} from "./interactive-graph-widget-generator";

Expand Down Expand Up @@ -1284,3 +1287,149 @@ describe("generateIGLockedLabel", () => {
});
});
});

describe("generateIGExponentialGraph", () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to round out the tests for the new graphs while I was in here.

it("builds a default exponential graph", () => {
// Arrange, Act
const exponentialGraph = generateIGExponentialGraph();

// Assert
expect(exponentialGraph).toEqual({
type: "exponential",
});
});

it("builds an exponential graph with all props", () => {
// Arrange, Act
const exponentialGraph = generateIGExponentialGraph({
coords: [
[0, 1],
[1, 3],
],
asymptote: 0,
startCoords: {
coords: [
[0, 1],
[1, 3],
],
asymptote: 0,
},
});

// Assert
expect(exponentialGraph).toEqual({
type: "exponential",
coords: [
[0, 1],
[1, 3],
],
asymptote: 0,
startCoords: {
coords: [
[0, 1],
[1, 3],
],
asymptote: 0,
},
});
});
});

describe("generateIGAbsoluteValueGraph", () => {
it("builds a default absolute-value graph", () => {
// Arrange, Act
const absoluteValueGraph = generateIGAbsoluteValueGraph();

// Assert
expect(absoluteValueGraph).toEqual({
type: "absolute-value",
});
});

it("builds an absolute-value graph with all props", () => {
// Arrange, Act
const absoluteValueGraph = generateIGAbsoluteValueGraph({
coords: [
[0, 0],
[1, 1],
],
startCoords: [
[0, 0],
[1, 1],
],
});

// Assert
expect(absoluteValueGraph).toEqual({
type: "absolute-value",
coords: [
[0, 0],
[1, 1],
],
startCoords: [
[0, 0],
[1, 1],
],
});
});
});

describe("generateInteractiveGraphQuestion", () => {
it("builds a default interactive graph question", () => {
// Arrange, Act
const question = generateInteractiveGraphQuestion();

// Assert
expect(question.content).toBe("[[☃ interactive-graph 1]]");
expect(question.widgets["interactive-graph 1"]).toBeDefined();
expect(question.widgets["interactive-graph 1"].type).toBe(
"interactive-graph",
);
});

it("infers graph type from correct answer type", () => {
// Arrange, Act
const question = generateInteractiveGraphQuestion({
correct: generateIGPointGraph({numPoints: 3}),
});

// Assert
const options = question.widgets["interactive-graph 1"].options;
expect(options.correct.type).toBe("point");
expect(options.graph.type).toBe("point");
});

it("does not override graph when explicitly provided", () => {
// Arrange, Act
const question = generateInteractiveGraphQuestion({
correct: generateIGPointGraph({numPoints: 3}),
graph: generateIGPointGraph({numPoints: 5}),
});

// Assert
const options = question.widgets["interactive-graph 1"].options;
expect(options.graph).toEqual({type: "point", numPoints: 5});
});

it("sets static mode when isStatic is true", () => {
// Arrange, Act
const question = generateInteractiveGraphQuestion({
isStatic: true,
});

// Assert
expect(question.widgets["interactive-graph 1"].static).toBe(true);
});

it("uses custom content when provided", () => {
// Arrange, Act
const question = generateInteractiveGraphQuestion({
content: "Custom content [[☃ interactive-graph 1]]",
});

// Assert
expect(question.content).toBe(
"Custom content [[☃ interactive-graph 1]]",
);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import interactiveGraphWidgetLogic from "../../widgets/interactive-graph";
import {getDefaultFigureForType} from "../get-default-figure-for-type";
import {generateTestPerseusRenderer} from "../test-utils";

import type {
InteractiveGraphWidget,
Expand All @@ -10,8 +11,10 @@ import type {
LockedPointType,
LockedPolygonType,
LockedVectorType,
PerseusGraphTypeAbsoluteValue,
PerseusGraphTypeAngle,
PerseusGraphTypeCircle,
PerseusGraphTypeExponential,
PerseusGraphTypeLinear,
PerseusGraphTypeLinearSystem,
PerseusGraphTypeNone,
Expand All @@ -23,6 +26,7 @@ import type {
PerseusGraphTypeSinusoid,
PerseusGraphTypeTangent,
PerseusInteractiveGraphWidgetOptions,
PerseusRenderer,
} from "../../data-schema";

export function generateInteractiveGraphWidget(
Expand Down Expand Up @@ -51,7 +55,7 @@ export function generateInteractiveGraphOptions(
}

export function generateIGAngleGraph(
options?: Partial<PerseusGraphTypeAngle>,
options?: Partial<Omit<PerseusGraphTypeAngle, "type">>,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just some additional type safety that brings these generators inline with the implementation of the rest of the IG generators.

): PerseusGraphTypeAngle {
return {
type: "angle",
Expand All @@ -60,7 +64,7 @@ export function generateIGAngleGraph(
}

export function generateIGCircleGraph(
options?: Partial<PerseusGraphTypeCircle>,
options?: Partial<Omit<PerseusGraphTypeCircle, "type">>,
): PerseusGraphTypeCircle {
return {
type: "circle",
Expand All @@ -69,7 +73,7 @@ export function generateIGCircleGraph(
}

export function generateIGLinearGraph(
options?: Partial<PerseusGraphTypeLinear>,
options?: Partial<Omit<PerseusGraphTypeLinear, "type">>,
): PerseusGraphTypeLinear {
return {
type: "linear",
Expand All @@ -78,7 +82,7 @@ export function generateIGLinearGraph(
}

export function generateIGLinearSystemGraph(
options?: Partial<PerseusGraphTypeLinearSystem>,
options?: Partial<Omit<PerseusGraphTypeLinearSystem, "type">>,
): PerseusGraphTypeLinearSystem {
return {
type: "linear-system",
Expand Down Expand Up @@ -217,3 +221,48 @@ export function generateIGLockedLabel(
...options,
};
}

export function generateIGExponentialGraph(
options?: Partial<Omit<PerseusGraphTypeExponential, "type">>,
): PerseusGraphTypeExponential {
return {
type: "exponential",
...options,
};
}

export function generateIGAbsoluteValueGraph(
options?: Partial<Omit<PerseusGraphTypeAbsoluteValue, "type">>,
): PerseusGraphTypeAbsoluteValue {
return {
type: "absolute-value",
...options,
};
}

export function generateInteractiveGraphQuestion(
options?: Partial<PerseusInteractiveGraphWidgetOptions> & {
content?: string;
isStatic?: boolean;
},
): PerseusRenderer {
const {content, isStatic, ...widgetOptions} = options ?? {};

// If `correct` is set but `graph` is not, default `graph` to match
// the correct answer's type. For graph types that need shared config
// (like numSides, numPoints, snapTo), callers should pass `graph`
// explicitly.
if (widgetOptions.correct && !widgetOptions.graph) {
widgetOptions.graph = {type: widgetOptions.correct.type};
}

return generateTestPerseusRenderer({
content: content ?? "[[☃ interactive-graph 1]]",
widgets: {
"interactive-graph 1": generateInteractiveGraphWidget({
static: isStatic ?? false,
options: generateInteractiveGraphOptions(widgetOptions),
}),
},
});
}
Loading
Loading