237 lines
7.5 KiB
Rust
237 lines
7.5 KiB
Rust
use gpui::{
|
|
Entity, InteractiveElement as _, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
|
|
MouseUpEvent, ParentElement as _, Pixels, Render, Styled as _, TestAppContext,
|
|
VisualTestContext, Window, div, point, px,
|
|
};
|
|
use gpui_native_data::sidebar::Sidebar;
|
|
use gpui_native_layout::shell::{LayoutState, Shell};
|
|
use gpui_native_theme::{ActiveTheme as _, Size, Theme};
|
|
|
|
struct Harness {
|
|
state: Entity<LayoutState>,
|
|
}
|
|
|
|
impl Render for Harness {
|
|
fn render(
|
|
&mut self,
|
|
_window: &mut Window,
|
|
_cx: &mut gpui::Context<Self>,
|
|
) -> impl gpui::IntoElement {
|
|
div().size_full().child(
|
|
Shell::new("test-layout", self.state.clone())
|
|
.left_sidebar(Sidebar::new("test-left"))
|
|
.center(
|
|
div()
|
|
.size_full()
|
|
.debug_selector(|| "test-center".to_string()),
|
|
)
|
|
.right_sidebar(
|
|
div()
|
|
.size_full()
|
|
.debug_selector(|| "test-right".to_string()),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn layout_window(cx: &mut TestAppContext) -> (Entity<LayoutState>, VisualTestContext) {
|
|
cx.update(Theme::init);
|
|
let state = cx.update(LayoutState::new);
|
|
let state_for_window = state.clone();
|
|
let window = cx.add_window(move |_, _| Harness {
|
|
state: state_for_window,
|
|
});
|
|
let mut visual = VisualTestContext::from_window(window.into(), cx);
|
|
redraw(&mut visual);
|
|
(state, visual)
|
|
}
|
|
|
|
fn redraw(visual: &mut VisualTestContext) {
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
}
|
|
|
|
fn bounds(visual: &mut VisualTestContext, selector: &'static str) -> Option<gpui::Bounds<Pixels>> {
|
|
visual.debug_bounds(selector)
|
|
}
|
|
|
|
fn center_of(bounds: gpui::Bounds<Pixels>) -> gpui::Point<Pixels> {
|
|
point(bounds.center().x, bounds.center().y)
|
|
}
|
|
|
|
fn press(visual: &mut VisualTestContext, at: gpui::Point<Pixels>, click_count: usize) {
|
|
visual.simulate_event(MouseDownEvent {
|
|
position: at,
|
|
click_count,
|
|
modifiers: Modifiers::default(),
|
|
button: MouseButton::Left,
|
|
first_mouse: false,
|
|
});
|
|
visual.simulate_event(MouseUpEvent {
|
|
position: at,
|
|
click_count,
|
|
modifiers: Modifiers::default(),
|
|
button: MouseButton::Left,
|
|
});
|
|
}
|
|
|
|
fn drag(visual: &mut VisualTestContext, from: gpui::Point<Pixels>, to: gpui::Point<Pixels>) {
|
|
visual.simulate_mouse_down(from, MouseButton::Left, Modifiers::default());
|
|
visual.simulate_event(MouseMoveEvent {
|
|
position: to,
|
|
pressed_button: Some(MouseButton::Left),
|
|
modifiers: Modifiers::default(),
|
|
});
|
|
visual.simulate_mouse_up(to, MouseButton::Left, Modifiers::default());
|
|
}
|
|
|
|
#[test]
|
|
fn left_sidebar_starts_visible_and_right_hidden() {
|
|
let mut cx = TestAppContext::single();
|
|
let (state, mut visual) = layout_window(&mut cx);
|
|
|
|
assert!(state.read_with(&cx, |state, _| state.is_left_sidebar_visible()));
|
|
assert!(!state.read_with(&cx, |state, _| state.is_right_sidebar_visible()));
|
|
|
|
assert!(
|
|
bounds(&mut visual, "sidebar-test-left").is_some(),
|
|
"left region missing"
|
|
);
|
|
assert!(
|
|
bounds(&mut visual, "test-center").is_some(),
|
|
"center missing"
|
|
);
|
|
assert!(
|
|
bounds(&mut visual, "test-right").is_none(),
|
|
"right region must be hidden by default"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn toggling_a_sidebar_shows_and_hides_its_region() {
|
|
let mut cx = TestAppContext::single();
|
|
let (state, mut visual) = layout_window(&mut cx);
|
|
|
|
cx.update(|cx| {
|
|
state.update(cx, |state, cx| state.set_right_sidebar_visible(true, cx));
|
|
});
|
|
redraw(&mut visual);
|
|
assert!(
|
|
bounds(&mut visual, "test-right").is_some(),
|
|
"right region missing"
|
|
);
|
|
|
|
cx.update(|cx| {
|
|
state.update(cx, |state, cx| state.set_left_sidebar_visible(false, cx));
|
|
});
|
|
redraw(&mut visual);
|
|
assert!(
|
|
bounds(&mut visual, "sidebar-test-left").is_none(),
|
|
"left region lingering"
|
|
);
|
|
// The center keeps filling the shell.
|
|
assert!(
|
|
bounds(&mut visual, "test-center").is_some(),
|
|
"center missing"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn the_header_toggle_hides_its_sidebar() {
|
|
let mut cx = TestAppContext::single();
|
|
let (state, mut visual) = layout_window(&mut cx);
|
|
|
|
// The toggle sits at the trailing end of the sidebar's header strip, an
|
|
// icon-only small button: derive its center from the theme metrics.
|
|
let metrics = cx.update(|cx| cx.theme().sizes.resolve(Size::default()));
|
|
let small = cx.update(|cx| cx.theme().sizes.resolve(Size::Small));
|
|
let region = bounds(&mut visual, "sidebar-test-left").expect("left sidebar missing");
|
|
// The Sidebar fills the content area below the strip; the toggle sits at
|
|
// the strip's trailing end, one strip height above the sidebar's top.
|
|
let toggle = point(
|
|
region.right() - metrics.dense_padding_x - small.control_height / 2.0,
|
|
region.top() - metrics.strip_height / 2.0,
|
|
);
|
|
press(&mut visual, toggle, 1);
|
|
redraw(&mut visual);
|
|
|
|
assert!(
|
|
!state.read_with(&cx, |state, _| state.is_left_sidebar_visible()),
|
|
"the toggle should hide the left sidebar"
|
|
);
|
|
assert!(bounds(&mut visual, "sidebar-test-left").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn dragging_the_seam_resizes_and_clamps() {
|
|
let mut cx = TestAppContext::single();
|
|
let (state, mut visual) = layout_window(&mut cx);
|
|
|
|
let before = state.read_with(&cx, |state, _| state.left_sidebar_width());
|
|
let seam = bounds(&mut visual, "resize-handle").expect("left seam missing");
|
|
|
|
drag(
|
|
&mut visual,
|
|
center_of(seam),
|
|
center_of(seam) + point(px(50.), px(0.)),
|
|
);
|
|
assert_eq!(
|
|
state.read_with(&cx, |state, _| state.left_sidebar_width()),
|
|
before + px(50.)
|
|
);
|
|
|
|
// A huge drag clamps instead of collapsing the sidebar.
|
|
let seam = bounds(&mut visual, "resize-handle").expect("seam missing");
|
|
drag(
|
|
&mut visual,
|
|
center_of(seam),
|
|
center_of(seam) + point(px(2000.), px(0.)),
|
|
);
|
|
assert_eq!(
|
|
state.read_with(&cx, |state, _| state.left_sidebar_width()),
|
|
px(480.)
|
|
);
|
|
|
|
// Dragging back left shrinks it to the low clamp, no drift from the first.
|
|
let seam = bounds(&mut visual, "resize-handle").expect("seam missing");
|
|
drag(
|
|
&mut visual,
|
|
center_of(seam),
|
|
center_of(seam) - point(px(2000.), px(0.)),
|
|
);
|
|
assert_eq!(
|
|
state.read_with(&cx, |state, _| state.left_sidebar_width()),
|
|
px(160.)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn the_right_seam_grows_the_right_sidebar_leftwards() {
|
|
let mut cx = TestAppContext::single();
|
|
let (state, mut visual) = layout_window(&mut cx);
|
|
|
|
// Hide the left sidebar so the shell has exactly one seam.
|
|
cx.update(|cx| {
|
|
state.update(cx, |state, cx| state.set_left_sidebar_visible(false, cx));
|
|
state.update(cx, |state, cx| state.set_right_sidebar_visible(true, cx));
|
|
});
|
|
redraw(&mut visual);
|
|
|
|
let before = state.read_with(&cx, |state, _| state.right_sidebar_width());
|
|
let seam = bounds(&mut visual, "resize-handle").expect("right seam missing");
|
|
|
|
// Dragging left (negative) grows the right sidebar.
|
|
drag(
|
|
&mut visual,
|
|
center_of(seam),
|
|
center_of(seam) - point(px(30.), px(0.)),
|
|
);
|
|
let grown = state.read_with(&cx, |state, _| state.right_sidebar_width());
|
|
assert_eq!(grown, before + px(30.));
|
|
|
|
// The right region's width follows the state.
|
|
let region = bounds(&mut visual, "test-right").expect("right region missing");
|
|
assert_eq!(region.size.width, grown);
|
|
}
|