Integration: refuse Paste Into a Lock-All group before pasting (#6352)

With the mask commands now honouring Lock All, Paste Into / Paste Outside with a
child of a Lock-All group active committed edit.paste and then failed on the
refused layer.layerMask.revealAll, leaving an unmasked pasted layer behind an
Err. paste_into now checks the destination group's locks first and refuses
cleanly. Test: mask_lock_all::paste_into_a_lock_all_group_refuses_before_pasting
(layer count and history unchanged). Also moves mask_lock_all.rs into main's
one-binary tests/it layout.

Co-authored-by: osaidahmed <170216456+osaidahmed@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Mann
2026-10-22 20:52:59 +02:00
co-authored by osaidahmed Claude Opus 5.5
parent 9095272487
commit edea110601
3 changed files with 29 additions and 0 deletions
+7
View File
@@ -366,6 +366,13 @@ fn paste_into(s: &mut Session, p: &Value, outside: bool) -> Result<Value> {
params["target"] = p.get("target").cloned().unwrap_or_default();
return crate::edit_cmds::paste_to_target(s, &params, false, Some(&limit), label);
}
// The pasted layer lands beside the active one, inside its group, and a Lock-All group refuses
// its layer mask: refuse before pasting rather than leave an unmasked paste behind.
let d = s.active().ok_or(EngineError::NoDocument)?;
let group = d.active_layer.and_then(|a| d.doc.path_of(a)).and_then(|p| d.doc.layer_at(p.split_last()?.1).map(|g| g.id));
if let Some(g) = group {
crate::commands::check_all_unlocked(&d.doc, g)?;
}
let key = step_key(s, "pasteInto");
params["coalesce"] = json!(key);
params["target"] = json!("pixels");
+1
View File
@@ -44,6 +44,7 @@ mod layer_mask_depth;
mod layer_props_several;
mod layer_range_panel_order;
mod live_line_transform;
mod mask_lock_all;
mod merge_transparent_bounds;
mod missing_layer_error;
mod nonfinite_style;
@@ -69,3 +69,24 @@ fn a_locked_layer_among_the_selected_layers_refuses_the_whole_mask() {
assert!(s.execute("layer.layerMask.hideAll", json!({})).is_err());
assert!(s.active().unwrap().doc.walk().iter().all(|(_, _, l)| l.mask.is_none()), "no layer got a mask");
}
#[test]
fn paste_into_a_lock_all_group_refuses_before_pasting() {
for cmd in ["edit.pasteSpecial.pasteInto", "edit.pasteSpecial.pasteOutside"] {
let mut s = Session::new();
s.execute("file.new", json!({"width": 40, "height": 30})).unwrap();
let child = s.execute("layer.new.layer", json!({"name": "Child"})).unwrap()["layer"].as_u64().unwrap();
s.execute("paint.stroke", json!({"points": [[5, 10], [20, 10]], "size": 8, "hardness": 1.0, "color": "#ff0000"})).unwrap();
s.execute("select.all", json!({})).unwrap();
s.execute("edit.copy", json!({})).unwrap();
let group = s.execute("layer.groupLayers", json!({})).unwrap()["layer"].as_u64().unwrap();
s.execute("layer.setProps", json!({"layer": group, "locks": {"all": true}})).unwrap();
s.execute("layer.select", json!({"layer": child, "mode": "replace"})).unwrap();
s.execute("select.rect", json!({"x": 2, "y": 2, "width": 20, "height": 15})).unwrap();
let layers = s.active().unwrap().doc.walk().len();
let undo = undo_depth(&s);
assert!(s.execute(cmd, json!({})).is_err(), "{cmd}");
assert_eq!(s.active().unwrap().doc.walk().len(), layers, "{cmd} pasted no layer");
assert_eq!(undo_depth(&s), undo, "{cmd} added no history step");
}
}