diff --git a/.changeset/fluffy-goats-suffer.md b/.changeset/fluffy-goats-suffer.md
new file mode 100644
index 0000000000..f4cff2fd69
--- /dev/null
+++ b/.changeset/fluffy-goats-suffer.md
@@ -0,0 +1,6 @@
+---
+"@khanacademy/perseus-dev-ui": minor
+"@khanacademy/perseus": patch
+---
+
+Add a "check answer" button to interactive graphs displayed in the dev flipbook UI
diff --git a/dev/flipbook.tsx b/dev/flipbook.tsx
index be2ffafcdb..f8f0a8e583 100644
--- a/dev/flipbook.tsx
+++ b/dev/flipbook.tsx
@@ -4,9 +4,10 @@ import {View} from "@khanacademy/wonder-blocks-core";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import Spacing from "@khanacademy/wonder-blocks-spacing";
import * as React from "react";
-import {useReducer} from "react";
+import {useReducer, useRef} from "react";
import {Renderer} from "../packages/perseus/src";
+import {isCorrect} from "../packages/perseus/src/util";
import {
flipbookModelReducer,
@@ -16,7 +17,11 @@ import {
setQuestions,
} from "./flipbook-model";
-import type {APIOptions, PerseusRenderer} from "../packages/perseus/src";
+import type {
+ APIOptions,
+ PerseusRenderer,
+ PerseusScore,
+} from "../packages/perseus/src";
import "../packages/perseus/src/styles/perseus-renderer.less";
@@ -73,7 +78,9 @@ export function Flipbook() {
Next
- {question != null && }
+ {question != null && (
+
+ )}
);
}
@@ -83,30 +90,63 @@ type QuestionRendererProps = {
apiOptions?: APIOptions;
};
-function QuestionRenderer({question, apiOptions = {}}: QuestionRendererProps) {
+function SideBySideQuestionRenderer({
+ question,
+ apiOptions = {},
+}: QuestionRendererProps) {
return (
-
-
+
+
+ );
+}
+
+function GradableRenderer(props: QuestionRendererProps) {
+ const {question, apiOptions} = props;
+ const rendererRef = useRef(null);
+
+ function describeScore(score: PerseusScore): string {
+ switch (score.type) {
+ case "invalid":
+ return "You didn't answer the question.";
+ case "points":
+ return isCorrect(score) ? "Correct!" : "Incorrect.";
+ }
+ }
+
+ return (
+
-
+
+
);
}
diff --git a/packages/perseus/src/__tests__/util.test.ts b/packages/perseus/src/__tests__/util.test.ts
index 88e83f0255..1845b58c05 100644
--- a/packages/perseus/src/__tests__/util.test.ts
+++ b/packages/perseus/src/__tests__/util.test.ts
@@ -1,4 +1,21 @@
-import Util from "../util";
+import Util, {isCorrect} from "../util";
+
+describe("isCorrect", () => {
+ it("is true given a score with all points earned", () => {
+ const score = {type: "points", earned: 3, total: 3} as const;
+ expect(isCorrect(score)).toBe(true);
+ });
+
+ it("is false given a score with some points unearned", () => {
+ const score = {type: "points", earned: 2, total: 3} as const;
+ expect(isCorrect(score)).toBe(false);
+ });
+
+ it("is false given an unanswered / invalid score", () => {
+ const score = {type: "invalid"} as const;
+ expect(isCorrect(score)).toBe(false);
+ });
+});
describe("#constrainedTickStepsFromTickSteps", () => {
it("should not changes the tick steps if there are fewer than (or exactly) 10 steps", () => {
diff --git a/packages/perseus/src/util.ts b/packages/perseus/src/util.ts
index 07fe7b9990..6188ff56d9 100644
--- a/packages/perseus/src/util.ts
+++ b/packages/perseus/src/util.ts
@@ -262,6 +262,10 @@ function combineScores(
);
}
+export function isCorrect(score: PerseusScore): boolean {
+ return score.type === "points" && score.earned >= score.total;
+}
+
function keScoreFromPerseusScore(
score: PerseusScore,
guess: any,
@@ -270,7 +274,7 @@ function keScoreFromPerseusScore(
if (score.type === "points") {
return {
empty: false,
- correct: score.earned >= score.total,
+ correct: isCorrect(score),
message: score.message,
guess: guess,
state: state,