feat: improve worksheet

This commit is contained in:
2026-08-10 01:52:15 -04:00
parent f475c630e0
commit 327ac371e4
17 changed files with 1469 additions and 49 deletions
+50
View File
@@ -423,17 +423,53 @@ pub enum Format {
MultipleResponse,
/// Two options, True and False.
TrueFalse,
/// A free-text answer the student writes rather than selects.
///
/// It carries no options and is not machine-scored. What a grader marks it
/// against, and what a solutions document shows, lives in the item's
/// [`crate::item::Solution`]: a model answer and, when the item is worth more
/// than a point, a rubric. This is the format for "explain", "derive", and
/// "estimate" prompts that a set of distractors would trivialize.
OpenResponse,
}
impl Format {
/// Every response format.
pub const ALL: [Format; 4] = [
Format::SingleBestAnswer,
Format::MultipleResponse,
Format::TrueFalse,
Format::OpenResponse,
];
/// The QTI question type Canvas expects for this format.
pub fn qti_type(self) -> &'static str {
match self {
Format::SingleBestAnswer => "multiple_choice_question",
Format::MultipleResponse => "multiple_answers_question",
Format::TrueFalse => "true_false_question",
Format::OpenResponse => "essay_question",
}
}
/// The snake_case token used in YAML.
pub fn as_str(self) -> &'static str {
match self {
Format::SingleBestAnswer => "single_best_answer",
Format::MultipleResponse => "multiple_response",
Format::TrueFalse => "true_false",
Format::OpenResponse => "open_response",
}
}
/// Whether items in this format present selectable options.
///
/// `false` only for [`Format::OpenResponse`]. Validation, assembly, and the
/// exporters branch on this rather than on the variant, so the day a second
/// free-text format is added it inherits the no-options handling for free.
pub fn has_options(self) -> bool {
!matches!(self, Format::OpenResponse)
}
}
/// How strongly an item is expected to separate strong from weak students.
@@ -633,4 +669,18 @@ mod tests {
Status::InReview
);
}
#[test]
fn open_response_is_the_only_format_without_options() {
assert_eq!(
serde_json::from_str::<Format>("\"open_response\"").unwrap(),
Format::OpenResponse
);
assert_eq!(Format::OpenResponse.as_str(), "open_response");
assert_eq!(Format::OpenResponse.qti_type(), "essay_question");
assert!(!Format::OpenResponse.has_options());
for f in Format::ALL {
assert_eq!(f.has_options(), f != Format::OpenResponse);
}
}
}