use gpui::{ Entity, Modifiers, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px, }; use gpui_native_core::input::{Input, InputState}; use gpui_native_theme::Theme; struct Harness { state: Entity, disabled: bool, } impl Render for Harness { fn render( &mut self, _window: &mut Window, _cx: &mut gpui::Context, ) -> impl gpui::IntoElement { div().size_full().child( Input::new("test-input", self.state.clone()) .placeholder("Placeholder") .disabled(self.disabled), ) } } fn input_window( initial: &str, disabled: bool, ) -> (TestAppContext, Entity, VisualTestContext) { let mut cx = TestAppContext::single(); cx.update(Theme::init); let state = cx.update(|cx| InputState::new_with_text(initial, cx)); let state_for_window = state.clone(); let window = cx.add_window(move |_, _| Harness { state: state_for_window, disabled, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); visual.update(|window, cx| { window.draw(cx).clear(cx); }); (cx, state, visual) } fn value(cx: &TestAppContext, state: &Entity) -> String { cx.update(|cx| state.read(cx).value().to_string()) } fn focus(visual: &mut VisualTestContext, state: &Entity) { // Programmatic focus: a click would move the caret to the click point. visual.update(|window, cx| { let handle = state.read(cx).focus_handle(); window.focus(&handle, cx); }); } #[test] fn typing_inserts_at_the_caret() { let (cx, state, mut visual) = input_window("", false); focus(&mut visual, &state); visual.simulate_keystrokes("h e l l o"); assert_eq!(value(&cx, &state), "hello"); visual.simulate_keystrokes("space w o r l d"); assert_eq!(value(&cx, &state), "hello world"); } #[test] fn arrow_left_moves_the_caret_and_typing_splits_the_text() { let (cx, state, mut visual) = input_window("ab", false); focus(&mut visual, &state); visual.simulate_keystrokes("left"); visual.simulate_keystrokes("x"); assert_eq!(value(&cx, &state), "axb"); } #[test] fn backspace_deletes_before_the_caret_and_delete_after() { let (cx, state, mut visual) = input_window("abc", false); focus(&mut visual, &state); visual.simulate_keystrokes("backspace"); assert_eq!(value(&cx, &state), "ab"); // The caret sits between 'a' and 'b'; forward delete removes 'b'. visual.simulate_keystrokes("left"); visual.simulate_keystrokes("delete"); assert_eq!(value(&cx, &state), "a"); } #[test] fn alt_arrow_moves_by_words() { let (cx, state, mut visual) = input_window("hello world", false); focus(&mut visual, &state); visual.simulate_keystrokes("alt-left"); visual.simulate_keystrokes("x"); assert_eq!(value(&cx, &state), "hello xworld"); visual.simulate_keystrokes("alt-left alt-left"); visual.simulate_keystrokes("shift-right shift-right"); visual.simulate_keystrokes("z"); assert_eq!(value(&cx, &state), "zllo xworld"); } #[test] fn select_all_then_type_replaces_the_value() { let (cx, state, mut visual) = input_window("abcd", false); focus(&mut visual, &state); visual.simulate_keystrokes("cmd-a"); visual.simulate_keystrokes("z"); assert_eq!(value(&cx, &state), "z"); } #[test] fn shift_arrow_selects_and_backspace_removes_the_selection() { let (cx, state, mut visual) = input_window("ab", false); focus(&mut visual, &state); // Caret starts at the end; selecting left twice covers "ab". visual.simulate_keystrokes("shift-left shift-left"); visual.simulate_keystrokes("backspace"); assert_eq!(value(&cx, &state), ""); } #[test] fn disabled_input_ignores_typing_and_clicks() { // No programmatic focus: a disabled field must not take focus, so the // key events fall to nothing and the input handler is never registered. let (cx, state, mut visual) = input_window("", true); visual.simulate_click(point(px(20.), px(14.)), Modifiers::default()); visual.simulate_keystrokes("h"); assert_eq!(value(&cx, &state), ""); } #[test] fn set_value_replaces_the_buffer() { let (cx, state, mut visual) = input_window("", false); visual.update(|window, cx| { state.update(cx, |state, cx| state.set_value("preset", window, cx)); }); assert_eq!(value(&cx, &state), "preset"); }