Skip to content

Commit

Permalink
Add "Check answer" buttons to the interactive graph flipbook (#1071)
Browse files Browse the repository at this point in the history
Issue: LEMS-1814

## Test plan:

```
yarn dev
open http://localhost:5173/#flipbook
```

Follow the onscreen instructions to get graph data into the flipbook.

Clicking the "Check answer" button should pop up an alert telling you
if you've answered the question correctly.

Author: benchristel

Reviewers: jeremywiebe, mark-fitzgerald, nedredmond, nishasy

Required Reviewers:

Approved By: jeremywiebe

Checks: ✅ codecov/project, ✅ codecov/patch, ✅ 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), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1071
  • Loading branch information
benchristel authored Mar 13, 2024
1 parent 6196375 commit eb637b3
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 16 deletions.
6 changes: 6 additions & 0 deletions .changeset/fluffy-goats-suffer.md
Original file line number Diff line number Diff line change
@@ -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
68 changes: 54 additions & 14 deletions dev/flipbook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";

Expand Down Expand Up @@ -73,7 +78,9 @@ export function Flipbook() {
Next
</Button>
</View>
{question != null && <QuestionRenderer question={question} />}
{question != null && (
<SideBySideQuestionRenderer question={question} />
)}
</View>
);
}
Expand All @@ -83,30 +90,63 @@ type QuestionRendererProps = {
apiOptions?: APIOptions;
};

function QuestionRenderer({question, apiOptions = {}}: QuestionRendererProps) {
function SideBySideQuestionRenderer({
question,
apiOptions = {},
}: QuestionRendererProps) {
return (
<div
<View
className="framework-perseus"
style={{
flexDirection: "row",
padding: Spacing.xLarge_32,
display: "flex",
gap: Spacing.small_12,
}}
className="framework-perseus"
>
<Renderer
content={question.content}
images={question.images}
widgets={question.widgets}
problemNum={0}
<GradableRenderer
question={question}
apiOptions={{...apiOptions, flags: {mafs: false}}}
/>
<GradableRenderer
question={question}
apiOptions={{...apiOptions, flags: {mafs: {segment: true}}}}
/>
</View>
);
}

function GradableRenderer(props: QuestionRendererProps) {
const {question, apiOptions} = props;
const rendererRef = useRef<Renderer>(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 (
<View style={{alignItems: "flex-start"}}>
<Renderer
ref={rendererRef}
content={question.content}
images={question.images}
widgets={question.widgets}
problemNum={0}
apiOptions={{...apiOptions, flags: {mafs: {segment: true}}}}
apiOptions={{...apiOptions}}
/>
</div>
<Button
onClick={() =>
rendererRef.current &&
// eslint-disable-next-line no-alert
alert(describeScore(rendererRef.current.score()))
}
>
Check answer
</Button>
</View>
);
}
19 changes: 18 additions & 1 deletion packages/perseus/src/__tests__/util.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/perseus/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit eb637b3

Please sign in to comment.