Fix unable to save field dialog if certain fields are deleted (#2663)

* Fix unable to save field dialog if certain fields are deleted

Implemented solution suggested in issue #2556

* Fix unable to save field dialog if certain fields are deleted

fixed code formating

* Fix unable to save field dialog if certain fields are deleted

Made new functions to check referencelessness.  Added unit test.
This commit is contained in:
Sam Waechter 2023-09-18 00:33:56 -04:00 committed by GitHub
parent a7b4c90146
commit e7bf248a62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 103 additions and 0 deletions

View File

@ -139,6 +139,7 @@ Nil Admirari <https://github.com/nihil-admirari>
Michael Winkworth <github.com/SteelColossus>
Mateusz Wojewoda <kawa1.11@o2.pl>
Jarrett Ye <jarrett.ye@outlook.com>
Sam Waechter <github.com/swektr>
********************

View File

@ -551,13 +551,21 @@ impl Notetype {
fields: HashMap<String, Option<String>>,
parsed: &mut [(Option<ParsedTemplate>, Option<ParsedTemplate>)],
) {
let first_remaining_field_name = &self.fields.get(0).unwrap().name;
let is_cloze = self.is_cloze();
for (idx, (q_opt, a_opt)) in parsed.iter_mut().enumerate() {
if let Some(q) = q_opt {
q.rename_and_remove_fields(&fields);
if !q.contains_field_replacement() || is_cloze && !q.contains_cloze_replacement() {
q.add_missing_field_replacement(first_remaining_field_name, is_cloze);
}
self.templates[idx].config.q_format = q.template_to_string();
}
if let Some(a) = a_opt {
a.rename_and_remove_fields(&fields);
if is_cloze && !a.contains_cloze_replacement() {
a.add_missing_field_replacement(first_remaining_field_name, is_cloze);
}
self.templates[idx].config.a_format = a.template_to_string();
}
}
@ -745,3 +753,73 @@ impl Collection {
}
}
}
// Tests
//---------------------------------------
#[cfg(test)]
mod test {
use super::*;
#[test]
fn update_templates_after_removing_crucial_fields() {
// Normal Test (all front fields removed)
let mut nt_norm = Notetype::default();
nt_norm.add_field("baz"); // Fields "foo" and "bar" were removed
nt_norm.fields[0].ord = Some(2);
nt_norm.add_template("Card 1", "front {{foo}}", "back {{bar}}");
nt_norm.templates[0].ord = Some(0);
let mut parsed = nt_norm.parsed_templates();
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
field_map.insert("foo".to_owned(), None);
field_map.insert("bar".to_owned(), None);
nt_norm.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
assert_eq!(nt_norm.templates[0].config.q_format, "front {{baz}}");
assert_eq!(nt_norm.templates[0].config.a_format, "back ");
// Cloze Test 1/2 (front and back cloze fields removed)
let mut nt_cloze = Notetype {
config: Notetype::new_cloze_config(),
..Default::default()
};
nt_cloze.add_field("baz"); // Fields "foo" and "bar" were removed
nt_cloze.fields[0].ord = Some(2);
nt_cloze.add_template("Card 1", "front {{cloze:foo}}", "back {{cloze:bar}}");
nt_cloze.templates[0].ord = Some(0);
let mut parsed = nt_cloze.parsed_templates();
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
field_map.insert("foo".to_owned(), None);
field_map.insert("bar".to_owned(), None);
nt_cloze.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
assert_eq!(nt_cloze.templates[0].config.q_format, "front {{cloze:baz}}");
assert_eq!(nt_cloze.templates[0].config.a_format, "back {{cloze:baz}}");
// Cloze Test 2/2 (only back cloze field is removed)
let mut nt_cloze = Notetype {
config: Notetype::new_cloze_config(),
..Default::default()
};
nt_cloze.add_field("foo");
nt_cloze.fields[0].ord = Some(0);
nt_cloze.add_field("baz");
nt_cloze.fields[1].ord = Some(2);
// ^ only field "bar" was removed
nt_cloze.add_template("Card 1", "front {{cloze:foo}}", "back {{cloze:bar}}");
nt_cloze.templates[0].ord = Some(0);
let mut parsed = nt_cloze.parsed_templates();
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
field_map.insert("bar".to_owned(), None);
nt_cloze.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
assert_eq!(nt_cloze.templates[0].config.q_format, "front {{cloze:foo}}");
assert_eq!(nt_cloze.templates[0].config.a_format, "back {{cloze:foo}}");
}
}

View File

@ -730,6 +730,30 @@ impl ParsedTemplate {
let old_nodes = std::mem::take(&mut self.0);
self.0 = rename_and_remove_fields(old_nodes, fields);
}
pub(crate) fn contains_cloze_replacement(&self) -> bool {
self.0.iter().any(|node| {
matches!(
node,
ParsedNode::Replacement {key:_, filters} if filters.iter().any(|f| f=="cloze")
)
})
}
pub(crate) fn contains_field_replacement(&self) -> bool {
self.0
.iter()
.any(|node| matches!(node, ParsedNode::Replacement { key: _, filters: _ }))
}
pub(crate) fn add_missing_field_replacement(&mut self, field_name: &str, is_cloze: bool) {
let key = String::from(field_name);
let filters = match is_cloze {
true => vec![String::from("cloze")],
false => Vec::new(),
};
self.0.push(ParsedNode::Replacement { key, filters });
}
}
fn rename_and_remove_fields(