diff options
| author | akbash <akbash@herdr.dev> | 2026-09-09 22:37:53 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-09 22:37:53 +0300 |
| commit | 1f1b20cb5d2a62437e1b21e237164478f9582f52 (patch) | |
| tree | 1915b0e64cd0feee72c21c290cc1afcf9eb12fc7 /src/client/shell/tests | |
| parent | 1682cab3263b27b296f477552a4e79ab54923091 (diff) | |
fix: retain double-click selections and make tests root-safe (#3854)
refs #3847
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/client/shell/tests')
| -rw-r--r-- | src/client/shell/tests/mouse_selection.rs | 170 |
1 files changed, 104 insertions, 66 deletions
diff --git a/src/client/shell/tests/mouse_selection.rs b/src/client/shell/tests/mouse_selection.rs index a8f5c532..a23ff45f 100644 --- a/src/client/shell/tests/mouse_selection.rs +++ b/src/client/shell/tests/mouse_selection.rs @@ -232,32 +232,39 @@ fn disabled_mouse_chrome_keeps_tab_wheel_but_removes_split_drag_hits() { #[test] fn client_double_click_selects_and_copies_endpoint_row_word() { - let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default())); - state.set_snapshot(Box::new(snapshot())); - state.set_pane_surface(surface()); - state.compose(106, 20).expect("composed frame"); - let pane = state.hits.panes[0].clone(); - let click = || { - RawInputEvent::Mouse(crossterm::event::MouseEvent { - kind: MouseEventKind::Down(MouseButton::Left), - column: pane.inner_rect.x + 1, - row: pane.inner_rect.y, - modifiers: KeyModifiers::empty(), - }) - }; - let release = || { - RawInputEvent::Mouse(crossterm::event::MouseEvent { - kind: MouseEventKind::Up(MouseButton::Left), - column: pane.inner_rect.x + 1, - row: pane.inner_rect.y, - modifiers: KeyModifiers::empty(), - }) - }; + // The row response may arrive on either side of the second mouse release. + for (copy_on_select, release_before_response) in + [(false, false), (false, true), (true, false), (true, true)] + { + let mut config = Config::default(); + config.ui.copy_on_select = copy_on_select; + let mut state = ClientShellState::new(ClientShellConfig::from_config(&config)); + state.set_snapshot(Box::new(snapshot())); + state.set_pane_surface(surface()); + state.compose(106, 20).expect("composed frame"); + let pane = state.hits.panes[0].clone(); + let click = || { + RawInputEvent::Mouse(crossterm::event::MouseEvent { + kind: MouseEventKind::Down(MouseButton::Left), + column: pane.inner_rect.x + 1, + row: pane.inner_rect.y, + modifiers: KeyModifiers::empty(), + }) + }; + let release = || { + RawInputEvent::Mouse(crossterm::event::MouseEvent { + kind: MouseEventKind::Up(MouseButton::Left), + column: pane.inner_rect.x + 1, + row: pane.inner_rect.y, + modifiers: KeyModifiers::empty(), + }) + }; - state.handle_raw_events(vec![click()]); - state.handle_raw_events(vec![release()]); - let second = state.handle_raw_events(vec![click()]); - let ClientShellAction::Endpoint { request, .. } = second + state.handle_raw_events(vec![click()]); + state.handle_raw_events(vec![release()]); + assert!(state.selection.is_none(), "plain clicks must not select"); + let second = state.handle_raw_events(vec![click()]); + let ClientShellAction::Endpoint { request, .. } = second .actions .iter() .find(|action| { @@ -271,48 +278,79 @@ fn client_double_click_selects_and_copies_endpoint_row_word() { else { unreachable!() }; - let word_request_id = request.id.clone(); - assert!(matches!( - &request.method, - crate::api::schema::Method::PaneSelectionRead(params) - if params.anchor == crate::api::schema::PaneTextPoint { row: 0, col: 0 } - && params.cursor == crate::api::schema::PaneTextPoint { row: 0, col: 3 } - )); + let word_request_id = request.id.clone(); + assert!(matches!( + &request.method, + crate::api::schema::Method::PaneSelectionRead(params) + if params.anchor == crate::api::schema::PaneTextPoint { row: 0, col: 0 } + && params.cursor == crate::api::schema::PaneTextPoint { row: 0, col: 3 } + )); - let (repaint, actions) = state.handle_endpoint_result( - "boot-1", - &word_request_id, - Ok(crate::api::schema::ResponseResult::PaneSelection { - pane_id: "pane_1".into(), - text: "LIVE".into(), - }), - ); - assert!(repaint); - assert!(state - .selection - .as_ref() - .is_some_and(crate::selection::Selection::is_finalized)); - let [ClientShellAction::Endpoint { request, .. }] = &actions[..] else { - panic!("auto-copy should read the selected word"); - }; - let copy_request_id = request.id.clone(); - assert!(matches!( - &request.method, - crate::api::schema::Method::PaneSelectionRead(params) - if params.anchor.col == 0 && params.cursor.col == 3 - )); - let (_, actions) = state.handle_endpoint_result( - "boot-1", - ©_request_id, - Ok(crate::api::schema::ResponseResult::PaneSelection { - pane_id: "pane_1".into(), - text: "LIVE".into(), - }), - ); - assert!(matches!( - &actions[..], - [ClientShellAction::ClipboardWrite(bytes)] if bytes == b"LIVE" - )); + if release_before_response { + let released = state.handle_raw_events(vec![release()]); + assert!(released.actions.is_empty()); + } + let (repaint, actions) = state.handle_endpoint_result( + "boot-1", + &word_request_id, + Ok(crate::api::schema::ResponseResult::PaneSelection { + pane_id: "pane_1".into(), + text: "LIVE".into(), + }), + ); + assert!(repaint); + assert!(state + .selection + .as_ref() + .is_some_and(crate::selection::Selection::is_finalized)); + let deadline = state.selection_highlight_clear_deadline; + if !release_before_response { + let released = state.handle_raw_events(vec![release()]); + assert!(released.actions.is_empty(), "release must not copy twice"); + } + assert!( + state.selection.as_ref().is_some_and(crate::selection::Selection::is_finalized), + "mouse release must retain the finalized word selection (copy_on_select={copy_on_select})" + ); + assert_eq!( + state.selection.as_ref().unwrap().ordered_cells(), + ((0, 0), (0, 3)) + ); + assert_eq!(state.selection_highlight_clear_deadline, deadline); + if !copy_on_select { + assert!(actions.is_empty(), "manual selection must not auto-copy"); + assert!(state.selection_highlight_clear_deadline.is_none()); + state.tick_copy_feedback(std::time::Instant::now() + std::time::Duration::from_secs(1)); + assert!( + state.selection.is_some(), + "manual selection must not expire" + ); + continue; + } + let [ClientShellAction::Endpoint { request, .. }] = &actions[..] else { + panic!("auto-copy should read the selected word"); + }; + let copy_request_id = request.id.clone(); + assert!(matches!( + &request.method, + crate::api::schema::Method::PaneSelectionRead(params) + if params.anchor.col == 0 && params.cursor.col == 3 + )); + let (_, actions) = state.handle_endpoint_result( + "boot-1", + ©_request_id, + Ok(crate::api::schema::ResponseResult::PaneSelection { + pane_id: "pane_1".into(), + text: "LIVE".into(), + }), + ); + assert!(matches!( + &actions[..], + [ClientShellAction::ClipboardWrite(bytes)] if bytes == b"LIVE" + )); + assert!(state.tick_copy_feedback(deadline.expect("auto-copy highlight deadline"))); + assert!(state.selection.is_none()); + } } #[test] |
