From 53ee38431db91fd2556bebe12087f47fc59fdf5b Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Tue, 20 Oct 2026 07:19:12 -0400 Subject: [PATCH] LR-VIEW-PHOTOGRID: follow test over five seeds; comment the mode switch and the names The random-edit test now runs five seeds instead of one. A grid-mode change re-reading every photo, and the group names growing unpruned, are now said in the comments (the "a day or two at most" claim was wrong for capture-time edits). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/grid.rs | 6 +- crates/ui-egui/src/tests_grid.rs | 133 +++++++++++++++--------------- 2 files changed, 73 insertions(+), 66 deletions(-) diff --git a/crates/ui-egui/src/panels/grid.rs b/crates/ui-egui/src/panels/grid.rs index 6ce3c49d9..da335188d 100644 --- a/crates/ui-egui/src/panels/grid.rs +++ b/crates/ui-egui/src/panels/grid.rs @@ -150,6 +150,8 @@ impl ListRead { /// than photos of its own, such as a stack): read it whole then. fn follow(self, stats: &mut GridStats, cat: &Catalog, ids: &Arc<[PhotoId]>, dates: Option<(SortKey, GroupBy)>, shaped: bool) -> Option { if self.dates != dates || self.shapes.is_some() != shaped { + // (a switch between the photo grid and the square grid reads every photo once, on + // purpose: the other mode's data was never read, so there is nothing to follow) return None; } let touched: HashSet = cat.touched_since(self.revision)?.into_iter().collect(); @@ -157,7 +159,9 @@ impl ListRead { return None; } let mut r = ListRead::new(ids.clone(), cat.revision, dates, shaped); - // (the groups' names go on: an import adds a day or two at most) + // (the groups' names go on, unpruned: pruning would renumber every photo's group; they are + // only reset by a whole read, and grow by a short string per day or month an edit puts a + // photo in that no photo was in before) r.names = self.names; r.named = self.named; let mut was = 0; diff --git a/crates/ui-egui/src/tests_grid.rs b/crates/ui-egui/src/tests_grid.rs index 875598db0..dd4e625c5 100644 --- a/crates/ui-egui/src/tests_grid.rs +++ b/crates/ui-egui/src/tests_grid.rs @@ -285,74 +285,77 @@ fn a_batch_of_new_photos_or_an_edit_to_one_is_read_photo_by_photo() { /// the list changed by the photos the edit touched, else read whole. #[test] fn a_grid_followed_through_edits_shows_what_a_grid_made_afresh_shows() { - let mut h = grid(ViewMode::PhotoGrid); - let mut dice = 11u64; - let mut roll = |sides: u64| { - dice = lightcraft_catalog::mix64(dice); - dice % sides - }; - for step in 0..120 { - let vis = h.app.session.visible().to_vec(); - if vis.is_empty() { - // (no picks shown: back to every photo) - h.app.session.filter.flag = None; - continue; - } - let id = vis[roll(vis.len() as u64) as usize].0; - let n = roll(1000); - match roll(12) { - 0 => { - let ops: Vec = - (0..1 + roll(20)).map(|k| photo(&mut h, n * 100 + k, &format!("2026-{:02}-{:02}T10:00:00", 1 + n % 12, 1 + k % 28))).collect(); - h.app.session.commit("Import", Op::Batch { ops }).unwrap(); + for seed in [11u64, 12, 13, 14, 15] { + let mut h = grid(ViewMode::PhotoGrid); + let mut dice = seed; + let mut roll = |sides: u64| { + dice = lightcraft_catalog::mix64(dice); + dice % sides + }; + for step in 0..120 { + let vis = h.app.session.visible().to_vec(); + if vis.is_empty() { + // (no picks shown: back to every photo) + h.app.session.filter.flag = None; + continue; } - 1 => { - let _ = h.app.session.commit("Delete", Op::SetDeleted { id: PhotoId(id), deleted: true }); - } - 2 => { - let _ = h - .app - .session - .commit("Capture time", Op::SetCaptured { id: PhotoId(id), captured: Some(format!("2026-{:02}-03T10:00:00", 1 + n % 12)) }); - } - 3 => { - h.app.session.execute("library.select", &json!({"ids": [id]})).unwrap(); - let r = h.app.session.execute("photo.rotateRight", &json!({})); - assert!(r.is_ok(), "{r:?}"); - } - 4 => { - let mut d = (*h.app.session.catalog.photo(PhotoId(id)).unwrap().develop).clone(); - d.crop.geometry.rect = lightcraft_geom::Rect::new(0.1, 0.0, 0.9 - 0.1 * (n % 4) as f64, 1.0); - h.app - .session - .commit("Crop", Op::SetDevelop { id: PhotoId(id), settings: std::sync::Arc::new(d), label: "Crop".into(), edited: None }) - .unwrap(); - } - 5 => h.app.session.commit("Rating", Op::SetRating { id: PhotoId(id), rating: (n % 6) as u8 }).unwrap(), - 6 => { - let key = ["captureDate", "fileName", "importDate"][n as usize % 3]; - let r = h.app.session.execute("library.sort", &json!({"key": key, "ascending": n.is_multiple_of(2)})); - assert!(r.is_ok(), "{r:?}"); - } - 7 => { - // a stack: an edit to more than its photos, read whole - let other = vis[n as usize % vis.len()].0; - let _ = h.app.session.execute("stack.group", &json!({"ids": [id, other], "top": id, "collapsed": n.is_multiple_of(2)})); - } - 8 => { - let _ = h.app.session.execute("edit.undo", &json!({})); - } - 9 => { - // picks, for the filter below to show some - for k in 0..1 + n % 40 { - let p = vis[(n as usize * 7 + k as usize * 13) % vis.len()]; - h.app.session.commit("Flag", Op::SetFlag { id: p, flag: lightcraft_catalog::Flag::Pick }).unwrap(); + let id = vis[roll(vis.len() as u64) as usize].0; + let n = roll(1000); + match roll(12) { + 0 => { + let ops: Vec = (0..1 + roll(20)) + .map(|k| photo(&mut h, n * 100 + k, &format!("2026-{:02}-{:02}T10:00:00", 1 + n % 12, 1 + k % 28))) + .collect(); + h.app.session.commit("Import", Op::Batch { ops }).unwrap(); } + 1 => { + let _ = h.app.session.commit("Delete", Op::SetDeleted { id: PhotoId(id), deleted: true }); + } + 2 => { + let _ = h + .app + .session + .commit("Capture time", Op::SetCaptured { id: PhotoId(id), captured: Some(format!("2026-{:02}-03T10:00:00", 1 + n % 12)) }); + } + 3 => { + h.app.session.execute("library.select", &json!({"ids": [id]})).unwrap(); + let r = h.app.session.execute("photo.rotateRight", &json!({})); + assert!(r.is_ok(), "{r:?}"); + } + 4 => { + let mut d = (*h.app.session.catalog.photo(PhotoId(id)).unwrap().develop).clone(); + d.crop.geometry.rect = lightcraft_geom::Rect::new(0.1, 0.0, 0.9 - 0.1 * (n % 4) as f64, 1.0); + h.app + .session + .commit("Crop", Op::SetDevelop { id: PhotoId(id), settings: std::sync::Arc::new(d), label: "Crop".into(), edited: None }) + .unwrap(); + } + 5 => h.app.session.commit("Rating", Op::SetRating { id: PhotoId(id), rating: (n % 6) as u8 }).unwrap(), + 6 => { + let key = ["captureDate", "fileName", "importDate"][n as usize % 3]; + let r = h.app.session.execute("library.sort", &json!({"key": key, "ascending": n.is_multiple_of(2)})); + assert!(r.is_ok(), "{r:?}"); + } + 7 => { + // a stack: an edit to more than its photos, read whole + let other = vis[n as usize % vis.len()].0; + let _ = h.app.session.execute("stack.group", &json!({"ids": [id, other], "top": id, "collapsed": n.is_multiple_of(2)})); + } + 8 => { + let _ = h.app.session.execute("edit.undo", &json!({})); + } + 9 => { + // picks, for the filter below to show some + for k in 0..1 + n % 40 { + let p = vis[(n as usize * 7 + k as usize * 13) % vis.len()]; + h.app.session.commit("Flag", Op::SetFlag { id: p, flag: lightcraft_catalog::Flag::Pick }).unwrap(); + } + } + 10 => h.app.ui.view = if h.app.ui.view == ViewMode::PhotoGrid { ViewMode::SquareGrid } else { ViewMode::PhotoGrid }, + _ => h.app.session.filter.flag = n.is_multiple_of(2).then_some(lightcraft_catalog::Flag::Pick), } - 10 => h.app.ui.view = if h.app.ui.view == ViewMode::PhotoGrid { ViewMode::SquareGrid } else { ViewMode::PhotoGrid }, - _ => h.app.session.filter.flag = n.is_multiple_of(2).then_some(lightcraft_catalog::Flag::Pick), + h.step(); + assert_as_made_afresh(&mut h, &format!("seed {seed}, after step {step}")); } - h.step(); - assert_as_made_afresh(&mut h, &format!("after step {step}")); } }