diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index 3d77bde15..58d5daf17 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -325,7 +325,7 @@ fn first_text_style(s: &Session) -> Option { fn same_text(s: &mut Session, cmd: &str, eq: fn(&CharStyle, &CharStyle) -> bool) -> Result { let r = first_text_style(s).ok_or_else(|| bad(cmd, "select a text object"))?; let ids = candidates(s, |n| match &n.kind { - NodeKind::Text(t) => t.runs.iter().any(|run| eq(&run.style, &r)), + NodeKind::Text(t) => t.runs.iter().any(|run| !run.text.is_empty() && eq(&run.style, &r)), _ => false, })?; finish_same(s, cmd, ids) diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs index f37855456..0c351dba7 100644 --- a/crates/engine/src/tests/menucmds.rs +++ b/crates/engine/src/tests/menucmds.rs @@ -1991,6 +1991,19 @@ fn select_same_text_fill_and_stroke_color_needs_both_to_match() { assert!(s.execute("select.same.textFillAndStrokeColor", &json!({})).is_err()); } +#[test] +fn select_same_character_colors_ignore_empty_text() { + let mut s = session(); + let a = id_of(&s.execute("text.create", &json!({"x": 0, "y": 20, "text": "a"})).unwrap()); + let b = id_of(&s.execute("text.create", &json!({"x": 0, "y": 40, "text": "b"})).unwrap()); + let empty = id_of(&s.execute("text.create", &json!({"x": 0, "y": 60, "text": ""})).unwrap()); + for cmd in ["select.same.textFillColor", "select.same.textStrokeColor", "select.same.textFillAndStrokeColor"] { + sel(&mut s, &[a]); + s.execute(cmd, &json!({})).unwrap(); + assert_eq!(selected(&s), vec![a, b], "{cmd}: empty object {empty:?}"); + } +} + #[test] fn select_same_link_block_series_selects_the_frames_of_a_thread() { let mut s = session();