diff --git a/crates/engine/src/cmd/brushsym/symbols.rs b/crates/engine/src/cmd/brushsym/symbols.rs index ee4487ba7..395c07052 100644 --- a/crates/engine/src/cmd/brushsym/symbols.rs +++ b/crates/engine/src/cmd/brushsym/symbols.rs @@ -493,10 +493,13 @@ pub(super) fn symbol_break_link(s: &mut Session, p: &Value) -> Result { if ids.is_empty() { return Err(EngineError::Other("no symbol instances selected".into())); } + let iso = s.doc()?.isolation; let out = s.edit("Break Link to Symbol", |d, sel| { let out = break_links(d, &ids, true)?; - // Hidden or locked art is left out of the selection. - sel.set(out.iter().copied().filter(|id| d.node(*id).is_some_and(|n| n.visible && !n.locked))); + // Art that is hidden or locked, or in a hidden or locked group or layer, is left out of the + // selection. + let locks = d.locked_artboards(); + sel.set(out.iter().copied().filter(|id| crate::cmd::select::is_selectable(d, iso, &locks, *id))); Ok(out) })?; Ok(json!({ "ids": ids_json(&out) })) diff --git a/crates/engine/src/tests/brushsym.rs b/crates/engine/src/tests/brushsym.rs index 34f2cc4b0..ccf277585 100644 --- a/crates/engine/src/tests/brushsym.rs +++ b/crates/engine/src/tests/brushsym.rs @@ -1358,6 +1358,34 @@ fn break_link_keeps_hidden_and_locked_instances_hidden_and_locked() { assert!(selected.iter().all(|id| d.node(*id).is_some_and(|n| n.visible && !n.locked)), "{selected:?}"); } +/// Break Link to Symbol on a selected group does not select the art of a shown, unlocked instance +/// that sits in a hidden or a locked group inside it. +#[test] +fn break_link_leaves_art_in_hidden_or_locked_groups_unselected() { + let mut s = session(); + let (shown, name, _) = make_symbol(&mut s); + let subgroup = |s: &mut Session, y: i64| { + let id = NodeId(s.execute("symbol.place", &json!({"name": name, "x": 300, "y": y})).unwrap()["id"].as_u64().unwrap()); + select(s, &[id]); + NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap()) + }; + let hidden = subgroup(&mut s, 200); + let locked = subgroup(&mut s, 100); + select(&mut s, &[shown, hidden, locked]); + let group = NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap()); + let doc = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc); + doc.node_mut(hidden).unwrap().visible = false; + doc.node_mut(locked).unwrap().locked = true; + select(&mut s, &[group]); + let r = s.execute("symbol.breakLink", &json!({})).unwrap(); + assert_eq!(r["ids"].as_array().unwrap().len(), 3, "{r}"); + let d = &s.doc().unwrap().doc; + let selected: Vec = s.doc().unwrap().selection.objects.to_vec(); + assert_eq!(selected.len(), 1, "{selected:?}"); + let a = d.ancestry(selected[0]).unwrap(); + assert!(!a.contains(&hidden) && !a.contains(&locked), "{a:?}"); +} + /// Delete Symbol, expanding the instances, keeps a hidden instance's art hidden and a locked /// instance's art locked. #[test]