From 0409a481c7571e887f8912f0a8cdb93cfdcc01a7 Mon Sep 17 00:00:00 2001 From: Codex-ytr83r Date: Sun, 13 Sep 2026 02:55:57 -0700 Subject: [PATCH] Correct saved gesture fixtures and preserve explicit cancellation assertions --- .../behavior/scenario/presentation_tests.rs | 91 ++++++++++++++++--- .../scenario/spacing/controller/tests.rs | 39 ++++++-- 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/crates/oa-desktop/src/behavior/scenario/presentation_tests.rs b/crates/oa-desktop/src/behavior/scenario/presentation_tests.rs index d0754c26..7c3e92e4 100644 --- a/crates/oa-desktop/src/behavior/scenario/presentation_tests.rs +++ b/crates/oa-desktop/src/behavior/scenario/presentation_tests.rs @@ -188,28 +188,93 @@ fn complete_fallback_scene_selects_but_cannot_capture_or_finish_coordinate_gestu ); assert!(input.press.is_none() && input.source.is_none()); } - // A gesture already captured in a Ready frame is cancelled when capability is revoked. - scene.coordinate_input = true; - scene.interaction = Some((context, CanvasTool::Select, true)); + // Capture from an actual Ready presentation, then revoke coordinate input + // with the complete fallback frame of the same Saved representation. + let Representation::Saved { diagram } = rep else { + unreachable!() + }; + let mut ready_project = f.project.clone(); + ready_project.diagrams.get_mut(&diagram).unwrap().layout = + Some(DiagramLayout::Scenario(ScenarioLayout::default())); + let ready_original = ready_project.clone(); + let ready = state.display(&ready_project, f.owner, f.call).unwrap(); + assert!(matches!( + ready.status(), + presentation::PresentationStatus::Ready + )); + let mut ready_scene = ready.into_scene().unwrap(); + let line = ready_scene + .lines + .iter() + .find(|line| line.id == f.call) + .unwrap(); + let ready_point = Point::new( + (line.points[0].x + line.points.last().unwrap().x) / 2., + line.points[0].y, + ); + let hit = ready_scene.hit_item(ready_point).unwrap(); + assert_eq!(hit.id, f.call); + assert_eq!(hit.role, SceneHitRole::MessageBody); + let stamp = spacing::Stamp { + context, + camera_epoch: 17, + }; + ready_scene.coordinate_input = true; + ready_scene.interaction = Some((context, CanvasTool::Select, true)); + ready_scene.spacing_stamp = Some(stamp); let mut input = SequenceInput::default(); - let _ = canvas::Program::update( - &scene, + let begin = canvas::Program::update( + &ready_scene, &mut input, &canvas::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), bounds, - mouse::Cursor::Available(point), - ); - assert!(input.press.is_some()); - scene.coordinate_input = false; + mouse::Cursor::Available(ready_point), + ) + .and_then(|action| action.into_inner().0) + .unwrap(); + let Message::ScenarioCanvas(CanvasEvent::Spacing(spacing::Event::Begin { + stamp: captured_stamp, + token, + target: spacing::Target::Message(target), + .. + })) = begin + else { + panic!("Ready Saved message must begin a stamped spacing gesture"); + }; + assert_eq!(captured_stamp, stamp); + assert_eq!(target, f.call); + assert!(matches!(input.press, Some(SequencePress::Spacing { + stamp: held_stamp, token: held_token + }) if held_stamp == stamp && held_token == token)); + scene.interaction = Some((context, CanvasTool::Select, true)); + assert!(!scene.coordinate_input && scene.spacing_stamp.is_none()); + let release = canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)); let result = canvas::Program::update( &scene, &mut input, - &canvas::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)), + &release, bounds, - mouse::Cursor::Available(point), + mouse::Cursor::Available(ready_point), + ) + .and_then(|action| action.into_inner().0); + assert!( + matches!(result, Some(Message::ScenarioCanvas(CanvasEvent::Spacing( + spacing::Event::Cancel { stamp: cancelled_stamp, token: cancelled_token } + ))) if cancelled_stamp == stamp && cancelled_token == token) ); - assert!(result.and_then(|action| action.into_inner().0).is_none()); - assert!(input.press.is_none()); + assert!(input.press.is_none() && input.source.is_none()); + assert!( + canvas::Program::update( + &scene, + &mut input, + &release, + bounds, + mouse::Cursor::Available(ready_point), + ) + .and_then(|action| action.into_inner().0) + .is_none() + ); + assert_eq!(ready_project, ready_original); assert_eq!(f.project, original); } diff --git a/crates/oa-desktop/src/behavior/scenario/spacing/controller/tests.rs b/crates/oa-desktop/src/behavior/scenario/spacing/controller/tests.rs index 1344207d..7dd17288 100644 --- a/crates/oa-desktop/src/behavior/scenario/spacing/controller/tests.rs +++ b/crates/oa-desktop/src/behavior/scenario/spacing/controller/tests.rs @@ -345,10 +345,22 @@ fn fractional_screen_packet_preserves_subpixel_motion_in_preview_and_finish() { let camera = state.camera_boundary(); let context = state.context(document, owner, doc.revision(), 4).unwrap(); let baseline = resolve(doc.project(), context.representation, message).unwrap(); - let line = baseline.scene.lines.iter().find(|line| line.id == message).unwrap(); + let line = baseline + .scene + .lines + .iter() + .find(|line| line.id == message) + .unwrap(); assert_eq!(line.points[0].y, 148.); let begin = packet(doc.project(), &state, context, 9, Target::Message(message)); - let Event::Begin { press_screen, canvas_bounds, scale, pan, .. } = begin else { + let Event::Begin { + press_screen, + canvas_bounds, + scale, + pan, + .. + } = begin + else { unreachable!() }; assert_eq!(canvas_bounds.y, 23.); @@ -364,8 +376,8 @@ fn fractional_screen_packet_preserves_subpixel_motion_in_preview_and_finish() { // Independent oracle: 148 + (10 - 2^-15) / .5 = 168 - 2^-14. let expected_y = f32::from_bits(0x4327_fffc); let mut expected = original.decoded.project.clone(); - expected.diagrams.get_mut(&diagram).unwrap().layout = Some( - oa_core::DiagramLayout::Scenario(oa_core::ScenarioLayout { + expected.diagrams.get_mut(&diagram).unwrap().layout = + Some(oa_core::DiagramLayout::Scenario(oa_core::ScenarioLayout { lanes: vec![], messages: vec![oa_core::ScenarioMessagePosition { address: oa_core::OccurrenceEndpoint { @@ -374,15 +386,26 @@ fn fractional_screen_packet_preserves_subpixel_motion_in_preview_and_finish() { }, send_y: expected_y, }], - }), - ); + })); state.begin_spacing(doc.project(), begin).unwrap(); state.move_spacing(end); let preview = state.spacing.as_ref().unwrap().preview.as_ref().unwrap(); assert_eq!(preview.project, expected); - let line = preview.geometry.scene.lines.iter().find(|line| line.id == message).unwrap(); + let line = preview + .geometry + .scene + .lines + .iter() + .find(|line| line.id == message) + .unwrap(); assert_eq!(line.points[0].y.to_bits(), expected_y.to_bits()); - let Finish::Commit { command, selected, scale, pan } = state.finish_spacing(end).unwrap() else { + let Finish::Commit { + command, + selected, + scale, + pan, + } = state.finish_spacing(end).unwrap() + else { panic!("fractional motion must remain a commit") }; assert_eq!(selected, message);