use std::{cell::RefCell, rc::Rc}; use gpui::{ AnyWindowHandle, AppContext as _, Context, InputEvent as _, IntoElement, KeyDownEvent, Keystroke, Modifiers, MouseButton, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px, }; use gpui_native_core::slider::Slider; use gpui_native_theme::Theme; struct Harness { changes: Rc>>, value: f32, disabled: bool, } impl Render for Harness { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let changes = self.changes.clone(); let view = cx.entity(); div().size_full().flex().flex_col().child( div().w(px(200.)).child( Slider::new("test-slider", self.value) .min(0.0) .max(100.0) .step(10.0) .disabled(self.disabled) .on_change(move |next, _, cx| { changes.borrow_mut().push(next); view.update(cx, |harness, cx| { harness.value = next; cx.notify(); }); }), ), ) } } /// A maximized window holding a 200px slider over 0..=100 stepped by 10 in /// the top-left corner: `x` maps to `round(x / 200 * 10) * 10`. fn slider_window(disabled: bool) -> (VisualTestContext, AnyWindowHandle, Rc>>) { let mut cx = TestAppContext::single(); cx.update(Theme::init); let changes = Rc::new(RefCell::new(Vec::new())); let harness_changes = changes.clone(); let window = cx.add_window(move |_, _| Harness { changes: harness_changes, value: 0.0, disabled, }); let window = window.into(); cx.update_window(window, |_, window, cx| { window.draw(cx).clear(cx); }) .unwrap(); (VisualTestContext::from_window(window, &cx), window, changes) } fn draw(cx: &mut VisualTestContext, window: AnyWindowHandle) { cx.update_window(window, |_, window, cx| { window.draw(cx).clear(cx); }) .unwrap(); } #[test] fn clicking_the_track_sets_the_snapped_value() { let (mut cx, _window, changes) = slider_window(false); cx.simulate_click(point(px(100.), px(10.)), Modifiers::default()); assert_eq!(*changes.borrow(), vec![50.0]); cx.simulate_click(point(px(40.), px(10.)), Modifiers::default()); assert_eq!(*changes.borrow(), vec![50.0, 20.0]); } #[test] fn dragging_reports_values_until_release() { let (mut cx, _window, changes) = slider_window(false); cx.simulate_mouse_down( point(px(40.), px(10.)), MouseButton::Left, Modifiers::default(), ); cx.simulate_mouse_move( point(px(120.), px(10.)), MouseButton::Left, Modifiers::default(), ); cx.simulate_mouse_up( point(px(120.), px(10.)), MouseButton::Left, Modifiers::default(), ); assert_eq!(*changes.borrow(), vec![20.0, 60.0]); // Moving after release must not change the value. cx.simulate_mouse_move( point(px(180.), px(10.)), MouseButton::Left, Modifiers::default(), ); assert_eq!(*changes.borrow(), vec![20.0, 60.0]); } #[test] fn drag_clamps_to_the_range_ends() { let (mut cx, _window, changes) = slider_window(false); cx.simulate_mouse_down( point(px(100.), px(10.)), MouseButton::Left, Modifiers::default(), ); // The drag captures the pointer: it keeps reporting past the track's // right edge, clamped to the top of the range. cx.simulate_mouse_move( point(px(400.), px(10.)), MouseButton::Left, Modifiers::default(), ); assert_eq!(*changes.borrow(), vec![50.0, 100.0]); } #[test] fn disabled_slider_does_not_report_changes() { let (mut cx, _window, changes) = slider_window(true); cx.simulate_click(point(px(100.), px(10.)), Modifiers::default()); assert!(changes.borrow().is_empty()); } #[test] fn arrow_keys_step_the_value_while_focused() { let (mut cx, window, changes) = slider_window(false); // The click focuses the slider and sets the snapped value at that point. cx.simulate_click(point(px(2.), px(10.)), Modifiers::default()); assert_eq!(*changes.borrow(), vec![0.0]); draw(&mut cx, window); for key in ["right", "right", "left"] { cx.update_window(window, |_, window, cx| { window.dispatch_event( KeyDownEvent { keystroke: Keystroke::parse(key).unwrap(), is_held: false, prefer_character_input: false, } .to_platform_input(), cx, ); }) .unwrap(); } assert_eq!(*changes.borrow(), vec![0.0, 10.0, 20.0, 10.0]); }