diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 98324fb58..9183444cf 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -186,134 +186,6 @@ fn color_theme(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { mod color_theme_tests { use super::*; - /// The course's chapter 20: the "Recipe Categories" title formatted on the page (size, All - /// Caps, tracking, space after), then New Paragraph Style from it: the dialog starts from the - /// text, and with Apply Style to Selection the paragraph takes the new style. Next Style and - /// Reset To Base are set from General. - #[test] - fn a_new_paragraph_style_starts_from_the_selected_text() { - let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - app.run("file.new", json!({})).unwrap(); - let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Egg Recipes\rBoil them."})).unwrap(); - app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 11})).unwrap(); - app.run("type.char", json!({"attrs": {"size": 20, "capitalization": "allCaps", "tracking": 50}})).unwrap(); - app.run("type.para", json!({"attrs": {"spaceAfter": 6}})).unwrap(); - open_new_style(&mut app, true); - let d = app.ui.dialog.clone().unwrap(); - assert_eq!( - (d.fields["c.size"].clone(), d.fields["c.capitalization"].clone(), d.fields["p.spaceAfter"].clone()), - (json!(20.0), json!("allCaps"), json!(6.0)) - ); - assert!(d.b("apply") && !d.fields.contains_key("c.fontFamily"), "only what differs from [No Paragraph Style]"); - if let Some(d) = app.ui.dialog.as_mut() { - d.fields.insert("rename".into(), json!("Recipe Categories")); - d.fields.insert("nextStyle".into(), json!(designcraft_doc::BASIC_PARAGRAPH)); - } - confirm(&mut app).unwrap(); - let a = app.run("type.selectionAttrs", json!({})).unwrap(); - assert_eq!(a["paragraphStyle"], "Recipe Categories", "applied to the selection"); - assert_eq!(a["paraOverrides"].as_u64().unwrap_or(0) + a["charOverrides"].as_u64().unwrap_or(0), 0); - let style = |app: &DesignApp| app.session.doc().unwrap().doc.styles.para("Recipe Categories").cloned().unwrap(); - assert_eq!(style(&app).next_style.as_deref(), Some(designcraft_doc::BASIC_PARAGRAPH)); - assert_eq!(style(&app).chars.size, Some(20.0)); - // Reset To Base: the style keeps nothing of its own, apart from the edits made then. - app.ui.dialog = Some(Dialog::new("paragraphStyleOptions", json!({"name": "Recipe Categories", "basedOn": designcraft_doc::BASIC_PARAGRAPH}))); - if let Some(d) = app.ui.dialog.as_mut() { - d.fields.insert("resetToBase".into(), json!(true)); - d.fields.insert("c.tracking".into(), json!(10.0)); - } - confirm(&mut app).unwrap(); - let s = style(&app); - assert_eq!((s.chars.size, s.chars.tracking, s.para.space_after), (None, Some(10.0), None), "{s:?}"); - assert_eq!(s.based_on.as_deref(), Some(designcraft_doc::BASIC_PARAGRAPH)); - } - - /// The course's chapter 22: "eggs" made bold and red on the page, then New Character Style - /// from it. The dialog is titled as InDesign's, starts from what the text has of its own - /// over its paragraph style ("[None] + Bold + color"), and Apply Style to Selection gives the - /// text the style, with no override left. - #[test] - fn a_new_character_style_starts_from_the_selected_text() { - use egui_kittest::{Harness, kittest::Queryable}; - let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - app.run("file.new", json!({})).unwrap(); - let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Drop eggs into boiling water."})).unwrap(); - app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 29})).unwrap(); - app.run("style.paragraph.create", json!({"name": "Recipe", "chars": {"size": 14}})).unwrap(); - app.run("style.paragraph.apply", json!({"name": "Recipe"})).unwrap(); - app.run("text.select", json!({"story": f["story"], "anchor": 5, "focus": 9})).unwrap(); - app.run("type.char", json!({"attrs": {"fontStyle": "Bold", "fill": "C=15 M=100 Y=100 K=0"}})).unwrap(); - open_new_style(&mut app, false); - let d = app.ui.dialog.clone().unwrap(); - assert_eq!((d.fields["c.fontStyle"].clone(), d.fields["c.fill"].clone()), (json!("Bold"), json!("C=15 M=100 Y=100 K=0"))); - assert!(d.b("apply")); - let mut h = Harness::builder().with_size(egui::vec2(1000.0, 700.0)).build_ui_state( - |ui, app: &mut DesignApp| { - let key = egui::Id::new("new_character_style_test_fonts"); - if ui.data(|d| d.get_temp::(key)).is_none() { - crate::theme::install_fonts(ui.ctx(), ""); - ui.data_mut(|d| d.insert_temp(key, true)); - return; - } - show(app, ui.ctx()); - }, - app, - ); - h.run_steps(4); - for l in ["New Character Style", "Apply Style to Selection"] { - assert!(h.query_by_label(l).is_some(), "{l}"); - } - let app = h.state_mut(); - if let Some(d) = app.ui.dialog.as_mut() { - d.fields.insert("rename".into(), json!("eggs")); - } - confirm(app).unwrap(); - let style = app.session.doc().unwrap().doc.styles.char_style("eggs").cloned().unwrap(); - assert_eq!((style.chars.font_style.as_deref(), style.chars.fill.as_deref()), (Some("Bold"), Some("C=15 M=100 Y=100 K=0"))); - assert_eq!(style.chars.size, None, "the size is the paragraph style's, not the text's own"); - let a = app.run("type.selectionAttrs", json!({})).unwrap(); - assert_eq!((a["characterStyle"].clone(), a["charOverrides"].clone()), (json!("eggs"), json!(0)), "applied to the selection"); - } - - /// A Based On changed in the dialog (without Reset To Base) is what the fields are checked - /// against: the style's own settings over the new parent. - #[test] - fn a_changed_based_on_is_what_the_fields_are_checked_against() { - let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - app.run("file.new", json!({})).unwrap(); - app.run("style.paragraph.create", json!({"name": "Loose", "para": {"wordSpaceMin": 1.2, "wordSpaceDesired": 1.3}})).unwrap(); - app.run("style.paragraph.create", json!({"name": "Child", "basedOn": "Loose"})).unwrap(); - let mut d = Dialog::new("paragraphStyleOptions", json!({"name": "Child"})); - d.fields.insert("p.wordSpaceDesired".into(), json!(1.0)); - assert!(check_paragraph_style_justification(&app, &d).is_err(), "below Loose's minimum"); - d.fields.insert("basedOn".into(), json!(designcraft_doc::BASIC_PARAGRAPH)); - assert_eq!(check_paragraph_style_justification(&app, &d), Ok(()), "[Basic Paragraph]'s minimum is lower"); - } - - /// With Reset To Base, the justification fields are checked against the base style (the - /// style's own minimum no longer applies), and Preview shows the reset on the page. - #[test] - fn reset_to_base_checks_and_previews_against_the_base() { - let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - app.run("file.new", json!({})).unwrap(); - let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Loose words"})).unwrap(); - app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 5})).unwrap(); - app.run("style.paragraph.create", json!({"name": "Loose", "basedOn": designcraft_doc::BASIC_PARAGRAPH, "para": {"wordSpaceMin": 1.2, "wordSpaceDesired": 1.3}, "chars": {"size": 30}})) - .unwrap(); - app.run("style.paragraph.apply", json!({"name": "Loose"})).unwrap(); - let mut d = Dialog::new("paragraphStyleOptions", json!({"name": "Loose"})); - d.fields.insert("p.wordSpaceDesired".into(), json!(1.1)); - assert!(check_paragraph_style_justification(&app, &d).is_err(), "below the style's own minimum"); - d.fields.insert("resetToBase".into(), json!(true)); - assert_eq!(check_paragraph_style_justification(&app, &d), Ok(()), "the base's minimum is 100%"); - d.fields.insert("preview".into(), json!(true)); - style_options_preview(&mut app, &mut d); - assert_eq!(d.fields[PREVIEWED]["resetToBase"], true); - let size = app.run("type.selectionAttrs", json!({})).unwrap()["chars"]["size"].clone(); - assert_eq!(size, json!(12.0), "the page shows the style reset to its base"); - app.session.cancel_interaction(); - } - #[test] fn color_theme_cancel_is_read_only_and_confirm_adds_the_preview() { let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index ee94dd4c2..20f6e9aba 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -3524,3 +3524,131 @@ fn character_style_options_set_the_digits() { assert_eq!(ctl(&mut app, "ui.dialog.confirm", json!({}))["ok"], true); assert_eq!(char_style(&app, "Emphasis").unwrap().chars.digits, Some(designcraft_doc::Digits::Farsi)); } + +/// The course's chapter 20: the "Recipe Categories" title formatted on the page (size, All +/// Caps, tracking, space after), then New Paragraph Style from it: the dialog starts from the +/// text, and with Apply Style to Selection the paragraph takes the new style. Next Style and +/// Reset To Base are set from General. +#[test] +fn a_new_paragraph_style_starts_from_the_selected_text() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Egg Recipes\rBoil them."})).unwrap(); + app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 11})).unwrap(); + app.run("type.char", json!({"attrs": {"size": 20, "capitalization": "allCaps", "tracking": 50}})).unwrap(); + app.run("type.para", json!({"attrs": {"spaceAfter": 6}})).unwrap(); + open_new_style(&mut app, true); + let d = app.ui.dialog.clone().unwrap(); + assert_eq!( + (d.fields["c.size"].clone(), d.fields["c.capitalization"].clone(), d.fields["p.spaceAfter"].clone()), + (json!(20.0), json!("allCaps"), json!(6.0)) + ); + assert!(d.b("apply") && !d.fields.contains_key("c.fontFamily"), "only what differs from [No Paragraph Style]"); + if let Some(d) = app.ui.dialog.as_mut() { + d.fields.insert("rename".into(), json!("Recipe Categories")); + d.fields.insert("nextStyle".into(), json!(designcraft_doc::BASIC_PARAGRAPH)); + } + confirm(&mut app).unwrap(); + let a = app.run("type.selectionAttrs", json!({})).unwrap(); + assert_eq!(a["paragraphStyle"], "Recipe Categories", "applied to the selection"); + assert_eq!(a["paraOverrides"].as_u64().unwrap_or(0) + a["charOverrides"].as_u64().unwrap_or(0), 0); + let style = |app: &DesignApp| app.session.doc().unwrap().doc.styles.para("Recipe Categories").cloned().unwrap(); + assert_eq!(style(&app).next_style.as_deref(), Some(designcraft_doc::BASIC_PARAGRAPH)); + assert_eq!(style(&app).chars.size, Some(20.0)); + // Reset To Base: the style keeps nothing of its own, apart from the edits made then. + app.ui.dialog = Some(Dialog::new("paragraphStyleOptions", json!({"name": "Recipe Categories", "basedOn": designcraft_doc::BASIC_PARAGRAPH}))); + if let Some(d) = app.ui.dialog.as_mut() { + d.fields.insert("resetToBase".into(), json!(true)); + d.fields.insert("c.tracking".into(), json!(10.0)); + } + confirm(&mut app).unwrap(); + let s = style(&app); + assert_eq!((s.chars.size, s.chars.tracking, s.para.space_after), (None, Some(10.0), None), "{s:?}"); + assert_eq!(s.based_on.as_deref(), Some(designcraft_doc::BASIC_PARAGRAPH)); +} + +/// The course's chapter 22: "eggs" made bold and red on the page, then New Character Style +/// from it. The dialog is titled as InDesign's, starts from what the text has of its own +/// over its paragraph style ("[None] + Bold + color"), and Apply Style to Selection gives the +/// text the style, with no override left. +#[test] +fn a_new_character_style_starts_from_the_selected_text() { + use egui_kittest::{Harness, kittest::Queryable}; + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Drop eggs into boiling water."})).unwrap(); + app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 29})).unwrap(); + app.run("style.paragraph.create", json!({"name": "Recipe", "chars": {"size": 14}})).unwrap(); + app.run("style.paragraph.apply", json!({"name": "Recipe"})).unwrap(); + app.run("text.select", json!({"story": f["story"], "anchor": 5, "focus": 9})).unwrap(); + app.run("type.char", json!({"attrs": {"fontStyle": "Bold", "fill": "C=15 M=100 Y=100 K=0"}})).unwrap(); + open_new_style(&mut app, false); + let d = app.ui.dialog.clone().unwrap(); + assert_eq!((d.fields["c.fontStyle"].clone(), d.fields["c.fill"].clone()), (json!("Bold"), json!("C=15 M=100 Y=100 K=0"))); + assert!(d.b("apply")); + let mut h = Harness::builder().with_size(egui::vec2(1000.0, 700.0)).build_ui_state( + |ui, app: &mut DesignApp| { + let key = egui::Id::new("new_character_style_test_fonts"); + if ui.data(|d| d.get_temp::(key)).is_none() { + crate::theme::install_fonts(ui.ctx(), ""); + ui.data_mut(|d| d.insert_temp(key, true)); + return; + } + show(app, ui.ctx()); + }, + app, + ); + h.run_steps(4); + for l in ["New Character Style", "Apply Style to Selection"] { + assert!(h.query_by_label(l).is_some(), "{l}"); + } + let app = h.state_mut(); + if let Some(d) = app.ui.dialog.as_mut() { + d.fields.insert("rename".into(), json!("eggs")); + } + confirm(app).unwrap(); + let style = app.session.doc().unwrap().doc.styles.char_style("eggs").cloned().unwrap(); + assert_eq!((style.chars.font_style.as_deref(), style.chars.fill.as_deref()), (Some("Bold"), Some("C=15 M=100 Y=100 K=0"))); + assert_eq!(style.chars.size, None, "the size is the paragraph style's, not the text's own"); + let a = app.run("type.selectionAttrs", json!({})).unwrap(); + assert_eq!((a["characterStyle"].clone(), a["charOverrides"].clone()), (json!("eggs"), json!(0)), "applied to the selection"); +} + +/// A Based On changed in the dialog (without Reset To Base) is what the fields are checked +/// against: the style's own settings over the new parent. +#[test] +fn a_changed_based_on_is_what_the_fields_are_checked_against() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + app.run("style.paragraph.create", json!({"name": "Loose", "para": {"wordSpaceMin": 1.2, "wordSpaceDesired": 1.3}})).unwrap(); + app.run("style.paragraph.create", json!({"name": "Child", "basedOn": "Loose"})).unwrap(); + let mut d = Dialog::new("paragraphStyleOptions", json!({"name": "Child"})); + d.fields.insert("p.wordSpaceDesired".into(), json!(1.0)); + assert!(check_paragraph_style_justification(&app, &d).is_err(), "below Loose's minimum"); + d.fields.insert("basedOn".into(), json!(designcraft_doc::BASIC_PARAGRAPH)); + assert_eq!(check_paragraph_style_justification(&app, &d), Ok(()), "[Basic Paragraph]'s minimum is lower"); +} + +/// With Reset To Base, the justification fields are checked against the base style (the +/// style's own minimum no longer applies), and Preview shows the reset on the page. +#[test] +fn reset_to_base_checks_and_previews_against_the_base() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let f = app.run("frame.create", json!({"rect": [36, 36, 400, 300], "content": "text", "text": "Loose words"})).unwrap(); + app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 5})).unwrap(); + app.run("style.paragraph.create", json!({"name": "Loose", "basedOn": designcraft_doc::BASIC_PARAGRAPH, "para": {"wordSpaceMin": 1.2, "wordSpaceDesired": 1.3}, "chars": {"size": 30}})) + .unwrap(); + app.run("style.paragraph.apply", json!({"name": "Loose"})).unwrap(); + let mut d = Dialog::new("paragraphStyleOptions", json!({"name": "Loose"})); + d.fields.insert("p.wordSpaceDesired".into(), json!(1.1)); + assert!(check_paragraph_style_justification(&app, &d).is_err(), "below the style's own minimum"); + d.fields.insert("resetToBase".into(), json!(true)); + assert_eq!(check_paragraph_style_justification(&app, &d), Ok(()), "the base's minimum is 100%"); + d.fields.insert("preview".into(), json!(true)); + style_options_preview(&mut app, &mut d); + assert_eq!(d.fields[PREVIEWED]["resetToBase"], true); + let size = app.run("type.selectionAttrs", json!({})).unwrap()["chars"]["size"].clone(); + assert_eq!(size, json!(12.0), "the page shows the style reset to its base"); + app.session.cancel_interaction(); +}