Files
photocraft/crates/engine/tests/pixel_lock_edits.rs
a32ba211c9 Clear, Cut and Image › Adjustments refuse a pixel-locked layer (#1103) (#1194)
* Clear, Cut and Image > Adjustments refuse a pixel-locked layer (#1103)

clear_area, destructive_adjust and auto_adjust took the layer's surface
directly. They now go through commands::paint_surface, which refuses a
pixel- or fully-locked layer with the same message painting uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* edit.cut: refuse a pixel-locked layer before filling the clipboard

copy() ran before clear_selected noticed the lock, so a refused Cut still
replaced the clipboard. Check the lock first (check_pixels_unlocked, shared
with paint_surface) and assert the clipboard is unchanged in the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: prekabreki <prekabreki@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 17:04:33 -04:00

62 lines
2.2 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Clear, Cut and Image › Adjustments refuse a pixel-locked layer and leave it unchanged, as
//! painting does (#1103).
use photocraft_engine::Session;
use serde_json::json;
/// A layer with a red stroke across it, pixels locked.
fn locked() -> Session {
let mut s = Session::new();
s.execute("file.new", json!({"width": 40, "height": 30})).unwrap();
s.execute("layer.new.layer", json!({})).unwrap();
s.execute("paint.stroke", json!({"points": [[5, 10], [20, 10]], "size": 8, "hardness": 1.0, "color": "#ff0000"})).unwrap();
s.execute("layer.lockLayers", json!({"pixels": true})).unwrap();
s
}
fn px(s: &Session) -> [f32; 4] {
let st = s.active().unwrap();
st.active_layer.and_then(|id| st.doc.layer(id)).unwrap().surface().unwrap().rgba(12, 10)
}
#[test]
fn locked_layer_edits_are_refused() {
for (cmd, p) in [
("edit.clear", json!({})),
("edit.cut", json!({})),
("image.adjustments.invert", json!({})),
("image.autoTone", json!({})),
("image.autoContrast", json!({})),
("image.autoColor", json!({})),
] {
let mut s = locked();
let before = px(&s);
let r = s.execute(cmd, p);
assert!(r.is_err(), "{cmd} edited a locked layer: {r:?}");
assert_eq!(px(&s), before, "{cmd} changed a locked layer's pixels");
}
}
#[test]
fn a_refused_cut_leaves_the_clipboard_alone() {
let mut s = locked();
// Copy the whole canvas first, then select a smaller area: a Cut that went through would
// replace the clipboard with a smaller clip.
s.execute("edit.copy", json!({})).unwrap();
let before = s.clipboard.as_ref().map(|c| c.bounds);
assert!(before.is_some());
s.execute("select.rect", json!({"x": 2, "y": 2, "width": 10, "height": 10})).unwrap();
assert!(s.execute("edit.cut", json!({})).is_err());
assert_eq!(s.clipboard.as_ref().map(|c| c.bounds), before, "a refused Cut replaced the clipboard");
}
#[test]
fn unlocked_layer_edits_still_work() {
let mut s = locked();
s.execute("layer.lockLayers", json!({"pixels": false})).unwrap();
s.execute("image.adjustments.invert", json!({})).unwrap();
assert_eq!(px(&s), [0.0, 1.0, 1.0, 1.0]);
s.execute("edit.clear", json!({})).unwrap();
assert_eq!(px(&s)[3], 0.0);
}