From 70a0d7a2d867cfa68f6dc901235daadc7cf03356 Mon Sep 17 00:00:00 2001 From: Simon Koch-Sultan <126534658+scks1@users.noreply.github.com> Date: Sun, 8 Nov 2026 15:36:50 -0500 Subject: [PATCH] engine: test ungrouping a top-level group with a group inside it Ungrouping a top-level group and a group inside it in one call, in either order, frees the shapes of both onto the slide in the top-level group's place, drawn where they were, returns them once each and selects them. The other ungroup tests leave the outermost group unnamed, so none checked that the inner group is found at the slide's top level after the same call frees the group around it. --- crates/engine/src/tests.rs | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index d1ce63f5d..7a417ab29 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -986,6 +986,53 @@ fn ungrouping_nested_groups_in_any_order_returns_only_freed_shapes() { assert_eq!(order, [sid(a), sid(b), sid(c), sid(d), sid(e)]); } +/// Ungrouping a top-level group and a group inside it in one call frees the shapes of both onto +/// the slide, in the top-level group's place and drawn where they were, whichever is named first. +#[test] +fn ungrouping_a_top_level_group_with_a_group_inside_it_frees_both() { + fn rect(s: &mut Session, x: f64, y: f64) -> u64 { + s.execute("shape.insert", &json!({"preset": "rect", "rect": [x, y, 100, 100]})).unwrap()["id"].as_u64().unwrap() + } + fn group(s: &mut Session, ids: [u64; 2]) -> u64 { + s.execute("arrange.group", &json!({"ids": ids})).unwrap()["id"].as_u64().unwrap() + } + let sid = |id: u64| ShapeId(u32::try_from(id).unwrap()); + let sites = |s: &Session, id: u64| { + let st = s.doc().unwrap(); + connect::sites_of(&st.doc, &st.selection, st.shapes(), sid(id)).unwrap() + }; + let roots = |s: &Session| -> Vec { s.doc().unwrap().shapes().iter().map(|x| x.id).collect() }; + for outer_first in [true, false] { + let mut s = session(); + s.execute("slide.new", &json!({"layout": "blank"})).unwrap(); + let [a, b, c, z] = [(100.0, 100.0), (300.0, 100.0), (100.0, 300.0), (500.0, 500.0)].map(|(x, y)| rect(&mut s, x, y)); + let inner = group(&mut s, [a, b]); + let outer = group(&mut s, [inner, c]); + s.execute("shape.resize", &json!({"ids": [outer], "w": 600})).unwrap(); + let before = [a, b, c].map(|id| sites(&s, id)); + let freed = [sid(a), sid(b), sid(c)]; + let mut expected = vec![]; + for x in roots(&s) { + if x == sid(outer) { + expected.extend(freed); + } else { + expected.push(x); + } + } + assert!(expected.contains(&sid(z))); + let ids = if outer_first { [outer, inner] } else { [inner, outer] }; + let r = s.execute("arrange.ungroup", &json!({"ids": ids})).unwrap(); + assert_eq!(r["ids"], json!([a, b, c]), "{ids:?}"); + assert_eq!(s.doc().unwrap().selection.shapes, freed, "{ids:?}"); + assert_eq!(roots(&s), expected, "{ids:?}"); + for (id, was) in [a, b, c].into_iter().zip(before) { + let now = sites(&s, id); + assert_eq!(was.len(), now.len(), "{ids:?}: shape {id}"); + assert!(was.iter().zip(&now).all(|(p, q)| (*p - *q).hypot() < 1e-6), "{ids:?}: shape {id} moved from {was:?} to {now:?}"); + } + } +} + /// Grouping a group again and again stops at `MAX_GROUP_DEPTH`: past about 39 levels a saved /// .deckcraft file no longer opens, and past 64 commands such as format.languageAll miss the text. #[test]