mod common;
#[path = "input/completions.rs"]
mod completions;
#[path = "input/composition.rs"]
mod composition;
#[path = "input/constraints.rs"]
mod constraints;
#[path = "input/editing.rs"]
mod editing;
#[path = "input/editor.rs"]
mod editor;
#[path = "input/history.rs"]
mod history;
#[path = "input/lifecycle.rs"]
mod lifecycle;
#[path = "input/textarea.rs"]
mod textarea;
use gpui::{
AppContext, Context, Entity, TestAppContext, Window, WindowHandle, div, prelude::*, px,
};
use gpui_component::input::{Input, InputState};
use gpui_kit::test::TestWindowExt;
struct Inputs {
first: Entity<InputState>,
second: Entity<InputState>,
guarded: Entity<InputState>,
secret: Entity<InputState>,
disabled: bool,
readonly: bool,
}
impl Render for Inputs {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.flex()
.flex_col()
.gap_4()
.child(Input::new(&self.first).id("first").w(px(240.)))
.child(Input::new(&self.second).id("second").w(px(240.)))
.child(
Input::new(&self.guarded)
.id("guarded")
.w(px(240.))
.disabled(self.disabled)
.readonly(self.readonly),
)
.child(Input::new(&self.secret).id("secret").w(px(240.)))
}
}
fn inputs(cx: &mut TestAppContext) -> (WindowHandle<gpui_kit::base::Root>, Entity<Inputs>) {
cx.update(gpui_component::init);
common::open_window(cx, None, |window, cx| {
cx.new(|cx| Inputs {
first: cx.new(|cx| InputState::new(window, cx)),
second: cx.new(|cx| InputState::new(window, cx)),
guarded: cx.new(|cx| InputState::new(window, cx).default_value("fixed")),
secret: cx.new(|cx| InputState::new(window, cx).masked(true)),
disabled: false,
readonly: false,
})
})
}
#[gpui::test]
fn text_goes_only_to_the_focused_input(cx: &mut TestAppContext) {
let (handle, handle_content) = inputs(cx);
cx.update_window(handle.into(), |_, window, cx| {
window.click("first", cx);
window.input("A🦀", cx);
let previous = window.find("first");
window.click("second", cx);
window.input("中文", cx);
assert_eq!(window.find("first").focused(), Some(false));
assert_eq!(window.find("second").focused(), Some(true));
assert_eq!(window.find("first").value(), Some("A🦀"));
assert_eq!(window.find("second").value(), Some("中文"));
assert_eq!(previous.focused(), Some(true));
})
.unwrap();
common::update_content(handle, &handle_content, cx, |view, _, cx| {
assert_eq!(view.first.read(cx).value(), "A🦀");
assert_eq!(view.second.read(cx).value(), "中文");
})
.unwrap();
}
#[gpui::test]
fn readonly_and_disabled_inputs_reject_native_typing(cx: &mut TestAppContext) {
let (handle, handle_content) = inputs(cx);
for disabled in [false, true] {
common::update_content(handle, &handle_content, cx, |view, _, cx| {
view.disabled = disabled;
view.readonly = !disabled;
cx.notify();
})
.unwrap();
cx.update_window(handle.into(), |_, window, cx| {
window.click("guarded", cx);
window.input("ignored", cx);
let input = window.find("guarded");
assert_eq!(input.disabled(), None);
assert_eq!(input.value(), Some("fixed"));
})
.unwrap();
common::update_content(handle, &handle_content, cx, |view, _, cx| {
assert_eq!(view.guarded.read(cx).value(), "fixed")
})
.unwrap();
}
}
#[gpui::test]
fn masked_input_handles_typing_without_reporting_secret_value(cx: &mut TestAppContext) {
let (handle, handle_content) = inputs(cx);
cx.update_window(handle.into(), |_, window, cx| {
window.click("secret", cx);
window.input("secret", cx);
let secret = window.find("secret");
assert_eq!(secret.focused(), Some(true));
assert_eq!(secret.value(), None);
})
.unwrap();
common::update_content(handle, &handle_content, cx, |view, _, cx| {
assert_eq!(view.secret.read(cx).value(), "secret")
})
.unwrap();
}
#[gpui::test]
fn existing_gpui_keyboard_editing_updates_observed_value(cx: &mut TestAppContext) {
let (handle, handle_content) = inputs(cx);
cx.update_window(handle.into(), |_, window, cx| {
window.click("first", cx);
window.input("ab", cx);
})
.unwrap();
cx.simulate_keystrokes(handle.into(), "backspace");
cx.update_window(handle.into(), |_, window, cx| {
window.refresh();
window.draw(cx).clear(cx);
assert_eq!(window.find("first").value(), Some("a"));
})
.unwrap();
common::update_content(handle, &handle_content, cx, |view, _, cx| {
assert_eq!(view.first.read(cx).value(), "a")
})
.unwrap();
}
struct ScopedInputs {
left: Entity<InputState>,
right: Entity<InputState>,
}
impl Render for ScopedInputs {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.flex()
.flex_col()
.gap_4()
.child(
div()
.id("left")
.child(Input::new(&self.left).id("name").w(px(240.))),
)
.child(
div()
.id("right")
.child(Input::new(&self.right).id("name").w(px(240.))),
)
}
}
fn scoped_inputs(
cx: &mut TestAppContext,
) -> (WindowHandle<gpui_kit::base::Root>, Entity<ScopedInputs>) {
cx.update(gpui_component::init);
common::open_window(cx, None, |window, cx| {
cx.new(|cx| ScopedInputs {
left: cx.new(|cx| InputState::new(window, cx)),
right: cx.new(|cx| InputState::new(window, cx)),
})
})
}
#[gpui::test]
fn scoped_keyboard_uses_the_focused_input_in_the_selected_scope(cx: &mut TestAppContext) {
let (handle, _) = scoped_inputs(cx);
cx.update_window(handle.into(), |_, window, cx| {
window.render_frame(cx);
let mut dialog = window.within("right");
dialog.click("name", cx);
dialog.input("Ada中", cx);
dialog.press("backspace", cx);
assert_eq!(dialog.find("name").value(), Some("Ada"));
dialog.press("shift-a", cx);
assert_eq!(dialog.find("name").value(), Some("AdaA"));
window.press("space", cx);
assert_eq!(window.within("right").find("name").value(), Some("AdaA "));
assert_eq!(window.within("left").find("name").value(), Some(""));
})
.unwrap();
}
#[gpui::test]
fn scoped_keyboard_rejects_focus_outside_the_selected_scope(cx: &mut TestAppContext) {
let (handle, _) = scoped_inputs(cx);
cx.update_window(handle.into(), |_, window, cx| {
window.render_frame(cx);
window.within("left").click("name", cx);
for typing in [false, true] {
let error = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut dialog = window.within("right");
if typing {
dialog.input("wrong", cx);
} else {
dialog.press("a", cx);
}
}))
.expect_err("scoped keyboard must reject focus in another scope");
let message = error.downcast_ref::<String>().unwrap();
assert!(message.contains("no observed keyboard focus inside scope"));
}
assert_eq!(window.within("left").find("name").value(), Some(""));
assert_eq!(window.within("right").find("name").value(), Some(""));
})
.unwrap();
}