From c7184844bcad9fbf0d9afabec8aec8b05d8d8b18 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 10:08:44 -0600 Subject: [PATCH 1/4] Add changeset --- .changeset/famous-horses-grab.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-horses-grab.md diff --git a/.changeset/famous-horses-grab.md b/.changeset/famous-horses-grab.md new file mode 100644 index 0000000000..827d88abf1 --- /dev/null +++ b/.changeset/famous-horses-grab.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +Introduces a validation function for the dropdown widget (extracted from dropdown scoring function). From 8eef0087896c25b39d1946359b571031a267f028 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 10:09:36 -0600 Subject: [PATCH 2/4] Split validation test and add null test --- .../widgets/dropdown/score-dropdown.test.ts | 24 +++----------- .../dropdown/validate-dropdown.test.ts | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 packages/perseus/src/widgets/dropdown/validate-dropdown.test.ts diff --git a/packages/perseus/src/widgets/dropdown/score-dropdown.test.ts b/packages/perseus/src/widgets/dropdown/score-dropdown.test.ts index 68030226b6..d583aba2fa 100644 --- a/packages/perseus/src/widgets/dropdown/score-dropdown.test.ts +++ b/packages/perseus/src/widgets/dropdown/score-dropdown.test.ts @@ -7,22 +7,6 @@ import type { } from "../../validation.types"; describe("scoreDropdown", () => { - it("returns invalid for user input of 0", () => { - // Arrange - const userInput: PerseusDropdownUserInput = { - value: 0, - }; - const rubric: PerseusDropdownRubric = { - choices: question1.widgets["dropdown 1"].options.choices, - }; - - // Act - const result = scoreDropdown(userInput, rubric); - - // Assert - expect(result).toHaveInvalidInput(); - }); - it("returns 0 points for incorrect answer", () => { // Arrange const userInput: PerseusDropdownUserInput = { @@ -33,10 +17,10 @@ describe("scoreDropdown", () => { }; // Act - const result = scoreDropdown(userInput, rubric); + const score = scoreDropdown(userInput, rubric); // Assert - expect(result).toHaveBeenAnsweredIncorrectly(); + expect(score).toHaveBeenAnsweredIncorrectly(); }); it("returns 1 point for correct answer", () => { @@ -49,9 +33,9 @@ describe("scoreDropdown", () => { }; // Act - const result = scoreDropdown(userInput, rubric); + const score = scoreDropdown(userInput, rubric); // Assert - expect(result).toHaveBeenAnsweredCorrectly(); + expect(score).toHaveBeenAnsweredCorrectly(); }); }); diff --git a/packages/perseus/src/widgets/dropdown/validate-dropdown.test.ts b/packages/perseus/src/widgets/dropdown/validate-dropdown.test.ts new file mode 100644 index 0000000000..5a457ef7f4 --- /dev/null +++ b/packages/perseus/src/widgets/dropdown/validate-dropdown.test.ts @@ -0,0 +1,31 @@ +import validateDropdown from "./validate-dropdown"; + +import type {PerseusDropdownUserInput} from "../../validation.types"; + +describe("validateDropdown", () => { + it("returns invalid for invalid input (user input of 0)", () => { + // Arrange + const userInput: PerseusDropdownUserInput = { + value: 0, + }; + + // Act + const validationError = validateDropdown(userInput); + + // Assert + expect(validationError).toHaveInvalidInput(); + }); + + it("returns null for a valid answer (user input that is not 0)", () => { + // Arrange + const userInput: PerseusDropdownUserInput = { + value: 2, + }; + + // Act + const validationError = validateDropdown(userInput); + + // Assert + expect(validationError).toBeNull(); + }); +}); From ef22f34db9d07f0a33143278d63ad22183e903b8 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 10:09:52 -0600 Subject: [PATCH 3/4] Split out validation logic to own function --- .../src/widgets/dropdown/score-dropdown.ts | 13 ++++++------ .../src/widgets/dropdown/validate-dropdown.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 packages/perseus/src/widgets/dropdown/validate-dropdown.ts diff --git a/packages/perseus/src/widgets/dropdown/score-dropdown.ts b/packages/perseus/src/widgets/dropdown/score-dropdown.ts index 129a7857f0..76ec2f96b1 100644 --- a/packages/perseus/src/widgets/dropdown/score-dropdown.ts +++ b/packages/perseus/src/widgets/dropdown/score-dropdown.ts @@ -1,3 +1,5 @@ +import validateDropdown from "./validate-dropdown"; + import type {PerseusScore} from "../../types"; import type { PerseusDropdownRubric, @@ -8,14 +10,11 @@ function scoreDropdown( userInput: PerseusDropdownUserInput, rubric: PerseusDropdownRubric, ): PerseusScore { - const selected = userInput.value; - if (selected === 0) { - return { - type: "invalid", - message: null, - }; + const validationError = validateDropdown(userInput); + if (validationError) { + return validationError; } - const correct = rubric.choices[selected - 1].correct; + const correct = rubric.choices[userInput.value - 1].correct; return { type: "points", earned: correct ? 1 : 0, diff --git a/packages/perseus/src/widgets/dropdown/validate-dropdown.ts b/packages/perseus/src/widgets/dropdown/validate-dropdown.ts new file mode 100644 index 0000000000..cc4c88f386 --- /dev/null +++ b/packages/perseus/src/widgets/dropdown/validate-dropdown.ts @@ -0,0 +1,21 @@ +import type {PerseusScore} from "../../types"; +import type {PerseusDropdownUserInput} from "../../validation.types"; + +/** + * Checks if the user has selected an item from the dropdown before scoring. + * @param userInput + * @see `scoreDropdown` for details on scoring + */ +function validateDropdown( + userInput: PerseusDropdownUserInput, +): Extract | null { + if (userInput.value === 0) { + return { + type: "invalid", + message: null, + }; + } + return null; +} + +export default validateDropdown; From d5bb04c211e321bb34d9a9085514a7f94e844222 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 21 Nov 2024 10:26:04 -0600 Subject: [PATCH 4/4] Clarify valid state in doc comment --- packages/perseus/src/widgets/dropdown/validate-dropdown.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/perseus/src/widgets/dropdown/validate-dropdown.ts b/packages/perseus/src/widgets/dropdown/validate-dropdown.ts index cc4c88f386..bbd08a7868 100644 --- a/packages/perseus/src/widgets/dropdown/validate-dropdown.ts +++ b/packages/perseus/src/widgets/dropdown/validate-dropdown.ts @@ -3,6 +3,7 @@ import type {PerseusDropdownUserInput} from "../../validation.types"; /** * Checks if the user has selected an item from the dropdown before scoring. + * This is shown with a userInput value / index other than 0. * @param userInput * @see `scoreDropdown` for details on scoring */