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,