Skip to content

Commit

Permalink
Dropdown: Extract validation out of scoring (#1898)
Browse files Browse the repository at this point in the history
## Summary:
To complete server-side scoring, we are separating out validation logic from scoring logic. This PR separates that logic and updates associated tests.

Issue: LEMS-2597

## Test plan:
- Confirm all checks pass
- Confirm widget still works as expected

Author: Myranae

Reviewers: Myranae, jeremywiebe, handeyeco

Required Reviewers:

Approved By: jeremywiebe

Checks: ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1898
  • Loading branch information
Myranae authored Nov 26, 2024
1 parent 0bd4270 commit 3a9b592
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-horses-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": minor
---

Introduces a validation function for the dropdown widget (extracted from dropdown scoring function).
24 changes: 4 additions & 20 deletions packages/perseus/src/widgets/dropdown/score-dropdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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", () => {
Expand All @@ -49,9 +33,9 @@ describe("scoreDropdown", () => {
};

// Act
const result = scoreDropdown(userInput, rubric);
const score = scoreDropdown(userInput, rubric);

// Assert
expect(result).toHaveBeenAnsweredCorrectly();
expect(score).toHaveBeenAnsweredCorrectly();
});
});
13 changes: 6 additions & 7 deletions packages/perseus/src/widgets/dropdown/score-dropdown.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import validateDropdown from "./validate-dropdown";

import type {PerseusScore} from "../../types";
import type {
PerseusDropdownRubric,
Expand All @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions packages/perseus/src/widgets/dropdown/validate-dropdown.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
20 changes: 20 additions & 0 deletions packages/perseus/src/widgets/dropdown/validate-dropdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {PerseusScore} from "../../types";
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.
*/
function validateDropdown(
userInput: PerseusDropdownUserInput,
): Extract<PerseusScore, {type: "invalid"}> | null {
if (userInput.value === 0) {
return {
type: "invalid",
message: null,
};
}
return null;
}

export default validateDropdown;

0 comments on commit 3a9b592

Please sign in to comment.