From 805a121e1537a34ffb4705da387132ff1b255250 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 16 Oct 2026 16:30:58 +0500 Subject: [PATCH] Hold the duplicate step for a dragged and a nudged copy (#567) Duplicate repeats the last duplicate's offset, but of the ways to move the copy before the second Cmd+D only `node.set` and Alt-drag were covered. Dragging it with the mouse is how almost everyone does it, and is what #567 describes; an arrow-key nudge is how a small offset is usually made exact. Both work, because the step is measured from where the copy ended up rather than recorded from the gesture, but nothing held them there. The drag test also asserts the drag moved the copy rather than making another one, left it selected, and left the original in place: the ways the feature could break without the step arithmetic breaking. Both fail if the step lookup in `edit.duplicate` is stubbed out. --- crates/engine/src/duplicate_spacing_tests.rs | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/engine/src/duplicate_spacing_tests.rs b/crates/engine/src/duplicate_spacing_tests.rs index b65fbb64..4c62179a 100644 --- a/crates/engine/src/duplicate_spacing_tests.rs +++ b/crates/engine/src/duplicate_spacing_tests.rs @@ -157,3 +157,49 @@ fn an_alt_drag_into_auto_layout_sets_no_step() { cmd_d(&mut s); assert_eq!(s.doc().unwrap().node(f).unwrap().children.len(), n + 1, "Duplicate still adds one to the flow"); } + +/// Drag `from` by `by` with no modifier: moving the selection, not copying it. +fn drag_move(s: &mut Session, from: (f64, f64), by: (f64, f64)) { + let ev = |kind, (x, y): (f64, f64)| PointerEvent::at(kind, x, y, 1.0); + s.pointer(&ev(PointerKind::Down, from)).unwrap(); + for k in 1..=4 { + let t = f64::from(k) / 4.0; + s.pointer(&ev(PointerKind::Move, (from.0 + by.0 * t, from.1 + by.1 * t))).unwrap(); + } + s.pointer(&ev(PointerKind::Up, (from.0 + by.0, from.1 + by.1))).unwrap(); +} + +/// The reported path (#567): Cmd+D, drag the copy with the mouse, Cmd+D again. The other tests +/// here set the copy's position with `node.set` or Alt-drag it; dragging is how it is usually done, +/// and the step is measured from where the copy ended up, whichever way it got there. +#[test] +fn duplicate_repeats_the_step_set_by_dragging_the_copy() { + let mut s = Session::new(); + let a = rect(&mut s, 0.0, 0.0); + ex(&mut s, "select.set", json!({"ids": [a]})); + let c1 = cmd_d(&mut s); + assert_eq!(at(&s, c1), (0.0, 0.0), "the first copy lands in place"); + + drag_move(&mut s, (50.0, 50.0), (150.0, 20.0)); + assert_eq!(at(&s, c1), (150.0, 20.0), "the drag moved the copy, it did not make another one"); + assert_eq!(s.selection(), vec![c1], "and left it selected"); + assert_eq!(at(&s, a), (0.0, 0.0), "the original stays"); + + let c2 = cmd_d(&mut s); + assert_eq!(at(&s, c2), (300.0, 40.0), "the next one as far again"); + let c3 = cmd_d(&mut s); + assert_eq!(at(&s, c3), (450.0, 60.0), "and again"); +} + +/// And a step set with the arrow keys, which is how a small offset is usually made exact. +#[test] +fn duplicate_repeats_a_nudged_step() { + let mut s = Session::new(); + let a = rect(&mut s, 0.0, 0.0); + ex(&mut s, "select.set", json!({"ids": [a]})); + let c1 = cmd_d(&mut s); + ex(&mut s, "transform.nudge", json!({"dx": 40, "dy": 0})); + assert_eq!(at(&s, c1), (40.0, 0.0)); + let c2 = cmd_d(&mut s); + assert_eq!(at(&s, c2), (80.0, 0.0), "the nudge is the step"); +}