From e862e0a613c0793c6fea7cfd37ad4145ed6cf686 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 13 Oct 2026 02:28:50 -0400 Subject: [PATCH] fix: integrate batched community PRs (#572 with #662's fallible spread_param; #524's test helper back in its module) Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/style.rs | 2 +- crates/ui-egui/src/panels/properties.rs | 86 ++++++++++++------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/engine/src/cmd/style.rs b/crates/engine/src/cmd/style.rs index 4fced4c..c4d20f0 100644 --- a/crates/engine/src/cmd/style.rs +++ b/crates/engine/src/cmd/style.rs @@ -1075,7 +1075,7 @@ fn has_text_or_selection(s: &Session) -> std::result::Result<(), String> { fn sample_color(s: &mut Session, p: &Value) -> Result { const ID: &str = "color.sample"; let at = super::point_param(p, "at").ok_or_else(|| bad(ID, "missing at"))?; - let sr = super::spread_param(p, "spread"); + let sr = super::spread_param(p, "spread")?; let st = s.doc()?; let (from, color) = image_color_at(&st.doc, sr, at).ok_or_else(|| bad(ID, "no placed image under the point"))?; let hex = color.to_hex(); diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 76ddf6b..d9c74e4 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -2345,6 +2345,49 @@ mod tests { // The angle and scale fields are there. assert!(h.query_by(|n| n.role() == egui::accesskit::Role::TextInput && n.value().as_deref() == Some("90°")).is_some()); } + + fn panel(app: DesignApp) -> Harness<'static, DesignApp> { + let mut h = Harness::builder().with_size(vec2(320.0, 1400.0)).build_ui_state( + |ui, app: &mut DesignApp| { + let id = egui::Id::new("test_fonts"); + if ui.data(|d| d.get_temp::(id)).is_none() { + crate::theme::install_fonts(ui.ctx(), ""); + ui.data_mut(|d| d.insert_temp(id, true)); + return; + } + show(app, ui); + }, + app, + ); + h.run_steps(3); + h + } + + #[test] + fn several_shapes_show_pathfinder_and_intersect_combines_them() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let a = app.run("frame.create", json!({"rect": [100, 100, 300, 300], "shape": "ellipse"})).unwrap()["id"].clone(); + let b = app.run("frame.create", json!({"rect": [200, 100, 300, 200]})).unwrap()["id"].clone(); + // One shape: no Pathfinder section. + app.run("selection.set", json!({"ids": [a]})).unwrap(); + let mut h = panel(app); + assert!(h.query_by_label("Pathfinder").is_none()); + // Two: the section and its five buttons; Intersect leaves one shape, the ellipse's quarter. + h.state_mut().run("selection.set", json!({"ids": [a, b]})).unwrap(); + h.run_steps(3); + assert!(h.query_by_label("Pathfinder").is_some()); + for op in ["Add", "Subtract", "Intersect", "Exclude Overlap", "Minus Back"] { + assert!(h.query_by_label(op).is_some(), "{op}"); + } + h.get_by_label("Intersect").click(); + h.run_steps(3); + let doc = &h.state().session.active().unwrap().doc; + let items = &doc.spreads[0].items; + assert_eq!(items.len(), 1, "one shape left"); + let r = items[0].bounds(); + assert!((r.x0 - 200.0).abs() < 0.5 && (r.y1 - 200.0).abs() < 0.5 && (r.x1 - 300.0).abs() < 0.5, "{r:?}"); + } } #[cfg(test)] @@ -2436,47 +2479,4 @@ mod effects_panel_tests { assert!(!it.isolate && !it.knockout, "group-only options stay disabled for a frame holding content"); assert_eq!(st.history.undo.len(), undo, "disabled controls do not create undo steps"); } - - fn panel(app: DesignApp) -> Harness<'static, DesignApp> { - let mut h = Harness::builder().with_size(vec2(320.0, 1400.0)).build_ui_state( - |ui, app: &mut DesignApp| { - let id = egui::Id::new("test_fonts"); - if ui.data(|d| d.get_temp::(id)).is_none() { - crate::theme::install_fonts(ui.ctx(), ""); - ui.data_mut(|d| d.insert_temp(id, true)); - return; - } - show(app, ui); - }, - app, - ); - h.run_steps(3); - h - } - - #[test] - fn several_shapes_show_pathfinder_and_intersect_combines_them() { - let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); - app.run("file.new", json!({})).unwrap(); - let a = app.run("frame.create", json!({"rect": [100, 100, 300, 300], "shape": "ellipse"})).unwrap()["id"].clone(); - let b = app.run("frame.create", json!({"rect": [200, 100, 300, 200]})).unwrap()["id"].clone(); - // One shape: no Pathfinder section. - app.run("selection.set", json!({"ids": [a]})).unwrap(); - let mut h = panel(app); - assert!(h.query_by_label("Pathfinder").is_none()); - // Two: the section and its five buttons; Intersect leaves one shape, the ellipse's quarter. - h.state_mut().run("selection.set", json!({"ids": [a, b]})).unwrap(); - h.run_steps(3); - assert!(h.query_by_label("Pathfinder").is_some()); - for op in ["Add", "Subtract", "Intersect", "Exclude Overlap", "Minus Back"] { - assert!(h.query_by_label(op).is_some(), "{op}"); - } - h.get_by_label("Intersect").click(); - h.run_steps(3); - let doc = &h.state().session.active().unwrap().doc; - let items = &doc.spreads[0].items; - assert_eq!(items.len(), 1, "one shape left"); - let r = items[0].bounds(); - assert!((r.x0 - 200.0).abs() < 0.5 && (r.y1 - 200.0).abs() < 0.5 && (r.x1 - 300.0).abs() < 0.5, "{r:?}"); - } }