From aa2f193196f6bd39e547fc723b6401a35bf274b5 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Fri, 1 May 2026 20:34:19 +0800 Subject: [PATCH] {"schema":"maestro/commit/1","summary":"Trim duplicate overlay tests","authority":"manual"} --- .../src/overlay/tests/export_actions.rs | 64 +-- .../src/overlay/tests/live_runtime.rs | 109 ++--- .../capture_affordances.rs | 378 ++++++++---------- .../rendering_behaviors/selection_runtime.rs | 139 +++---- .../rendering_behaviors/toolbar_layout.rs | 199 ++++----- .../src/overlay/tests/scroll_input_runtime.rs | 165 +++----- .../src/overlay/tests/self_capture_runtime.rs | 172 ++++---- .../overlay/tests/stream_refresh_runtime.rs | 266 ++++++------ .../src/overlay/tests/toolbar_runtime.rs | 88 ++-- 9 files changed, 636 insertions(+), 944 deletions(-) diff --git a/packages/rsnap-overlay/src/overlay/tests/export_actions.rs b/packages/rsnap-overlay/src/overlay/tests/export_actions.rs index b9bd17d0..a713fe80 100644 --- a/packages/rsnap-overlay/src/overlay/tests/export_actions.rs +++ b/packages/rsnap-overlay/src/overlay/tests/export_actions.rs @@ -150,60 +150,34 @@ fn complete_host_effect_request_runs_overlay_exit_cleanup() { } #[test] -fn current_export_image_includes_frozen_brush_strokes() { +fn current_export_image_includes_frozen_brush_strokes_with_selected_color() { let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); - - session.state.begin_freeze(monitor); - session - .state - .commit_frozen_final_image(monitor, RgbaImage::from_pixel(8, 8, Rgba([12, 34, 56, 255]))); - - session.state.frozen_capture_rect = Some(RectPoints::new(0, 0, 8, 8)); - - tests::promote_session_export_authority_ready(&mut session); - - session.toolbar_state.selected_tool = FrozenToolbarTool::Pen; - - assert!(session.begin_frozen_brush_stroke(GlobalPoint::new(2, 2))); - assert!(session.update_frozen_brush_stroke(GlobalPoint::new(5, 2))); - assert!(session.finish_frozen_brush_stroke()); - - let export_image = session.current_export_image().expect("annotated export image"); - - assert_eq!(export_image.get_pixel(7, 7), &Rgba([12, 34, 56, 255])); - assert_eq!( - export_image.get_pixel(2, 2), - &Rgba(session.toolbar_state.brush_style.color.export_rgba()) - ); -} -#[test] -fn current_export_image_uses_selected_brush_color() { - let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); + for color in [FrozenAnnotationColor::Red, FrozenAnnotationColor::Green] { + let mut session = OverlaySession::new(); - session.state.begin_freeze(monitor); - session - .state - .commit_frozen_final_image(monitor, RgbaImage::from_pixel(8, 8, Rgba([12, 34, 56, 255]))); + session.state.begin_freeze(monitor); + session.state.commit_frozen_final_image( + monitor, + RgbaImage::from_pixel(8, 8, Rgba([12, 34, 56, 255])), + ); - session.state.frozen_capture_rect = Some(RectPoints::new(0, 0, 8, 8)); + session.state.frozen_capture_rect = Some(RectPoints::new(0, 0, 8, 8)); - tests::promote_session_export_authority_ready(&mut session); + tests::promote_session_export_authority_ready(&mut session); - session.toolbar_state.selected_tool = FrozenToolbarTool::Pen; - session.toolbar_state.brush_style.color = FrozenAnnotationColor::Green; + session.toolbar_state.selected_tool = FrozenToolbarTool::Pen; + session.toolbar_state.brush_style.color = color; - assert!(session.begin_frozen_brush_stroke(GlobalPoint::new(2, 2))); - assert!(session.finish_frozen_brush_stroke()); + assert!(session.begin_frozen_brush_stroke(GlobalPoint::new(2, 2))); + assert!(session.update_frozen_brush_stroke(GlobalPoint::new(5, 2))); + assert!(session.finish_frozen_brush_stroke()); - let export_image = session.current_export_image().expect("annotated export image"); + let export_image = session.current_export_image().expect("annotated export image"); - assert_eq!( - export_image.get_pixel(2, 2), - &Rgba(session.toolbar_state.brush_style.color.export_rgba()) - ); + assert_eq!(export_image.get_pixel(7, 7), &Rgba([12, 34, 56, 255])); + assert_eq!(export_image.get_pixel(2, 2), &Rgba(color.export_rgba())); + } } #[test] diff --git a/packages/rsnap-overlay/src/overlay/tests/live_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/live_runtime.rs index 90baf95e..bf18e13f 100644 --- a/packages/rsnap-overlay/src/overlay/tests/live_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/live_runtime.rs @@ -1757,28 +1757,17 @@ fn wants_global_frozen_hotkeys_only_in_plain_frozen_mode() { #[cfg(target_os = "macos")] #[test] -fn global_escape_hotkey_cancels_live_capture() { - let mut session = OverlaySession::new(); - - session.state.mode = OverlayMode::Live; - - assert!(matches!( - session.handle_global_escape_hotkey(), - OverlayControl::Exit(OverlayExit::Cancelled) - )); -} - -#[cfg(target_os = "macos")] -#[test] -fn global_escape_hotkey_cancels_frozen_capture() { - let mut session = OverlaySession::new(); +fn global_escape_hotkey_cancels_active_capture_modes() { + for mode in [OverlayMode::Live, OverlayMode::Frozen] { + let mut session = OverlaySession::new(); - session.state.mode = OverlayMode::Frozen; + session.state.mode = mode; - assert!(matches!( - session.handle_global_escape_hotkey(), - OverlayControl::Exit(OverlayExit::Cancelled) - )); + assert!(matches!( + session.handle_global_escape_hotkey(), + OverlayControl::Exit(OverlayExit::Cancelled) + )); + } } #[cfg(target_os = "macos")] @@ -1799,44 +1788,33 @@ fn global_loupe_hotkey_tracks_live_hold_state() { #[cfg(target_os = "macos")] #[test] -fn global_frozen_copy_hotkey_queues_copy_without_key_window() { +fn global_frozen_copy_hotkey_queues_copy_unless_text_edit_is_active() { let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); - session.session_active = true; + for (label, active_text_edit, expected_action) in + [("no active text edit", false, Some(PngAction::Copy)), ("active text edit", true, None)] + { + let mut session = OverlaySession::new(); - session.state.begin_freeze(monitor); + session.session_active = true; - tests::finish_frozen_ready_state(&mut session, monitor, tests::test_frozen_image()); + session.state.begin_freeze(monitor); - assert!(matches!( - session.handle_global_frozen_hotkey(FrozenGlobalHotkey::Copy), - OverlayControl::Continue - )); - assert_eq!(session.pending_png_action, Some(PngAction::Copy)); -} + tests::finish_frozen_ready_state(&mut session, monitor, tests::test_frozen_image()); -#[cfg(target_os = "macos")] -#[test] -fn global_frozen_copy_hotkey_ignores_active_text_edit() { - let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); - - session.session_active = true; - - session.state.begin_freeze(monitor); - - tests::finish_frozen_ready_state(&mut session, monitor, tests::test_frozen_image()); + if active_text_edit { + session.state.frozen_capture_rect = Some(RectPoints::new(100, 120, 220, 180)); + session.toolbar_state.selected_tool = FrozenToolbarTool::Text; - session.state.frozen_capture_rect = Some(RectPoints::new(100, 120, 220, 180)); - session.toolbar_state.selected_tool = FrozenToolbarTool::Text; + assert!(session.begin_frozen_text_edit_at(monitor, GlobalPoint::new(140, 160))); + } - assert!(session.begin_frozen_text_edit_at(monitor, GlobalPoint::new(140, 160))); - assert!(matches!( - session.handle_global_frozen_hotkey(FrozenGlobalHotkey::Copy), - OverlayControl::Continue - )); - assert_eq!(session.pending_png_action, None); + assert!(matches!( + session.handle_global_frozen_hotkey(FrozenGlobalHotkey::Copy), + OverlayControl::Continue + )); + assert_eq!(session.pending_png_action, expected_action, "{label}"); + } } #[test] @@ -2202,29 +2180,18 @@ fn live_hud_rgb_text_uses_fixed_width_placeholders() { } #[test] -fn stable_live_loupe_side_prefers_configured_patch_side() { - let mut state = OverlayState::new(); +fn stable_live_loupe_side_prefers_configured_patch_side_over_runtime_patch() { + for (patch_width, patch_height) in [(17, 19), (25, 25)] { + let mut state = OverlayState::new(); - state.loupe_patch_side_px = 21; - state.loupe = Some(LoupeSample { - center: GlobalPoint::new(100, 120), - patch: RgbaImage::from_pixel(17, 19, image::Rgba([0, 0, 0, 255])), - }); - - assert_eq!(hud_helpers::stable_live_loupe_side_px(&state), 21); -} - -#[test] -fn stable_live_loupe_side_ignores_larger_runtime_patch() { - let mut state = OverlayState::new(); - - state.loupe_patch_side_px = 21; - state.loupe = Some(LoupeSample { - center: GlobalPoint::new(100, 120), - patch: RgbaImage::from_pixel(25, 25, image::Rgba([0, 0, 0, 255])), - }); + state.loupe_patch_side_px = 21; + state.loupe = Some(LoupeSample { + center: GlobalPoint::new(100, 120), + patch: RgbaImage::from_pixel(patch_width, patch_height, image::Rgba([0, 0, 0, 255])), + }); - assert_eq!(hud_helpers::stable_live_loupe_side_px(&state), 21); + assert_eq!(hud_helpers::stable_live_loupe_side_px(&state), 21); + } } #[test] diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/capture_affordances.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/capture_affordances.rs index 8cb0f4fe..2ded0fda 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/capture_affordances.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/capture_affordances.rs @@ -9,58 +9,78 @@ use crate::overlay::tests::rendering_behaviors::{ }; #[test] -fn selection_size_badge_rect_fits_below_capture_rect() { +fn selection_size_badge_rect_uses_visible_slot_for_common_edges() { let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(120.0, 160.0), Vec2::new(320.0, 240.0)); - let badge_rect = - WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, Vec2::new(92.0, 26.0)); - - assert_eq!(badge_rect.max.x, capture_rect.max.x); - assert_eq!(badge_rect.min.y, capture_rect.max.y + SELECTION_SIZE_BADGE_GAP_PX); -} - -#[test] -fn selection_size_badge_rect_falls_inside_when_no_space_below() { - let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(120.0, 420.0), Vec2::new(320.0, 160.0)); - let badge_rect = - WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, Vec2::new(92.0, 26.0)); - assert_eq!(badge_rect.max.x, capture_rect.max.x); - assert_eq!(badge_rect.max.y, capture_rect.max.y - SELECTION_SIZE_BADGE_INSIDE_MARGIN_PX); - assert!(badge_rect.max.y <= screen_rect.max.y - SELECTION_SIZE_BADGE_SCREEN_MARGIN_PX); -} - -#[test] -fn selection_size_badge_rect_clamps_left_narrow_capture_into_viewport() { - let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - - for capture_min_x in [0.0, 20.0] { - let capture_rect = - Rect::from_min_size(Pos2::new(capture_min_x, 160.0), Vec2::new(40.0, 120.0)); + for (label, capture_rect, expected_min_y, expected_max_y, expected_min_x) in [ + ( + "fits below", + Rect::from_min_size(Pos2::new(120.0, 160.0), Vec2::new(320.0, 240.0)), + Some(160.0 + 240.0 + SELECTION_SIZE_BADGE_GAP_PX), + None, + None, + ), + ( + "falls inside when no space below", + Rect::from_min_size(Pos2::new(120.0, 420.0), Vec2::new(320.0, 160.0)), + None, + Some(420.0 + 160.0 - SELECTION_SIZE_BADGE_INSIDE_MARGIN_PX), + None, + ), + ( + "clamps left edge", + Rect::from_min_size(Pos2::new(0.0, 160.0), Vec2::new(40.0, 120.0)), + None, + None, + Some(screen_rect.min.x), + ), + ( + "clamps narrow near-left capture", + Rect::from_min_size(Pos2::new(20.0, 160.0), Vec2::new(40.0, 120.0)), + None, + None, + Some(screen_rect.min.x), + ), + ( + "keeps tiny bottom capture visible", + Rect::from_min_size(Pos2::new(120.0, 588.0), Vec2::new(140.0, 12.0)), + None, + Some(screen_rect.max.y), + None, + ), + ] { let badge_rect = WindowRenderer::selection_size_badge_rect( screen_rect, capture_rect, Vec2::new(92.0, 26.0), ); - assert_eq!(badge_rect.min.x, screen_rect.min.x); - assert!(badge_rect.max.x > capture_rect.max.x); + if let Some(expected_min_y) = expected_min_y { + assert_eq!(badge_rect.min.y, expected_min_y, "{label}"); + } + if let Some(expected_max_y) = expected_max_y { + assert_eq!(badge_rect.max.y, expected_max_y, "{label}"); + } + if let Some(expected_min_x) = expected_min_x { + assert_eq!(badge_rect.min.x, expected_min_x, "{label}"); + assert!(badge_rect.max.x > capture_rect.max.x, "{label}"); + } else { + assert_eq!(badge_rect.max.x, capture_rect.max.x, "{label}"); + } + + if label == "falls inside when no space below" { + assert!( + badge_rect.max.y <= screen_rect.max.y - SELECTION_SIZE_BADGE_SCREEN_MARGIN_PX, + "{label}", + ); + } + if label == "keeps tiny bottom capture visible" { + assert!(badge_rect.min.y < capture_rect.min.y, "{label}"); + assert!(badge_rect.min.y >= screen_rect.min.y, "{label}"); + } } } -#[test] -fn selection_size_badge_rect_keeps_tiny_bottom_capture_visible() { - let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(120.0, 588.0), Vec2::new(140.0, 12.0)); - let badge_rect = - WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, Vec2::new(92.0, 26.0)); - - assert_eq!(badge_rect.max.y, screen_rect.max.y); - assert!(badge_rect.min.y < capture_rect.min.y); - assert!(badge_rect.min.y >= screen_rect.min.y); -} - #[test] fn frozen_drag_region_selection_size_badge_uses_above_slot_when_bottom_toolbar_slot_overlaps() { let monitor = tests::test_monitor(); @@ -295,37 +315,36 @@ fn selection_size_badge_text_uses_monitor_pixel_dimensions() { } #[test] -fn selection_size_badge_layout_keeps_visual_bounds_within_right_edge_rect() { +fn selection_size_badge_layout_keeps_visual_bounds_inside_badge_rect() { let ctx = tests::test_egui_context(); let layout = WindowRenderer::selection_size_badge_layout(&ctx, "240x160", HudTheme::Light, 1.0); let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(760.0, 160.0), Vec2::new(40.0, 120.0)); - let badge_rect = - WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, layout.badge_size); - let text_anchor = WindowRenderer::selection_size_badge_text_anchor(badge_rect, layout, 1.0); - let visual_bounds = - WindowRenderer::selection_size_badge_visual_bounds(text_anchor, layout.text_size, 1.0); - assert_eq!(badge_rect.max.x, capture_rect.max.x); - assert!(visual_bounds.min.x >= badge_rect.min.x); - assert!(visual_bounds.max.x <= badge_rect.max.x); -} - -#[test] -fn selection_size_badge_layout_keeps_visual_bounds_within_bottom_fallback_rect() { - let ctx = tests::test_egui_context(); - let layout = WindowRenderer::selection_size_badge_layout(&ctx, "240x160", HudTheme::Light, 1.0); - let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(120.0, 588.0), Vec2::new(140.0, 12.0)); - let badge_rect = - WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, layout.badge_size); - let text_anchor = WindowRenderer::selection_size_badge_text_anchor(badge_rect, layout, 1.0); - let visual_bounds = - WindowRenderer::selection_size_badge_visual_bounds(text_anchor, layout.text_size, 1.0); - - assert_eq!(badge_rect.max.y, screen_rect.max.y); - assert!(visual_bounds.min.y >= badge_rect.min.y); - assert!(visual_bounds.max.y <= badge_rect.max.y); + for (label, capture_rect) in [ + ("right edge rect", Rect::from_min_size(Pos2::new(760.0, 160.0), Vec2::new(40.0, 120.0))), + ( + "bottom fallback rect", + Rect::from_min_size(Pos2::new(120.0, 588.0), Vec2::new(140.0, 12.0)), + ), + ] { + let badge_rect = + WindowRenderer::selection_size_badge_rect(screen_rect, capture_rect, layout.badge_size); + let text_anchor = WindowRenderer::selection_size_badge_text_anchor(badge_rect, layout, 1.0); + let visual_bounds = + WindowRenderer::selection_size_badge_visual_bounds(text_anchor, layout.text_size, 1.0); + + if label == "right edge rect" { + assert_eq!(badge_rect.max.x, capture_rect.max.x, "{label}"); + } + if label == "bottom fallback rect" { + assert_eq!(badge_rect.max.y, screen_rect.max.y, "{label}"); + } + + assert!(visual_bounds.min.x >= badge_rect.min.x, "{label}"); + assert!(visual_bounds.max.x <= badge_rect.max.x, "{label}"); + assert!(visual_bounds.min.y >= badge_rect.min.y, "{label}"); + assert!(visual_bounds.max.y <= badge_rect.max.y, "{label}"); + } } #[test] @@ -392,37 +411,26 @@ fn live_capture_size_badge_target_skips_fullscreen_fallback_while_primary_down() } #[test] -fn frozen_capture_size_badge_target_uses_frozen_rect() { +fn frozen_capture_size_badge_target_uses_frozen_rect_even_when_tiny() { let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(1_000.0, 800.0)); - let mut state = OverlayState::new(); - state.mode = OverlayMode::Frozen; - state.frozen_capture_rect = Some(RectPoints::new(140, 180, 320, 240)); - - assert_eq!( - WindowRenderer::frozen_capture_size_badge_target(&state, screen_rect), - Some(SelectionSizeBadgeTarget { - rect: Rect::from_min_size(Pos2::new(140.0, 180.0), Vec2::new(320.0, 240.0)), - size_points: RectPoints::new(140, 180, 320, 240), - }) - ); -} - -#[test] -fn frozen_capture_size_badge_target_keeps_tiny_frozen_rect() { - let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(1_000.0, 800.0)); - let mut state = OverlayState::new(); - - state.mode = OverlayMode::Frozen; - state.frozen_capture_rect = Some(RectPoints::new(140, 180, 2, 1)); - - assert_eq!( - WindowRenderer::frozen_capture_size_badge_target(&state, screen_rect), - Some(SelectionSizeBadgeTarget { - rect: Rect::from_min_size(Pos2::new(140.0, 180.0), Vec2::new(2.0, 1.0)), - size_points: RectPoints::new(140, 180, 2, 1), - }) - ); + for frozen_rect in [RectPoints::new(140, 180, 320, 240), RectPoints::new(140, 180, 2, 1)] { + let mut state = OverlayState::new(); + + state.mode = OverlayMode::Frozen; + state.frozen_capture_rect = Some(frozen_rect); + + assert_eq!( + WindowRenderer::frozen_capture_size_badge_target(&state, screen_rect), + Some(SelectionSizeBadgeTarget { + rect: Rect::from_min_size( + Pos2::new(frozen_rect.x as f32, frozen_rect.y as f32), + Vec2::new(frozen_rect.width as f32, frozen_rect.height as f32) + ), + size_points: frozen_rect, + }) + ); + } } #[test] @@ -467,70 +475,66 @@ fn render_frozen_capture_affordance_keeps_tiny_frozen_badge_path() { } #[test] -fn render_live_capture_affordances_keep_hover_scrim_when_flow_disabled() { - let ctx = tests::test_egui_context(); - let layer = LayerId::new(Order::Foreground, Id::new("live-hover-flow-disabled")); - let painter = ctx.layer_painter(layer); - let monitor = tests::test_monitor(); - let screen_rect = - Rect::from_min_size(Pos2::ZERO, Vec2::new(monitor.width as f32, monitor.height as f32)); - let mut selection_dashed_border_cache = SelectionDashedBorderCache::default(); - let mut state = OverlayState::new(); - let mut selection_flow_geometry_cache = SelectionFlowGeometryCache::default(); - - state.mode = OverlayMode::Live; - state.hovered_window_rect = Some(MonitorRectPoints { - monitor_id: monitor.id, - rect: RectPoints::new(100, 120, 240, 320), - }); - - assert!(WindowRenderer::render_live_capture_affordances( - &ctx, - &painter, - &state, - monitor, - screen_rect, - HudTheme::Light, - false, - 1.0, - &mut selection_flow_geometry_cache, - &mut selection_dashed_border_cache, - )); - assert_eq!(selection_dashed_border_cache.key, None); -} +fn render_live_capture_affordances_updates_flow_or_dash_for_target() { + #[derive(Clone, Copy)] + enum TargetKind { + Hover, + Drag, + Fullscreen, + } -#[test] -fn render_live_capture_affordances_draw_hover_flow_when_enabled() { let ctx = tests::test_egui_context(); - let layer = LayerId::new(Order::Foreground, Id::new("live-hover-flow-enabled")); - let painter = ctx.layer_painter(layer); let monitor = tests::test_monitor(); let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(monitor.width as f32, monitor.height as f32)); - let mut selection_dashed_border_cache = SelectionDashedBorderCache::default(); - let mut state = OverlayState::new(); - let mut selection_flow_geometry_cache = SelectionFlowGeometryCache::default(); - state.mode = OverlayMode::Live; - state.hovered_window_rect = Some(MonitorRectPoints { - monitor_id: monitor.id, - rect: RectPoints::new(100, 120, 240, 320), - }); - - assert!(WindowRenderer::render_live_capture_affordances( - &ctx, - &painter, - &state, - monitor, - screen_rect, - HudTheme::Light, - true, - 1.0, - &mut selection_flow_geometry_cache, - &mut selection_dashed_border_cache, - )); - assert!(!selection_flow_geometry_cache.is_empty()); - assert_eq!(selection_dashed_border_cache.key, None); + for (label, target_kind, flow_enabled, theme, expected_flow, expected_dash) in [ + ("hover scrim with flow disabled", TargetKind::Hover, false, HudTheme::Light, false, false), + ("hover flow when enabled", TargetKind::Hover, true, HudTheme::Light, true, false), + ("drag border when flow disabled", TargetKind::Drag, false, HudTheme::Light, false, true), + ("idle fullscreen skips flow", TargetKind::Fullscreen, true, HudTheme::Dark, false, false), + ] { + let layer = LayerId::new(Order::Foreground, Id::new(label)); + let painter = ctx.layer_painter(layer); + let mut selection_dashed_border_cache = SelectionDashedBorderCache::default(); + let mut state = OverlayState::new(); + let mut selection_flow_geometry_cache = SelectionFlowGeometryCache::default(); + + state.mode = OverlayMode::Live; + + match target_kind { + TargetKind::Hover => { + state.hovered_window_rect = Some(MonitorRectPoints { + monitor_id: monitor.id, + rect: RectPoints::new(100, 120, 240, 320), + }); + }, + TargetKind::Drag => { + state.drag_rect = Some(MonitorRectPoints { + monitor_id: monitor.id, + rect: RectPoints::new(100, 120, 240, 320), + }); + }, + TargetKind::Fullscreen => { + state.cursor = Some(GlobalPoint::new(240, 260)); + }, + } + + assert!(WindowRenderer::render_live_capture_affordances( + &ctx, + &painter, + &state, + monitor, + screen_rect, + theme, + flow_enabled, + 1.0, + &mut selection_flow_geometry_cache, + &mut selection_dashed_border_cache, + )); + assert_eq!(!selection_flow_geometry_cache.is_empty(), expected_flow, "{label}"); + assert_eq!(selection_dashed_border_cache.key.is_some(), expected_dash, "{label}"); + } } #[test] @@ -550,70 +554,6 @@ fn selection_flow_light_palette_uses_lower_luminance_colors() { } } -#[test] -fn render_live_capture_affordances_draw_drag_border_when_flow_disabled() { - let ctx = tests::test_egui_context(); - let layer = LayerId::new(Order::Foreground, Id::new("live-drag-flow-disabled")); - let painter = ctx.layer_painter(layer); - let monitor = tests::test_monitor(); - let screen_rect = - Rect::from_min_size(Pos2::ZERO, Vec2::new(monitor.width as f32, monitor.height as f32)); - let mut selection_dashed_border_cache = SelectionDashedBorderCache::default(); - let mut state = OverlayState::new(); - let mut selection_flow_geometry_cache = SelectionFlowGeometryCache::default(); - - state.mode = OverlayMode::Live; - state.drag_rect = Some(MonitorRectPoints { - monitor_id: monitor.id, - rect: RectPoints::new(100, 120, 240, 320), - }); - - assert!(WindowRenderer::render_live_capture_affordances( - &ctx, - &painter, - &state, - monitor, - screen_rect, - HudTheme::Light, - false, - 1.0, - &mut selection_flow_geometry_cache, - &mut selection_dashed_border_cache, - )); - assert!(selection_dashed_border_cache.key.is_some()); -} - -#[test] -fn render_live_capture_affordances_skips_fullscreen_flow_without_hover_or_drag() { - let ctx = tests::test_egui_context(); - let layer = LayerId::new(Order::Foreground, Id::new("live-idle-no-flow")); - let painter = ctx.layer_painter(layer); - let monitor = tests::test_monitor(); - let screen_rect = - Rect::from_min_size(Pos2::ZERO, Vec2::new(monitor.width as f32, monitor.height as f32)); - let mut selection_dashed_border_cache = SelectionDashedBorderCache::default(); - let mut state = OverlayState::new(); - let mut selection_flow_geometry_cache = SelectionFlowGeometryCache::default(); - - state.mode = OverlayMode::Live; - state.cursor = Some(GlobalPoint::new(240, 260)); - - assert!(WindowRenderer::render_live_capture_affordances( - &ctx, - &painter, - &state, - monitor, - screen_rect, - HudTheme::Dark, - true, - 1.0, - &mut selection_flow_geometry_cache, - &mut selection_dashed_border_cache, - )); - assert!(selection_flow_geometry_cache.is_empty()); - assert_eq!(selection_dashed_border_cache.key, None); -} - #[test] fn live_capture_size_badge_target_keeps_tiny_drag_rect() { let monitor = tests::test_monitor(); diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/selection_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/selection_runtime.rs index 155a1038..46ff41d4 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/selection_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/selection_runtime.rs @@ -79,29 +79,42 @@ fn frozen_selection_drag_starts_corner_resize_from_handle_hit_zone() { } #[test] -fn frozen_selection_drag_updates_capture_rect_and_toolbar_position() { +fn frozen_selection_drag_or_resize_updates_capture_rect_and_toolbar_position() { let monitor = tests::test_monitor(); let capture_rect = RectPoints::new(100, 120, 200, 240); - let mut session = OverlaySession::new(); - session.state.begin_freeze(monitor); + for (press, drag_to, expected_rect) in [ + ( + GlobalPoint::new(150, 180), + GlobalPoint::new(300, 360), + RectPoints::new(250, 300, 200, 240), + ), + ( + GlobalPoint::new(95, 115), + GlobalPoint::new(160, 190), + RectPoints::new(165, 195, 135, 165), + ), + ] { + let mut session = OverlaySession::new(); - tests::finish_frozen_display_state(&mut session, monitor, tests::test_frozen_image()); + session.state.begin_freeze(monitor); - session.state.frozen_capture_rect = Some(capture_rect); - session.frozen_capture_source = FrozenCaptureSource::DragRegion; + tests::finish_frozen_display_state(&mut session, monitor, tests::test_frozen_image()); - session.seed_frozen_toolbar_default_position(monitor, capture_rect); + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; - assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); - assert!(session.update_frozen_selection_drag_rect(GlobalPoint::new(300, 360))); + session.seed_frozen_toolbar_default_position(monitor, capture_rect); - let expected_rect = RectPoints::new(250, 300, 200, 240); - let expected_toolbar_pos = - session.frozen_toolbar_default_position_for_capture_rect(monitor, expected_rect); + assert!(session.begin_frozen_selection_drag(press)); + assert!(session.update_frozen_selection_drag_rect(drag_to)); - assert_eq!(session.state.frozen_capture_rect, Some(expected_rect)); - assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); + let expected_toolbar_pos = + session.frozen_toolbar_default_position_for_capture_rect(monitor, expected_rect); + + assert_eq!(session.state.frozen_capture_rect, Some(expected_rect)); + assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); + } } #[test] @@ -252,32 +265,6 @@ fn frozen_selection_drag_does_not_rearm_initial_frontmost_restore() { assert!(!session.preserve_frontmost_on_next_toolbar_show); } -#[test] -fn frozen_selection_resize_updates_capture_rect_and_toolbar_position() { - let monitor = tests::test_monitor(); - let capture_rect = RectPoints::new(100, 120, 200, 240); - let mut session = OverlaySession::new(); - - session.state.begin_freeze(monitor); - - tests::finish_frozen_display_state(&mut session, monitor, tests::test_frozen_image()); - - session.state.frozen_capture_rect = Some(capture_rect); - session.frozen_capture_source = FrozenCaptureSource::DragRegion; - - session.seed_frozen_toolbar_default_position(monitor, capture_rect); - - assert!(session.begin_frozen_selection_drag(GlobalPoint::new(95, 115))); - assert!(session.update_frozen_selection_drag_rect(GlobalPoint::new(160, 190))); - - let expected_rect = RectPoints::new(165, 195, 135, 165); - let expected_toolbar_pos = - session.frozen_toolbar_default_position_for_capture_rect(monitor, expected_rect); - - assert_eq!(session.state.frozen_capture_rect, Some(expected_rect)); - assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); -} - #[test] fn frozen_selection_resize_preserves_handle_press_offset() { let monitor = tests::test_monitor(); @@ -400,61 +387,53 @@ fn cropped_frozen_capture_image_uses_moved_capture_rect() { } #[test] -fn auto_center_frozen_capture_rect_recenters_detected_content() { +fn auto_center_frozen_capture_rect_recenters_detected_content_across_tools() { let monitor = tests::test_monitor_with_scale(80, 60, 2_000); let capture_rect = RectPoints::new(20, 16, 40, 24); - let mut image = RgbaImage::from_pixel(160, 120, Rgba([14, 16, 20, 255])); - let mut session = OverlaySession::new(); - for y in 40..52 { - for x in 52..68 { - image.put_pixel(x, y, Rgba([228, 232, 240, 255])); + for (label, selected_tool, seed_toolbar) in [ + ("pointer tool recenters toolbar", FrozenToolbarTool::Pointer, true), + ("annotation tool remains eligible", FrozenToolbarTool::Mosaic, false), + ] { + let mut image = RgbaImage::from_pixel(160, 120, Rgba([14, 16, 20, 255])); + let mut session = OverlaySession::new(); + + for y in 40..52 { + for x in 52..68 { + image.put_pixel(x, y, Rgba([228, 232, 240, 255])); + } } - } - session.state.begin_freeze(monitor); + session.state.begin_freeze(monitor); - tests::finish_frozen_ready_state(&mut session, monitor, image); + tests::finish_frozen_ready_state(&mut session, monitor, image); - session.state.frozen_capture_rect = Some(capture_rect); - session.frozen_capture_source = FrozenCaptureSource::DragRegion; + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + session.toolbar_state.selected_tool = selected_tool; - session.seed_frozen_toolbar_default_position(monitor, capture_rect); + if seed_toolbar { + session.seed_frozen_toolbar_default_position(monitor, capture_rect); + } - assert!(session.auto_center_frozen_capture_rect()); + assert!(session.frozen_auto_center_available(), "{label}"); + assert!(session.auto_center_frozen_capture_rect(), "{label}"); - let expected_rect = RectPoints::new(10, 11, 40, 24); - let expected_toolbar_pos = - session.frozen_toolbar_default_position_for_capture_rect(monitor, expected_rect); + let expected_rect = RectPoints::new(10, 11, 40, 24); - assert_eq!(session.state.frozen_capture_rect, Some(expected_rect)); - assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); -} + assert_eq!(session.state.frozen_capture_rect, Some(expected_rect), "{label}"); -#[test] -fn auto_center_frozen_capture_rect_works_outside_pointer_mode() { - let monitor = tests::test_monitor_with_scale(80, 60, 2_000); - let capture_rect = RectPoints::new(20, 16, 40, 24); - let mut image = RgbaImage::from_pixel(160, 120, Rgba([14, 16, 20, 255])); - let mut session = OverlaySession::new(); + if seed_toolbar { + let expected_toolbar_pos = + session.frozen_toolbar_default_position_for_capture_rect(monitor, expected_rect); - for y in 40..52 { - for x in 52..68 { - image.put_pixel(x, y, Rgba([228, 232, 240, 255])); + assert_eq!( + session.toolbar_state.floating_position, + Some(expected_toolbar_pos), + "{label}", + ); } } - - session.state.begin_freeze(monitor); - - tests::finish_frozen_ready_state(&mut session, monitor, image); - - session.state.frozen_capture_rect = Some(capture_rect); - session.frozen_capture_source = FrozenCaptureSource::DragRegion; - session.toolbar_state.selected_tool = FrozenToolbarTool::Mosaic; - - assert!(session.frozen_auto_center_available()); - assert!(session.auto_center_frozen_capture_rect()); - assert_eq!(session.state.frozen_capture_rect, Some(RectPoints::new(10, 11, 40, 24))); } #[test] diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/toolbar_layout.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/toolbar_layout.rs index f6c49bd8..b05c7343 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/toolbar_layout.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors/toolbar_layout.rs @@ -363,47 +363,55 @@ fn frozen_toolbar_default_position_centers_on_capture_rect_midpoint() { } #[test] -fn frozen_toolbar_default_position_fits_below_capture_rect() { - let monitor = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(50.0, 100.0), Vec2::new(300.0, 200.0)); +fn frozen_toolbar_default_position_places_or_falls_inside_for_configured_edge() { let toolbar_size = Vec2::new(460.0, 54.0); - let pos = WindowRenderer::frozen_toolbar_default_window_pos( - monitor, - capture_rect, - toolbar_size, - toolbar_size, - ToolbarPlacement::Bottom, - ); - let expected_x = (capture_rect.center().x - toolbar_size.x / 2.0).clamp( - TOOLBAR_SCREEN_MARGIN_PX, - (monitor.max.x - toolbar_size.x - TOOLBAR_SCREEN_MARGIN_PX).max(TOOLBAR_SCREEN_MARGIN_PX), - ); - assert!((pos.x - expected_x).abs() < f32::EPSILON); - assert_eq!(pos.y, capture_rect.max.y + TOOLBAR_CAPTURE_GAP_PX); -} - -#[test] -fn frozen_toolbar_default_position_falls_inside_when_no_space_below_capture_rect() { - let monitor = Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 600.0)); - let toolbar_size = Vec2::new(460.0, 54.0); - let capture_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 560.0)); - let pos = WindowRenderer::frozen_toolbar_default_window_pos( - monitor, - capture_rect, - toolbar_size, - toolbar_size, - ToolbarPlacement::Bottom, - ); - let expected_x = (capture_rect.center().x - toolbar_size.x / 2.0).clamp( - TOOLBAR_SCREEN_MARGIN_PX, - (monitor.max.x - toolbar_size.x - TOOLBAR_SCREEN_MARGIN_PX).max(TOOLBAR_SCREEN_MARGIN_PX), - ); - let expected_y = capture_rect.max.y - TOOLBAR_SCREEN_MARGIN_PX - toolbar_size.y; + for (label, monitor, capture_rect, placement, expected_y) in [ + ( + "bottom placement fits below capture rect", + Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)), + Rect::from_min_size(Pos2::new(50.0, 100.0), Vec2::new(300.0, 200.0)), + ToolbarPlacement::Bottom, + 100.0 + 200.0 + TOOLBAR_CAPTURE_GAP_PX, + ), + ( + "bottom placement falls inside when below overflows", + Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 600.0)), + Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 560.0)), + ToolbarPlacement::Bottom, + 560.0 - TOOLBAR_SCREEN_MARGIN_PX - toolbar_size.y, + ), + ( + "top placement fits above capture rect", + Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)), + Rect::from_min_size(Pos2::new(50.0, 180.0), Vec2::new(300.0, 200.0)), + ToolbarPlacement::Top, + 180.0 - TOOLBAR_CAPTURE_GAP_PX - toolbar_size.y, + ), + ( + "top placement falls inside when above overflows", + Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 600.0)), + Rect::from_min_size(Pos2::new(0.0, 20.0), Vec2::new(500.0, 400.0)), + ToolbarPlacement::Top, + 20.0 + TOOLBAR_SCREEN_MARGIN_PX, + ), + ] { + let pos = WindowRenderer::frozen_toolbar_default_window_pos( + monitor, + capture_rect, + toolbar_size, + toolbar_size, + placement, + ); + let expected_x = (capture_rect.center().x - toolbar_size.x / 2.0).clamp( + TOOLBAR_SCREEN_MARGIN_PX, + (monitor.max.x - toolbar_size.x - TOOLBAR_SCREEN_MARGIN_PX) + .max(TOOLBAR_SCREEN_MARGIN_PX), + ); - assert_eq!(pos.x, expected_x); - assert_eq!(pos.y, capture_rect.max.y - TOOLBAR_SCREEN_MARGIN_PX - toolbar_size.y); - assert_eq!(pos.y, expected_y); + assert_eq!(pos.x, expected_x, "{label}"); + assert_eq!(pos.y, expected_y, "{label}"); + } } #[test] @@ -431,48 +439,6 @@ fn frozen_toolbar_default_window_position_clamps_using_primary_anchor_width() { assert_eq!(pos.y, capture_rect.max.y + TOOLBAR_CAPTURE_GAP_PX); } -#[test] -fn frozen_toolbar_top_default_position_fits_above_capture_rect() { - let monitor = Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(50.0, 180.0), Vec2::new(300.0, 200.0)); - let toolbar_size = Vec2::new(460.0, 54.0); - let pos = WindowRenderer::frozen_toolbar_default_window_pos( - monitor, - capture_rect, - toolbar_size, - toolbar_size, - ToolbarPlacement::Top, - ); - let expected_x = (capture_rect.center().x - toolbar_size.x / 2.0).clamp( - TOOLBAR_SCREEN_MARGIN_PX, - (monitor.max.x - toolbar_size.x - TOOLBAR_SCREEN_MARGIN_PX).max(TOOLBAR_SCREEN_MARGIN_PX), - ); - - assert_eq!(pos.x, expected_x); - assert_eq!(pos.y, capture_rect.min.y - TOOLBAR_CAPTURE_GAP_PX - toolbar_size.y); -} - -#[test] -fn frozen_toolbar_top_default_position_falls_inside_when_no_space_above_capture_rect() { - let monitor = Rect::from_min_size(Pos2::ZERO, Vec2::new(500.0, 600.0)); - let capture_rect = Rect::from_min_size(Pos2::new(0.0, 20.0), Vec2::new(500.0, 400.0)); - let toolbar_size = Vec2::new(460.0, 54.0); - let pos = WindowRenderer::frozen_toolbar_default_window_pos( - monitor, - capture_rect, - toolbar_size, - toolbar_size, - ToolbarPlacement::Top, - ); - let expected_x = (capture_rect.center().x - toolbar_size.x / 2.0).clamp( - TOOLBAR_SCREEN_MARGIN_PX, - (monitor.max.x - toolbar_size.x - TOOLBAR_SCREEN_MARGIN_PX).max(TOOLBAR_SCREEN_MARGIN_PX), - ); - - assert_eq!(pos.x, expected_x); - assert_eq!(pos.y, capture_rect.min.y + TOOLBAR_SCREEN_MARGIN_PX); -} - #[test] fn overlay_session_computes_frozen_toolbar_reserved_rect_without_inline_toolbar_state() { let monitor = tests::test_monitor(); @@ -843,51 +809,36 @@ fn frozen_toolbar_overlay_viewport_sample_recovers_from_toolbar_window_pollution } #[test] -fn live_loupe_default_position_hangs_below_hud_strip_when_space_exists() { - let monitor = MonitorRect { - id: 1, - origin: GlobalPoint::new(0, 0), - width: 800, - height: 600, - scale_factor_x1000: 1_000, - }; - let hud_outer = GlobalPoint::new(220, 120); - let pos = OverlaySession::live_loupe_default_position( - monitor, - Some(GlobalPoint::new(100, 100)), - Some(hud_outer), - Some(52), - 232, - 232, - ) - .unwrap(); - - assert_eq!(pos.x, hud_outer.x); - assert_eq!(pos.y, hud_outer.y + 52 + HUD_LOUPE_STRIP_GAP_POINTS); -} - -#[test] -fn live_loupe_default_position_falls_above_hud_strip_when_below_overflows() { - let monitor = MonitorRect { - id: 1, - origin: GlobalPoint::new(0, 0), - width: 800, - height: 500, - scale_factor_x1000: 1_000, - }; - let hud_outer = GlobalPoint::new(220, 300); - let pos = OverlaySession::live_loupe_default_position( - monitor, - Some(GlobalPoint::new(100, 100)), - Some(hud_outer), - Some(52), - 232, - 232, - ) - .unwrap(); +fn live_loupe_default_position_hangs_below_hud_or_falls_above_on_overflow() { + for (label, monitor_height, hud_outer, expected_y) in [ + ("space below hud", 600, GlobalPoint::new(220, 120), 120 + 52 + HUD_LOUPE_STRIP_GAP_POINTS), + ( + "below hud overflows", + 500, + GlobalPoint::new(220, 300), + 300 - HUD_LOUPE_STRIP_GAP_POINTS - 232, + ), + ] { + let monitor = MonitorRect { + id: 1, + origin: GlobalPoint::new(0, 0), + width: 800, + height: monitor_height, + scale_factor_x1000: 1_000, + }; + let pos = OverlaySession::live_loupe_default_position( + monitor, + Some(GlobalPoint::new(100, 100)), + Some(hud_outer), + Some(52), + 232, + 232, + ) + .unwrap(); - assert_eq!(pos.x, hud_outer.x); - assert_eq!(pos.y, hud_outer.y - HUD_LOUPE_STRIP_GAP_POINTS - 232); + assert_eq!(pos.x, hud_outer.x, "{label}"); + assert_eq!(pos.y, expected_y, "{label}"); + } } #[test] diff --git a/packages/rsnap-overlay/src/overlay/tests/scroll_input_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/scroll_input_runtime.rs index 3c99e887..a4f26a97 100644 --- a/packages/rsnap-overlay/src/overlay/tests/scroll_input_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/scroll_input_runtime.rs @@ -15,72 +15,19 @@ fn wrapped_pixel_delta_normalizes_back_to_signed_values() { } #[test] -fn positive_vertical_wheel_delta_maps_to_upward_scroll_capture() { - assert_eq!( - OverlaySession::scroll_capture_direction_from_wheel_delta(&MouseScrollDelta::LineDelta( - 0.0, 1.0 - )), - Some(ScrollDirection::Up) - ); -} - -#[test] -fn negative_vertical_wheel_delta_maps_to_downward_scroll_capture() { - assert_eq!( - OverlaySession::scroll_capture_direction_from_wheel_delta(&MouseScrollDelta::LineDelta( - 0.0, -1.0 - )), - Some(ScrollDirection::Down) - ); -} - -#[test] -fn external_scroll_input_inside_capture_rect_uses_upward_observation_for_positive_delta() { - let monitor = MonitorRect { - id: 1, - origin: GlobalPoint::new(0, 0), - width: 1_000, - height: 800, - scale_factor_x1000: 1_000, - }; - let mut session = OverlaySession::new(); - - session.scroll_capture.active = true; - session.scroll_capture.monitor = Some(monitor); - session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); - - session.handle_external_scroll_input_delta_y(150.0, 160.0, 4.0, true, false); - - assert_eq!(session.scroll_capture.input_direction, Some(ScrollDirection::Up)); - assert!(session.scroll_capture.input_direction_at.is_some()); - assert!(session.scroll_capture.input_gesture_active); - assert_eq!(session.scroll_capture.downward_motion_rows_pending, 0.0); -} - -#[test] -fn external_scroll_input_inside_capture_rect_uses_downward_observation_for_negative_delta() { - let monitor = MonitorRect { - id: 1, - origin: GlobalPoint::new(0, 0), - width: 1_000, - height: 800, - scale_factor_x1000: 1_000, - }; - let mut session = OverlaySession::new(); - - session.scroll_capture.active = true; - session.scroll_capture.monitor = Some(monitor); - session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); - - session.handle_external_scroll_input_delta_y(150.0, 160.0, -4.0, true, false); - - assert_eq!(session.scroll_capture.input_direction, Some(ScrollDirection::Down)); - assert!(session.scroll_capture.input_direction_at.is_some()); - assert!(session.scroll_capture.input_gesture_active); +fn vertical_wheel_delta_maps_to_scroll_capture_direction() { + for (delta_y, expected) in [(1.0, ScrollDirection::Up), (-1.0, ScrollDirection::Down)] { + assert_eq!( + OverlaySession::scroll_capture_direction_from_wheel_delta( + &MouseScrollDelta::LineDelta(0.0, delta_y,) + ), + Some(expected) + ); + } } #[test] -fn upward_external_scroll_input_clears_existing_downward_motion_backlog() { +fn external_scroll_input_inside_capture_rect_tracks_direction_and_downward_backlog() { let monitor = MonitorRect { id: 1, origin: GlobalPoint::new(0, 0), @@ -88,17 +35,32 @@ fn upward_external_scroll_input_clears_existing_downward_motion_backlog() { height: 800, scale_factor_x1000: 1_000, }; - let mut session = OverlaySession::new(); - - session.scroll_capture.active = true; - session.scroll_capture.monitor = Some(monitor); - session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); - session.scroll_capture.downward_motion_rows_pending = 128.0; - session.handle_external_scroll_input_delta_y(150.0, 160.0, 12.0, true, false); - - assert_eq!(session.scroll_capture.input_direction, Some(ScrollDirection::Up)); - assert_eq!(session.scroll_capture.downward_motion_rows_pending, 0.0); + for (label, delta_y, initial_downward_motion, expected_direction, expected_downward_motion) in [ + ("positive delta", 4.0, 0.0, ScrollDirection::Up, Some(0.0)), + ("negative delta", -4.0, 0.0, ScrollDirection::Down, None), + ("positive delta clears downward backlog", 12.0, 128.0, ScrollDirection::Up, Some(0.0)), + ] { + let mut session = OverlaySession::new(); + + session.scroll_capture.active = true; + session.scroll_capture.monitor = Some(monitor); + session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); + session.scroll_capture.downward_motion_rows_pending = initial_downward_motion; + + session.handle_external_scroll_input_delta_y(150.0, 160.0, delta_y, true, false); + + assert_eq!(session.scroll_capture.input_direction, Some(expected_direction), "{label}"); + assert!(session.scroll_capture.input_direction_at.is_some(), "{label}"); + assert!(session.scroll_capture.input_gesture_active, "{label}"); + + if let Some(expected_downward_motion) = expected_downward_motion { + assert_eq!( + session.scroll_capture.downward_motion_rows_pending, expected_downward_motion, + "{label}", + ); + } + } } #[test] @@ -292,7 +254,7 @@ fn external_scroll_input_extends_passthrough_window_inside_capture_rect() { } #[test] -fn terminal_positive_scroll_event_sets_upward_observation_before_finishing() { +fn terminal_scroll_event_sets_direction_before_finishing() { let monitor = MonitorRect { id: 1, origin: GlobalPoint::new(0, 0), @@ -300,41 +262,22 @@ fn terminal_positive_scroll_event_sets_upward_observation_before_finishing() { height: 800, scale_factor_x1000: 1_000, }; - let mut session = OverlaySession::new(); - - session.scroll_capture.active = true; - session.scroll_capture.monitor = Some(monitor); - session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); - - session.handle_external_scroll_input_delta_y(150.0, 160.0, 4.0, false, true); - - assert_eq!(session.scroll_capture.input_direction, Some(ScrollDirection::Up)); - assert!(session.scroll_capture.input_direction_at.is_some()); - assert!(!session.scroll_capture.input_gesture_active); - assert!(session.scroll_capture_input_allows_growth()); -} -#[test] -fn terminal_negative_scroll_event_still_allows_downward_growth() { - let monitor = MonitorRect { - id: 1, - origin: GlobalPoint::new(0, 0), - width: 1_000, - height: 800, - scale_factor_x1000: 1_000, - }; - let mut session = OverlaySession::new(); + for (delta_y, expected_direction) in [(4.0, ScrollDirection::Up), (-4.0, ScrollDirection::Down)] + { + let mut session = OverlaySession::new(); - session.scroll_capture.active = true; - session.scroll_capture.monitor = Some(monitor); - session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); + session.scroll_capture.active = true; + session.scroll_capture.monitor = Some(monitor); + session.scroll_capture.capture_rect_pixels = Some(RectPoints::new(100, 120, 200, 240)); - session.handle_external_scroll_input_delta_y(150.0, 160.0, -4.0, false, true); + session.handle_external_scroll_input_delta_y(150.0, 160.0, delta_y, false, true); - assert_eq!(session.scroll_capture.input_direction, Some(ScrollDirection::Down)); - assert!(session.scroll_capture.input_direction_at.is_some()); - assert!(!session.scroll_capture.input_gesture_active); - assert!(session.scroll_capture_input_allows_growth()); + assert_eq!(session.scroll_capture.input_direction, Some(expected_direction)); + assert!(session.scroll_capture.input_direction_at.is_some()); + assert!(!session.scroll_capture.input_gesture_active); + assert!(session.scroll_capture_input_allows_growth()); + } } #[cfg(target_os = "macos")] @@ -390,18 +333,6 @@ fn fresh_downward_direction_allows_growth_without_active_gesture() { assert!(session.scroll_capture_input_allows_growth()); } -#[test] -fn upward_direction_still_allows_growth_gate() { - let mut session = OverlaySession::new(); - - session.scroll_capture.active = true; - session.scroll_capture.input_direction = Some(ScrollDirection::Up); - session.scroll_capture.input_direction_at = Some(Instant::now()); - session.scroll_capture.input_gesture_active = true; - - assert!(session.scroll_capture_input_allows_growth()); -} - #[test] fn upward_input_does_not_dirty_later_downward_growth() { let document = [ diff --git a/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs index 47f9b58b..16305055 100644 --- a/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs @@ -972,92 +972,102 @@ fn apply_self_capture_exception_window_ids_to_active_streams_defers_worker_refre #[cfg(target_os = "macos")] #[test] -fn captured_freeze_response_applies_deferred_worker_refresh() { - let monitor = tests::test_monitor(); - let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); - - tests::set_session_inflight_freeze_capture(&mut session, Some(monitor)); - - session.pending_self_capture_exception_window_ids_worker_refresh = true; - - let control = session.maybe_tick_worker_response_limiter(WorkerResponse::CapturedFreeze { - monitor, - image: tests::test_frozen_image(), - window_image: None, - captured_window_id: None, - }); +fn completing_blocking_worker_response_applies_deferred_worker_refresh() { + fn arrange_freeze(session: &mut OverlaySession) { + tests::set_session_inflight_freeze_capture(session, Some(tests::test_monitor())); + } - assert!(matches!(control, super::OverlayControl::Continue)); - assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); - assert!(!session.pending_self_capture_exception_window_ids_worker_refresh); -} + fn arrange_hit_test(session: &mut OverlaySession) { + session.pending_click_hit_test_request_id = Some(11); + } -#[cfg(target_os = "macos")] -#[test] -fn freeze_error_response_applies_deferred_worker_refresh() { - let monitor = tests::test_monitor(); - let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); + fn arrange_window_list(session: &mut OverlaySession) { + session.window_list_refresh_inflight = true; + } - tests::set_session_inflight_freeze_capture(&mut session, Some(monitor)); + fn arrange_png_encode(session: &mut OverlaySession) { + session.png_encode_inflight = true; + } - session.pending_self_capture_exception_window_ids_worker_refresh = true; + fn captured_freeze_response() -> WorkerResponse { + WorkerResponse::CapturedFreeze { + monitor: tests::test_monitor(), + image: tests::test_frozen_image(), + window_image: None, + captured_window_id: None, + } + } - let control = session.maybe_tick_worker_response_limiter(WorkerResponse::Error { - source: WorkerErrorSource::FreezeCapture, - message: String::from("freeze failed"), - }); + fn freeze_error_response() -> WorkerResponse { + WorkerResponse::Error { + source: WorkerErrorSource::FreezeCapture, + message: String::from("freeze failed"), + } + } - assert!(matches!(control, super::OverlayControl::Continue)); - assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); - assert!(!session.pending_self_capture_exception_window_ids_worker_refresh); - assert_eq!(session.state.error_message.as_deref(), Some("freeze failed")); -} + fn hit_test_response() -> WorkerResponse { + WorkerResponse::HitTestWindow { + monitor: tests::test_monitor(), + point: GlobalPoint::new(24, 36), + request_id: 11, + hit: None, + } + } -#[cfg(target_os = "macos")] -#[test] -fn hit_test_response_applies_deferred_worker_refresh() { - let monitor = tests::test_monitor(); - let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); + fn window_list_response() -> WorkerResponse { + WorkerResponse::RefreshedWindowList { + snapshot: Arc::new(WindowListSnapshot { + captured_at: Instant::now(), + windows: Arc::new(vec![WindowRect { + window_id: Some(9), + x: 10, + y: 12, + width: 30, + height: 40, + }]), + }), + } + } - session.pending_click_hit_test_request_id = Some(11); - session.pending_self_capture_exception_window_ids_worker_refresh = true; + fn png_error_response() -> WorkerResponse { + WorkerResponse::Error { + source: WorkerErrorSource::EncodePng, + message: String::from("encode failed"), + } + } - let control = session.maybe_tick_worker_response_limiter(WorkerResponse::HitTestWindow { - monitor, - point: GlobalPoint::new(24, 36), - request_id: 11, - hit: None, - }); + for (label, arrange, response, expected_error) in [ + ( + "captured freeze", + arrange_freeze as fn(&mut OverlaySession), + captured_freeze_response as fn() -> WorkerResponse, + None, + ), + ("freeze error", arrange_freeze, freeze_error_response, Some("freeze failed")), + ("hit test", arrange_hit_test, hit_test_response, None), + ("window list refresh", arrange_window_list, window_list_response, None), + ("png encode error", arrange_png_encode, png_error_response, None), + ] { + let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); - assert!(matches!(control, super::OverlayControl::Continue)); - assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); - assert!(!session.pending_self_capture_exception_window_ids_worker_refresh); -} + arrange(&mut session); -#[cfg(target_os = "macos")] -#[test] -fn window_list_refresh_response_applies_deferred_worker_refresh() { - let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); + session.pending_self_capture_exception_window_ids_worker_refresh = true; - session.window_list_refresh_inflight = true; - session.pending_self_capture_exception_window_ids_worker_refresh = true; + let control = session.maybe_tick_worker_response_limiter(response()); - let control = session.maybe_tick_worker_response_limiter(WorkerResponse::RefreshedWindowList { - snapshot: Arc::new(WindowListSnapshot { - captured_at: Instant::now(), - windows: Arc::new(vec![WindowRect { - window_id: Some(9), - x: 10, - y: 12, - width: 30, - height: 40, - }]), - }), - }); + assert!(matches!(control, super::OverlayControl::Continue), "{label}"); + assert_ne!( + session.worker.as_ref().unwrap().debug_id(), + original_worker_debug_id, + "{label}" + ); + assert!(!session.pending_self_capture_exception_window_ids_worker_refresh, "{label}"); - assert!(matches!(control, super::OverlayControl::Continue)); - assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); - assert!(!session.pending_self_capture_exception_window_ids_worker_refresh); + if let Some(expected_error) = expected_error { + assert_eq!(session.state.error_message.as_deref(), Some(expected_error), "{label}"); + } + } } #[cfg(target_os = "macos")] @@ -1097,24 +1107,6 @@ fn stale_window_list_refresh_response_is_dropped_after_self_capture_filter_chang assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); } -#[cfg(target_os = "macos")] -#[test] -fn png_error_response_applies_deferred_worker_refresh() { - let (mut session, original_worker_debug_id) = tests::configured_session_with_macos_worker(); - - session.png_encode_inflight = true; - session.pending_self_capture_exception_window_ids_worker_refresh = true; - - let control = session.maybe_tick_worker_response_limiter(WorkerResponse::Error { - source: WorkerErrorSource::EncodePng, - message: String::from("encode failed"), - }); - - assert!(matches!(control, super::OverlayControl::Continue)); - assert_ne!(session.worker.as_ref().unwrap().debug_id(), original_worker_debug_id); - assert!(!session.pending_self_capture_exception_window_ids_worker_refresh); -} - #[cfg(target_os = "macos")] #[test] fn capture_monitor_region_error_clears_scroll_capture_inflight_and_pauses_session() { diff --git a/packages/rsnap-overlay/src/overlay/tests/stream_refresh_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/stream_refresh_runtime.rs index 0f0e88fc..449ceaa3 100644 --- a/packages/rsnap-overlay/src/overlay/tests/stream_refresh_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/stream_refresh_runtime.rs @@ -93,136 +93,149 @@ fn drain_external_scroll_input_worker_path_does_not_arm_live_stream_stale_grace( #[cfg(target_os = "macos")] #[test] -fn force_stream_refresh_stays_disabled_while_downward_gesture_is_still_active() { +fn force_stream_refresh_tracks_gesture_and_downward_input_freshness() { let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = Some(now); - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.downward_motion_rows_pending = 512.0; - - assert!(!session.scroll_capture_should_force_stream_refresh_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn stale_stream_refresh_stays_disabled_while_gesture_is_still_active() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.last_stream_event_at = Some( - now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + Duration::from_millis(1), - ); - - assert!(!session.scroll_capture_should_schedule_stale_stream_refresh_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn stale_stream_refresh_reenables_after_gesture_ends() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_gesture_active = false; - - assert!(session.scroll_capture_should_schedule_stale_stream_refresh_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn stale_stream_refresh_reenables_during_gesture_after_stream_goes_dead() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.last_stream_event_at = Some( - now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW - Duration::from_millis(1), - ); - - assert!(session.scroll_capture_should_schedule_stale_stream_refresh_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn post_stall_burst_search_stays_enabled_during_active_gesture_when_downward_backlog_is_fresh() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.pending_post_stall_burst_after_seq = Some(80); - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = Some(now); - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.downward_motion_rows_pending = 512.0; - assert!(session.scroll_capture_should_allow_post_stall_burst_search_at(81, now)); + for (label, input_at, gesture_active, expected) in [ + ("disabled while downward gesture is active", now, true, false), + ( + "enabled for fresh pending downward motion after gesture end", + now - SCROLL_CAPTURE_INPUT_FRESHNESS + Duration::from_millis(50), + false, + true, + ), + ( + "disabled after downward input becomes stale", + now - SCROLL_CAPTURE_INPUT_FRESHNESS - Duration::from_millis(1), + false, + false, + ), + ] { + let mut session = OverlaySession::new(); + + session.scroll_capture.input_direction = Some(ScrollDirection::Down); + session.scroll_capture.input_direction_at = Some(input_at); + session.scroll_capture.input_gesture_active = gesture_active; + session.scroll_capture.downward_motion_rows_pending = 512.0; + + assert_eq!(session.scroll_capture_should_force_stream_refresh_at(now), expected, "{label}"); + } } #[cfg(target_os = "macos")] #[test] -fn force_stream_refresh_stays_enabled_for_fresh_pending_downward_motion_after_gesture_end() { +fn stale_stream_refresh_tracks_gesture_and_dead_stream_deadline() { let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = - Some(now - SCROLL_CAPTURE_INPUT_FRESHNESS + Duration::from_millis(50)); - session.scroll_capture.input_gesture_active = false; - session.scroll_capture.downward_motion_rows_pending = 512.0; - - assert!(session.scroll_capture_should_force_stream_refresh_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn force_stream_refresh_stops_after_downward_input_becomes_stale() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = - Some(now - SCROLL_CAPTURE_INPUT_FRESHNESS - Duration::from_millis(1)); - session.scroll_capture.input_gesture_active = false; - session.scroll_capture.downward_motion_rows_pending = 512.0; - - assert!(!session.scroll_capture_should_force_stream_refresh_at(now)); + for (label, gesture_active, last_stream_event_at, expected) in [ + ( + "disabled while active gesture stream is still fresh", + true, + Some( + now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + + Duration::from_millis(1), + ), + false, + ), + ("reenabled after gesture ends", false, None, true), + ( + "reenabled during gesture after stream goes dead", + true, + Some( + now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + - Duration::from_millis(1), + ), + true, + ), + ] { + let mut session = OverlaySession::new(); + + session.scroll_capture.input_gesture_active = gesture_active; + session.scroll_capture.last_stream_event_at = last_stream_event_at; + + assert_eq!( + session.scroll_capture_should_schedule_stale_stream_refresh_at(now), + expected, + "{label}", + ); + } } #[cfg(target_os = "macos")] #[test] -fn post_stall_burst_search_stays_enabled_while_fresh_downward_backlog_remains() { +fn post_stall_burst_search_tracks_downward_backlog_freshness() { let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.pending_post_stall_burst_after_seq = Some(80); - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = Some(now); - session.scroll_capture.input_gesture_active = false; - session.scroll_capture.downward_motion_rows_pending = 512.0; - assert!(session.scroll_capture_should_allow_post_stall_burst_search_at(81, now)); - assert!(session.scroll_capture_should_allow_post_stall_burst_search_at( - 82, - now + Duration::from_millis(50) - )); + for (label, input_at, gesture_active, expected, verify_followup) in [ + ("enabled during active gesture when backlog is fresh", now, true, true, false), + ("enabled while fresh backlog remains", now, false, true, true), + ( + "disabled after downward backlog goes stale", + now - SCROLL_CAPTURE_INPUT_FRESHNESS - Duration::from_millis(1), + false, + false, + false, + ), + ] { + let mut session = OverlaySession::new(); + + session.scroll_capture.pending_post_stall_burst_after_seq = Some(80); + session.scroll_capture.input_direction = Some(ScrollDirection::Down); + session.scroll_capture.input_direction_at = Some(input_at); + session.scroll_capture.input_gesture_active = gesture_active; + session.scroll_capture.downward_motion_rows_pending = 512.0; + + assert_eq!( + session.scroll_capture_should_allow_post_stall_burst_search_at(81, now), + expected, + "{label}", + ); + + if verify_followup { + assert!( + session.scroll_capture_should_allow_post_stall_burst_search_at( + 82, + now + Duration::from_millis(50) + ), + "{label} followup", + ); + } + } } #[cfg(target_os = "macos")] #[test] -fn post_stall_burst_search_arms_for_large_capture_time_gap_even_when_frame_seq_is_contiguous() { +fn post_stall_burst_search_arms_only_for_large_capture_time_gap() { let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = Some(now); - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.downward_motion_rows_pending = 512.0; - session.scroll_capture.last_consumed_stream_frame_captured_at = Some( - now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW - Duration::from_millis(1), - ); - assert!(session.scroll_capture_should_arm_post_stall_burst_for_time_gap_at(now)); + for (label, last_frame_at, expected) in [ + ( + "large capture time gap", + now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + - Duration::from_millis(1), + true, + ), + ( + "small capture time gap", + now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + + Duration::from_millis(10), + false, + ), + ] { + let mut session = OverlaySession::new(); + + session.scroll_capture.input_direction = Some(ScrollDirection::Down); + session.scroll_capture.input_direction_at = Some(now); + session.scroll_capture.input_gesture_active = true; + session.scroll_capture.downward_motion_rows_pending = 512.0; + session.scroll_capture.last_consumed_stream_frame_captured_at = Some(last_frame_at); + + assert_eq!( + session.scroll_capture_should_arm_post_stall_burst_for_time_gap_at(now), + expected, + "{label}", + ); + } } #[cfg(target_os = "macos")] @@ -283,36 +296,3 @@ fn consuming_live_frame_backlog_arms_time_gap_burst_after_draining_fresh_input() assert_eq!(session.scroll_capture.last_external_scroll_input_seq, 1); assert_eq!(session.scroll_capture.pending_post_stall_burst_after_seq, Some(8)); } - -#[cfg(target_os = "macos")] -#[test] -fn post_stall_burst_search_does_not_arm_for_small_capture_time_gap() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = Some(now); - session.scroll_capture.input_gesture_active = true; - session.scroll_capture.downward_motion_rows_pending = 512.0; - session.scroll_capture.last_consumed_stream_frame_captured_at = Some( - now - SCROLL_CAPTURE_ACTIVE_GESTURE_STALE_REFRESH_DEAD_WINDOW + Duration::from_millis(10), - ); - - assert!(!session.scroll_capture_should_arm_post_stall_burst_for_time_gap_at(now)); -} - -#[cfg(target_os = "macos")] -#[test] -fn post_stall_burst_search_stops_after_downward_backlog_goes_stale() { - let now = Instant::now(); - let mut session = OverlaySession::new(); - - session.scroll_capture.pending_post_stall_burst_after_seq = Some(80); - session.scroll_capture.input_direction = Some(ScrollDirection::Down); - session.scroll_capture.input_direction_at = - Some(now - SCROLL_CAPTURE_INPUT_FRESHNESS - Duration::from_millis(1)); - session.scroll_capture.input_gesture_active = false; - session.scroll_capture.downward_motion_rows_pending = 512.0; - - assert!(!session.scroll_capture_should_allow_post_stall_burst_search_at(81, now)); -} diff --git a/packages/rsnap-overlay/src/overlay/tests/toolbar_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/toolbar_runtime.rs index 91bc63db..88cb6400 100644 --- a/packages/rsnap-overlay/src/overlay/tests/toolbar_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/toolbar_runtime.rs @@ -31,41 +31,31 @@ fn toolbar_cursor_left_during_drag_keeps_drag_session_alive() { } #[test] -fn toolbar_drag_start_eligibility_prefers_live_cursor_over_stale_cache() { - let mut session = OverlaySession::new(); +fn toolbar_drag_start_eligibility_uses_live_cursor_then_cached_pointer() { #[cfg(target_os = "macos")] let primary_origin = overlay::frozen_toolbar_window_primary_origin(); #[cfg(not(target_os = "macos"))] let primary_origin = Pos2::ZERO; - let primary_rect = - WindowRenderer::frozen_toolbar_primary_rect(&session.toolbar_state, primary_origin); + let primary_rect = WindowRenderer::frozen_toolbar_primary_rect( + &OverlaySession::new().toolbar_state, + primary_origin, + ); + let cached_cursor = primary_rect.center(); let stale_cursor = Pos2::new(primary_rect.right() + 12.0, primary_rect.center().y); - let live_cursor = primary_rect.center(); + assert!(primary_rect.contains(cached_cursor)); assert!(!primary_rect.contains(stale_cursor)); - assert!(primary_rect.contains(live_cursor)); - session.toolbar_pointer_local = Some(stale_cursor); + for (label, cached_pointer, live_pointer) in [ + ("live cursor wins over stale cache", stale_cursor, Some(cached_cursor)), + ("cached pointer wins when live cursor is missing", cached_cursor, None), + ] { + let mut session = OverlaySession::new(); - assert!(session.resolve_toolbar_drag_start_eligibility(Some(live_cursor))); -} + session.toolbar_pointer_local = Some(cached_pointer); -#[test] -fn toolbar_drag_start_eligibility_falls_back_to_cached_pointer_when_live_cursor_is_missing() { - let mut session = OverlaySession::new(); - #[cfg(target_os = "macos")] - let primary_origin = overlay::frozen_toolbar_window_primary_origin(); - #[cfg(not(target_os = "macos"))] - let primary_origin = Pos2::ZERO; - let primary_rect = - WindowRenderer::frozen_toolbar_primary_rect(&session.toolbar_state, primary_origin); - let cached_cursor = primary_rect.center(); - - assert!(primary_rect.contains(cached_cursor)); - - session.toolbar_pointer_local = Some(cached_cursor); - - assert!(session.resolve_toolbar_drag_start_eligibility(None)); + assert!(session.resolve_toolbar_drag_start_eligibility(live_pointer), "{label}"); + } } #[test] @@ -144,22 +134,31 @@ fn frozen_toolbar_selected_mode_uses_fill_without_border() { } #[test] -fn toolbar_window_hides_until_frozen_pixels_exist() { +fn toolbar_window_visibility_tracks_frozen_display_readiness() { let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); - session.state.begin_freeze(monitor); + for (label, has_display_pixels, expected_hide) in + [("no frozen pixels", false, true), ("seeded preview pixels", true, false)] + { + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); - assert!(session.should_hide_toolbar_window(monitor)); + if has_display_pixels { + tests::finish_frozen_display_state(&mut session, monitor, tests::test_frozen_image()); + } - tests::set_session_pending_freeze_capture(&mut session, Some(monitor)); + assert_eq!(session.should_hide_toolbar_window(monitor), expected_hide, "{label}"); - assert!(session.should_hide_toolbar_window(monitor)); + tests::set_session_pending_freeze_capture(&mut session, Some(monitor)); - tests::set_session_pending_freeze_capture(&mut session, None); - tests::set_session_inflight_freeze_capture(&mut session, Some(monitor)); + assert_eq!(session.should_hide_toolbar_window(monitor), expected_hide, "{label} pending"); - assert!(session.should_hide_toolbar_window(monitor)); + tests::set_session_pending_freeze_capture(&mut session, None); + tests::set_session_inflight_freeze_capture(&mut session, Some(monitor)); + + assert_eq!(session.should_hide_toolbar_window(monitor), expected_hide, "{label} inflight"); + } } #[cfg(target_os = "macos")] @@ -180,27 +179,6 @@ fn toolbar_window_is_needed_for_seeded_preview_before_final_capture_ready() { assert!(!tests::session_export_authority_ready(&session)); } -#[test] -fn toolbar_window_stays_visible_while_final_capture_is_pending() { - let monitor = tests::test_monitor(); - let mut session = OverlaySession::new(); - - session.state.begin_freeze(monitor); - - tests::finish_frozen_display_state(&mut session, monitor, tests::test_frozen_image()); - - assert!(!session.should_hide_toolbar_window(monitor)); - - tests::set_session_pending_freeze_capture(&mut session, Some(monitor)); - - assert!(!session.should_hide_toolbar_window(monitor)); - - tests::set_session_pending_freeze_capture(&mut session, None); - tests::set_session_inflight_freeze_capture(&mut session, Some(monitor)); - - assert!(!session.should_hide_toolbar_window(monitor)); -} - #[test] fn frozen_toolbar_clamps_floating_position() { let monitor = Rect::from_min_size(Pos2::new(-200.0, -100.0), Vec2::new(500.0, 400.0));