aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell/tests
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-09-09 22:37:53 +0300
committerGitHub <noreply@github.com>2026-09-09 22:37:53 +0300
commit1f1b20cb5d2a62437e1b21e237164478f9582f52 (patch)
tree1915b0e64cd0feee72c21c290cc1afcf9eb12fc7 /src/client/shell/tests
parent1682cab3263b27b296f477552a4e79ab54923091 (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.rs170
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",
- &copy_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",
+ &copy_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]