use gpui::{
Context, Modifiers, MouseButton, TestAppContext, VisualTestContext, Window, div, prelude::*,
};
use gpui_ui_kit::input::Input;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct InputTestView;
impl Render for InputTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(
Input::new("test-input")
.placeholder("Enter text...")
.value("Hello"),
)
}
}
#[gpui::test]
async fn test_input_renders(cx: &mut TestAppContext) {
let _window = cx.add_window(|_window, _cx| InputTestView);
}
struct InputWithCallbackView {
value: Rc<RefCell<String>>,
}
impl Render for InputWithCallbackView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let value = self.value.borrow().clone();
let value_rc = self.value.clone();
div().child(
Input::new("callback-input")
.placeholder("Type here...")
.value(value)
.on_text_change(move |text, _window, _cx| {
*value_rc.borrow_mut() = text;
}),
)
}
}
#[gpui::test]
async fn test_input_with_callback(cx: &mut TestAppContext) {
let value = Rc::new(RefCell::new("initial".to_string()));
let value_clone = value.clone();
let _window = cx.add_window(move |_window, _cx| InputWithCallbackView { value: value_clone });
assert_eq!(*value.borrow(), "initial");
}
#[gpui::test]
async fn test_input_configurations(cx: &mut TestAppContext) {
use gpui_ui_kit::input::{InputSize, InputVariant};
struct ConfigTestView;
impl Render for ConfigTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.child(Input::new("small-input").size(InputSize::Sm).value("Small"))
.child(
Input::new("filled-input")
.variant(InputVariant::Filled)
.value("Filled"),
)
.child(
Input::new("disabled-input")
.disabled(true)
.value("Disabled"),
)
.child(
Input::new("readonly-input")
.readonly(true)
.value("Readonly"),
)
.child(
Input::new("error-input")
.error("This is an error")
.value("Error"),
)
}
}
let _window = cx.add_window(|_window, _cx| ConfigTestView);
}
struct InputKeyboardTestView {
text_changes: Arc<RefCell<Vec<String>>>,
render_count: Arc<AtomicUsize>,
}
impl Render for InputKeyboardTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.render_count.fetch_add(1, Ordering::SeqCst);
let text_changes = self.text_changes.clone();
div().id("test-container").size_full().child(
Input::new("keyboard-test-input")
.placeholder("Type here...")
.value("")
.on_text_change(move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
}),
)
}
}
#[gpui::test]
async fn test_input_click_to_focus_and_type(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let render_count = Arc::new(AtomicUsize::new(0));
let text_changes_clone = text_changes.clone();
let render_count_clone = render_count.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardTestView {
text_changes: text_changes_clone,
render_count: render_count_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
let initial_renders = render_count.load(Ordering::SeqCst);
assert!(initial_renders >= 1, "Should have rendered at least once");
if let Some(bounds) = cx.debug_bounds("keyboard-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("abc");
cx.run_until_parked();
let changes = text_changes.borrow();
assert!(!changes.is_empty(), "Should have captured text changes");
let last_change = changes.last().unwrap();
assert!(
last_change.contains("abc") || last_change == "abc",
"Last text change should contain 'abc', got: {}",
last_change
);
} else {
eprintln!(
"Note: Could not find 'keyboard-test-input' in debug bounds. Skipping click test."
);
}
}
#[gpui::test]
async fn test_input_focus_persists_across_renders(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let render_count = Arc::new(AtomicUsize::new(0));
let text_changes_clone = text_changes.clone();
let render_count_clone = render_count.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardTestView {
text_changes: text_changes_clone,
render_count: render_count_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
let renders_before = render_count.load(Ordering::SeqCst);
cx.simulate_input("x");
cx.run_until_parked();
cx.simulate_input("y");
cx.run_until_parked();
cx.simulate_input("z");
cx.run_until_parked();
let renders_after = render_count.load(Ordering::SeqCst);
assert!(
renders_after > renders_before,
"Should have re-rendered after typing. Before: {}, After: {}",
renders_before,
renders_after
);
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.len() >= 3,
"Should have captured all typed characters, got: {}",
last
);
}
}
}
struct InputOnChangeTestView {
confirmed_value: Arc<RefCell<Option<String>>>,
}
impl Render for InputOnChangeTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let confirmed = self.confirmed_value.clone();
div().size_full().child(
Input::new("onchange-test-input")
.placeholder("Type and press Enter...")
.value("")
.on_change(move |text, _window, _cx| {
*confirmed.borrow_mut() = Some(text.to_string());
}),
)
}
}
#[gpui::test]
async fn test_input_on_change_called_on_enter(cx: &mut TestAppContext) {
let confirmed_value: Arc<RefCell<Option<String>>> = Arc::new(RefCell::new(None));
let confirmed_clone = confirmed_value.clone();
let window = cx.add_window(move |_window, _cx| InputOnChangeTestView {
confirmed_value: confirmed_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("onchange-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("test value");
cx.run_until_parked();
cx.simulate_keystrokes("enter");
cx.run_until_parked();
let confirmed = confirmed_value.borrow();
assert!(
confirmed.is_some(),
"on_change should have been called on Enter"
);
assert_eq!(
confirmed.as_ref().unwrap(),
"test value",
"Confirmed value should match typed text"
);
}
}
struct InputEscapeTestView {
confirmed_value: Arc<RefCell<Option<String>>>,
cancelled: Arc<RefCell<bool>>,
}
impl Render for InputEscapeTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let confirmed = self.confirmed_value.clone();
let cancelled = self.cancelled.clone();
div().size_full().child(
Input::new("escape-test-input")
.placeholder("Type and press Escape...")
.value("")
.on_change(move |text, _window, _cx| {
*confirmed.borrow_mut() = Some(text.to_string());
})
.on_edit_end({
let cancelled = cancelled.clone();
move |result, _window, _cx| {
if result.is_none() {
*cancelled.borrow_mut() = true;
}
}
}),
)
}
}
#[gpui::test]
async fn test_input_escape_cancels_edit(cx: &mut TestAppContext) {
let confirmed_value: Arc<RefCell<Option<String>>> = Arc::new(RefCell::new(None));
let cancelled: Arc<RefCell<bool>> = Arc::new(RefCell::new(false));
let confirmed_clone = confirmed_value.clone();
let cancelled_clone = cancelled.clone();
let window = cx.add_window(move |_window, _cx| InputEscapeTestView {
confirmed_value: confirmed_clone,
cancelled: cancelled_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("escape-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("draft text");
cx.run_until_parked();
cx.simulate_keystrokes("escape");
cx.run_until_parked();
let confirmed = confirmed_value.borrow();
assert!(
confirmed.is_none(),
"on_change should NOT be called on Escape"
);
assert!(
*cancelled.borrow(),
"on_edit_end should be called with None on Escape"
);
}
}
struct InputDoubleClickTestView {
text_changes: Arc<RefCell<Vec<String>>>,
}
impl Render for InputDoubleClickTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let text_changes = self.text_changes.clone();
div().size_full().child(
Input::new("doubleclick-test-input")
.value("Hello World")
.on_text_change(move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
}),
)
}
}
#[gpui::test]
async fn test_input_double_click_selects_all(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputDoubleClickTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("doubleclick-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("X");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.contains("X"),
"After double-click and typing, text should contain 'X', got: {}",
last
);
}
}
}
struct InputDragSelectTestView {
text_changes: Arc<RefCell<Vec<String>>>,
}
impl Render for InputDragSelectTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let text_changes = self.text_changes.clone();
div().size_full().child(
Input::new("drag-select-input")
.value("Select some text here")
.on_text_change(move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
}),
)
}
}
#[gpui::test]
async fn test_input_mouse_drag_selects_text(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputDragSelectTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("drag-select-input") {
let start = gpui::point(bounds.left() + gpui::px(10.0), bounds.center().y);
let end = gpui::point(bounds.right() - gpui::px(10.0), bounds.center().y);
cx.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_mouse_move(end, Some(MouseButton::Left), Modifiers::default());
cx.run_until_parked();
cx.simulate_mouse_up(end, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("NEW");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.contains("NEW"),
"After drag select and typing, text should contain 'NEW', got: {}",
last
);
}
}
}
struct InputKeyboardNavTestView {
text_changes: Arc<RefCell<Vec<String>>>,
}
impl Render for InputKeyboardNavTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let text_changes = self.text_changes.clone();
div().size_full().child(
Input::new("keyboard-nav-input")
.value("ABCDEF")
.on_text_change(move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
}),
)
}
}
#[gpui::test]
async fn test_input_backspace_deletes(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("right");
cx.run_until_parked();
cx.simulate_keystrokes("backspace");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.len() < 6 || !last.ends_with("F"),
"Backspace should have deleted a character, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_delete_key(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("home");
cx.run_until_parked();
cx.simulate_keystrokes("delete");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
!last.starts_with("A") || last.len() < 6,
"Delete should have removed first character, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_arrow_key_navigation(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("end");
cx.run_until_parked();
cx.simulate_keystrokes("left left");
cx.run_until_parked();
cx.simulate_input("X");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.contains("X"),
"Should have inserted 'X' after arrow navigation, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_ctrl_a_moves_to_beginning(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("right");
cx.run_until_parked();
cx.simulate_keystrokes("ctrl-a");
cx.run_until_parked();
cx.simulate_input("Z");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.starts_with("Z"),
"Ctrl+A should move cursor to beginning, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_ctrl_e_moves_to_end(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("home");
cx.run_until_parked();
cx.simulate_keystrokes("ctrl-e");
cx.run_until_parked();
cx.simulate_input("Z");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert!(
last.ends_with("Z"),
"Ctrl+E should move cursor to end, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_ctrl_k_kills_to_end(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("home right right");
cx.run_until_parked();
cx.simulate_keystrokes("ctrl-k");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert_eq!(
last, "AB",
"Ctrl+K should kill text after cursor, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_ctrl_u_kills_to_beginning(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputKeyboardNavTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("keyboard-nav-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_keystrokes("home right right right right");
cx.run_until_parked();
cx.simulate_keystrokes("ctrl-u");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert_eq!(
last, "EF",
"Ctrl+U should kill text before cursor, got: {}",
last
);
}
}
}
struct InputClipboardTestView {
text_changes: Arc<RefCell<Vec<String>>>,
}
impl Render for InputClipboardTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let text_changes = self.text_changes.clone();
div()
.size_full()
.child(Input::new("clipboard-test-input").value("").on_text_change(
move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
},
))
}
}
#[gpui::test]
async fn test_input_copy_paste(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputClipboardTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("clipboard-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("Hello");
cx.run_until_parked();
#[cfg(target_os = "macos")]
cx.simulate_keystrokes("cmd-a");
#[cfg(not(target_os = "macos"))]
cx.simulate_keystrokes("ctrl-a");
cx.run_until_parked();
#[cfg(target_os = "macos")]
cx.simulate_keystrokes("cmd-c");
#[cfg(not(target_os = "macos"))]
cx.simulate_keystrokes("ctrl-c");
cx.run_until_parked();
cx.simulate_keystrokes("right");
cx.simulate_input(" ");
cx.run_until_parked();
#[cfg(target_os = "macos")]
cx.simulate_keystrokes("cmd-v");
#[cfg(not(target_os = "macos"))]
cx.simulate_keystrokes("ctrl-v");
cx.run_until_parked();
let changes = text_changes.borrow();
if !changes.is_empty() {
let last = changes.last().unwrap();
assert_eq!(
last, "Hello Hello",
"Paste should append copied text, got: {}",
last
);
}
}
}
#[gpui::test]
async fn test_input_cut(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputClipboardTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("clipboard-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("RemoveKeep");
cx.run_until_parked();
cx.simulate_keystrokes("home");
for _ in 0..6 {
cx.simulate_keystrokes("shift-right");
}
cx.run_until_parked();
#[cfg(target_os = "macos")]
cx.simulate_keystrokes("cmd-x");
#[cfg(not(target_os = "macos"))]
cx.simulate_keystrokes("ctrl-x");
cx.run_until_parked();
{
let changes = text_changes.borrow();
let last = changes.last().unwrap();
assert_eq!(
last, "Keep",
"Cut should remove selected text, got: {}",
last
);
}
cx.simulate_keystrokes("end");
#[cfg(target_os = "macos")]
cx.simulate_keystrokes("cmd-v");
#[cfg(not(target_os = "macos"))]
cx.simulate_keystrokes("ctrl-v");
cx.run_until_parked();
{
let changes = text_changes.borrow();
let last = changes.last().unwrap();
assert_eq!(
last, "KeepRemove",
"Paste after cut should restore text, got: {}",
last
);
}
}
}
struct InputDisabledTestView {
text_changes: Arc<RefCell<Vec<String>>>,
}
impl Render for InputDisabledTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let text_changes = self.text_changes.clone();
div().size_full().child(
Input::new("disabled-test-input")
.value("Cannot edit this")
.disabled(true)
.on_text_change(move |text, _window, _cx| {
text_changes.borrow_mut().push(text);
}),
)
}
}
#[gpui::test]
async fn test_input_disabled_no_input(cx: &mut TestAppContext) {
let text_changes: Arc<RefCell<Vec<String>>> = Arc::new(RefCell::new(Vec::new()));
let text_changes_clone = text_changes.clone();
let window = cx.add_window(move |_window, _cx| InputDisabledTestView {
text_changes: text_changes_clone,
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.run_until_parked();
if let Some(bounds) = cx.debug_bounds("disabled-test-input") {
let center = bounds.center();
cx.simulate_mouse_down(center, MouseButton::Left, Modifiers::default());
cx.simulate_mouse_up(center, MouseButton::Left, Modifiers::default());
cx.run_until_parked();
cx.simulate_input("X");
cx.run_until_parked();
let changes = text_changes.borrow();
assert!(
changes.is_empty(),
"Disabled input should not accept input, got {} changes",
changes.len()
);
}
}