gpui-kit 0.6.1

GPUI Kit: one dependency for building desktop applications with GPUI, GPUI Base, GPUI Component and default assets.
Documentation
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<Inputs> {
    cx.update(gpui_component::init);
    cx.add_window(|window, 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 = 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();
    handle
        .update(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 = inputs(cx);
    for disabled in [false, true] {
        handle
            .update(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();
        handle
            .update(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 = 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();
    handle
        .update(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 = 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();
    handle
        .update(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<ScopedInputs> {
    cx.update(gpui_component::init);
    cx.add_window(|window, 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"));
        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();
}