Files
Ely-GPUI-Components/src/menus/tests/mod.rs
T

411 lines
12 KiB
Rust

use gpui::{
App, Context, FocusHandle, InteractiveElement, IntoElement, KeyUpEvent, Keystroke, Modifiers,
MouseButton, ParentElement, Render, SharedString, Styled, TestAppContext, VisualTestContext,
Window, div, point, prelude::*, px,
};
mod more;
use super::{ContextMenu, DropdownMenu, Menu, MenuItem, SearchableMenu};
use crate::{primitives::FocusScope, theme::Theme};
/// What the rows did, and the state the check and radio rows show.
struct Desk {
root: FocusHandle,
ran: Vec<SharedString>,
wrap: bool,
theme: SharedString,
view_rows: bool,
enters_above: usize,
shift: bool,
asked: Option<(u64, gpui::Point<gpui::Pixels>)>,
}
impl Desk {
fn menu(&self, cx: &mut Context<Self>) -> Menu {
let run = |name: &'static str| {
let view = cx.entity();
move |_: &mut Window, cx: &mut App| {
view.update(cx, |desk, cx| {
desk.ran.push(name.into());
cx.notify();
})
}
};
let (wrap, light, dark) = (cx.entity(), cx.entity(), cx.entity());
let theme = Menu::new()
.item(
MenuItem::radio("Light", self.theme == "light")
.on_click(move |_, cx| light.update(cx, |desk, _| desk.theme = "light".into())),
)
.item(
MenuItem::radio("Dark", self.theme == "dark")
.on_click(move |_, cx| dark.update(cx, |desk, _| desk.theme = "dark".into())),
);
let edit = Menu::new()
.item(
MenuItem::new("Cut")
.keys("secondary-x")
.on_click(run("cut")),
)
.item(MenuItem::new("Copy").disabled(true).on_click(run("copy")))
.item(MenuItem::new("Paste").on_click(run("paste")));
if !self.view_rows {
return edit;
}
edit.group(
"View",
[
MenuItem::check("Word wrap", self.wrap)
.on_click(move |_, cx| wrap.update(cx, |desk, _| desk.wrap = !desk.wrap)),
MenuItem::submenu("Theme", theme),
],
)
}
}
impl Render for Desk {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let menu = self.menu(cx);
let above = cx.entity();
div()
.size_full()
.on_key_down(move |event, _, cx| {
if event.keystroke.key == "enter" {
above.update(cx, |desk, _| desk.enters_above += 1);
}
})
.child(
FocusScope::new(&self.root)
.size_full()
.when(self.shift, |scope| scope.child(div().h(px(80.0))))
.child(DropdownMenu::new("edit", "Edit", menu.clone()))
.child(
ContextMenu::new("area", menu.clone())
.child(div().w(px(200.0)).h(px(120.0))),
)
.child(SearchableMenu::new("find", "Find", menu.clone()))
.child(
ContextMenu::new("late", menu).manual(self.asked).child(
div()
.debug_selector(|| "late".into())
.w(px(200.0))
.h(px(40.0)),
),
),
)
}
}
pub(super) fn settle(cx: &mut VisualTestContext) {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
cx.run_until_parked();
}
/// Presses and releases `key`, with a frame in between as on a real keyboard.
pub(super) fn press(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
settle(cx);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
fn desk(cx: &mut TestAppContext) -> (gpui::Entity<Desk>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
crate::forms::bind_keys(cx);
});
let (view, cx) = cx.add_window_view(|_, cx| Desk {
root: cx.focus_handle(),
ran: Vec::new(),
wrap: false,
theme: "light".into(),
view_rows: true,
enters_above: 0,
shift: false,
asked: None,
});
settle(cx);
(view, cx)
}
fn ran(view: &gpui::Entity<Desk>, cx: &mut VisualTestContext) -> Vec<SharedString> {
view.read_with(cx, |desk, _| desk.ran.clone())
}
fn click(x: f32, y: f32, cx: &mut VisualTestContext) {
let at = point(px(x), px(y));
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_click(at, Modifiers::none());
settle(cx);
}
#[gpui::test]
fn the_keyboard_opens_walks_past_disabled_rows_and_runs(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"]);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"], "the release left the menu shut");
}
#[gpui::test]
fn submenus_open_right_and_close_left(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("up", cx);
press("right", cx);
press("down", cx);
press("left", cx);
press("right", cx);
press("down", cx);
press("enter", cx);
assert_eq!(view.read_with(cx, |desk, _| desk.theme.clone()), "dark");
}
#[gpui::test]
fn a_check_row_reports_to_its_owner(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("up", cx);
press("up", cx);
press("enter", cx);
assert!(view.read_with(cx, |desk, _| desk.wrap));
}
#[gpui::test]
fn escape_backs_out_one_level_then_closes(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("up", cx);
press("right", cx);
press("escape", cx);
press("up", cx);
press("enter", cx);
assert!(
view.read_with(cx, |desk, _| desk.wrap),
"one escape left the root open"
);
press("enter", cx);
press("escape", cx);
press("down", cx);
press("enter", cx);
assert!(
ran(&view, cx).is_empty(),
"the second escape closed the menu"
);
}
#[gpui::test]
fn a_right_click_opens_the_context_menu_where_it_lands(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
let at = point(px(60.0), px(80.0));
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_mouse_down(at, MouseButton::Right, Modifiers::none());
cx.simulate_mouse_up(at, MouseButton::Right, Modifiers::none());
settle(cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
}
#[gpui::test]
fn a_manual_context_menu_opens_only_from_its_owner(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
let at = cx.debug_bounds("late").expect("the host").center();
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_mouse_down(at, MouseButton::Right, Modifiers::none());
cx.simulate_mouse_up(at, MouseButton::Right, Modifiers::none());
settle(cx);
press("down", cx);
press("enter", cx);
assert!(ran(&view, cx).is_empty(), "a right click opens nothing");
view.update(cx, |desk, cx| {
desk.asked = Some((1, point(px(60.0), px(80.0))));
cx.notify();
});
settle(cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
}
#[gpui::test]
fn the_trigger_toggles_and_a_press_outside_closes(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
click(12.0, 12.0, cx);
click(12.0, 12.0, cx);
press("down", cx);
press("enter", cx);
assert!(
ran(&view, cx).is_empty(),
"the second press on the trigger closed it"
);
click(12.0, 12.0, cx);
click(600.0, 400.0, cx);
press("down", cx);
press("enter", cx);
assert!(ran(&view, cx).is_empty(), "the press outside closed it");
}
#[gpui::test]
fn rows_the_owner_removes_leave_the_menu_usable(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("up", cx);
view.update(cx, |desk, cx| {
desk.view_rows = false;
cx.notify();
});
settle(cx);
press("enter", cx);
assert!(ran(&view, cx).is_empty(), "the removed row ran");
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
}
#[gpui::test]
fn closing_from_the_trigger_hands_focus_back(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
click(12.0, 12.0, cx);
press("enter", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"], "focus came back to the trigger");
}
#[gpui::test]
fn confirm_keys_stay_inside_the_menu(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
click(12.0, 12.0, cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
assert_eq!(view.read_with(cx, |desk, _| desk.enters_above), 0);
}
#[gpui::test]
fn modified_confirm_keys_do_not_pick(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
press("enter", cx);
press("ctrl-enter", cx);
press("shift-space", cx);
assert!(ran(&view, cx).is_empty(), "a modified key picked");
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
}
#[gpui::test]
fn a_second_right_click_moves_the_context_menu(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
cx.update(|window, cx| window.focus_next(cx));
for at in [point(px(150.0), px(120.0)), point(px(20.0), px(40.0))] {
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_mouse_down(at, MouseButton::Right, Modifiers::none());
cx.simulate_mouse_up(at, MouseButton::Right, Modifiers::none());
settle(cx);
}
press("down", cx);
press("enter", cx);
assert_eq!(
ran(&view, cx),
["cut"],
"the menu stayed open at the second click"
);
}
#[gpui::test]
fn a_left_press_in_the_region_closes_the_context_menu(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
let at = point(px(150.0), px(120.0));
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_mouse_down(at, MouseButton::Right, Modifiers::none());
cx.simulate_mouse_up(at, MouseButton::Right, Modifiers::none());
settle(cx);
click(20.0, 40.0, cx);
press("down", cx);
press("enter", cx);
assert!(ran(&view, cx).is_empty(), "the left press closed the menu");
}
fn open_find(cx: &mut VisualTestContext) {
cx.update(|window, cx| {
window.focus_next(cx);
window.focus_next(cx);
});
press("enter", cx);
}
#[gpui::test]
fn the_filter_runs_the_best_fit(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
open_find(cx);
cx.simulate_input("pa");
settle(cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"]);
}
#[gpui::test]
fn space_types_into_the_filter(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
open_find(cx);
cx.simulate_input("pa");
settle(cx);
press("space", cx);
assert!(ran(&view, cx).is_empty(), "space picked a row");
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"]);
}
#[gpui::test]
fn arrows_walk_rows_from_the_filter(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
open_find(cx);
press("down", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["paste"]);
}
#[gpui::test]
fn an_open_dropdown_follows_its_host(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
click(12.0, 12.0, cx);
view.update(cx, |desk, cx| {
desk.shift = true;
cx.notify();
});
settle(cx);
settle(cx);
cx.simulate_mouse_move(point(px(40.0), px(131.0)), None, Modifiers::none());
settle(cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"], "the panel moved with its button");
}
#[gpui::test]
fn clearing_the_filter_marks_the_first_row_again(cx: &mut TestAppContext) {
let (view, cx) = desk(cx);
open_find(cx);
cx.simulate_input("t");
settle(cx);
press("down", cx);
press("backspace", cx);
press("enter", cx);
assert_eq!(ran(&view, cx), ["cut"]);
}