Blend: tests for a pick dropped by Redo and for the history step

A Redo made outside the Blend tool drops its first pick, as an edit or an
Undo does, and two picks with no edit between them blend. History::step
changes with each new step, Undo and Redo, also when an Undo and a new
edit leave as many steps as before, and stays the same for a selection
change and for an edit merged into an undo group.
This commit is contained in:
dotCooCoo
2026-11-01 06:53:56 -08:00
parent 9703b86074
commit 4b663906e9
2 changed files with 68 additions and 12 deletions
+31 -12
View File
@@ -234,31 +234,50 @@ fn escape_drops_the_blend_tools_first_pick() {
}
#[test]
fn an_edit_or_undo_made_outside_the_blend_tool_drops_its_first_pick() {
for undo in [false, true] {
fn an_edit_undo_or_redo_made_outside_the_blend_tool_drops_its_first_pick() {
for case in ["edit", "undo", "redo"] {
let undo = case == "undo";
let mut s = session();
let (a, b, c) = (rect(&mut s, 0.0, 0.0, 40.0), rect(&mut s, 200.0, 0.0, 40.0), rect(&mut s, 400.0, 0.0, 40.0));
if case == "redo" {
s.execute("paint.setFill", &json!({"ids": [c.0], "color": "#ff0000"})).unwrap();
s.execute("edit.undo", &json!({})).unwrap();
}
s.select_tool("blend", ViewInfo::default()).unwrap();
let click = |s: &mut Session, x: f64, y: f64| s.pointer(&PointerEvent::new(PointerKind::Down, x, y), ViewInfo::default()).unwrap();
// Undo takes away c, the object picked first; an edit recolors c, which is not picked.
// Undo takes away c, the object picked first; an edit or a redo recolors c, which is not picked.
click(&mut s, if undo { 420.0 } else { 20.0 }, 20.0);
assert!(!s.overlays(ViewInfo::default()).is_empty());
if undo {
s.execute("edit.undo", &json!({})).unwrap();
} else {
s.execute("paint.setFill", &json!({"ids": [c.0], "color": "#ff0000"})).unwrap();
}
assert!(s.overlays(ViewInfo::default()).is_empty(), "undo {undo}: the pick is not marked");
match case {
"undo" => s.execute("edit.undo", &json!({})).unwrap(),
"redo" => s.execute("edit.redo", &json!({})).unwrap(),
_ => s.execute("paint.setFill", &json!({"ids": [c.0], "color": "#ff0000"})).unwrap(),
};
assert!(s.overlays(ViewInfo::default()).is_empty(), "{case}: the pick is not marked");
let (layers, depth) = (s.doc().unwrap().doc.layers.clone(), s.doc().unwrap().history.undo.len());
click(&mut s, 220.0, 20.0);
assert_eq!(s.doc().unwrap().selection.objects, vec![b], "undo {undo}: b is picked first");
assert_eq!((&s.doc().unwrap().doc.layers, s.doc().unwrap().history.undo.len()), (&layers, depth), "undo {undo}: the click changes no art");
assert_eq!(s.doc().unwrap().selection.objects, vec![b], "{case}: b is picked first");
assert_eq!((&s.doc().unwrap().doc.layers, s.doc().unwrap().history.undo.len()), (&layers, depth), "{case}: the click changes no art");
click(&mut s, if undo { 20.0 } else { 420.0 }, 20.0);
let g = s.doc().unwrap().selection.objects[0];
assert_eq!(spec(&s, g).0, if undo { vec![a, b] } else { vec![b, c] }, "undo {undo}");
assert_eq!(spec(&s, g).0, if undo { vec![a, b] } else { vec![b, c] }, "{case}");
}
}
#[test]
fn two_picks_with_no_edit_between_them_blend() {
let mut s = session();
let (a, b) = (rect(&mut s, 0.0, 0.0, 40.0), rect(&mut s, 200.0, 0.0, 40.0));
s.select_tool("blend", ViewInfo::default()).unwrap();
let click = |s: &mut Session, x: f64, y: f64| s.pointer(&PointerEvent::new(PointerKind::Down, x, y), ViewInfo::default()).unwrap();
click(&mut s, 20.0, 20.0);
// Drawing the overlays between the picks does not drop the first one.
assert!(!s.overlays(ViewInfo::default()).is_empty());
click(&mut s, 220.0, 20.0);
let g = s.doc().unwrap().selection.objects[0];
assert_eq!(spec(&s, g).0, vec![a, b]);
}
#[test]
fn removing_the_blend_the_blend_tool_adds_to_ends_adding() {
let mut s = session();
+37
View File
@@ -179,3 +179,40 @@ fn redo_during_an_interaction_keeps_the_history_and_the_document_in_step() {
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(first_x(&s), start);
}
/// `History::step` changes with every new step, Undo and Redo, also when an undo and a new edit
/// leave as many steps as before, and stays the same for an edit merged into an undo group and for
/// a selection change.
#[test]
fn the_history_step_changes_with_each_step_undo_and_redo() {
let mut s = session_with_rects(2);
let step = |s: &Session| s.doc().unwrap().history.step();
let start = step(&s);
assert!(start.0 != 0 && start.1 == 0, "the Add step, and nothing to redo: {start:?}");
s.execute("object.move", &json!({"dx": 1, "dy": 0})).unwrap();
let moved = step(&s);
assert!(moved.0 != start.0 && moved.1 == 0, "{moved:?}");
s.execute("edit.undo", &json!({})).unwrap();
let undone = step(&s);
assert!(undone.0 == start.0 && undone.1 != 0, "{undone:?}");
s.execute("edit.redo", &json!({})).unwrap();
let redone = step(&s);
assert!(![start, moved, undone].contains(&redone) && redone.1 == 0, "{redone:?}");
// An undo and a new edit: as many undo steps as after the move, at a different step.
s.execute("edit.undo", &json!({})).unwrap();
let depth = s.doc().unwrap().history.undo.len();
s.execute("object.move", &json!({"dx": 0, "dy": 1})).unwrap();
let other = step(&s);
assert_eq!(s.doc().unwrap().history.undo.len(), depth + 1);
assert!(![start, moved, undone, redone].contains(&other), "{other:?}");
s.execute("select.none", &json!({})).unwrap();
assert_eq!(step(&s), other, "a selection change is no step");
s.execute("select.all", &json!({})).unwrap();
s.begin_undo_group();
s.execute("object.move", &json!({"dx": 1, "dy": 0})).unwrap();
let grouped = step(&s);
s.execute("object.move", &json!({"dx": 1, "dy": 0})).unwrap();
assert_eq!(step(&s), grouped, "the second move is merged into the first");
s.end_undo_group(false);
assert_ne!(grouped, other);
}