Kit · K04: compat/kit runs Ely beside the newest GPUI Kit in one window; check.sh holds it to Kit's newest release

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 13:23:23 -04:00
1 parent 98de2a455f
commit 99615e1a0c
10 files changed
+352 -6

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
use ely_gpui_component::Assets;
use gpui_kit::AssetSource;
#[test]
fn one_source_serves_both_libraries() {
let kit = gpui_kit::assets::Assets;
let both = Assets::before(gpui_kit::assets::Assets);
let ely = Assets.list("").unwrap();
assert!(ely.len() > 300, "Ely bundles {} files", ely.len());
for path in &ely {
let served = both.load(path).unwrap().expect("Ely's file loads");
assert_eq!(
served,
Assets.load(path).unwrap().unwrap(),
"{path} is Ely's"
);
}
let icons = kit.list("icons/").unwrap();
assert!(!icons.is_empty(), "Kit lists its icons");
for path in &icons {
let served = both.load(path).unwrap().expect("Kit's icon loads");
if Assets.load(path).unwrap().is_none() {
assert_eq!(served, kit.load(path).unwrap().unwrap(), "{path} is Kit's");
}
}
}
+112
View File
@@ -0,0 +1,112 @@
use ely_gpui_component::{buttons::Button, overlays::Dialog, primitives::FocusScope};
use gpui_kit::{
AppContext, Bounds, Context, Entity, FocusHandle, IntoElement, KeyUpEvent, Keystroke,
ParentElement, Render, Styled, TestAppContext, VisualTestContext, Window, WindowBounds,
WindowOptions,
component::{
WindowExt,
input::{Editor, EditorState, Input, InputState},
},
div, point,
prelude::*,
px, size,
};
pub const CODE: &str = "fn main() {}";
/// Ely's root scope inside Kit's `Root`.
pub struct Desk {
pub root: FocusHandle,
pub open: FocusHandle,
pub line: Entity<InputState>,
pub code: Entity<EditorState>,
pub ely_dialog: bool,
}
impl Render for Desk {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
FocusScope::new(&self.root)
.root()
.size_full()
.p_4()
.flex()
.flex_col()
.gap_4()
.child(
div().debug_selector(|| "ely-open".into()).child(
Button::new("ely-open", "Open")
.focus_handle(&self.open)
.on_click(|_, window, cx| {
window.open_dialog(cx, |dialog, _, _| dialog.title("Kit dialog"));
}),
),
)
.child(Input::new(&self.line))
.child(div().h(px(120.)).child(Editor::new(&self.code).size_full()))
.when(self.ely_dialog, |desk| {
desk.child(
Dialog::new("ely-dialog", "Ely dialog", move |_, cx| {
view.update(cx, |desk, cx| {
desk.ely_dialog = false;
cx.notify();
})
})
.action(|_| Button::new("ely-first", "First"))
.action(|_| Button::new("ely-second", "Second")),
)
})
}
}
/// Starts both libraries in order; Kit opens the window.
pub fn open(cx: &mut TestAppContext, ely_last: bool) -> (Entity<Desk>, VisualTestContext) {
cx.update(|cx| {
if ely_last {
gpui_kit::init(cx);
ely_gpui_component::init_for_tests(cx);
} else {
ely_gpui_component::init_for_tests(cx);
gpui_kit::init(cx);
}
});
let (window, desk) = cx.update(|cx| {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds {
origin: point(px(0.), px(0.)),
size: size(px(800.), px(600.)),
})),
..Default::default()
};
gpui_kit::open_window(options, cx, |window, cx| {
cx.new(|cx| Desk {
root: cx.focus_handle(),
open: cx.focus_handle().tab_stop(true),
line: cx.new(|cx| InputState::new(window, cx)),
code: cx.new(|cx| {
EditorState::new(window, cx)
.language("rust")
.default_value(CODE)
}),
ely_dialog: false,
})
})
.expect("Kit opens the window")
});
let cx = VisualTestContext::from_window(window, cx);
cx.run_until_parked();
(desk, cx)
}
/// Whether the handle holds focus.
pub fn focused(cx: &mut VisualTestContext, handle: &FocusHandle) -> bool {
cx.update(|window, _| handle.is_focused(window))
}
/// Key down and up; gpui presses on the release.
pub fn press(cx: &mut VisualTestContext, key: &str) {
cx.simulate_keystrokes(key);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("a key"),
});
}
+117
View File
@@ -0,0 +1,117 @@
use std::time::Duration;
use gpui_kit::{
FocusHandle, Focusable, Modifiers, TestAppContext, VisualTestContext, test::TestWindowExt,
};
use crate::desk::{focused, open, press};
/// Draws until `done` holds; fails after a second.
fn settle(cx: &mut VisualTestContext, done: impl Fn(&mut VisualTestContext) -> bool) {
for _ in 0..50 {
cx.run_until_parked();
cx.update(|window, cx| window.render_frame(cx));
if done(cx) {
return;
}
std::thread::sleep(Duration::from_millis(20));
}
panic!("the window did not settle");
}
fn kit_dialog_open(cx: &mut VisualTestContext) -> bool {
cx.update(|window, _| window.try_find("dialog").is_some())
}
/// Opens Kit's dialog, tabs inside it, closes it.
fn kit_dialog_round_trip(
cx: &mut TestAppContext,
open_with: impl FnOnce(&mut VisualTestContext, &Handles),
back_to: impl Fn(&Handles) -> FocusHandle,
) {
let (desk, mut cx) = open(cx, true);
let handles = cx.update(|_, cx| {
let desk = desk.read(cx);
Handles {
button: desk.open.clone(),
line: desk.line.read(cx).focus_handle(cx),
code: desk.code.read(cx).focus_handle(cx),
}
});
open_with(&mut cx, &handles);
settle(&mut cx, kit_dialog_open);
for _ in 0..3 {
cx.simulate_keystrokes("tab");
for handle in [&handles.button, &handles.line, &handles.code] {
assert!(!focused(&mut cx, handle), "Tab stays in Kit's dialog");
}
}
cx.simulate_keystrokes("escape");
settle(&mut cx, |cx| !kit_dialog_open(cx));
assert!(
focused(&mut cx, &back_to(&handles)),
"focus returns where it was"
);
}
struct Handles {
button: FocusHandle,
line: FocusHandle,
code: FocusHandle,
}
#[gpui_kit::test]
fn enter_on_an_ely_button_opens_a_kit_dialog_that_hands_focus_back(cx: &mut TestAppContext) {
kit_dialog_round_trip(
cx,
|cx, handles| {
cx.update(|window, cx| window.focus(&handles.button, cx));
press(cx, "enter");
},
|handles| handles.button.clone(),
);
}
#[gpui_kit::test]
fn a_press_on_an_ely_button_leaves_focus_for_the_kit_dialog_to_return(cx: &mut TestAppContext) {
kit_dialog_round_trip(
cx,
|cx, handles| {
cx.update(|window, cx| window.focus(&handles.line, cx));
let bounds = cx.debug_bounds("ely-open").expect("Ely's button drew");
cx.simulate_click(bounds.center(), Modifiers::none());
},
|handles| handles.line.clone(),
);
}
#[gpui_kit::test]
fn an_ely_dialog_over_kit_fields_traps_tab_and_hands_focus_back(cx: &mut TestAppContext) {
let (desk, mut cx) = open(cx, true);
let (button, line, code) = cx.update(|_, cx| {
let desk = desk.read(cx);
(
desk.open.clone(),
desk.line.read(cx).focus_handle(cx),
desk.code.read(cx).focus_handle(cx),
)
});
cx.update(|window, cx| window.focus(&line, cx));
desk.update(&mut cx, |desk, cx| {
desk.ely_dialog = true;
cx.notify();
});
settle(&mut cx, |cx| !focused(cx, &line));
for _ in 0..3 {
cx.simulate_keystrokes("tab");
for handle in [&button, &line, &code] {
assert!(!focused(&mut cx, handle), "Tab stays in Ely's dialog");
}
}
cx.simulate_keystrokes("escape");
settle(&mut cx, |cx| focused(cx, &line));
assert!(
!desk.read_with(&cx, |desk, _| desk.ely_dialog),
"Escape closed it"
);
}
+53
View File
@@ -0,0 +1,53 @@
use gpui_kit::{Focusable, TestAppContext};
use crate::desk::{CODE, focused, open};
fn tab_runs_through_both(cx: &mut TestAppContext, ely_last: bool) {
let (desk, mut cx) = open(cx, ely_last);
let (open_button, line, code) = cx.update(|_, cx| {
let desk = desk.read(cx);
(desk.open.clone(), desk.line.clone(), desk.code.clone())
});
cx.update(|window, cx| window.focus(&open_button, cx));
cx.simulate_keystrokes("tab");
let line_focus = cx.update(|_, cx| line.read(cx).focus_handle(cx));
assert!(
focused(&mut cx, &line_focus),
"Tab leaves Ely's button for Kit's field"
);
cx.simulate_keystrokes("tab");
let code_focus = cx.update(|_, cx| code.read(cx).focus_handle(cx));
assert!(
focused(&mut cx, &code_focus),
"Tab reaches Kit's code editor"
);
cx.simulate_keystrokes("tab");
assert!(
focused(&mut cx, &code_focus),
"Tab stays in the code editor"
);
let text = cx.update(|_, cx| code.read(cx).value().to_string());
assert!(
text.len() > CODE.len() && text.trim() == CODE,
"Tab indents: {text:?}"
);
cx.simulate_keystrokes("shift-tab");
let text = cx.update(|_, cx| code.read(cx).value().to_string());
assert_eq!(text, CODE, "Shift-Tab outdents");
cx.update(|window, cx| window.focus(&line_focus, cx));
cx.simulate_keystrokes("shift-tab");
assert!(
focused(&mut cx, &open_button),
"Shift-Tab returns to Ely's button"
);
}
#[gpui_kit::test]
fn tab_runs_through_both_with_ely_started_last(cx: &mut TestAppContext) {
tab_runs_through_both(cx, true);
}
#[gpui_kit::test]
fn tab_runs_through_both_with_kit_started_last(cx: &mut TestAppContext) {
tab_runs_through_both(cx, false);
}
+10
View File
@@ -0,0 +1,10 @@
//! Ely and GPUI Kit in one app and one window.
#[cfg(test)]
mod assets;
#[cfg(test)]
mod desk;
#[cfg(test)]
mod dialogs;
#[cfg(test)]
mod focus;