-
Notifications
You must be signed in to change notification settings - Fork 351
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
[LEMS-2849] Answerless Radio #2233
Conversation
Size Change: +591 B (+0.07%) Total Size: 873 kB
ℹ️ View Unchanged
|
packages/perseus-core/src/parse-perseus-json/perseus-parsers/radio-widget.ts
Show resolved
Hide resolved
const numCorrect: number = _.reduce( | ||
widgetOptions.choices, | ||
function (memo, choice) { | ||
return choice.correct ? memo + 1 : memo; | ||
}, | ||
0, | ||
); |
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 main idea is to pull this logic out of transform
and call it during the upgrade process. That way the widget can do all the pre-scoring things it needs to without answers (ie. render and be answerable).
// assert that functionality previous based on answers still works | ||
expect(screen.getByRole("group", {name: "Choose 2 answers:"})); | ||
|
||
const userInput = renderer.getUserInputMap(); |
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.
This is important because we can do everything we need to do pre-scoring with the answerless data now as far as I can tell.
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 left a few minor comments and suggestions inline, but nothing blocking. LGTM!
packages/perseus-core/src/parse-perseus-json/perseus-parsers/radio-widget.ts
Outdated
Show resolved
Hide resolved
packages/perseus/src/widgets/radio/__tests__/answerless-radio.test.tsx
Outdated
Show resolved
Hide resolved
constant("radio"), | ||
object({ | ||
numCorrect: optional(number), |
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.
This makes me slightly nervous. numCorrect
only needs to be optional because we'll be running this parser on two different inputs:
- data loaded from datastore, where
numCorrect
is usually not present (and maybe never needs to be present, since it can be derived?) - data sent to the the client, where
numCorrect
must be present if the data is answerless.
I worry that if the parsers accumulate a lot of irregularities of this kind, we'll be back in the position of not really understanding our data schema.
But I also think that it would be a pain in the butt to maintain separate parsers for answerless and answerful data.
One idea I'm toying with is letting the ParseContext
keep track of whether we're parsing answerless or answerful data. Then we could write widget options parsers like this:
object({
numCorrect: ifAnswerful(optional(number)).ifAnswerless(number),
})
But maybe that's overkill? I guess one thing I don't understand is whether the plan is to persist numCorrect
in the answerful data or not.
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.
For NumericInput v2 numCorrect
will:
- always be in the data store
- always be in the full widget options
- will conditionally be in the "public" widget options
I added a commit since your comment that changes things. Maybe this is resolved now?
…rt of answerless Radio
npm Snapshot: PublishedGood news!! We've packaged up the latest commit from this PR (49819c4) and published it to npm. You Example: pnpm add @khanacademy/perseus@PR2233 If you are working in Khan Academy's webapp, you can run: ./dev/tools/bump_perseus_version.sh -t PR2233 |
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.
Nice. I like the approach of removing countChoices
in get getPublicWidgetOptions
.
// How many of the choices are correct, which is conditionally used to tell | ||
// learners ahead of time how many options they'll need. | ||
numCorrect?: number; |
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.
Does updating this file require a stakeholder update? Apologies for pointing this out each time, but the memories of what Ned went through and Jeremy always talking about protecting this file are strong 😅
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.
Thanks for bringing this up: https://khanacademy.slack.com/archives/C49296Q7P/p1740496295273589
numCorrect: PerseusRadioWidgetOptions["numCorrect"], | ||
) { | ||
return multipleSelect && countChoices && numCorrect; | ||
} |
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.
It's kind of unfortunate that this code is coupled to logic in the widget. We're predicting when the widget is going to need EDIT: I see we're calling this function in the widget! In that case, I think it should be named something like numCorrect
, which feels brittle.revealNumCorrect
or showNumCorrectInInstructions
.
It seems like we could leverage the type system's ability to model the different possibilities here. Something like:
type PerseusRadioWidgetOptions = {
responseFormat: SingleSelect | MultiSelect,
// ...
}
type SingleSelect = {type: "single-select"};
type MultiSelect = {
type: "multi-select",
/**
* If present, the number of choices the learner must select for
* their response to be accepted. Responses with a different number
* of selected choices are marked invalid rather than incorrect. The
* number of correct choices is displayed in the widget instructions.
*
* If null, the learner can submit a response with any number of
* selected options. Responses with the wrong number of options
* selected are marked incorrect.
*/
numCorrect: number | null,
}
This would require a major version bump of the widget options format, so perhaps it's not something we want to take on right now. It might be worth migrating to this in the future. EDIT: ugh, sorry for the confusion. I was reading the diff of one commit thinking it was the whole PR. I see now that you are bumping the major version. In that case, I think changing to this more-structured format would be nice if we can make it work.
* This was super annoying to figure out. | ||
* When in "edit" mode (which the editor is obviously) | ||
* the only way to select an option from the radio | ||
* is to click the A/B/C/etc icons themselves. | ||
* We have a `elem.getAttribute("data-is-radio-icon")` check | ||
* to make sure that what we're clicking is the icon and | ||
* not some other part of the radio choice. | ||
* It's just a div, hence the test ID. |
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.
Hopefully this can become alot simpler with the upcoming Radio rebasing on top of Wonder Blocks work.
// now there should be 3 correct answers | ||
expect(options?.numCorrect).toBe(3); |
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.
Stylistically, I find using an onChange
jest.fn()
for these types of things simpler to grok. With a var we set in the callback you have a to trace up through the test to understand that the onChange
callback sets it.
With an onChange
mock, we could use:
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({
numCorrect: 3
});
In my mind that's less logic to support the test than having a let options
we manage. 🤷♂️
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.
Not a hill I'll die on, but I'm not totally sure I agree. I think...
expect(options?.numCorrect).toBe(3);
...is much more legible than...
expect(onChangeMock).toHaveBeenCalledWith(
expect.objectContaining({
numCorrect: 3,
}),
);
I also think from a KISS perspective that a simple callback is easier to follow/debug than a Jest abstraction. But it's not a big deal in this particular case, so I made the change.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @khanacademy/math-input@23.0.0 ### Major Changes - [#2226](#2226) [`909148cdc`](909148c) Thanks [@handeyeco](https://github.com/handeyeco)! - Answerless Expression: Expression can render and is interactive with answerless data ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 - @khanacademy/keypad-context@1.1.1 ## @khanacademy/perseus@56.0.0 ### Major Changes - [#2233](#2233) [`a0aee41b6`](a0aee41) Thanks [@handeyeco](https://github.com/handeyeco)! - RadioWidget v2 in support of answerless Radio ### Patch Changes - [#2279](#2279) [`e02cc4109`](e02cc41) Thanks [@nishasy](https://github.com/nishasy)! - [SR] Tweak strings for Segment and Angle graphs - [#2270](#2270) [`941343ee3`](941343e) Thanks [@catandthemachines](https://github.com/catandthemachines)! - Fixing side snapping within polygons to be keyboard accessible. - [#2226](#2226) [`909148cdc`](909148c) Thanks [@handeyeco](https://github.com/handeyeco)! - Answerless Expression: Expression can render and is interactive with answerless data - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 - @khanacademy/math-input@23.0.0 - @khanacademy/keypad-context@1.1.1 - @khanacademy/kmath@0.4.1 - @khanacademy/perseus-linter@1.3.1 - @khanacademy/perseus-score@2.3.1 ## @khanacademy/perseus-core@5.0.0 ### Major Changes - [#2233](#2233) [`a0aee41b6`](a0aee41) Thanks [@handeyeco](https://github.com/handeyeco)! - RadioWidget v2 in support of answerless Radio - [#2226](#2226) [`909148cdc`](909148c) Thanks [@handeyeco](https://github.com/handeyeco)! - Answerless Expression: Expression can render and is interactive with answerless data ## @khanacademy/perseus-editor@17.9.0 ### Minor Changes - [#2226](#2226) [`909148cdc`](909148c) Thanks [@handeyeco](https://github.com/handeyeco)! - Answerless Expression: Expression can render and is interactive with answerless data ### Patch Changes - [#2233](#2233) [`a0aee41b6`](a0aee41) Thanks [@handeyeco](https://github.com/handeyeco)! - RadioWidget v2 in support of answerless Radio - Updated dependencies \[[`e02cc4109`](e02cc41), [`941343ee3`](941343e), [`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus@56.0.0 - @khanacademy/perseus-core@5.0.0 - @khanacademy/math-input@23.0.0 - @khanacademy/keypad-context@1.1.1 - @khanacademy/kmath@0.4.1 - @khanacademy/perseus-linter@1.3.1 - @khanacademy/perseus-score@2.3.1 ## @khanacademy/keypad-context@1.1.1 ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 ## @khanacademy/kmath@0.4.1 ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 ## @khanacademy/perseus-linter@1.3.1 ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 ## @khanacademy/perseus-score@2.3.1 ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 - @khanacademy/kmath@0.4.1 ## @khanacademy/perseus-dev-ui@5.3.1 ### Patch Changes - Updated dependencies \[[`a0aee41b6`](a0aee41), [`909148cdc`](909148c)]: - @khanacademy/perseus-core@5.0.0 - @khanacademy/math-input@23.0.0 - @khanacademy/kmath@0.4.1 - @khanacademy/perseus-linter@1.3.1 Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary:
So this focuses on
numCorrect
which originally derived from the answers but now is its own widget option. I'm sure there's an argument that this also shouldn't be sent to the FE, but it's conditionally used to render help text:Select 2 answers
. I don't know, I don't think it's that big of a deal to have it but I also made it optional so we can remove it from questions that don't need it:needsNumCorrect = options.multipleSelect === true && options.countChoices === true
.The important thing is that we can render, answer, and score a Radio widget that's been stripped of answers. As far as I can tell, this supports all behavior pre-scoring.
Issue: LEMS-2849
Test plan:
Radio should continue to be renderable, answerable, and scorable.