From ffc631861ed48fd857ea7e9e4cc5ac9a19fe9abe Mon Sep 17 00:00:00 2001 From: aiongg Date: Mon, 26 Oct 2026 19:46:28 +0000 Subject: [PATCH] refactor(ui-egui): move dialogs_insert tests to dialogs_insert/tests.rs --- crates/ui-egui/src/dialogs/dialogs_insert.rs | 171 +----------------- .../src/dialogs/dialogs_insert/tests.rs | 168 +++++++++++++++++ 2 files changed, 169 insertions(+), 170 deletions(-) create mode 100644 crates/ui-egui/src/dialogs/dialogs_insert/tests.rs diff --git a/crates/ui-egui/src/dialogs/dialogs_insert.rs b/crates/ui-egui/src/dialogs/dialogs_insert.rs index bb555b003..0e68e6a6c 100644 --- a/crates/ui-egui/src/dialogs/dialogs_insert.rs +++ b/crates/ui-egui/src/dialogs/dialogs_insert.rs @@ -1429,173 +1429,4 @@ fn field_options(ui: &mut Ui, f: &mut FieldForm, t: &Tokens) { } #[cfg(test)] -mod tests { - use super::*; - - fn app() -> WordApp { - WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("one\ntwo")), Default::default()) - } - - fn drop_cap_form(a: &mut WordApp) -> Option { - a.run("ui.dialog", json!({"name": "dropCap"})).unwrap(); - match a.dialog.take() { - Some(crate::dialogs::Dialog::Insert(d)) => match *d { - InsertDialog::DropCap(f) => Some(f), - d => panic!("not drop cap: {d:?}"), - }, - _ => None, - } - } - - /// #2601: Drop Cap Options… opens on the paragraph's drop cap (the defaults without one: - /// 3 lines, distance 0, the text's font) in the interface unit, and OK runs `insert.dropCap` - /// with the chosen values; it reopens showing them. Choosing the text's own font sends none. - #[test] - fn drop_cap_dialog_prefills_and_applies() { - let mut a = app(); - a.run("file.options", json!({"units": "centimeters"})).unwrap(); - let mut f = drop_cap_form(&mut a).expect("the dialog"); - assert_eq!((f.position.as_str(), f.lines, f.distance), ("none", 3, 0.0)); - assert_eq!(f.font, f.text_font, "the text's font"); - assert!(!f.font.is_empty()); - assert_eq!(f.params(), json!({"position": "none"})); - f.position = "margin".into(); - f.lines = 4; - f.distance = 1.0; - a.run("insert.dropCap", f.params()).unwrap(); - let props = |a: &WordApp| a.session.doc.body.first().and_then(|b| b.as_para()).map(|p| p.props.clone()).unwrap_or_default(); - let p = props(&a); - assert_eq!((p.drop_cap, p.drop_cap_margin, p.drop_cap_font.as_deref()), (Some(4), Some(true), None)); - assert!((p.drop_cap_distance.unwrap_or_default() - wordcraft_geom::PT_PER_CM).abs() < 0.01, "1 cm: {:?}", p.drop_cap_distance); - let mut f = drop_cap_form(&mut a).expect("the dialog"); - assert_eq!((f.position.as_str(), f.lines), ("margin", 4)); - assert!((f.distance - 1.0).abs() < 0.01, "{} cm", f.distance); - f.position = "dropped".into(); - f.font = "Liberation Mono".into(); - a.run("insert.dropCap", f.params()).unwrap(); - let p = props(&a); - assert_eq!((p.drop_cap, p.drop_cap_margin, p.drop_cap_font.as_deref()), (Some(4), None, Some("Liberation Mono"))); - assert_eq!(drop_cap_form(&mut a).map(|f| f.font), Some("Liberation Mono".to_string())); - assert_eq!(wordcraft_engine::cmd::insert::drop_cap_state(&a.session).position, "dropped", "the menu checks Dropped"); - - // Where Word greys Drop Cap out, there is no dialog. - a.run("caret.docEnd", json!({})).unwrap(); - a.run("text.newParagraph", json!({})).unwrap(); - assert!(drop_cap_form(&mut a).is_none(), "an empty paragraph"); - } - - /// More Columns: two unequal columns with a line between apply as one undo step. - #[test] - fn columns_dialog_applies_unequal_columns_in_one_step() { - let mut a = app(); - a.run("ui.dialog", json!({"name": "columns"})).unwrap(); - let Some(crate::dialogs::Dialog::Insert(d)) = a.dialog.take() else { panic!("no dialog") }; - let InsertDialog::Columns(mut f) = *d else { panic!("not columns") }; - assert_eq!((f.count, f.equal), (1, true)); - f.preset("left"); - assert_eq!(f.current_preset(), Some("left")); - // Widen the first column: the second gives up the room, the total stays the text width. - if let Some(c) = f.cols.first_mut() { - c.0 += 0.5; - } - f.rebalance(0); - let total: f32 = f.cols.iter().map(|(w, s)| w + s).sum(); - assert!((total - f.text_width).abs() < 0.01, "{total} vs {}", f.text_width); - f.separator = true; - let undo_before = a.session.undo_depth(); - a.run("layout.columns", f.params()).unwrap(); - let c = wordcraft_engine::cmd::page::sect(&a.session).columns; - assert_eq!((c.count, c.separator, c.widths.len()), (2, true, 2)); - assert_eq!(a.session.undo_depth(), undo_before + 1, "one undo step"); - // The dialog reopens with what was applied. - let again = ColumnsForm::read(&a); - assert!(!again.equal && again.separator && again.cols.len() == 2); - a.run("edit.undo", json!({})).unwrap(); - assert_eq!(wordcraft_engine::cmd::page::sect(&a.session).columns.count, 1); - } - - /// With text selected, More Columns applies to the selected text by default, as in Word - /// (#2024): the other paragraphs keep one column. - #[test] - fn columns_dialog_applies_to_the_selected_text() { - let mut a = app(); - assert!(!ColumnsForm::read(&a).selection, "no selection, no Selected text"); - a.run("select.paragraph", json!({})).unwrap(); - let mut f = ColumnsForm::read(&a); - assert_eq!((f.selection, f.apply.as_str()), (true, "selection")); - f.preset("two"); - a.run("layout.columns", f.params()).unwrap(); - let counts: Vec = a.session.doc.sections().iter().map(|(_, x)| x.columns.count).collect(); - assert_eq!(counts, vec![2, 1]); - } - - /// The Symbol dialog inserts in the chosen font and remembers the symbol. - #[test] - fn symbol_dialog_inserts_with_its_font() { - let mut a = app(); - let mut f = SymbolForm::new(&a, 0); - f.font = "DejaVu Sans".into(); - f.selected = Some('\u{2665}'); - let p = f.params().unwrap(); - assert_eq!(p, json!({"char": "\u{2665}", "font": "DejaVu Sans"})); - a.run("insert.symbol", p).unwrap(); - remember_symbol(&mut a, "\u{2665}", "DejaVu Sans"); - assert!(a.session.doc.plain_text(wordcraft_doc::StoryRef::Body).starts_with('\u{2665}')); - assert_eq!(a.ui.recent_symbols.first().map(|r| r.font.as_str()), Some("DejaVu Sans")); - assert_eq!(subset_of('\u{2665}'), Some("Miscellaneous Symbols")); - // (normal text) inserts without a font. - f.font.clear(); - assert_eq!(f.params().unwrap(), json!({"char": "\u{2665}"})); - } - - /// The Field dialog builds the field code from its options. - #[test] - fn field_dialog_builds_the_instruction() { - let a = app(); - let mut f = FieldForm::new(&a); - assert_eq!(f.instruction(), r#"DATE \@ "M/d/yyyy" \* MERGEFORMAT"#); - f.date_format = "yyyy-MM-dd".into(); - f.preserve = false; - assert_eq!(f.instruction(), r#"DATE \@ "yyyy-MM-dd""#); - f.select("PAGE"); - f.number_format = "roman".into(); - assert_eq!(f.instruction(), r"PAGE \* roman"); - f.select("SEQ"); - f.target = "Table".into(); - assert_eq!(f.instruction(), r"SEQ Table"); - f.select("REF"); - f.target = "intro".into(); - assert_eq!(f.instruction(), r"REF intro \h"); - f.select("STYLEREF"); - assert_eq!(f.target, "Heading 1"); - f.style_switches = "pl".into(); - assert_eq!(f.instruction(), r#"STYLEREF "Heading 1" \l \p"#); - f.select("MERGEFIELD"); - f.target = "First Name".into(); - assert_eq!(f.params().unwrap(), json!({"instr": r#"MERGEFIELD "First Name""#, "result": "«First Name»"})); - // Field Codes: the typed code wins. - f.codes = true; - f.code = "NUMPAGES \\* Arabic".into(); - assert_eq!(f.final_code(), "NUMPAGES \\* Arabic"); - f.code = " ".into(); - assert!(f.params().is_none()); - } - - /// The Field dialog lists Word's document information fields, and the counts take a number - /// format. - #[test] - fn field_dialog_lists_the_document_information_fields() { - let listed: Vec<&str> = FIELDS.iter().filter(|(_, cat, _)| *cat == "Document Information").map(|(n, _, _)| *n).collect(); - for name in ["COMMENTS", "KEYWORDS", "LASTSAVEDBY", "NUMCHARS", "REVNUM", "SUBJECT"] { - assert!(listed.contains(&name), "{name} in {listed:?}"); - } - let a = app(); - let mut f = FieldForm::new(&a); - f.select("REVNUM"); - f.number_format = "ROMAN".into(); - f.preserve = false; - assert_eq!(f.instruction(), r"REVNUM \* ROMAN"); - f.select("SUBJECT"); - assert_eq!(f.instruction(), "SUBJECT"); - } -} +mod tests; diff --git a/crates/ui-egui/src/dialogs/dialogs_insert/tests.rs b/crates/ui-egui/src/dialogs/dialogs_insert/tests.rs new file mode 100644 index 000000000..e000ef7e3 --- /dev/null +++ b/crates/ui-egui/src/dialogs/dialogs_insert/tests.rs @@ -0,0 +1,168 @@ +use super::*; + +fn app() -> WordApp { + WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("one\ntwo")), Default::default()) +} + +fn drop_cap_form(a: &mut WordApp) -> Option { + a.run("ui.dialog", json!({"name": "dropCap"})).unwrap(); + match a.dialog.take() { + Some(crate::dialogs::Dialog::Insert(d)) => match *d { + InsertDialog::DropCap(f) => Some(f), + d => panic!("not drop cap: {d:?}"), + }, + _ => None, + } +} + +/// #2601: Drop Cap Options… opens on the paragraph's drop cap (the defaults without one: +/// 3 lines, distance 0, the text's font) in the interface unit, and OK runs `insert.dropCap` +/// with the chosen values; it reopens showing them. Choosing the text's own font sends none. +#[test] +fn drop_cap_dialog_prefills_and_applies() { + let mut a = app(); + a.run("file.options", json!({"units": "centimeters"})).unwrap(); + let mut f = drop_cap_form(&mut a).expect("the dialog"); + assert_eq!((f.position.as_str(), f.lines, f.distance), ("none", 3, 0.0)); + assert_eq!(f.font, f.text_font, "the text's font"); + assert!(!f.font.is_empty()); + assert_eq!(f.params(), json!({"position": "none"})); + f.position = "margin".into(); + f.lines = 4; + f.distance = 1.0; + a.run("insert.dropCap", f.params()).unwrap(); + let props = |a: &WordApp| a.session.doc.body.first().and_then(|b| b.as_para()).map(|p| p.props.clone()).unwrap_or_default(); + let p = props(&a); + assert_eq!((p.drop_cap, p.drop_cap_margin, p.drop_cap_font.as_deref()), (Some(4), Some(true), None)); + assert!((p.drop_cap_distance.unwrap_or_default() - wordcraft_geom::PT_PER_CM).abs() < 0.01, "1 cm: {:?}", p.drop_cap_distance); + let mut f = drop_cap_form(&mut a).expect("the dialog"); + assert_eq!((f.position.as_str(), f.lines), ("margin", 4)); + assert!((f.distance - 1.0).abs() < 0.01, "{} cm", f.distance); + f.position = "dropped".into(); + f.font = "Liberation Mono".into(); + a.run("insert.dropCap", f.params()).unwrap(); + let p = props(&a); + assert_eq!((p.drop_cap, p.drop_cap_margin, p.drop_cap_font.as_deref()), (Some(4), None, Some("Liberation Mono"))); + assert_eq!(drop_cap_form(&mut a).map(|f| f.font), Some("Liberation Mono".to_string())); + assert_eq!(wordcraft_engine::cmd::insert::drop_cap_state(&a.session).position, "dropped", "the menu checks Dropped"); + + // Where Word greys Drop Cap out, there is no dialog. + a.run("caret.docEnd", json!({})).unwrap(); + a.run("text.newParagraph", json!({})).unwrap(); + assert!(drop_cap_form(&mut a).is_none(), "an empty paragraph"); +} + +/// More Columns: two unequal columns with a line between apply as one undo step. +#[test] +fn columns_dialog_applies_unequal_columns_in_one_step() { + let mut a = app(); + a.run("ui.dialog", json!({"name": "columns"})).unwrap(); + let Some(crate::dialogs::Dialog::Insert(d)) = a.dialog.take() else { panic!("no dialog") }; + let InsertDialog::Columns(mut f) = *d else { panic!("not columns") }; + assert_eq!((f.count, f.equal), (1, true)); + f.preset("left"); + assert_eq!(f.current_preset(), Some("left")); + // Widen the first column: the second gives up the room, the total stays the text width. + if let Some(c) = f.cols.first_mut() { + c.0 += 0.5; + } + f.rebalance(0); + let total: f32 = f.cols.iter().map(|(w, s)| w + s).sum(); + assert!((total - f.text_width).abs() < 0.01, "{total} vs {}", f.text_width); + f.separator = true; + let undo_before = a.session.undo_depth(); + a.run("layout.columns", f.params()).unwrap(); + let c = wordcraft_engine::cmd::page::sect(&a.session).columns; + assert_eq!((c.count, c.separator, c.widths.len()), (2, true, 2)); + assert_eq!(a.session.undo_depth(), undo_before + 1, "one undo step"); + // The dialog reopens with what was applied. + let again = ColumnsForm::read(&a); + assert!(!again.equal && again.separator && again.cols.len() == 2); + a.run("edit.undo", json!({})).unwrap(); + assert_eq!(wordcraft_engine::cmd::page::sect(&a.session).columns.count, 1); +} + +/// With text selected, More Columns applies to the selected text by default, as in Word +/// (#2024): the other paragraphs keep one column. +#[test] +fn columns_dialog_applies_to_the_selected_text() { + let mut a = app(); + assert!(!ColumnsForm::read(&a).selection, "no selection, no Selected text"); + a.run("select.paragraph", json!({})).unwrap(); + let mut f = ColumnsForm::read(&a); + assert_eq!((f.selection, f.apply.as_str()), (true, "selection")); + f.preset("two"); + a.run("layout.columns", f.params()).unwrap(); + let counts: Vec = a.session.doc.sections().iter().map(|(_, x)| x.columns.count).collect(); + assert_eq!(counts, vec![2, 1]); +} + +/// The Symbol dialog inserts in the chosen font and remembers the symbol. +#[test] +fn symbol_dialog_inserts_with_its_font() { + let mut a = app(); + let mut f = SymbolForm::new(&a, 0); + f.font = "DejaVu Sans".into(); + f.selected = Some('\u{2665}'); + let p = f.params().unwrap(); + assert_eq!(p, json!({"char": "\u{2665}", "font": "DejaVu Sans"})); + a.run("insert.symbol", p).unwrap(); + remember_symbol(&mut a, "\u{2665}", "DejaVu Sans"); + assert!(a.session.doc.plain_text(wordcraft_doc::StoryRef::Body).starts_with('\u{2665}')); + assert_eq!(a.ui.recent_symbols.first().map(|r| r.font.as_str()), Some("DejaVu Sans")); + assert_eq!(subset_of('\u{2665}'), Some("Miscellaneous Symbols")); + // (normal text) inserts without a font. + f.font.clear(); + assert_eq!(f.params().unwrap(), json!({"char": "\u{2665}"})); +} + +/// The Field dialog builds the field code from its options. +#[test] +fn field_dialog_builds_the_instruction() { + let a = app(); + let mut f = FieldForm::new(&a); + assert_eq!(f.instruction(), r#"DATE \@ "M/d/yyyy" \* MERGEFORMAT"#); + f.date_format = "yyyy-MM-dd".into(); + f.preserve = false; + assert_eq!(f.instruction(), r#"DATE \@ "yyyy-MM-dd""#); + f.select("PAGE"); + f.number_format = "roman".into(); + assert_eq!(f.instruction(), r"PAGE \* roman"); + f.select("SEQ"); + f.target = "Table".into(); + assert_eq!(f.instruction(), r"SEQ Table"); + f.select("REF"); + f.target = "intro".into(); + assert_eq!(f.instruction(), r"REF intro \h"); + f.select("STYLEREF"); + assert_eq!(f.target, "Heading 1"); + f.style_switches = "pl".into(); + assert_eq!(f.instruction(), r#"STYLEREF "Heading 1" \l \p"#); + f.select("MERGEFIELD"); + f.target = "First Name".into(); + assert_eq!(f.params().unwrap(), json!({"instr": r#"MERGEFIELD "First Name""#, "result": "«First Name»"})); + // Field Codes: the typed code wins. + f.codes = true; + f.code = "NUMPAGES \\* Arabic".into(); + assert_eq!(f.final_code(), "NUMPAGES \\* Arabic"); + f.code = " ".into(); + assert!(f.params().is_none()); +} + +/// The Field dialog lists Word's document information fields, and the counts take a number +/// format. +#[test] +fn field_dialog_lists_the_document_information_fields() { + let listed: Vec<&str> = FIELDS.iter().filter(|(_, cat, _)| *cat == "Document Information").map(|(n, _, _)| *n).collect(); + for name in ["COMMENTS", "KEYWORDS", "LASTSAVEDBY", "NUMCHARS", "REVNUM", "SUBJECT"] { + assert!(listed.contains(&name), "{name} in {listed:?}"); + } + let a = app(); + let mut f = FieldForm::new(&a); + f.select("REVNUM"); + f.number_format = "ROMAN".into(); + f.preserve = false; + assert_eq!(f.instruction(), r"REVNUM \* ROMAN"); + f.select("SUBJECT"); + assert_eq!(f.instruction(), "SUBJECT"); +}