From 5b85ca6358d8e2241fc4d507ad2e7f26610651e1 Mon Sep 17 00:00:00 2001 From: makesomethingshit Date: Sat, 17 Oct 2026 14:23:20 +0900 Subject: [PATCH] test(blend): pin symmetric spine handles and invalid conversion inputs (#2544) --- crates/engine/src/tests_blendspine.rs | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/crates/engine/src/tests_blendspine.rs b/crates/engine/src/tests_blendspine.rs index 9d6f4d44f..33d825bc3 100644 --- a/crates/engine/src/tests_blendspine.rs +++ b/crates/engine/src/tests_blendspine.rs @@ -66,6 +66,57 @@ fn a_spine_point_added_and_moved_bends_the_blend() { assert!(center(&steps(&s, g)[1]).distance(Point::new(55.0, 5.0)) < 1e-6); } +/// #2544: an Anchor Point drag on a blend spine must create two symmetric +/// handles, not bend the adjacent segment and leave one handle behind. +#[test] +fn spine_anchor_conversion_preserves_keys_and_undo() { + let mut s = session(); + let (a, b, g) = blend(&mut s); + s.execute("object.blend.spine.addAnchor", &json!({"id": g.0, "x": 55, "y": 5})).unwrap(); + let keys_before = spec(&s, g).key_anchors; + s.execute( + "object.blend.spine.convertAnchor", + &json!({"id": g.0, "anchor": 1, "to": "smooth", "x": 55, "y": 35}), + ) + .unwrap(); + let sp = spec(&s, g); + assert_eq!(sp.key_anchors, keys_before); + let middle = sp.spine.as_ref().unwrap().subpaths[0].anchors[1]; + assert_eq!(middle.kind, vectorcraft_geom::AnchorKind::Smooth); + assert_eq!(middle.p, Point::new(55.0, 5.0)); + assert_eq!(middle.h_in, Point::new(55.0, -25.0)); + assert_eq!(middle.h_out, Point::new(55.0, 35.0)); + assert_eq!(center(&node(&s, a)), Point::new(5.0, 5.0)); + assert_eq!(center(&node(&s, b)), Point::new(105.0, 5.0)); + + s.execute("edit.undo", &json!({})).unwrap(); + let before = spec(&s, g); + assert_eq!(before.spine.unwrap().subpaths[0].anchors[1].h_out, Point::new(55.0, 5.0)); + s.execute("edit.redo", &json!({})).unwrap(); + assert_eq!(spec(&s, g).spine.unwrap().subpaths[0].anchors[1], middle); + + s.execute("object.blend.spine.convertAnchor", &json!({"id": g.0, "anchor": 1, "to": "corner"})).unwrap(); + let corner = spec(&s, g).spine.unwrap().subpaths[0].anchors[1]; + assert!(!corner.has_in() && !corner.has_out()); + assert_eq!(corner.kind, vectorcraft_geom::AnchorKind::Corner); +} + +#[test] +fn spine_conversion_rejects_invalid_arguments_without_mutation() { + let mut s = session(); + let (_, _, g) = blend(&mut s); + let before = spec(&s, g); + for params in [ + json!({"id": g.0, "anchor": 5, "to": "smooth", "x": 20, "y": 20}), + json!({"id": g.0, "anchor": 0, "to": "unknown"}), + json!({"id": g.0, "anchor": 0, "to": "smooth", "x": 10}), + json!({"id": g.0, "anchor": 0, "to": "smooth", "x": f64::MAX, "y": 1}), + ] { + assert!(s.execute("object.blend.spine.convertAnchor", ¶ms).is_err(), "{params}"); + assert_eq!(spec(&s, g), before); + } +} + #[test] fn moving_a_key_point_moves_the_key_and_the_other_way_round() { let mut s = session();