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::select::Select; use gpui_native_theme::Theme; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Strategy { Curated, Uncensored, } const STRATEGIES: [(Strategy, &str); 2] = [ (Strategy::Curated, "Curated"), (Strategy::Uncensored, "Uncensored"), ]; type Changes = Rc>>>; fn draw(visual: &mut VisualTestContext) { visual.update(|window, cx| { window.draw(cx).clear(cx); }); } 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()); } struct Harness { strategy: Option, 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( Select::new("strategy") .options(STRATEGIES) .selected(self.strategy) .on_change(move |next, _window, cx| { changes.borrow_mut().push(next); view.update(cx, |harness, cx| { harness.strategy = next; cx.notify(); }); }), ) } } /// One select: trigger band is `16..46` vertically (`p_4` + the 30 px field). fn select_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 { strategy: None, changes: harness_changes, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); draw(&mut visual); (cx, visual, changes) } struct Pair { first: Option, second: Option, first_changes: Changes, second_changes: Changes, } impl Render for Pair { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let first_changes = self.first_changes.clone(); let second_changes = self.second_changes.clone(); let first_view = view.clone(); let second_view = view.clone(); div().size_full().p_4().child( div() .flex() .flex_col() .gap_2() .child( Select::new("first") .options(STRATEGIES) .selected(self.first) .on_change(move |next, _window, cx| { first_changes.borrow_mut().push(next); first_view.update(cx, |pair, cx| { pair.first = next; cx.notify(); }); }), ) .child( Select::new("second") .options(STRATEGIES) .selected(self.second) .on_change(move |next, _window, cx| { second_changes.borrow_mut().push(next); second_view.update(cx, |pair, cx| { pair.second = next; cx.notify(); }); }), ), ) } } /// Two selects: bands `16..46` and `54..84` (the 8 px gap between). fn pair_window() -> (TestAppContext, VisualTestContext, Changes, Changes) { let mut cx = TestAppContext::single(); cx.update(Theme::init); let first_changes = Rc::new(RefCell::new(Vec::new())); let second_changes = Rc::new(RefCell::new(Vec::new())); let first = first_changes.clone(); let second = second_changes.clone(); let window = cx.add_window(move |_, _| Pair { first: None, second: None, first_changes: first, second_changes: second, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); draw(&mut visual); (cx, visual, first_changes, second_changes) } #[test] fn choosing_an_option_reports_the_value_and_closes() { let (_cx, mut visual, changes) = select_window(); // The trigger opens the popup; the panel grabs focus, so the first option // is keyboard-highlighted and Enter activates it. click(&mut visual, point(px(100.), px(31.))); draw(&mut visual); visual.simulate_keystrokes("enter"); draw(&mut visual); assert_eq!(*changes.borrow(), vec![Some(Strategy::Curated)]); // The popup closed with the choice, so another Enter reports nothing. visual.simulate_keystrokes("enter"); draw(&mut visual); assert_eq!(*changes.borrow(), vec![Some(Strategy::Curated)]); // The trigger reopens; the second option is two Downs away. click(&mut visual, point(px(100.), px(31.))); draw(&mut visual); visual.simulate_keystrokes("down"); visual.simulate_keystrokes("enter"); draw(&mut visual); assert_eq!( *changes.borrow(), vec![Some(Strategy::Curated), Some(Strategy::Uncensored)] ); } #[test] fn the_controlled_value_round_trips_through_the_owner() { let (_cx, mut visual, changes) = select_window(); click(&mut visual, point(px(100.), px(31.))); draw(&mut visual); visual.simulate_keystrokes("down"); visual.simulate_keystrokes("enter"); draw(&mut visual); // The harness stored the reported value, so the select now holds it. click(&mut visual, point(px(100.), px(31.))); draw(&mut visual); visual.simulate_keystrokes("enter"); draw(&mut visual); assert_eq!( *changes.borrow(), vec![Some(Strategy::Uncensored), Some(Strategy::Uncensored)] ); } #[test] fn two_selects_keep_their_popups_independent() { let (_cx, mut visual, first_changes, second_changes) = pair_window(); // Opening the second select never touches the first. click(&mut visual, point(px(100.), px(69.))); draw(&mut visual); visual.simulate_keystrokes("enter"); draw(&mut visual); assert_eq!(*first_changes.borrow(), vec![]); assert_eq!(*second_changes.borrow(), vec![Some(Strategy::Curated)]); }