use std::cell::RefCell; use std::rc::Rc; use gpui::{ Axis, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px, }; use gpui_native_layout::resize_handle::ResizeHandle; use gpui_native_theme::Theme; type DeltaSink = Rc>>; type ResetSink = Rc>; struct Harness { deltas: Rc>>, resets: Rc>, axis: Axis, disabled: bool, } impl Render for Harness { fn render( &mut self, _window: &mut Window, _cx: &mut gpui::Context, ) -> impl gpui::IntoElement { let deltas = self.deltas.clone(); let resets = self.resets.clone(); div() .size_full() .flex() .flex_row() .child(div().w(px(120.)).h_full()) .child( ResizeHandle::new("test-handle") .axis(self.axis) .disabled(self.disabled) .on_resize(move |delta, _, _| deltas.borrow_mut().push(delta)) .on_reset(move |_, _| *resets.borrow_mut() += 1), ) .child(div().flex_1().h_full()) } } fn handle_window( axis: Axis, disabled: bool, ) -> (TestAppContext, DeltaSink, ResetSink, VisualTestContext) { let mut cx = TestAppContext::single(); cx.update(Theme::init); let deltas = Rc::new(RefCell::new(Vec::new())); let resets = Rc::new(RefCell::new(0usize)); let deltas_for_window = deltas.clone(); let resets_for_window = resets.clone(); let window = cx.add_window(move |_, _| Harness { deltas: deltas_for_window, resets: resets_for_window, axis, disabled, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); visual.update(|window, cx| { window.draw(cx).clear(cx); }); (cx, deltas, resets, visual) } fn handle_center(visual: &mut VisualTestContext) -> gpui::Point { let bounds = visual .debug_bounds("resize-handle") .expect("resize handle missing"); point(bounds.center().x, bounds.center().y) } fn click(visual: &mut VisualTestContext, position: gpui::Point) { visual.simulate_mouse_down(position, MouseButton::Left, Modifiers::default()); visual.simulate_mouse_up(position, MouseButton::Left, Modifiers::default()); } #[test] fn dragging_reports_signed_deltas() { let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Horizontal, false); let start = handle_center(&mut visual); visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default()); visual.simulate_event(MouseMoveEvent { position: point(start.x + px(30.), start.y), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); visual.simulate_event(MouseMoveEvent { position: point(start.x + px(10.), start.y), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default()); assert_eq!(*deltas.borrow(), vec![px(30.), px(-20.)]); } #[test] fn vertical_seams_report_y_deltas() { let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Vertical, false); let start = handle_center(&mut visual); visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default()); visual.simulate_event(MouseMoveEvent { position: point(start.x, start.y + px(40.)), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default()); assert_eq!(*deltas.borrow(), vec![px(40.)]); } #[test] fn moves_without_a_drag_report_nothing() { let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Horizontal, false); let start = handle_center(&mut visual); // Hovering and moving across the seam without pressing does nothing. visual.simulate_event(MouseMoveEvent { position: point(start.x + px(30.), start.y), pressed_button: None, modifiers: Modifiers::default(), }); // A press-and-move that started elsewhere must not reach the handle either. visual.simulate_mouse_down( point(px(20.), px(20.)), MouseButton::Left, Modifiers::default(), ); visual.simulate_event(MouseMoveEvent { position: point(px(50.), px(20.)), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); assert!(deltas.borrow().is_empty()); } #[test] fn arrow_keys_move_the_seam_when_focused() { let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Horizontal, false); let center = handle_center(&mut visual); click(&mut visual, center); visual.simulate_keystrokes("left"); visual.simulate_keystrokes("right"); assert_eq!(*deltas.borrow(), vec![px(-32.), px(32.)]); } #[test] fn double_click_resets_and_starts_no_drag() { let (_cx, deltas, resets, mut visual) = handle_window(Axis::Horizontal, false); let center = handle_center(&mut visual); for click_count in [1usize, 2] { visual.simulate_event(MouseDownEvent { position: center, click_count, modifiers: Modifiers::default(), button: MouseButton::Left, first_mouse: false, }); visual.simulate_event(MouseUpEvent { position: center, click_count, modifiers: Modifiers::default(), button: MouseButton::Left, }); } assert_eq!(*resets.borrow(), 1); // The double click must not leave a drag behind. visual.simulate_event(MouseMoveEvent { position: point(center.x + px(20.), center.y), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); assert!(deltas.borrow().is_empty()); } #[test] fn disabled_handle_ignores_drag_and_keys() { let (_cx, deltas, resets, mut visual) = handle_window(Axis::Horizontal, true); let center = handle_center(&mut visual); visual.simulate_mouse_down(center, MouseButton::Left, Modifiers::default()); visual.simulate_event(MouseMoveEvent { position: point(center.x + px(30.), center.y), pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }); visual.simulate_mouse_up(center, MouseButton::Left, Modifiers::default()); visual.simulate_keystrokes("left"); assert!(deltas.borrow().is_empty()); assert_eq!(*resets.borrow(), 0); }