use std::sync::Arc;
use crate::event::{Event, EventResult, Key, Modifiers, MouseButton};
use crate::geometry::{Point, Size};
use crate::text::Font;
use crate::widget::Widget;
use super::super::geometry::BAR_H;
use super::super::model::MenuItem;
use super::{MenuBar, TopMenu};
fn test_font() -> Arc<Font> {
const FONT_BYTES: &[u8] = include_bytes!("../../../../../demo/assets/CascadiaCode.ttf");
Arc::new(Font::from_slice(FONT_BYTES).expect("font"))
}
#[test]
fn desktop_drag_release_in_neutral_space_closes_popup() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
)],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(bar.popup.is_open());
let neutral = Point::new(280.0, 170.0);
bar.on_event(&Event::MouseMove { pos: neutral });
bar.on_event(&Event::MouseUp {
pos: neutral,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(
!bar.popup.is_open(),
"drag-release in neutral space must close the popup",
);
}
#[test]
fn mobile_backdrop_tap_dismisses_popup() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
)],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
let file_pos = Point::new(8.0, 8.0);
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseMove { pos: file_pos });
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseDown {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(bar.popup.is_open());
let backdrop = Point::new(280.0, 170.0);
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseMove { pos: backdrop });
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseDown {
pos: backdrop,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: backdrop,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(
!bar.popup.is_open(),
"tapping outside the menu bar and popup body must dismiss the popup on mobile",
);
}
#[test]
fn hover_after_release_switches_open_top_menu_on_desktop() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![
TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
),
TopMenu::new(
"Edit",
vec![MenuItem::action("Copy", "edit.copy").into()],
),
],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert_eq!(bar.open_index, Some(0));
bar.on_event(&Event::MouseMove {
pos: Point::new(60.0, 8.0),
});
assert_eq!(
bar.open_index,
Some(1),
"moving the cursor over a different top menu after release \
must switch the open popup (desktop hover-switch)"
);
}
#[test]
fn desktop_drag_and_release_on_sibling_keeps_new_menu_open() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![
TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
),
TopMenu::new(
"Edit",
vec![MenuItem::action("Copy", "edit.copy").into()],
),
],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseMove {
pos: Point::new(60.0, 8.0),
});
assert_eq!(bar.open_index, Some(1), "drag-switch must reach Edit");
bar.on_event(&Event::MouseUp {
pos: Point::new(60.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(
bar.popup.is_open(),
"release on sibling top-menu bar must keep its popup open"
);
assert_eq!(bar.open_index, Some(1));
}
#[test]
fn desktop_drag_switch_then_release_off_closes() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![
TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
),
TopMenu::new(
"Edit",
vec![MenuItem::action("Copy", "edit.copy").into()],
),
],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseMove {
pos: Point::new(60.0, 8.0),
});
bar.on_event(&Event::MouseMove {
pos: Point::new(280.0, 170.0),
});
bar.on_event(&Event::MouseUp {
pos: Point::new(280.0, 170.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(!bar.popup.is_open());
}
#[test]
fn desktop_press_press_press_neutral_closes_active_menu() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![
TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
),
TopMenu::new(
"Edit",
vec![MenuItem::action("Copy", "edit.copy").into()],
),
],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseDown {
pos: Point::new(60.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert_eq!(bar.open_index, Some(1));
bar.on_event(&Event::MouseDown {
pos: Point::new(280.0, 170.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(!bar.popup.is_open());
}
#[test]
fn mobile_tap_currently_open_top_menu_closes() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
)],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
let file_pos = Point::new(8.0, 8.0);
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseMove { pos: file_pos });
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseDown {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(bar.popup.is_open());
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseMove { pos: file_pos });
crate::touch_state::note_touch_event();
bar.on_event(&Event::MouseDown {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: file_pos,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(!bar.popup.is_open());
}
#[test]
fn click_close_suppresses_hover_until_cursor_leaves() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![
TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
),
TopMenu::new(
"Edit",
vec![MenuItem::action("Copy", "edit.copy").into()],
),
],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(!bar.popup.is_open());
assert_eq!(
bar.suppress_hover_for,
Some(0),
"click-to-close must suppress hover on the just-closed bar item",
);
bar.on_event(&Event::MouseMove {
pos: Point::new(60.0, 8.0),
});
assert_eq!(bar.suppress_hover_for, None);
assert_eq!(bar.hover_index, Some(1));
}
#[test]
fn escape_closes_active_menu() {
crate::touch_state::clear_last_touch_event_for_testing();
let viewport = Size::new(300.0, 180.0);
crate::widget::set_current_viewport(viewport);
let mut bar = MenuBar::new(
test_font(),
vec![TopMenu::new(
"File",
vec![MenuItem::action("New", "file.new").into()],
)],
|_| {},
);
bar.layout(Size::new(300.0, BAR_H));
bar.on_event(&Event::MouseDown {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
bar.on_event(&Event::MouseUp {
pos: Point::new(8.0, 8.0),
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert!(bar.popup.is_open());
bar.on_event(&Event::KeyDown {
key: Key::Escape,
modifiers: Modifiers::default(),
});
assert!(!bar.popup.is_open(), "ESC must close the active menu");
}