diff --git a/crates/oa-desktop/src/behavior/state_label_canvas_tests.rs b/crates/oa-desktop/src/behavior/state_label_canvas_tests.rs index ee46ed43..46ebe4b9 100644 --- a/crates/oa-desktop/src/behavior/state_label_canvas_tests.rs +++ b/crates/oa-desktop/src/behavior/state_label_canvas_tests.rs @@ -323,3 +323,141 @@ fn state_multiline_label_group_preview_replaces_old_hit_rectangle_and_cancel_res ); assert_eq!(f.project, before); } + +#[test] +fn state_multiline_label_group_commit_matches_fixed_project_and_one_step_history() { + let (f, edge, _) = multiline_label_fixture(); + let b = placement(&f, f.b); + let c = placement(&f, f.c); + let before = f.project.clone(); + // Declare the complete expected model before driving input. No event, preview, + // movement planner or resulting document supplies the expected displacement. + let mut expected = before.clone(); + for id in [b, c] { + let node = expected + .diagrams + .get_mut(&f.diagram) + .unwrap() + .nodes + .get_mut(&id) + .unwrap(); + node.position.x += 40.; + node.position.y += 40.; + } + let original_route = before.diagrams[&f.diagram].routes[0].clone(); + let mut document = Document::from_project(before.clone(), Default::default()).unwrap(); + let original_source = document.bundle().clone(); + let mut view_canvas = canvas(&f, Tool::Select, 0.75); + let selection = group_selection(&view_canvas, [Target::Placement(b), Target::Placement(c)]); + let snapshot = selection + .snapshot(selection_context(view_canvas.context)) + .unwrap(); + view_canvas.selection = Some(&selection); + view_canvas.selected = f.c; + let mut state = State::default(); + let baseline = view_canvas.scene(&state).unwrap(); + let baseline_label = &baseline.routed.edges[&edge].label; + assert_eq!(baseline_label.center, Position { x: 440., y: 260. }); + // Explicit 0.75 scale: thirty screen pixels is the declared forty world + // units. The nonzero pan/origin and ordered pointer helpers stay in play. + let start = screen(&view_canvas, Point::new(780., 200.)); + let end = start + Vector::new(30., 30.); + motion(&view_canvas, &mut state, start, end); + assert!( + input( + &view_canvas, + &mut state, + mouse::Event::ButtonPressed(mouse::Button::Left), + end + ) + .is_none() + ); + assert!(state.group.is_some()); + motion(&view_canvas, &mut state, end, end); + let preview = view_canvas + .scene_with_bounds(&state, Some(bounds())) + .unwrap(); + let preview_label = &preview.routed.edges[&edge].label; + assert_eq!(preview_label.center, Position { x: 480., y: 300. }); + assert_eq!( + preview_label.bounds.position, + Position { + x: baseline_label.bounds.position.x + 40., + y: baseline_label.bounds.position.y + 40., + } + ); + assert_eq!(preview_label.bounds.size, baseline_label.bounds.size); + assert_eq!(preview_label.state_layout, baseline_label.state_layout); + assert_eq!( + f.project, before, + "Preview must not author its derived label" + ); + let event = input( + &view_canvas, + &mut state, + mouse::Event::ButtonReleased(mouse::Button::Left), + end, + ) + .unwrap(); + let Event::Selection(SelectionEvent::Move(actual_snapshot, delta)) = event else { + panic!("Expected a group movement event, got {event:?}"); + }; + assert_eq!(actual_snapshot, snapshot); + assert_eq!(delta, Position { x: 40., y: 40. }); + assert!(state.group.is_none() && state.press.is_none()); + assert!( + input( + &view_canvas, + &mut state, + mouse::Event::ButtonReleased(mouse::Button::Left), + end + ) + .is_none() + ); + // This is the same production snapshot-to-command boundary used by App's + // SelectionEvent::Move handler; App admission has its existing separate tests. + let ids = crate::app::multiselect::placements(&actual_snapshot).unwrap(); + let command = + oa_core::diagram_edit::translate_placements(document.project(), f.diagram, &ids, delta) + .unwrap(); + document.execute(command).unwrap(); + assert_eq!(document.project(), &expected); + assert_eq!( + document.project().diagrams[&f.diagram].routes[0], + original_route, + "Authored fraction, offset, attachments and empty waypoints remain exact" + ); + let committed = StateCanvas { + project: document.project(), + ..canvas(&f, Tool::Select, 0.75) + } + .scene(&State::default()) + .unwrap(); + assert_eq!( + committed.routed.edges[&edge].label, *preview_label, + "Measured settled label, including raw text and every line cell, matches preview" + ); + let edited_source = document.bundle().clone(); + assert_ne!( + edited_source, original_source, + "The group move is a real source edit" + ); + assert!(document.undo()); + assert_eq!(document.project(), &before); + assert_eq!(document.bundle(), &original_source); + assert!( + !document.undo(), + "One group release produces exactly one history entry" + ); + assert!(document.redo()); + assert_eq!(document.project(), &expected); + assert_eq!(document.bundle(), &edited_source); + assert!(!document.redo()); + assert_eq!( + selection + .snapshot(selection_context(view_canvas.context)) + .unwrap(), + snapshot + ); + assert_eq!(f.project, before); +}