Files
gpui-native-kit/crates/layout/tests/resizable.rs
T
2026-11-06 20:13:08 +07:00

166 lines
5.5 KiB
Rust

use gpui::{
AppContext as _, Context, Entity, IntoElement, Modifiers, MouseButton, MouseDownEvent,
MouseMoveEvent, ParentElement as _, Pixels, Render, Styled as _, Subscription, TestAppContext,
VisualTestContext, Window, div, point, px,
};
use gpui_native_layout::resizable::{Resizable, ResizablePane, ResizableState};
use gpui_native_theme::{ActiveTheme as _, Size, Theme};
const GROUP_WIDTH: f32 = 200.0;
const GROUP_HEIGHT: f32 = 300.0;
struct Harness {
state: Entity<ResizableState>,
_observation: Subscription,
}
impl Render for Harness {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().w(px(GROUP_WIDTH)).h(px(GROUP_HEIGHT)).child(
Resizable::new("test-split", self.state.clone())
.debug_selector(|| "test-pane".to_string())
.size_full()
.pane(div())
.pane(div()),
)
}
}
fn split_window(sizes: [Pixels; 1]) -> (TestAppContext, Entity<ResizableState>, VisualTestContext) {
let mut cx = TestAppContext::single();
cx.update(Theme::init);
let state = cx.update(ResizableState::new);
state.update(&mut cx, |state, cx| state.set_sizes(sizes, cx));
let window_state = state.clone();
let window = cx.add_window(move |_, cx: &mut Context<Harness>| {
let observation = cx.observe(&window_state, |_, _, cx| cx.notify());
Harness {
state: window_state,
_observation: observation,
}
});
let window = window.into();
cx.update_window(window, |_, window, cx| {
window.draw(cx).clear(cx);
})
.unwrap();
let visual = VisualTestContext::from_window(window, &cx);
(cx, state, visual)
}
fn divider_center(visual: &mut VisualTestContext) -> gpui::Point<gpui::Pixels> {
let bounds = visual
.debug_bounds("resize-handle")
.expect("divider rendered");
point(bounds.center().x, bounds.center().y)
}
fn drag_divider(visual: &mut VisualTestContext, delta: gpui::Pixels) {
let start = divider_center(visual);
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
visual.simulate_event(MouseMoveEvent {
position: point(start.x, start.y + delta),
pressed_button: Some(MouseButton::Left),
modifiers: Modifiers::default(),
});
visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default());
}
#[test]
fn a_drag_moves_the_pane_and_its_rendered_height() {
let (mut cx, state, mut visual) = split_window([px(100.)]);
drag_divider(&mut visual, px(60.));
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(160.)]));
// The pane re-renders at the new size after the state notifies the owner.
visual.update(|window, cx| window.draw(cx).clear(cx));
let bounds = visual
.debug_bounds("test-pane-0")
.expect("first pane rendered");
assert_eq!(bounds.size.height, px(160.));
}
#[test]
fn a_drag_stops_where_the_remainder_keeps_its_floor() {
let (mut cx, state, mut visual) = split_window([px(100.)]);
let thickness = cx.update(|cx| cx.theme().sizes.resolve(Size::default()).dense_padding_x);
let floor = ResizablePane::DEFAULT_MIN;
drag_divider(&mut visual, px(1000.));
state.update(&mut cx, |state, _| {
assert_eq!(state.sizes(), &[px(GROUP_HEIGHT) - thickness - floor]);
});
}
#[test]
fn a_double_click_resets_the_pane_to_the_equal_split() {
let (mut cx, state, mut visual) = split_window([px(240.)]);
let thickness = cx.update(|cx| cx.theme().sizes.resolve(Size::default()).dense_padding_x);
let start = divider_center(&mut visual);
visual.simulate_event(MouseDownEvent {
position: start,
modifiers: Modifiers::default(),
button: MouseButton::Left,
click_count: 2,
first_mouse: false,
});
state.update(&mut cx, |state, _| {
assert_eq!(
state.sizes(),
&[px((GROUP_HEIGHT - thickness.as_f32()) / 2.)]
);
});
}
#[test]
fn arrow_keys_resize_the_focused_pane() {
let (mut cx, state, mut visual) = split_window([px(100.)]);
let start = divider_center(&mut visual);
// A single click focuses the handle without resetting anything.
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default());
visual.simulate_keystrokes("down");
visual.simulate_keystrokes("up");
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(100.)]));
}
#[test]
fn an_unfocused_handle_ignores_arrow_keys() {
let (mut cx, state, mut visual) = split_window([px(100.)]);
let start = divider_center(&mut visual);
let _ = start;
visual.simulate_keystrokes("down");
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(100.)]));
}
#[test]
fn the_panes_and_the_divider_fill_the_group_exactly() {
let (_cx, _state, mut visual) = split_window([px(100.)]);
visual.update(|window, cx| window.draw(cx).clear(cx));
let first = visual
.debug_bounds("test-pane-0")
.expect("first pane rendered");
let second = visual
.debug_bounds("test-pane-1")
.expect("remainder pane rendered");
let divider = visual
.debug_bounds("resize-handle")
.expect("divider rendered");
assert_eq!(
first.size.height + divider.size.height + second.size.height,
px(GROUP_HEIGHT)
);
assert_eq!(
second.size.height,
px(GROUP_HEIGHT) - first.size.height - divider.size.height
);
}