use std::{cell::RefCell, rc::Rc}; use gpui::{ Context, IntoElement, MouseButton, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px, }; use gpui_native_core::segmented::Segmented; use gpui_native_theme::Theme; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Filter { All, Popular, Recent, } const FILTERS: [(Filter, &str); 3] = [ (Filter::All, "All"), (Filter::Popular, "Popular"), (Filter::Recent, "Recent"), ]; type Changes = Rc>>; struct Harness { filter: Filter, changes: Changes, } 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().p_4().child( Segmented::new("filter", self.filter) .options(FILTERS) .on_change(move |next, _window, cx| { changes.borrow_mut().push(next); view.update(cx, |harness, cx| { harness.filter = next; cx.notify(); }); }), ) } } fn filter_window() -> (TestAppContext, VisualTestContext, Changes) { 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 { filter: Filter::All, changes: harness_changes, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); visual.update(|window, cx| { window.draw(cx).clear(cx); }); (cx, visual, changes) } fn click(visual: &mut VisualTestContext, position: gpui::Point) { visual.simulate_mouse_down(position, MouseButton::Left, gpui::Modifiers::default()); visual.simulate_mouse_up(position, MouseButton::Left, gpui::Modifiers::default()); } /// The strip hugs its labels in the `p_4` band: the three segments span /// roughly `18..142` horizontally and `17.6..33.9` vertically (macOS's 13 pt /// root font), so clicks land near `32` (All), `70` (Popular), and `118` /// (Recent). #[test] fn clicking_a_segment_reports_it() { let (_cx, mut visual, changes) = filter_window(); click(&mut visual, point(px(70.), px(27.))); assert_eq!(*changes.borrow(), vec![Filter::Popular]); click(&mut visual, point(px(118.), px(27.))); assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]); } #[test] fn re_clicking_the_selected_segment_reports_nothing() { let (_cx, mut visual, changes) = filter_window(); click(&mut visual, point(px(70.), px(27.))); assert_eq!(*changes.borrow(), vec![Filter::Popular]); click(&mut visual, point(px(70.), px(27.))); assert_eq!(*changes.borrow(), vec![Filter::Popular]); } #[test] fn arrows_move_the_selection_when_the_strip_has_focus() { let (_cx, mut visual, changes) = filter_window(); // Clicking a segment hands the strip the keyboard, like the list. click(&mut visual, point(px(32.), px(27.))); visual.simulate_keystrokes("right"); assert_eq!(*changes.borrow(), vec![Filter::Popular]); visual.simulate_keystrokes("right"); assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]); // The strip never wraps: right on the last segment reports nothing. visual.simulate_keystrokes("right"); assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]); // Home and end jump to the ends. visual.simulate_keystrokes("home"); assert_eq!( *changes.borrow(), vec![Filter::Popular, Filter::Recent, Filter::All] ); visual.simulate_keystrokes("end"); assert_eq!( *changes.borrow(), vec![Filter::Popular, Filter::Recent, Filter::All, Filter::Recent] ); } /// The selected plate exists after a choice and settles on the chosen /// segment's slot; without it the active background disappears entirely. #[test] fn the_plate_settles_on_the_chosen_segment() { let (_cx, mut visual, changes) = filter_window(); click(&mut visual, point(px(70.), px(27.))); assert_eq!(*changes.borrow(), vec![Filter::Popular]); // Let the spring settle in real time (it steps on Instant::now()), then // check the plate landed in Popular's slot (~58..124). std::thread::sleep(std::time::Duration::from_millis(600)); visual.update(|window, cx| { window.draw(cx).clear(cx); }); let plate = visual .debug_bounds("segmented-plate") .expect("plate missing"); assert!( plate.left() >= px(44.) && plate.left() <= px(94.), "plate settled at x {:?}", plate.origin ); }