From d83d721e96f1583a5b7c17bab1bfdbe216db4803 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 11:52:06 -0600 Subject: [PATCH 1/7] Add, refine, and rename plotter data types --- packages/perseus/src/validation.types.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/perseus/src/validation.types.ts b/packages/perseus/src/validation.types.ts index 68674223c7..e361e533be 100644 --- a/packages/perseus/src/validation.types.ts +++ b/packages/perseus/src/validation.types.ts @@ -185,7 +185,15 @@ export type PerseusOrdererUserInput = { current: ReadonlyArray; }; -export type PerseusPlotterRubric = PerseusPlotterWidgetOptions; +export type PerseusPlotterScoringData = { + // The Y values that represent the correct answer expected + correct: ReadonlyArray; +} & PerseusPlotterValidationData; + +export type PerseusPlotterValidationData = { + // The Y values the graph should start with + starting: ReadonlyArray; +}; export type PerseusPlotterUserInput = ReadonlyArray; @@ -233,7 +241,7 @@ export type Rubric = | PerseusNumberLineRubric | PerseusNumericInputRubric | PerseusOrdererRubric - | PerseusPlotterRubric + | PerseusPlotterScoringData | PerseusRadioRubric | PerseusSorterRubric | PerseusTableRubric; From e1e21998d5b35c3ce726956b6ff7588caa40ab74 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 11:52:55 -0600 Subject: [PATCH 2/7] Split out validation logic and rename rubric --- .../src/widgets/plotter/score-plotter.ts | 16 +++++----- .../src/widgets/plotter/validate-plotter.ts | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 packages/perseus/src/widgets/plotter/validate-plotter.ts diff --git a/packages/perseus/src/widgets/plotter/score-plotter.ts b/packages/perseus/src/widgets/plotter/score-plotter.ts index 0af3432de6..41674387e9 100644 --- a/packages/perseus/src/widgets/plotter/score-plotter.ts +++ b/packages/perseus/src/widgets/plotter/score-plotter.ts @@ -1,8 +1,10 @@ import Util from "../../util"; +import validatePlotter from "./validate-plotter"; + import type {PerseusScore} from "../../types"; import type { - PerseusPlotterRubric, + PerseusPlotterScoringData, PerseusPlotterUserInput, } from "../../validation.types"; @@ -10,17 +12,15 @@ const {deepEq} = Util; function scorePlotter( userInput: PerseusPlotterUserInput, - rubric: PerseusPlotterRubric, + scoringData: PerseusPlotterScoringData, ): PerseusScore { - if (deepEq(userInput, rubric.starting)) { - return { - type: "invalid", - message: null, - }; + const validationError = validatePlotter(userInput, scoringData); + if (validationError) { + return validationError; } return { type: "points", - earned: deepEq(userInput, rubric.correct) ? 1 : 0, + earned: deepEq(userInput, scoringData.correct) ? 1 : 0, total: 1, message: null, }; diff --git a/packages/perseus/src/widgets/plotter/validate-plotter.ts b/packages/perseus/src/widgets/plotter/validate-plotter.ts new file mode 100644 index 0000000000..3755f978fc --- /dev/null +++ b/packages/perseus/src/widgets/plotter/validate-plotter.ts @@ -0,0 +1,31 @@ +import Util from "../../util"; + +import type {PerseusScore} from "../../types"; +import type { + PerseusPlotterUserInput, + PerseusPlotterValidationData, +} from "../../validation.types"; + +const {deepEq} = Util; + +/** + * Checks user input to confirm it is not the same as the starting values for the graph. + * This means the user has modified the graph, and the question can be scored. + * @param userInput + * @param validationData + * @see 'scorePlotter' for more details on scoring. + */ +function validatePlotter( + userInput: PerseusPlotterUserInput, + validationData: PerseusPlotterValidationData, +): Extract | null { + if (deepEq(userInput, validationData.starting)) { + return { + type: "invalid", + message: null, + }; + } + return null; +} + +export default validatePlotter; From d6069302d61976f9d1fc8690056f8e5319049a2b Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 11:54:23 -0600 Subject: [PATCH 3/7] Split out tests for validation Also refactor the scoring function to not need the helper functions and rename rubric. --- .../src/widgets/plotter/score-plotter.test.ts | 63 +++++-------------- .../widgets/plotter/validate-plotter.test.ts | 38 +++++++++++ 2 files changed, 52 insertions(+), 49 deletions(-) create mode 100644 packages/perseus/src/widgets/plotter/validate-plotter.test.ts diff --git a/packages/perseus/src/widgets/plotter/score-plotter.test.ts b/packages/perseus/src/widgets/plotter/score-plotter.test.ts index 381caf1847..96dc8595fc 100644 --- a/packages/perseus/src/widgets/plotter/score-plotter.test.ts +++ b/packages/perseus/src/widgets/plotter/score-plotter.test.ts @@ -1,75 +1,40 @@ import scorePlotter from "./score-plotter"; import type { - PerseusPlotterRubric, + PerseusPlotterScoringData, PerseusPlotterUserInput, } from "../../validation.types"; -const baseRubric: PerseusPlotterRubric = { - categories: [ - "$1^{\\text{st}} \\text{}$", - "$2^{\\text{nd}} \\text{}$", - "$3^{\\text{rd}} \\text{}$", - "$4^{\\text{th}} \\text{}$", - "$5^{\\text{th}} \\text{}$", - ], - picBoxHeight: 300, - picSize: 300, - picUrl: "", - plotDimensions: [0, 0], - correct: [15, 25, 5, 10, 10], - labelInterval: 1, - labels: ["School grade", "Number of absent students"], - maxY: 30, - scaleY: 5, - snapsPerLine: 1, - starting: [0, 0, 0, 0, 0], - type: "bar", -}; - -function generateRubric( - extend?: Partial, -): PerseusPlotterRubric { - return {...baseRubric, ...extend}; -} - describe("scorePlotter", () => { - it("is invalid if the start and end are the same", () => { - // Arrange - const rubric = generateRubric(); - - const userInput: PerseusPlotterUserInput = rubric.starting; - - // Act - const result = scorePlotter(userInput, rubric); - - // Assert - expect(result).toHaveInvalidInput(); - }); - it("can be answered correctly", () => { // Arrange - const rubric = generateRubric(); + const scoringData: PerseusPlotterScoringData = { + correct: [15, 25, 5, 10, 10], + starting: [0, 0, 0, 0, 0], + }; - const userInput: PerseusPlotterUserInput = rubric.correct; + const userInput: PerseusPlotterUserInput = scoringData.correct; // Act - const result = scorePlotter(userInput, rubric); + const score = scorePlotter(userInput, scoringData); // Assert - expect(result).toHaveBeenAnsweredCorrectly(); + expect(score).toHaveBeenAnsweredCorrectly(); }); it("can be answered incorrectly", () => { // Arrange - const rubric = generateRubric(); + const scoringData: PerseusPlotterScoringData = { + correct: [15, 25, 5, 10, 10], + starting: [0, 0, 0, 0, 0], + }; const userInput: PerseusPlotterUserInput = [8, 6, 7, 5, 3, 0, 9]; // Act - const result = scorePlotter(userInput, rubric); + const score = scorePlotter(userInput, scoringData); // Assert - expect(result).toHaveBeenAnsweredIncorrectly(); + expect(score).toHaveBeenAnsweredIncorrectly(); }); }); diff --git a/packages/perseus/src/widgets/plotter/validate-plotter.test.ts b/packages/perseus/src/widgets/plotter/validate-plotter.test.ts new file mode 100644 index 0000000000..a4c298f351 --- /dev/null +++ b/packages/perseus/src/widgets/plotter/validate-plotter.test.ts @@ -0,0 +1,38 @@ +import validatePlotter from "./validate-plotter"; + +import type { + PerseusPlotterUserInput, + PerseusPlotterValidationData, +} from "../../validation.types"; + +describe("validatePlotter", () => { + it("is invalid if the start and end are the same", () => { + // Arrange + const validationData: PerseusPlotterValidationData = { + starting: [0, 0, 0, 0, 0], + }; + + const userInput: PerseusPlotterUserInput = validationData.starting; + + // Act + const validationError = validatePlotter(userInput, validationData); + + // Assert + expect(validationError).toHaveInvalidInput(); + }); + + it("returns null if the start and end are not the same and the user has modified the graph", () => { + // Arrange + const validationData: PerseusPlotterValidationData = { + starting: [0, 0, 0, 0, 0], + }; + + const userInput: PerseusPlotterUserInput = [0, 1, 2, 3, 4]; + + // Act + const validationError = validatePlotter(userInput, validationData); + + // Assert + expect(validationError).toBeNull(); + }); +}); From 04ac86ead36963b7d2211223ae0a26617e7c9b0e Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 11:55:23 -0600 Subject: [PATCH 4/7] Rename rubric in plotter --- packages/perseus/src/widgets/plotter/plotter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/perseus/src/widgets/plotter/plotter.tsx b/packages/perseus/src/widgets/plotter/plotter.tsx index a0b1178d6c..8e985698ac 100644 --- a/packages/perseus/src/widgets/plotter/plotter.tsx +++ b/packages/perseus/src/widgets/plotter/plotter.tsx @@ -18,14 +18,14 @@ import scorePlotter from "./score-plotter"; import type {PerseusPlotterWidgetOptions} from "../../perseus-types"; import type {Widget, WidgetExports, WidgetProps} from "../../types"; import type { - PerseusPlotterRubric, + PerseusPlotterScoringData, PerseusPlotterUserInput, } from "../../validation.types"; import type {UnsupportedWidgetPromptJSON} from "../../widget-ai-utils/unsupported-widget"; type RenderProps = PerseusPlotterWidgetOptions; -type Props = WidgetProps & { +type Props = WidgetProps & { labelInterval: NonNullable; picSize: NonNullable; }; From 58fe8b226334806b4db778654586c6b680dfd077 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 11:55:29 -0600 Subject: [PATCH 5/7] Add changeset --- .changeset/nice-fans-swim.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-fans-swim.md diff --git a/.changeset/nice-fans-swim.md b/.changeset/nice-fans-swim.md new file mode 100644 index 0000000000..04c7fe08ce --- /dev/null +++ b/.changeset/nice-fans-swim.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +Introduces a validation function for the plotter widget (extracted from the scoring function). From 7d128565fb9de54da94d6cfaaf4f72aadaf2e2d0 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 12:04:34 -0600 Subject: [PATCH 6/7] Remove unneeded import --- packages/perseus/src/validation.types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/perseus/src/validation.types.ts b/packages/perseus/src/validation.types.ts index e361e533be..924df38960 100644 --- a/packages/perseus/src/validation.types.ts +++ b/packages/perseus/src/validation.types.ts @@ -40,7 +40,6 @@ import type { PerseusNumberLineWidgetOptions, PerseusNumericInputAnswer, PerseusOrdererWidgetOptions, - PerseusPlotterWidgetOptions, PerseusRadioChoice, PerseusGraphCorrectType, } from "./perseus-types"; From c75de6df23bf3ded7303ffd1cc2bd77f78d55ed2 Mon Sep 17 00:00:00 2001 From: Tamara Date: Fri, 22 Nov 2024 09:24:21 -0600 Subject: [PATCH 7/7] Remove unnecessary part of doc --- packages/perseus/src/widgets/plotter/validate-plotter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/perseus/src/widgets/plotter/validate-plotter.ts b/packages/perseus/src/widgets/plotter/validate-plotter.ts index 3755f978fc..0904ba8f8e 100644 --- a/packages/perseus/src/widgets/plotter/validate-plotter.ts +++ b/packages/perseus/src/widgets/plotter/validate-plotter.ts @@ -11,8 +11,7 @@ const {deepEq} = Util; /** * Checks user input to confirm it is not the same as the starting values for the graph. * This means the user has modified the graph, and the question can be scored. - * @param userInput - * @param validationData + * * @see 'scorePlotter' for more details on scoring. */ function validatePlotter(