Files
lightcraft/crates/engine/src/tests_spots.rs
T
Brandon ThomasandClaude Opus 5.5 d91aaa4ca1 LR-REM-SPOT-EDIT + LR-REM-BRUSH-PARAMS: select, move, resize, refresh and delete spots; brush keys
Engine: spot.select / spot.update (move target or source, size, feather, opacity, mode) /
spot.refreshSource (next-best source away from the current one) / spot.delete (selected spot);
spot.add resolves the automatic source (so it has a pin and stays put at every size) and selects
the new spot. Remove tool: pins for targets and the selected spot's source, click to select, drag
to move; Size/Feather/Opacity sliders edit new and selected spots; ⌫ deletes the selected spot
(never the photo), / refreshes its source, [ ] size and Shift+[ ] feather (Masking brush too).
Command tests and headless pointer/keyboard tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:20:06 -04:00

58 lines
3.0 KiB
Rust

//! Remove tool commands: add (automatic source), select, move target/source, edit, refresh, delete.
use serde_json::json;
use crate::Session;
fn spots(s: &Session) -> Vec<lightcraft_develop::Spot> {
s.develop_of(s.active().unwrap()).unwrap().spots.clone()
}
#[test]
fn spots_select_move_edit_refresh_and_delete() {
let mut s = Session::with_demo();
// a remove spot gets a resolved source (a pin to show) and is selected
let r = s.execute("spot.add", &json!({"points": [[0.4, 0.5]], "size": 0.02})).unwrap();
assert_eq!(r["index"], 0);
assert_eq!(s.active_spot, Some(0));
let src0 = spots(&s)[0].source_offset.expect("automatic source resolved");
assert!(src0.x.hypot(src0.y) > 0.02, "the source is outside the spot: {src0:?}");
s.execute("spot.add", &json!({"mode": "clone", "points": [[0.7, 0.3]], "source": [0.1, 0.0]})).unwrap();
assert_eq!(s.active_spot, Some(1));
assert_eq!(s.execute("library.state", &json!({})).unwrap()["activeSpot"], 1);
// select
s.execute("spot.select", &json!({"index": 0})).unwrap();
assert_eq!(s.active_spot, Some(0));
assert!(s.execute("spot.select", &json!({"index": 9})).is_err());
// move the target (the source follows: its offset is relative), then the source alone
s.execute("spot.update", &json!({"move": [0.05, -0.1]})).unwrap();
let sp = &spots(&s)[0];
assert!((sp.points[0].x - 0.45).abs() < 1e-9 && (sp.points[0].y - 0.4).abs() < 1e-9);
assert_eq!(sp.source_offset, Some(src0));
s.execute("spot.update", &json!({"moveSource": [0.01, 0.02]})).unwrap();
let o = spots(&s)[0].source_offset.unwrap();
assert!((o.x - src0.x - 0.01).abs() < 1e-9 && (o.y - src0.y - 0.02).abs() < 1e-9);
// size / feather / opacity of the selected spot (clamped), one undo step each
s.execute("spot.update", &json!({"size": 0.03, "feather": 20, "opacity": 150})).unwrap();
let sp = &spots(&s)[0];
assert_eq!((sp.size, sp.feather, sp.opacity), (0.03, 20.0, 100.0));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(spots(&s)[0].size, 0.02);
// refresh: a different source, at least a spot radius away
let before = spots(&s)[0].source_offset.unwrap();
let r = s.execute("spot.refreshSource", &json!({})).unwrap();
let after = spots(&s)[0].source_offset.unwrap();
assert!((after.x - before.x).hypot(after.y - before.y) > 0.01, "{r}");
// delete the selected spot: the selection clears; deleting before a selected one shifts it
s.execute("spot.delete", &json!({})).unwrap();
assert_eq!((spots(&s).len(), s.active_spot), (1, None));
assert_eq!(spots(&s)[0].mode, lightcraft_develop::SpotMode::Clone);
assert!(s.execute("spot.delete", &json!({})).is_err(), "nothing selected");
s.execute("spot.add", &json!({"points": [[0.2, 0.2]], "source": [0.05, 0.0]})).unwrap();
s.execute("spot.delete", &json!({"index": 0})).unwrap();
assert_eq!(s.active_spot, Some(0));
// another photo: no selection
s.execute("library.next", &json!({})).unwrap();
assert_eq!(s.active_spot, None);
}