Skip to content

Commit 22e7de3

Browse files
authored
Answerless Plotter (#2423)
## Summary: Makes some small adjustments to plotter to set it up for answerless rendering. Was rendering without answers already, so just some type cleanup in the widget itself. Added tests and an answerless Storybook story as well. Issue: LEMS-2977 ## Test plan: - Confirm all checks pass - Confirm widget still acts as expected - No changes should be noted in the user experience Author: Myranae Reviewers: benchristel, handeyeco Required Reviewers: Approved By: benchristel Checks: ✅ 8 checks were successful Pull Request URL: #2423
1 parent e780748 commit 22e7de3

File tree

6 files changed

+126
-3
lines changed

6 files changed

+126
-3
lines changed

.changeset/late-pillows-search.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@khanacademy/perseus": minor
3+
"@khanacademy/perseus-core": minor
4+
---
5+
6+
Update Plotter widget to render with answerless data. Adds test and stories for answerless rendering.

packages/perseus-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export {default as getIFramePublicWidgetOptions} from "./widgets/iframe/iframe-u
156156
export {default as getMatrixPublicWidgetOptions} from "./widgets/matrix/matrix-util";
157157
export type {MatrixPublicWidgetOptions} from "./widgets/matrix/matrix-util";
158158
export {default as getPlotterPublicWidgetOptions} from "./widgets/plotter/plotter-util";
159+
export type {PlotterPublicWidgetOptions} from "./widgets/plotter/plotter-util";
159160
export {
160161
default as getMatcherPublicWidgetOptions,
161162
shuffleMatcher,

packages/perseus-core/src/widgets/plotter/plotter-util.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import type {PerseusPlotterWidgetOptions} from "../../data-schema";
44
* For details on the individual options, see the
55
* PerseusPlotterWidgetOptions type
66
*/
7-
type PlotterPublicWidgetOptions = Omit<PerseusPlotterWidgetOptions, "correct">;
7+
export type PlotterPublicWidgetOptions = Omit<
8+
PerseusPlotterWidgetOptions,
9+
"correct"
10+
>;
811

912
/**
1013
* Given a PerseusPlotterWidgetOptions object, return a new object with only

packages/perseus/src/widgets/plotter/plotter.stories.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ export const Basic: Story = {
1919
item: generateTestPerseusItem({question: question1}),
2020
},
2121
};
22+
23+
export const AnswerlessPlotter: Story = {
24+
args: {
25+
item: generateTestPerseusItem({question: question1}),
26+
startAnswerless: true,
27+
},
28+
};

packages/perseus/src/widgets/plotter/plotter.test.tsx

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import {screen, render, waitFor} from "@testing-library/react";
1+
import {scorePerseusItem} from "@khanacademy/perseus-score";
2+
import {act, render, screen, waitFor} from "@testing-library/react";
23
import React from "react";
34

45
import {testDependencies} from "../../../../../testing/test-dependencies";
56
import * as Dependencies from "../../dependencies";
67
import {ApiOptions} from "../../perseus-api";
8+
import {getAnswerfulItem, getAnswerlessItem} from "../../util/test-utils";
9+
import {renderQuestion} from "../__testutils__/renderQuestion";
710

811
import {Plotter} from "./plotter";
912

13+
import type {PerseusPlotterWidgetOptions} from "@khanacademy/perseus-core";
14+
1015
describe("plotter widget", () => {
1116
beforeEach(() => {
1217
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
@@ -59,4 +64,104 @@ describe("plotter widget", () => {
5964
).not.toBeInTheDocument();
6065
});
6166
});
67+
68+
const plotterOptions: PerseusPlotterWidgetOptions = {
69+
categories: ["$1^{\\text{st}} \\text{}$"],
70+
picBoxHeight: 300,
71+
picSize: 300,
72+
picUrl: "",
73+
plotDimensions: [380, 300],
74+
correct: [15],
75+
labelInterval: 1,
76+
labels: ["School grade", "Number of absent students"],
77+
maxY: 30,
78+
scaleY: 5,
79+
snapsPerLine: 1,
80+
starting: [0],
81+
type: "bar",
82+
};
83+
84+
test("the answerless test data doesn't contain answers", () => {
85+
// Arrange / Act / Assert
86+
expect(
87+
getAnswerlessItem("plotter", plotterOptions).question.widgets[
88+
"plotter 1"
89+
].options.correct,
90+
).toBeUndefined();
91+
});
92+
93+
describe.each([
94+
["answerless", getAnswerlessItem("plotter", plotterOptions)],
95+
["answerful", getAnswerfulItem("plotter", plotterOptions)],
96+
])("given %s options", (_, {question}) => {
97+
it("renders correctly", async () => {
98+
// Arrange / Act
99+
renderQuestion(question);
100+
101+
// Assert
102+
expect(await screen.findByText("School grade")).toBeInTheDocument();
103+
expect(
104+
await screen.findByText("Number of absent students"),
105+
).toBeInTheDocument();
106+
});
107+
108+
it("can given an invalid score", () => {
109+
// Arrange
110+
const {renderer} = renderQuestion(question);
111+
112+
// Act
113+
const userInput = renderer.getUserInputMap();
114+
const score = scorePerseusItem(
115+
getAnswerfulItem("plotter", plotterOptions).question,
116+
userInput,
117+
"en",
118+
);
119+
120+
// Assert
121+
expect(userInput).toEqual({"plotter 1": [0]});
122+
expect(score).toHaveInvalidInput();
123+
});
124+
125+
it("can be answered correctly", () => {
126+
// Arrange
127+
const {renderer} = renderQuestion(question);
128+
129+
// Act
130+
const [plotter] = renderer.findWidgets("plotter 1");
131+
132+
act(() => plotter.setState({values: [15]}));
133+
const userInput = renderer.getUserInputMap();
134+
135+
const score = scorePerseusItem(
136+
getAnswerfulItem("plotter", plotterOptions).question,
137+
userInput,
138+
"en",
139+
);
140+
141+
// Assert
142+
expect(userInput).toEqual({"plotter 1": [15]});
143+
expect(score).toHaveBeenAnsweredCorrectly();
144+
});
145+
146+
it("can be scored incorrectly", () => {
147+
// Arrange
148+
const {renderer} = renderQuestion(question);
149+
150+
// Act
151+
const [plotter] = renderer.findWidgets("plotter 1");
152+
153+
act(() => plotter.setState({values: [7]})); // mock user entering a value
154+
const userInput = renderer.getUserInputMap();
155+
156+
const score = scorePerseusItem(
157+
getAnswerfulItem("plotter", plotterOptions).question,
158+
userInput,
159+
"en",
160+
);
161+
162+
// Assert
163+
expect(userInput).toEqual({"plotter 1": [7]});
164+
expect(score).toHaveBeenAnsweredIncorrectly();
165+
});
166+
});
62167
});

packages/perseus/src/widgets/plotter/plotter.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import type {
1818
PerseusPlotterWidgetOptions,
1919
PerseusPlotterUserInput,
2020
} from "@khanacademy/perseus-core";
21+
import type {PlotterPublicWidgetOptions} from "@khanacademy/perseus-core/src/widgets/plotter/plotter-util";
2122

22-
type RenderProps = PerseusPlotterWidgetOptions;
23+
type RenderProps = PlotterPublicWidgetOptions;
2324

2425
type Props = WidgetProps<RenderProps> & {
2526
labelInterval: NonNullable<PerseusPlotterWidgetOptions["labelInterval"]>;

0 commit comments

Comments
 (0)