use gpui::{
Focusable, Modifiers, MouseButton, Point, TestAppContext, VisualTestContext, div, point,
prelude::*, px, size,
};
use ui::{
menu::{self, Item},
menubar::{self, Menu, Menubar},
};
const RECENT: usize = 1;
const WIDTH: gpui::Pixels = px(900.0);
const HEIGHT: gpui::Pixels = px(600.0);
const SWEEP: f32 = 2.0;
fn menus() -> Vec<Menu> {
vec![Menu::new(
"File",
vec![
Item::action("New Window").with_keystroke("⌘N"),
Item::submenu(
"Open Recent",
vec![
Item::action("bezel.md"),
Item::Separator,
Item::action("Clear Menu"),
],
),
Item::Separator,
Item::action("Close").disabled(),
],
)]
}
fn open(cx: &mut TestAppContext) -> (gpui::Entity<Menubar>, VisualTestContext) {
cx.update(|cx| {
theme::Theme::install(theme::Appearance::Dark, cx);
menubar::init(cx);
});
let window = cx.add_window(|_, cx| Menubar::new(menus(), cx));
let bar = window.root(cx).unwrap();
let mut visual = VisualTestContext::from_window(window.into(), cx);
visual.simulate_resize(size(WIDTH, HEIGHT));
visual.update(|window, cx| {
let handle = bar.read(cx).focus_handle(cx);
window.focus(&handle, cx);
});
visual.simulate_keystrokes("enter");
visual.run_until_parked();
(bar, visual)
}
fn cursor(bar: &gpui::Entity<Menubar>, cx: &mut VisualTestContext) -> (Vec<usize>, Option<usize>) {
cx.update(|_, cx| {
let cursor = bar.read(cx).cursor();
(cursor.open().to_vec(), cursor.row())
})
}
fn move_to(cx: &mut VisualTestContext, at: Point<gpui::Pixels>) {
cx.simulate_mouse_move(at, MouseButton::Left, Modifiers::default());
}
fn submenu_row_y(bar: &gpui::Entity<Menubar>, cx: &mut VisualTestContext) -> gpui::Pixels {
assert_eq!(
cursor(bar, cx),
(vec![], None),
"the menu opens with nothing lit"
);
for step in 0..(f32::from(HEIGHT) / SWEEP) as usize {
let y = px(step as f32 * SWEEP);
move_to(cx, point(px(24.0), y));
if cursor(bar, cx).0 == [RECENT] {
return y;
}
}
panic!("never found the submenu row by sweeping the card");
}
fn probe_right(
bar: &gpui::Entity<Menubar>,
cx: &mut VisualTestContext,
y: gpui::Pixels,
) -> Option<(Point<gpui::Pixels>, Option<usize>)> {
(0..(f32::from(WIDTH) / SWEEP) as usize).find_map(|step| {
let at = point(px(step as f32 * SWEEP), y);
move_to(cx, at);
let (open, row) = cursor(bar, cx);
(open == [RECENT] && row.is_some()).then_some((at, row))
})
}
#[gpui::test]
fn the_panel_paints_where_the_pointer_can_reach_it(cx: &mut TestAppContext) {
let (bar, mut cx) = open(cx);
let y = submenu_row_y(&bar, &mut cx);
assert_eq!(
probe_right(&bar, &mut cx, y).map(|(_, row)| row),
Some(Some(0)),
"the panel never answered the pointer to the right of the row it hangs on"
);
}
#[gpui::test]
fn a_row_in_the_panel_answers_a_click(cx: &mut TestAppContext) {
let (bar, mut cx) = open(cx);
let y = submenu_row_y(&bar, &mut cx);
let (at, _) = probe_right(&bar, &mut cx, y).expect("no row in the panel");
cx.simulate_click(at, Modifiers::default());
cx.run_until_parked();
assert!(
cx.update(|_, cx| bar.read(cx).open_menu().is_none()),
"choosing a row in a submenu closes the bar"
);
}
#[gpui::test]
fn the_arrows_walk_into_the_panel_and_back_out(cx: &mut TestAppContext) {
let (bar, mut cx) = open(cx);
cx.simulate_keystrokes("down down right");
assert_eq!(cursor(&bar, &mut cx), (vec![RECENT], Some(0)));
cx.simulate_keystrokes("left");
assert_eq!(cursor(&bar, &mut cx), (vec![], Some(RECENT)));
cx.simulate_keystrokes("enter");
assert_eq!(cursor(&bar, &mut cx), (vec![RECENT], Some(0)));
assert!(cx.update(|_, cx| bar.read(cx).open_menu().is_some()));
cx.simulate_keystrokes("escape");
assert_eq!(cursor(&bar, &mut cx), (vec![], Some(RECENT)));
cx.simulate_keystrokes("escape");
assert!(cx.update(|_, cx| bar.read(cx).open_menu().is_none()));
}
struct Pinned {
items: Vec<Item>,
cursor: menu::Cursor,
hit: Option<Vec<usize>>,
}
fn nested() -> Vec<Item> {
vec![
Item::action("New Window"),
Item::submenu(
"Open Recent",
vec![
Item::submenu("More", vec![Item::action("bezel.md")]),
Item::action("Clear Menu"),
],
),
]
}
impl gpui::Render for Pinned {
fn render(
&mut self,
_window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl IntoElement {
let theme = theme::Theme::of(cx).clone();
div()
.size_full()
.flex()
.justify_end()
.items_start()
.child(menu::card(
&theme,
"pinned",
&self.items,
&self.cursor,
cx,
|view: &mut Self, hit, _, _| {
if let menu::Hit::Point(path) = hit {
view.hit = Some(path);
}
},
))
}
}
fn pinned(cx: &mut TestAppContext, open: &[usize]) -> (gpui::Entity<Pinned>, VisualTestContext) {
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| {
let items = nested();
let mut cursor = menu::Cursor::default();
cursor.point_at(&items, open);
Pinned {
items,
cursor,
hit: None,
}
});
let view = window.root(cx).unwrap();
let visual = VisualTestContext::from_window(window.into(), cx);
visual.simulate_resize(size(WIDTH, HEIGHT));
visual.run_until_parked();
(view, visual)
}
fn row_at(
view: &gpui::Entity<Pinned>,
cx: &mut VisualTestContext,
at: Point<gpui::Pixels>,
) -> Option<Vec<usize>> {
cx.update(|_, cx| view.update(cx, |view, _| view.hit = None));
move_to(cx, at);
cx.update(|_, cx| view.read(cx).hit.clone())
}
fn sweep_x(
view: &gpui::Entity<Pinned>,
cx: &mut VisualTestContext,
y: gpui::Pixels,
path: &[usize],
) -> Option<gpui::Pixels> {
(0..(f32::from(WIDTH) / SWEEP) as usize).find_map(|step| {
let x = px(step as f32 * SWEEP);
(row_at(view, cx, point(x, y)).as_deref() == Some(path)).then_some(x)
})
}
fn pinned_row_y(view: &gpui::Entity<Pinned>, cx: &mut VisualTestContext) -> gpui::Pixels {
let x = WIDTH - px(24.0);
for step in 0..(f32::from(HEIGHT) / SWEEP) as usize {
let y = px(step as f32 * SWEEP);
if row_at(view, cx, point(x, y)).as_deref() == Some(&[RECENT][..]) {
return y;
}
}
panic!("never found the submenu row by sweeping the pinned card");
}
#[gpui::test]
fn a_panel_with_no_room_to_its_right_opens_leftward(cx: &mut TestAppContext) {
let (view, mut cx) = pinned(cx, &[RECENT]);
let y = pinned_row_y(&view, &mut cx);
let row = sweep_x(&view, &mut cx, y, &[RECENT]).expect("no submenu row in the pinned card");
let panel = sweep_x(&view, &mut cx, y, &[RECENT, 0]).expect("the panel answered nowhere");
assert!(
panel < row,
"the panel opened at the window edge ({panel}) instead of flipping to the card's left ({row})"
);
}
#[gpui::test]
fn a_flipped_chain_keeps_going_the_same_way(cx: &mut TestAppContext) {
let (view, mut cx) = pinned(cx, &[RECENT, 0]);
let y = pinned_row_y(&view, &mut cx);
let first = sweep_x(&view, &mut cx, y, &[RECENT, 0]).expect("the first panel answered nowhere");
let second =
sweep_x(&view, &mut cx, y, &[RECENT, 0, 0]).expect("the second panel answered nowhere");
assert!(
second < first,
"the second panel opened back across its parent ({second} is right of {first})"
);
}