Files
lightcraft/crates/engine/src/tests_spots.rs
T
Andrew FrahnandClaude Opus 5.5 03a3bbd561 Up / Down move through the grid's rows (#1171)
library.up / library.down (Up / Down) go to the cell above / below in the
grids (nearest by position in justified rows; the top / bottom row stays
put), and step one photo in the filmstrip.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 01:39:01 +11:00

85 lines
4.3 KiB
Rust

//! Remove tool commands: add (automatic source), select, move target/source, edit, refresh, delete.
use serde_json::json;
use crate::Session;
/// `library.up` / `library.down` (#1171): move by the given number of photos, clamped at the ends.
#[test]
fn up_and_down_move_by_rows_and_stop_at_the_ends() {
let mut s = Session::with_demo();
let vis = s.visible_cloned();
assert!(vis.len() >= 6);
s.selection = crate::Selection::single(vis[0]);
s.execute("library.down", &json!({"rows": 4})).unwrap();
assert_eq!(s.selection.active, Some(vis[4]));
s.execute("library.up", &json!({})).unwrap();
assert_eq!(s.selection.active, Some(vis[3]), "default is one photo (the filmstrip)");
s.execute("library.up", &json!({"rows": 99})).unwrap();
assert_eq!(s.selection.active, Some(vis[0]), "clamped at the first photo");
s.execute("library.down", &json!({"rows": u64::MAX})).unwrap();
assert_eq!(s.selection.active, vis.last().copied(), "hostile counts clamp, never overflow");
}
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);
}
#[test]
fn a_content_aware_spot_has_no_source() {
let mut s = Session::with_demo();
s.execute("spot.add", &json!({"mode": "fill", "points": [[0.4, 0.5]], "size": 0.03})).unwrap();
let sp = &spots(&s)[0];
assert_eq!((sp.mode, sp.source_offset), (lightcraft_develop::SpotMode::Fill, None));
assert_eq!(serde_json::to_value(sp).unwrap()["mode"], "fill");
assert!(s.execute("spot.update", &json!({"mode": "nonsense"})).is_err());
}