-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Categorizer: Extract validation out of scoring #1862
base: main
Are you sure you want to change the base?
Changes from 4 commits
e6f0b85
4e1f7b2
e2552af
52e6aad
28766a3
27c222e
c77806a
539b8cc
109df1b
261fcf1
9437cfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@khanacademy/perseus": minor | ||
--- | ||
|
||
Split out validation function for the `categorizer` widget. This can be used to check if the user selected an answer for every row, confirming the question is ready to be scored. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,3 +1,5 @@ | ||||||||||||||||||
import validateCategorizer from "./validate-categorizer"; | ||||||||||||||||||
|
||||||||||||||||||
import type {PerseusStrings} from "../../strings"; | ||||||||||||||||||
import type {PerseusScore} from "../../types"; | ||||||||||||||||||
import type { | ||||||||||||||||||
|
@@ -10,22 +12,17 @@ function scoreCategorizer( | |||||||||||||||||
rubric: PerseusCategorizerRubric, | ||||||||||||||||||
strings: PerseusStrings, | ||||||||||||||||||
): PerseusScore { | ||||||||||||||||||
let completed = true; | ||||||||||||||||||
const validationResult = validateCategorizer(userInput, rubric, strings); | ||||||||||||||||||
if (validationResult) { | ||||||||||||||||||
return validationResult; | ||||||||||||||||||
} | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this might make things more clear
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated! We might want to update the language in the table widget to be consistent with this if this is preferred. |
||||||||||||||||||
|
||||||||||||||||||
let allCorrect = true; | ||||||||||||||||||
rubric.values.forEach((value, i) => { | ||||||||||||||||||
if (userInput.values[i] == null) { | ||||||||||||||||||
completed = false; | ||||||||||||||||||
} | ||||||||||||||||||
if (userInput.values[i] !== value) { | ||||||||||||||||||
allCorrect = false; | ||||||||||||||||||
} | ||||||||||||||||||
}); | ||||||||||||||||||
if (!completed) { | ||||||||||||||||||
return { | ||||||||||||||||||
type: "invalid", | ||||||||||||||||||
message: strings.invalidSelection, | ||||||||||||||||||
}; | ||||||||||||||||||
} | ||||||||||||||||||
return { | ||||||||||||||||||
type: "points", | ||||||||||||||||||
earned: allCorrect ? 1 : 0, | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import {mockStrings} from "../../strings"; | ||
|
||
import validateCategorizer from "./validate-categorizer"; | ||
|
||
import type {PerseusCategorizerRubric} from "../../validation.types"; | ||
|
||
describe("validateCategorizer", () => { | ||
it("tells the learner its not complete if not selected", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a test case for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added! |
||
const rubric: PerseusCategorizerRubric = { | ||
values: [1, 3], | ||
items: ["apples", "oranges"], | ||
}; | ||
|
||
const userInput = { | ||
values: [2], | ||
} as const; | ||
const score = validateCategorizer(userInput, rubric, mockStrings); | ||
|
||
expect(score).toHaveInvalidInput( | ||
"Make sure you select something for every row.", | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
import type {PerseusStrings} from "../../strings"; | ||||||||||||||||||||||||||||||||||||||||||||||||
import type {PerseusScore} from "../../types"; | ||||||||||||||||||||||||||||||||||||||||||||||||
import type { | ||||||||||||||||||||||||||||||||||||||||||||||||
PerseusCategorizerRubric, | ||||||||||||||||||||||||||||||||||||||||||||||||
PerseusCategorizerUserInput, | ||||||||||||||||||||||||||||||||||||||||||||||||
} from "../../validation.types"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
function validateCategorizer( | ||||||||||||||||||||||||||||||||||||||||||||||||
userInput: PerseusCategorizerUserInput, | ||||||||||||||||||||||||||||||||||||||||||||||||
rubric: PerseusCategorizerRubric, | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CC @jeremywiebe I think this highlights a possible need for more even more clarification of types (or maybe just a helper): (In the examples I use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love this! We just have to make sure whatever the info on rubric is that it doesn't convey the answer in any way. This definitely clarifies things. |
||||||||||||||||||||||||||||||||||||||||||||||||
strings: PerseusStrings, | ||||||||||||||||||||||||||||||||||||||||||||||||
): Extract<PerseusScore, {type: "invalid"}> | null { | ||||||||||||||||||||||||||||||||||||||||||||||||
let completed = true; | ||||||||||||||||||||||||||||||||||||||||||||||||
rubric.items.forEach((_, i) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
if (userInput.values[i] == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||
completed = false; | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||
if (!completed) { | ||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||
type: "invalid", | ||||||||||||||||||||||||||||||||||||||||||||||||
message: strings.invalidSelection, | ||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is just moved code, but what about something like:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm all for simplifying. I'll try to simplify the validation logic in the future if it seems like a straightforward update. Was trying to leave as close to original as possible as we've mentioned not changing the logic, but I think just simplifying what's there is probably fine. Updated :) |
||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
export default validateCategorizer; |
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We chatted on Slack, but we agreed to model the Rubric like this:
We also agreed to rename the term
Rubric
toScoringData
in the Server-Side Scoring area in a future task. :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above comment I made was from an older version of the code. I updated Rubric to be the following:
Is it also okay to rename Rubric as we go and to use the ticket for renaming any we missed? It helps my mental model to have the names updated while I'm working, but I can totally leave the name change to the very end if it's preferred to do them all at once. And if it's preferred to do them all at once, should I revert the name change in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started renaming things to
ScoringData
in one of my PRs. Then as I thought about it, I thought it might be more confusing to have a partial migration state inmain
. I was going to leave things as "Rubric" until we're done this pass of validation splitting and then we could do a single pass over everything to update our terms from Rubric to ScoringData. But I don't think it's a huge deal either way. :)