diff options
Diffstat (limited to 'src/client/shell/tests')
| -rw-r--r-- | src/client/shell/tests/agents_worktrees_notifications.rs | 2 | ||||
| -rw-r--r-- | src/client/shell/tests/copy.rs | 11 | ||||
| -rw-r--r-- | src/client/shell/tests/graphics.rs | 8 | ||||
| -rw-r--r-- | src/client/shell/tests/input.rs | 16 | ||||
| -rw-r--r-- | src/client/shell/tests/mod.rs | 1 | ||||
| -rw-r--r-- | src/client/shell/tests/popup_focus_projection.rs | 15 | ||||
| -rw-r--r-- | src/client/shell/tests/text_editing.rs | 395 | ||||
| -rw-r--r-- | src/client/shell/tests/workspace_navigation.rs | 5 |
8 files changed, 428 insertions, 25 deletions
diff --git a/src/client/shell/tests/agents_worktrees_notifications.rs b/src/client/shell/tests/agents_worktrees_notifications.rs index c89991b1..5b8b1224 100644 --- a/src/client/shell/tests/agents_worktrees_notifications.rs +++ b/src/client/shell/tests/agents_worktrees_notifications.rs @@ -917,7 +917,7 @@ fn named_workspace_overlay_targets_projected_source_workspace() { .. }, .. - })) if value == "repo" && source_workspace_id.as_deref() == Some("ws_1") + })) if value.as_str() == "repo" && source_workspace_id.as_deref() == Some("ws_1") )); let create = state.handle_input_bytes(b"\r"); let [ClientShellAction::Endpoint { request, .. }] = &create.actions[..] else { diff --git a/src/client/shell/tests/copy.rs b/src/client/shell/tests/copy.rs index c83f7fc6..8ee35f45 100644 --- a/src/client/shell/tests/copy.rs +++ b/src/client/shell/tests/copy.rs @@ -1,10 +1,10 @@ use super::*; #[test] -fn pasted_help_and_copy_queries_strip_control_characters() { +fn pasted_help_and_copy_queries_normalize_single_line_text() { let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default())); state.overlay = Some(ClientShellOverlay::Help(ClientHelpOverlay { - query: String::new(), + query: TextEditor::default(), search_focused: true, scroll: 0, })); @@ -13,10 +13,11 @@ fn pasted_help_and_copy_queries_strip_control_characters() { assert!(matches!( state.overlay, Some(ClientShellOverlay::Help(ClientHelpOverlay { ref query, .. })) - if query == "workspace" + if query.as_str() == "work space" )); state.overlay = None; + state.mode = ClientShellMode::Copy; state.copy_mode = Some(ClientCopyModeState { pane_id: "pane_1".into(), content_revision: 0, @@ -28,7 +29,7 @@ fn pasted_help_and_copy_queries_strip_control_characters() { selection: None, search_prompt: Some(ClientCopySearchPrompt { direction: crate::api::schema::PaneCopySearchDirection::Forward, - query: String::new(), + query: TextEditor::default(), }), search_query: String::new(), search_direction: None, @@ -47,7 +48,7 @@ fn pasted_help_and_copy_queries_strip_control_characters() { .as_ref() .and_then(|copy_mode| copy_mode.search_prompt.as_ref()) .map(|prompt| prompt.query.as_str()), - Some("needle") + Some("needle ") ); } diff --git a/src/client/shell/tests/graphics.rs b/src/client/shell/tests/graphics.rs index 1b0071f1..c5fa1f4a 100644 --- a/src/client/shell/tests/graphics.rs +++ b/src/client/shell/tests/graphics.rs @@ -210,7 +210,6 @@ fn every_dialog_and_menu_occludes_its_panel_not_the_whole_screen() { ClientShellOverlay::Rename(ClientRenameOverlay { title: "rename", input: "name".into(), - replace_on_type: false, target: ClientRenameTarget::Pane { pane_id: "pane_1".into(), }, @@ -221,12 +220,12 @@ fn every_dialog_and_menu_occludes_its_panel_not_the_whole_screen() { detail: "confirm".into(), }), ClientShellOverlay::Help(ClientHelpOverlay { - query: String::new(), + query: TextEditor::default(), search_focused: false, scroll: 0, }), ClientShellOverlay::Navigator(ClientNavigatorOverlay { - query: String::new(), + query: TextEditor::default(), search_focused: false, selected: None, scroll: 0, @@ -238,7 +237,6 @@ fn every_dialog_and_menu_occludes_its_panel_not_the_whole_screen() { repo_name: "repo".into(), branch: "branch".into(), checkout_path: "path".into(), - replace_on_type: false, error: None, creating: false, }), @@ -246,7 +244,7 @@ fn every_dialog_and_menu_occludes_its_panel_not_the_whole_screen() { source_workspace_id: "ws_1".into(), entries: Vec::new(), selected: 0, - query: String::new(), + query: TextEditor::default(), search_focused: false, error: None, opening: false, diff --git a/src/client/shell/tests/input.rs b/src/client/shell/tests/input.rs index 35916ecf..b55cf9a2 100644 --- a/src/client/shell/tests/input.rs +++ b/src/client/shell/tests/input.rs @@ -107,6 +107,15 @@ fn full_host_palette_response_is_sent_as_one_theme_update() { #[test] fn modal_paste_shortcut_modifiers_are_platform_specific() { let key = |code, modifiers| crate::input::TerminalKey::new(code, modifiers); + for macos in [false, true] { + assert!(!input::is_modal_paste_shortcut_for_platform( + &key( + KeyCode::Char('v'), + KeyModifiers::CONTROL | KeyModifiers::ALT + ), + macos + )); + } assert!(input::is_modal_paste_shortcut_for_platform( &key(KeyCode::Char('v'), KeyModifiers::CONTROL), @@ -142,8 +151,7 @@ fn modal_paste_inserts_clipboard_text_through_overlay_text_path() { let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default())); state.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay { title: "rename pane", - input: "replace me".into(), - replace_on_type: true, + input: TextEditor::new("replace me", true), target: ClientRenameTarget::Pane { pane_id: "pane_1".into(), }, @@ -159,8 +167,8 @@ fn modal_paste_inserts_clipboard_text_through_overlay_text_path() { assert!(outcome.repaint); assert!(matches!( state.overlay, - Some(ClientShellOverlay::Rename(ClientRenameOverlay { ref input, replace_on_type: false, .. })) - if input == "feature/pasted" + Some(ClientShellOverlay::Rename(ClientRenameOverlay { ref input, .. })) + if input.as_str() == "feature/pasted" )); } diff --git a/src/client/shell/tests/mod.rs b/src/client/shell/tests/mod.rs index b6b600bc..787d8a75 100644 --- a/src/client/shell/tests/mod.rs +++ b/src/client/shell/tests/mod.rs @@ -5,6 +5,7 @@ use crate::protocol::{ PaneSurfaceSplit, PaneSurfaceSplitDirection, SurfaceRect, }; use crossterm::event::MouseEvent; +mod text_editing; pub(super) fn snapshot() -> ClientShellSnapshot { ClientShellSnapshot { diff --git a/src/client/shell/tests/popup_focus_projection.rs b/src/client/shell/tests/popup_focus_projection.rs index 044ef145..aa44bf6d 100644 --- a/src/client/shell/tests/popup_focus_projection.rs +++ b/src/client/shell/tests/popup_focus_projection.rs @@ -37,8 +37,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { state.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay { title: "rename pane", - input: String::new(), - replace_on_type: false, + input: TextEditor::default(), target: ClientRenameTarget::Pane { pane_id: "pane_1".into(), }, @@ -49,9 +48,8 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { ClientWorktreeCreateOverlay { source_workspace_id: "ws_1".into(), repo_name: "repo".into(), - branch: String::new(), + branch: TextEditor::default(), checkout_path: String::new(), - replace_on_type: false, error: None, creating: true, }, @@ -67,7 +65,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { source_workspace_id: "ws_1".into(), entries: Vec::new(), selected: 0, - query: String::new(), + query: TextEditor::default(), search_focused: false, error: None, opening: false, @@ -80,7 +78,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { assert!(state.modal_paste_target_active()); state.overlay = Some(ClientShellOverlay::Navigator(ClientNavigatorOverlay { - query: String::new(), + query: TextEditor::default(), search_focused: false, selected: None, scroll: 0, @@ -94,7 +92,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { assert!(state.modal_paste_target_active()); state.overlay = Some(ClientShellOverlay::Help(ClientHelpOverlay { - query: String::new(), + query: TextEditor::default(), search_focused: false, scroll: 0, })); @@ -116,7 +114,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { selection: None, search_prompt: Some(ClientCopySearchPrompt { direction: crate::api::schema::PaneCopySearchDirection::Forward, - query: String::new(), + query: TextEditor::default(), }), search_query: String::new(), search_direction: None, @@ -127,6 +125,7 @@ fn modal_paste_target_requires_a_focused_editable_client_field() { search_generation: 0, copy_after_search: false, }); + state.mode = ClientShellMode::Copy; assert!(state.modal_paste_target_active()); state.popup_pending = true; assert!(!state.modal_paste_target_active()); diff --git a/src/client/shell/tests/text_editing.rs b/src/client/shell/tests/text_editing.rs new file mode 100644 index 00000000..b63ce9f6 --- /dev/null +++ b/src/client/shell/tests/text_editing.rs @@ -0,0 +1,395 @@ +use super::*; +use crate::input::{KeybindAction, KeybindMatch, TerminalKey, TextCommit}; + +fn shell(field: usize) -> ClientShellState { + let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default())); + state.set_snapshot(Box::new(snapshot())); + let mut frame = surface(); + frame.panes[0].scroll = Some(crate::protocol::PaneSurfaceScrollMetrics { + offset_from_bottom: 0, + max_offset_from_bottom: 0, + viewport_rows: 2, + }); + state.set_pane_surface(frame); + state.compose(106, 30).expect("initial shell"); + match field { + 0 => state.open_new_workspace_overlay(), + 1 => state.open_rename_workspace_overlay(), + 2 => state.open_new_tab_overlay(), + 3 => state.open_rename_tab_overlay(), + 4 => state.open_rename_pane_overlay(), + 5 => { + state.handle_worktree_endpoint_result( + PendingEndpointKind::PrepareWorktreeCreate { + workspace_id: "ws_1".into(), + }, + Ok(worktree_list_result(None)), + &mut ClientShellInput::default(), + ); + } + 6 => { + state.open_navigator_overlay(); + state.handle_input_bytes(b"/"); + } + 7 => { + state.overlay = Some(ClientShellOverlay::Help(ClientHelpOverlay { + query: TextEditor::default(), + search_focused: true, + scroll: 0, + })) + } + 8 => { + state.handle_worktree_endpoint_result( + PendingEndpointKind::PrepareWorktreeOpen { + workspace_id: "ws_1".into(), + }, + Ok(worktree_list_result(None)), + &mut ClientShellInput::default(), + ); + state.handle_input_bytes(b"/"); + } + 9 => { + state.record_binding( + KeybindMatch::Action(KeybindAction::CopyMode), + &mut ClientShellInput::default(), + ); + state.handle_input_bytes(b"/"); + } + _ => unreachable!(), + } + state +} + +fn editor(state: &mut ClientShellState) -> &mut TextEditor { + match state.overlay.as_mut() { + Some(ClientShellOverlay::Rename(v)) => &mut v.input, + Some(ClientShellOverlay::Navigator(v)) => &mut v.query, + Some(ClientShellOverlay::Help(v)) => &mut v.query, + Some(ClientShellOverlay::WorktreeCreate(v)) => &mut v.branch, + Some(ClientShellOverlay::WorktreeOpen(v)) => &mut v.query, + _ => { + &mut state + .copy_mode + .as_mut() + .expect("copy mode") + .search_prompt + .as_mut() + .expect("prompt") + .query + } + } +} + +fn press(state: &mut ClientShellState, code: KeyCode, modifiers: KeyModifiers) -> ClientShellInput { + state.handle_raw_events(vec![RawInputEvent::Key(TerminalKey::new(code, modifiers))]) +} + +#[test] +fn all_ten_fields_route_shared_text_editing() { + for field in 0..10 { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from("ab"); + press(&mut state, KeyCode::Left, KeyModifiers::NONE); + let result = press(&mut state, KeyCode::Char('X'), KeyModifiers::NONE); + assert!(result.repaint, "field {field}"); + assert!(result.requests.is_empty() && result.actions.is_empty()); + assert_eq!(editor(&mut state).as_str(), "aXb"); + } +} + +#[test] +fn text_delivery_paths_insert_at_the_cursor() { + for delivery in 0..4 { + let mut state = shell(0); + *editor(&mut state) = TextEditor::from("ab"); + press(&mut state, KeyCode::Left, KeyModifiers::NONE); + let result = match delivery { + 0 => state.handle_raw_events(vec![RawInputEvent::Key( + TerminalKey::new(KeyCode::Char('x'), KeyModifiers::NONE) + .with_generated_text(Some("X".into())), + )]), + 1 => state.handle_raw_events(vec![RawInputEvent::Text(TextCommit::new("X"))]), + 2 => state.handle_raw_events(vec![RawInputEvent::Paste("X".into())]), + _ => { + let mut result = ClientShellInput::default(); + assert!(state.handle_modal_paste_shortcut_with( + &TerminalKey::new(KeyCode::Char('v'), KeyModifiers::CONTROL), + &mut result, + || Some("X".into()) + )); + result + } + }; + assert!(result.repaint, "delivery {delivery}"); + assert!(result.requests.is_empty() && result.actions.is_empty()); + assert_eq!(editor(&mut state).as_str(), "aXb"); + } +} + +#[test] +fn rename_clear_exceptions_remain_local_and_copy_prompt_yields_to_popup() { + for field in 0..5 { + for (code, modifiers) in [ + (KeyCode::Char('c'), KeyModifiers::CONTROL), + (KeyCode::Backspace, KeyModifiers::SUPER), + ] { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from("name"); + press(&mut state, code, modifiers); + assert!(state.overlay.is_some()); + assert!(editor(&mut state).is_empty()); + } + } + let mut state = shell(9); + *editor(&mut state) = TextEditor::from("query"); + state.popup_terminal_id = Some("popup-test".into()); + let result = state.handle_raw_events(vec![ + RawInputEvent::Text(TextCommit::new("text")), + RawInputEvent::Paste("paste".into()), + ]); + assert!( + matches!(&result.requests[..], [ClientMessage::ClientShellPopupInput { terminal_id, events }] if terminal_id == "popup-test" && events.len() == 2) + ); + assert_eq!(editor(&mut state).as_str(), "query"); + assert!(!state.modal_paste_target_active()); +} + +#[test] +fn cursor_movement_preserves_filter_selection_scroll_and_branch_error() { + for field in [5, 6, 7, 8] { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from("ab"); + match state.overlay.as_mut().expect("overlay") { + ClientShellOverlay::WorktreeCreate(v) => { + v.checkout_path = "sentinel".into(); + v.error = Some("error".into()); + } + ClientShellOverlay::Navigator(v) => { + v.scroll = 3; + v.selected = Some(ClientNavigatorTarget::Pane { + endpoint_id: ClientEndpointId::Local, + pane_id: "pane_1".into(), + }); + } + ClientShellOverlay::Help(v) => v.scroll = 3, + ClientShellOverlay::WorktreeOpen(v) => v.selected = 3, + _ => unreachable!(), + } + press(&mut state, KeyCode::Home, KeyModifiers::NONE); + press(&mut state, KeyCode::Char('u'), KeyModifiers::CONTROL); // Empty kill must not refresh results. + match state.overlay.as_ref().expect("overlay") { + ClientShellOverlay::WorktreeCreate(v) => { + assert_eq!(v.checkout_path, "sentinel"); + assert_eq!(v.error.as_deref(), Some("error")); + } + ClientShellOverlay::Navigator(v) => { + assert_eq!(v.scroll, 3); + assert!(v.selected.is_some()); + } + ClientShellOverlay::Help(v) => assert_eq!(v.scroll, 3), + ClientShellOverlay::WorktreeOpen(v) => assert_eq!(v.selected, 3), + _ => unreachable!(), + } + press(&mut state, KeyCode::Char('k'), KeyModifiers::CONTROL); + match state.overlay.as_ref().expect("overlay") { + ClientShellOverlay::WorktreeCreate(v) => { + assert_ne!(v.checkout_path, "sentinel"); + assert!(v.error.is_none()); + } + ClientShellOverlay::Navigator(v) => assert!(v.selected.is_none()), + ClientShellOverlay::Help(v) => assert_eq!(v.scroll, 0), + ClientShellOverlay::WorktreeOpen(v) => assert_eq!(v.selected, 0), + _ => unreachable!(), + } + } +} + +#[test] +fn enter_and_escape_preserve_overlay_actions_with_generated_text() { + use crate::api::schema::Method; + for field in [5, 7, 8] { + for code in [KeyCode::Enter, KeyCode::Esc] { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from("feature"); + let before = editor(&mut state).clone(); + let result = state.handle_raw_events(vec![RawInputEvent::Key( + TerminalKey::new(code, KeyModifiers::NONE) + .with_generated_text(Some("printable".into())), + )]); + assert!(result.repaint); + assert!(result.requests.is_empty()); + if code == KeyCode::Enter && field != 7 { + assert_eq!(editor(&mut state), &before); + let [ClientShellAction::Endpoint { request, .. }] = &result.actions[..] else { + panic!("field {field} should submit"); + }; + match &request.method { + Method::WorktreeCreate(params) if field == 5 => { + assert_eq!(params.branch.as_deref(), Some("feature")); + } + Method::WorktreeOpen(params) if field == 8 => { + assert_eq!(params.path.as_deref(), Some("/repo-feature")); + } + _ => panic!("wrong method for field {field}"), + } + } else { + assert!(result.actions.is_empty()); + if field == 7 && code == KeyCode::Esc { + let Some(ClientShellOverlay::Help(help)) = &state.overlay else { + panic!("Escape should leave help open"); + }; + assert!(!help.search_focused); + assert!(help.query.is_empty()); + assert_eq!(help.scroll, 0); + } else { + assert!(state.overlay.is_none(), "field {field}, {code:?}"); + } + } + } + } +} + +#[test] +fn busy_worktree_inputs_ignore_edits_paste_and_cancel() { + for field in [5, 8] { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from("ab"); + match state.overlay.as_mut().expect("overlay") { + ClientShellOverlay::WorktreeCreate(v) => v.creating = true, + ClientShellOverlay::WorktreeOpen(v) => v.opening = true, + _ => unreachable!(), + } + let before = editor(&mut state).clone(); + for code in [ + KeyCode::Left, + KeyCode::Backspace, + KeyCode::Esc, + KeyCode::Enter, + ] { + press(&mut state, code, KeyModifiers::NONE); + } + state.handle_raw_events(vec![RawInputEvent::Paste("ignored".into())]); + assert_eq!(editor(&mut state), &before); + assert!(!state.modal_paste_target_active()); + } +} + +#[test] +fn focused_filters_keep_ctrl_n_p_navigation_and_literal_commands() { + for field in [6, 7, 8] { + let mut state = shell(field); + if let Some(ClientShellOverlay::Navigator(navigator)) = state.overlay.as_mut() { + navigator.selected = None; + } + if let Some(ClientShellOverlay::WorktreeOpen(open)) = state.overlay.as_mut() { + let mut second = open.entries[0].clone(); + second.path = "/second".into(); + open.entries.push(second); + } + state.compose(106, 30).expect("filter frame"); + let before = match state.overlay.as_ref().expect("overlay") { + ClientShellOverlay::Navigator(v) => format!("{:?}", v.selected), + ClientShellOverlay::Help(v) => v.scroll.to_string(), + ClientShellOverlay::WorktreeOpen(v) => v.selected.to_string(), + _ => unreachable!(), + }; + press(&mut state, KeyCode::Char('n'), KeyModifiers::CONTROL); + let after = match state.overlay.as_ref().expect("overlay") { + ClientShellOverlay::Navigator(v) => format!("{:?}", v.selected), + ClientShellOverlay::Help(v) => v.scroll.to_string(), + ClientShellOverlay::WorktreeOpen(v) => v.selected.to_string(), + _ => unreachable!(), + }; + assert_ne!(before, after, "field {field}"); + press(&mut state, KeyCode::Char('p'), KeyModifiers::CONTROL); + assert!(editor(&mut state).is_empty()); + for ch in ['j', 'k', '?'] { + press(&mut state, KeyCode::Char(ch), KeyModifiers::NONE); + } + assert_eq!(editor(&mut state).as_str(), "jk?"); + } +} + +#[test] +fn all_naming_targets_preserve_submission_and_empty_semantics() { + use crate::api::schema::Method; + for field in 0..5 { + for empty in [false, true] { + let mut state = shell(field); + *editor(&mut state) = TextEditor::from(if empty { " " } else { " ab " }); + if !empty { + press(&mut state, KeyCode::Home, KeyModifiers::NONE); + press(&mut state, KeyCode::Char('X'), KeyModifiers::NONE); + } + let result = press(&mut state, KeyCode::Enter, KeyModifiers::NONE); + assert!(state.overlay.is_none()); + if empty && matches!(field, 1 | 3) { + assert!(result.actions.is_empty()); + continue; + } + let [ClientShellAction::Endpoint { request, .. }] = &result.actions[..] else { + panic!("naming target {field}"); + }; + let expected = if empty { "" } else { "X ab" }; + match &request.method { + Method::WorkspaceCreate(v) => { + assert_eq!(v.label.as_deref(), (!empty).then_some(expected)) + } + Method::WorkspaceRename(v) => assert_eq!(v.label, expected), + Method::TabCreate(v) => { + assert_eq!(v.label.as_deref(), (!empty).then_some(expected)) + } + Method::TabRename(v) => assert_eq!(v.label, expected), + Method::PaneRename(v) => assert_eq!(v.label.as_deref(), Some(expected)), + _ => panic!("wrong method"), + } + } + } +} + +#[test] +fn copy_search_owns_prefix_but_parked_prompt_does_not_steal_input() { + let mut state = shell(9); + *editor(&mut state) = TextEditor::from("ab"); + press(&mut state, KeyCode::Char('b'), KeyModifiers::CONTROL); + assert_eq!(state.mode, ClientShellMode::Copy); + state.handle_raw_events(vec![RawInputEvent::Text(TextCommit::new("X"))]); + assert_eq!(editor(&mut state).as_str(), "aXb"); + state.open_rename_pane_overlay(); + assert!(state.modal_paste_target_active()); + state.handle_raw_events(vec![RawInputEvent::Paste("name".into())]); + assert_eq!(editor(&mut state).as_str(), "name"); + state.overlay = None; + assert_eq!(editor(&mut state).as_str(), "aXb"); + state.mode = ClientShellMode::Terminal; + assert!(!state.modal_paste_target_active()); + let input = state.handle_raw_events(vec![RawInputEvent::Text(TextCommit::new("terminal"))]); + assert!( + matches!(&input.requests[..], [ClientMessage::ClientShellPaneInput { events, .. }] if matches!(&events[..], [ClientPaneInputEvent::TextCommit(text)] if text == "terminal")) + ); + assert_eq!(editor(&mut state).as_str(), "aXb"); + state.mode = ClientShellMode::Copy; + press(&mut state, KeyCode::Esc, KeyModifiers::NONE); + press(&mut state, KeyCode::Char('b'), KeyModifiers::CONTROL); + assert_eq!(state.mode, ClientShellMode::Prefix); +} + +#[test] +fn every_field_renders_long_unicode_across_resize_without_mutation() { + for field in 0..10 { + let mut state = shell(field); + *editor(&mut state) = TextEditor::new(&"e\u{301}δΈπ©βπ»".repeat(40), false); + for position in [KeyCode::Home, KeyCode::End, KeyCode::Left] { + press(&mut state, position, KeyModifiers::NONE); + for (width, height) in [(120, 40), (60, 20), (12, 6), (1, 1), (120, 40)] { + let before = editor(&mut state).clone(); + if let Some(frame) = state.compose(width, height) { + if let Some(cursor) = frame.cursor.filter(|cursor| cursor.visible) { + assert!(cursor.x < width && cursor.y < height, "field {field}"); + } + } + assert_eq!(editor(&mut state), &before); + } + } + } +} diff --git a/src/client/shell/tests/workspace_navigation.rs b/src/client/shell/tests/workspace_navigation.rs index 3e19b87c..2a100957 100644 --- a/src/client/shell/tests/workspace_navigation.rs +++ b/src/client/shell/tests/workspace_navigation.rs @@ -223,11 +223,12 @@ fn foreign_workspace_preview_blocks_paste_into_hidden_copy_search() { state.set_pane_surface(pane_surface); state.compose(100, 28).unwrap(); assert!(state.enter_copy_mode(&mut ClientShellInput::default())); + enter_navigation(&mut state); + // Seed the hidden prompt after navigation: text editing consumes the prefix key. state.copy_mode.as_mut().unwrap().search_prompt = Some(ClientCopySearchPrompt { direction: crate::api::schema::PaneCopySearchDirection::Forward, query: "original".into(), }); - enter_navigation(&mut state); preview_key(&mut state, b"\x1b[B"); assert!(state.workspace_preview_action_blocked()); assert!(!state.modal_paste_target_active()); @@ -241,7 +242,7 @@ fn foreign_workspace_preview_blocks_paste_into_hidden_copy_search() { assert!(paste.actions.is_empty() && paste.requests.is_empty()); assert_eq!( state.copy_mode.unwrap().search_prompt.unwrap().query, - "original" + "original".into() ); } |
