use std::{cell::RefCell, rc::Rc}; use gpui::{ Context, IntoElement, Modifiers, MouseButton, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext, Window, div, point, px, }; use gpui_native_data::tabs::{Tab, Tabs}; use gpui_native_theme::Theme; type Changes = Rc>>; type Closes = Rc>>; struct Harness { selected: usize, 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().child( Tabs::new("test-tabs") .selected(self.selected) .on_change(move |next, _, cx| { changes.borrow_mut().push(next); view.update(cx, |harness, cx| { harness.selected = next; cx.notify(); }); }) .tabs(|_, _| { vec![ Tab::new("one", "One"), Tab::new("two", "Two"), Tab::new("off", "Off").disabled(true), Tab::new("end", "End"), ] }), ) } } /// Same-length labels keep the strip's geometry predictable: each tab is /// roughly 40 px wide with a 4 px gap, so clicks at `(20/62/104/146, 12)` /// land on One / Two / the disabled Off / End. fn tabs_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 { selected: 0, 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, Modifiers::default()); visual.simulate_mouse_up(position, MouseButton::Left, Modifiers::default()); } struct CloseHarness { changes: Changes, closes: Closes, } impl Render for CloseHarness { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let changes = self.changes.clone(); let closes = self.closes.clone(); let view = cx.entity(); let close_view = view.clone(); div().size_full().child( Tabs::new("close-tabs") .on_change(move |next, _, cx| { changes.borrow_mut().push(next); view.update(cx, |_, cx| cx.notify()); }) .on_close(move |index, _, cx| { closes.borrow_mut().push(index); close_view.update(cx, |_, cx| cx.notify()); }) .tabs(|_, _| { vec![ Tab::new("one", "One").closeable(true), Tab::new("two", "Two"), ] }), ) } } fn close_tabs_window() -> (TestAppContext, VisualTestContext, Changes, Closes) { let mut cx = TestAppContext::single(); cx.update(Theme::init); let changes = Rc::new(RefCell::new(Vec::new())); let closes = Rc::new(RefCell::new(Vec::new())); let harness_changes = changes.clone(); let harness_closes = closes.clone(); let window = cx.add_window(move |_, _| CloseHarness { changes: harness_changes, closes: harness_closes, }); let mut visual = VisualTestContext::from_window(window.into(), &cx); visual.update(|window, cx| { window.draw(cx).clear(cx); }); (cx, visual, changes, closes) } #[test] fn clicking_a_tab_selects_it_and_hands_the_strip_the_keyboard() { let (_cx, mut visual, changes) = tabs_window(); // Tab 0 is already selected; selecting it again reports nothing. click(&mut visual, point(px(20.), px(12.))); assert!(changes.borrow().is_empty()); // The click focused the strip, so the arrows switch from here. visual.simulate_keystrokes("right"); assert_eq!(*changes.borrow(), vec![1]); } #[test] fn clicking_a_disabled_tab_reports_nothing() { let (_cx, mut visual, changes) = tabs_window(); click(&mut visual, point(px(104.), px(12.))); assert!(changes.borrow().is_empty()); } #[test] fn arrow_keys_skip_disabled_tabs_and_never_wrap() { let (_cx, mut visual, changes) = tabs_window(); click(&mut visual, point(px(20.), px(12.))); // One: already selected, no report visual.simulate_keystrokes("right"); // Two visual.simulate_keystrokes("right"); // over the disabled tab → End visual.simulate_keystrokes("right"); // last; no wrap assert_eq!(*changes.borrow(), vec![1, 3]); visual.simulate_keystrokes("left"); // over the disabled tab → Two visual.simulate_keystrokes("home"); // One visual.simulate_keystrokes("end"); // End assert_eq!(*changes.borrow(), vec![1, 3, 1, 0, 3]); } #[test] fn clicking_the_close_control_reports_a_close_without_selecting() { let (_cx, mut visual, changes, closes) = close_tabs_window(); // Only the closeable tab ships a close control. let selector: &'static str = Box::leak(String::from("close-tabs-close-One").into_boxed_str()); let close_bounds = visual .debug_bounds(selector) .expect("closeable tab renders a close control"); assert!(visual.debug_bounds("close-tabs-close-Two").is_none()); click(&mut visual, close_bounds.center()); assert_eq!(*closes.borrow(), vec![0]); assert!(changes.borrow().is_empty(), "a close is not a selection"); }