typeanswer: fix cleanup

Fix: Add prepare_expected back in for the 'nothing typed' & 'correctly typed' cases. This also makes expected_original redundant again.

Style: %s/provided/typed/g

Style: rename one ch → c

Testcase: whitespace_is_trimmed: added a check for the "correctly typed" path and renamed it to tags_removed (there's no whitespace?)

Testcase: empty_input_shows_as_code: changed to also check that tags get trimmed
This commit is contained in:
Andreas Reis 2024-09-28 20:01:57 +02:00
parent 1b7390f22d
commit 5f04afe891

View file

@ -33,79 +33,77 @@ macro_rules! format_typeans {
} }
// Public API // Public API
pub fn compare_answer(expected: &str, provided: &str) -> String { pub fn compare_answer(expected: &str, typed: &str) -> String {
if provided.is_empty() { if typed.is_empty() {
format_typeans!(htmlescape::encode_minimal(expected)) format_typeans!(htmlescape::encode_minimal(&prepare_expected(expected)))
} else { } else {
Diff::new(expected, provided).to_html() Diff::new(expected, typed).to_html()
} }
} }
struct Diff { struct Diff {
provided: Vec<char>, typed: Vec<char>,
expected: Vec<char>, expected: Vec<char>,
expected_original: String,
} }
impl Diff { impl Diff {
fn new(expected: &str, provided: &str) -> Self { fn new(expected: &str, typed: &str) -> Self {
Self { Self {
provided: normalize_to_nfc(provided).chars().collect(), typed: normalize_to_nfc(typed).chars().collect(),
expected: normalize_to_nfc(&prepare_expected(expected)) expected: normalize_to_nfc(&prepare_expected(expected))
.chars() .chars()
.collect(), .collect(),
expected_original: expected.to_string(),
} }
} }
// Entry Point // Entry Point
fn to_html(&self) -> String { fn to_html(&self) -> String {
if self.provided == self.expected { if self.typed == self.expected {
format_typeans!(format!( format_typeans!(format!(
"<span class=typeGood>{}</span>", "<span class=typeGood>{}</span>",
self.expected_original &self.expected.iter().collect::<String>()
)) ))
} else { } else {
let output = self.to_tokens(); let output = self.to_tokens();
let provided_html = render_tokens(&output.provided_tokens); let typed_html = render_tokens(&output.typed_tokens);
let expected_html = render_tokens(&output.expected_tokens); let expected_html = render_tokens(&output.expected_tokens);
format_typeans!(format!( format_typeans!(format!(
"{provided_html}<br><span id=typearrow>&darr;</span><br>{expected_html}" "{typed_html}<br><span id=typearrow>&darr;</span><br>{expected_html}"
)) ))
} }
} }
fn to_tokens(&self) -> DiffTokens { fn to_tokens(&self) -> DiffTokens {
let mut matcher = SequenceMatcher::new(&self.provided, &self.expected); let mut matcher = SequenceMatcher::new(&self.typed, &self.expected);
let mut provided_tokens = Vec::new(); let mut typed_tokens = Vec::new();
let mut expected_tokens = Vec::new(); let mut expected_tokens = Vec::new();
for opcode in matcher.get_opcodes() { for opcode in matcher.get_opcodes() {
let provided_slice = slice(&self.provided, opcode.first_start, opcode.first_end); let typed_slice = slice(&self.typed, opcode.first_start, opcode.first_end);
let expected_slice = slice(&self.expected, opcode.second_start, opcode.second_end); let expected_slice = slice(&self.expected, opcode.second_start, opcode.second_end);
match opcode.tag.as_str() { match opcode.tag.as_str() {
"equal" => { "equal" => {
provided_tokens.push(DiffToken::good(provided_slice)); typed_tokens.push(DiffToken::good(typed_slice));
expected_tokens.push(DiffToken::good(expected_slice)); expected_tokens.push(DiffToken::good(expected_slice));
} }
"delete" => provided_tokens.push(DiffToken::bad(provided_slice)), "delete" => typed_tokens.push(DiffToken::bad(typed_slice)),
"insert" => { "insert" => {
provided_tokens.push(DiffToken::missing( typed_tokens.push(DiffToken::missing(
"-".repeat(expected_slice.chars().count()), "-".repeat(expected_slice.chars().count()),
)); ));
expected_tokens.push(DiffToken::missing(expected_slice)); expected_tokens.push(DiffToken::missing(expected_slice));
} }
"replace" => { "replace" => {
provided_tokens.push(DiffToken::bad(provided_slice)); typed_tokens.push(DiffToken::bad(typed_slice));
expected_tokens.push(DiffToken::missing(expected_slice)); expected_tokens.push(DiffToken::missing(expected_slice));
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
DiffTokens { DiffTokens {
provided_tokens, typed_tokens,
expected_tokens, expected_tokens,
} }
} }
@ -139,7 +137,7 @@ fn isolate_leading_mark(text: &str) -> Cow<str> {
if text if text
.chars() .chars()
.next() .next()
.map_or(false, |ch| GeneralCategory::of(ch).is_mark()) .map_or(false, |c| GeneralCategory::of(c).is_mark())
{ {
format!("\u{a0}{text}").into() format!("\u{a0}{text}").into()
} else { } else {
@ -149,7 +147,7 @@ fn isolate_leading_mark(text: &str) -> Cow<str> {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct DiffTokens { struct DiffTokens {
provided_tokens: Vec<DiffToken>, typed_tokens: Vec<DiffToken>,
expected_tokens: Vec<DiffToken>, expected_tokens: Vec<DiffToken>,
} }
@ -212,7 +210,7 @@ mod test {
let ctx = Diff::new("¿Y ahora qué vamos a hacer?", "y ahora qe vamosa hacer"); let ctx = Diff::new("¿Y ahora qué vamos a hacer?", "y ahora qe vamosa hacer");
let output = ctx.to_tokens(); let output = ctx.to_tokens();
assert_eq!( assert_eq!(
output.provided_tokens, output.typed_tokens,
vec![ vec![
bad("y"), bad("y"),
good(" ahora q"), good(" ahora q"),
@ -245,18 +243,18 @@ mod test {
} }
#[test] #[test]
fn missed_chars_only_shown_in_provided_when_after_good() { fn missed_chars_only_shown_in_typed_when_after_good() {
let ctx = Diff::new("1", "23"); let ctx = Diff::new("1", "23");
assert_eq!(ctx.to_tokens().provided_tokens, &[bad("23")]); assert_eq!(ctx.to_tokens().typed_tokens, &[bad("23")]);
let ctx = Diff::new("12", "1"); let ctx = Diff::new("12", "1");
assert_eq!(ctx.to_tokens().provided_tokens, &[good("1"), missing("-"),]); assert_eq!(ctx.to_tokens().typed_tokens, &[good("1"), missing("-"),]);
} }
#[test] #[test]
fn missed_chars_counted_correctly() { fn missed_chars_counted_correctly() {
let ctx = Diff::new("нос", "нс"); let ctx = Diff::new("нос", "нс");
assert_eq!( assert_eq!(
ctx.to_tokens().provided_tokens, ctx.to_tokens().typed_tokens,
&[good("н"), missing("-"), good("с")] &[good("н"), missing("-"), good("с")]
); );
} }
@ -266,7 +264,7 @@ mod test {
// this was not parsed as expected with dissimilar 1.0.4 // this was not parsed as expected with dissimilar 1.0.4
let ctx = Diff::new("쓰다듬다", "스다뜸다"); let ctx = Diff::new("쓰다듬다", "스다뜸다");
assert_eq!( assert_eq!(
ctx.to_tokens().provided_tokens, ctx.to_tokens().typed_tokens,
&[bad(""), good(""), bad(""), good(""),] &[bad(""), good(""), bad(""), good(""),]
); );
} }
@ -285,13 +283,17 @@ mod test {
} }
#[test] #[test]
fn whitespace_is_trimmed() { fn tags_removed() {
assert_eq!(prepare_expected("<div>foo</div>"), "foo"); assert_eq!(prepare_expected("<div>123</div>"), "123");
assert_eq!(
Diff::new("<div>123</div>", "123").to_html(),
"<code id=typeans><span class=typeGood>123</span></code>"
);
} }
#[test] #[test]
fn empty_input_shows_as_code() { fn empty_input_shows_as_code() {
let ctx = compare_answer("123", ""); let ctx = compare_answer("<div>123</div>", "");
assert_eq!(ctx, "<code id=typeans>123</code>"); assert_eq!(ctx, "<code id=typeans>123</code>");
} }