use std::cell::RefCell;
use std::rc::Rc;
use teksilo_canvas::SizeProposal;
use crate::event::{EventResponse, Key, Modifiers, WidgetEvent};
use crate::overlay::{DismissBehavior, OverlayPlacement, OverlayRequest};
use crate::shortcut::{KeyStroke, Shortcut};
use crate::test_widgets::{FillWidget, StackWidget};
use crate::window::NoopWindowOps;
use crate::{Action, BuildContext, Intent, LayoutContext, LayoutResponse, Widget, WidgetId};
use super::WidgetTree;
type Calls = Rc<RefCell<Vec<&'static str>>>;
#[derive(Debug)]
struct Root {
calls: Calls,
button: Rc<RefCell<Option<WidgetId>>>,
editor: Rc<RefCell<Option<WidgetId>>>,
}
#[derive(Debug)]
struct Editor {
calls: Calls,
}
impl Widget for Editor {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let calls = self.calls.clone();
ctx.register_action(Action::new("test.save").on_invoke(move |_, _| {
calls.borrow_mut().push("editor");
}));
ctx.apply_self_handlers(crate::widget_builder::HandlerSet::new().focusable(true));
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(100.0, 20.0).into()
}
}
impl Widget for Root {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let calls = self.calls.clone();
ctx.register_action(Action::new("test.save").on_invoke(move |_, _| {
calls.borrow_mut().push("root");
}));
ctx.register_shortcut(
Shortcut::new("test.save")
.primary(KeyStroke::ctrl(Key::S))
.build(),
);
let button = ctx.add(FillWidget::new().focusable());
*self.button.borrow_mut() = Some(button);
let editor = ctx.add(Editor {
calls: self.calls.clone(),
});
*self.editor.borrow_mut() = Some(editor);
vec![button, editor]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(400.0, 300.0).into()
}
}
struct Window {
tree: WidgetTree,
button: WidgetId,
editor: WidgetId,
calls: Calls,
}
impl Window {
fn new() -> Self {
let calls = Calls::default();
let button = Rc::new(RefCell::new(None));
let editor = Rc::new(RefCell::new(None));
let mut tree = WidgetTree::new();
tree.add(Root {
calls: calls.clone(),
button: button.clone(),
editor: editor.clone(),
});
tree.layout(SizeProposal::exact(400.0, 300.0));
let button = button.borrow().expect("the root built its button");
let editor = editor.borrow().expect("the root built its editor");
Self {
tree,
button,
editor,
calls,
}
}
fn show(&mut self, content: WidgetId, anchor: WidgetId) {
self.tree.show_overlay(OverlayRequest::new(
content,
anchor,
OverlayPlacement::Below,
DismissBehavior::Manual,
));
self.tree.layout(SizeProposal::exact(400.0, 300.0));
}
fn focused_popup(&mut self, anchor: WidgetId) -> WidgetId {
let field = self.tree.add(FillWidget::new().focusable());
let content = self.tree.add(StackWidget::new().child(field));
self.show(content, anchor);
self.tree.focus(field);
field
}
fn send_save_from_focus(&mut self) {
self.tree.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.save"));
});
}
fn calls(&self) -> Vec<&'static str> {
self.calls.borrow().clone()
}
}
#[test]
fn focus_in_an_overlay_reaches_a_scoped_action_on_the_root() {
let mut window = Window::new();
let button = window.button;
window.focused_popup(button);
window.send_save_from_focus();
assert_eq!(window.calls(), ["root"]);
}
#[test]
fn the_walk_crosses_nested_overlays() {
let mut window = Window::new();
let button = window.button;
let row = window.tree.add(FillWidget::new().focusable());
let menu = window.tree.add(StackWidget::new().child(row));
window.show(menu, button);
window.focused_popup(row);
window.send_save_from_focus();
assert_eq!(window.calls(), ["root"]);
}
fn bounded<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
let (done, result) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let _ = done.send(f());
});
result
.recv_timeout(std::time::Duration::from_secs(10))
.expect("the walk did not end")
}
#[test]
fn an_overlay_anchored_inside_itself_ends_the_walk() {
let (chain, field, content, calls) = bounded(|| {
let mut window = Window::new();
let field = window.tree.add(FillWidget::new().focusable());
let content = window.tree.add(StackWidget::new().child(field));
window.show(content, field);
window.tree.focus(field);
let chain = window.tree.intent_walk(field, &[]).0;
window.send_save_from_focus();
(chain, field, content, window.calls())
});
assert_eq!(chain, [field, content], "back at the field, the walk ends");
assert!(calls.is_empty(), "nothing on the way takes it");
}
#[test]
fn two_overlays_attached_to_each_other_end_the_walk() {
let (chain, ids, calls) = bounded(|| {
let mut window = Window::new();
let first_field = window.tree.add(FillWidget::new().focusable());
let first = window.tree.add(StackWidget::new().child(first_field));
let second_field = window.tree.add(FillWidget::new().focusable());
let second = window.tree.add(StackWidget::new().child(second_field));
window.show(first, second_field);
window.show(second, first_field);
window.tree.focus(first_field);
let chain = window.tree.intent_walk(first_field, &[]).0;
window.send_save_from_focus();
(
chain,
[first_field, first, second_field, second],
window.calls(),
)
});
assert_eq!(chain, ids, "each overlay crossed once");
assert!(calls.is_empty(), "the root is not on the way");
}
fn closing_row(content: Rc<RefCell<Option<WidgetId>>>) -> impl Widget {
use crate::widget_builder::WidgetBuilder;
FillWidget::new()
.focusable()
.on_key(move |event, ctx| match event {
WidgetEvent::KeyDown {
key: Key::Enter, ..
} => {
let content = content.borrow().expect("the row is in an overlay");
ctx.dismiss_overlay_by_content(content);
ctx.send_intent(Intent::new("test.save"));
EventResponse::Handled
}
_ => EventResponse::Ignored,
})
}
#[test]
fn an_intent_sent_as_its_overlay_closes_still_reaches_the_anchor() {
let mut window = Window::new();
let button = window.button;
let content_slot = Rc::new(RefCell::new(None));
let row = window.tree.add(closing_row(content_slot.clone()));
let content = window.tree.add(StackWidget::new().child(row));
*content_slot.borrow_mut() = Some(content);
window.show(content, button);
window.tree.focus(row);
window.tree.press_key(Key::Enter, Modifiers::NONE);
assert!(
window
.tree
.overlay_manager()
.find_by_content(content)
.is_none(),
"the row closed its overlay"
);
assert_eq!(window.calls(), ["root"]);
}
#[test]
fn a_scoped_shortcut_does_not_cross_from_an_overlay_to_its_anchor() {
let mut window = Window::new();
let button = window.button;
let field = window.focused_popup(button);
window.tree.press_key(Key::S, Modifiers::COMMAND);
assert!(window.calls().is_empty(), "the root's Ctrl+S stays outside");
assert_eq!(window.tree.focused(), Some(field));
window.send_save_from_focus();
assert_eq!(window.calls(), ["root"], "the intent still crosses");
}
#[test]
fn a_scoped_shortcut_does_not_apply_to_an_overlay_attached_outside_its_scope() {
let mut window = Window::new();
let elsewhere = window.tree.add(FillWidget::new());
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.focused_popup(elsewhere);
window.tree.press_key(Key::S, Modifiers::COMMAND);
assert!(window.calls().is_empty());
}
fn row_sending_from_focus(close: fn(&mut crate::widget::EventContext)) -> impl Widget {
use crate::widget_builder::WidgetBuilder;
FillWidget::new()
.focusable()
.on_key(move |event, ctx| match event {
WidgetEvent::KeyDown {
key: Key::Enter, ..
} => {
close(ctx);
ctx.send_intent_from_focus(Intent::new("test.save"));
EventResponse::Handled
}
_ => EventResponse::Ignored,
})
}
fn menu_over_the_editor(window: &mut Window, row: impl Widget + 'static) -> WidgetId {
let (button, editor) = (window.button, window.editor);
let row = window.tree.add(row);
let menu = window.tree.add(StackWidget::new().child(row));
window.tree.focus(editor);
window.show(menu, button);
window
.tree
.overlay_manager_mut()
.set_top_focus_restore(editor);
window.tree.focus(row);
row
}
#[test]
fn a_rows_intent_from_focus_starts_where_closing_its_menu_put_focus() {
let mut window = Window::new();
menu_over_the_editor(
&mut window,
row_sending_from_focus(|ctx| ctx.dismiss_self_overlay_chain()),
);
window.tree.press_key(Key::Enter, Modifiers::NONE);
assert_eq!(window.tree.focused(), Some(window.editor));
assert_eq!(
window.calls(),
["editor"],
"the editor focus went back to answers, not the menu's button"
);
}
#[test]
fn with_no_focus_left_a_rows_intent_from_focus_crosses_its_closed_menu() {
let mut window = Window::new();
let button = window.button;
let row = window
.tree
.add(row_sending_from_focus(|ctx| ctx.dismiss_all_overlays()));
let menu = window.tree.add(StackWidget::new().child(row));
window.show(menu, button);
window.tree.focus(row);
window.tree.press_key(Key::Enter, Modifiers::NONE);
assert_eq!(window.tree.focused(), None, "nothing handed focus back");
assert_eq!(window.calls(), ["root"], "from the row, through the button");
}
#[derive(Debug)]
struct Panel {
calls: Calls,
field: Rc<RefCell<Option<WidgetId>>>,
}
impl Widget for Panel {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
use crate::widget_builder::WidgetBuilder;
let calls = self.calls.clone();
ctx.register_action(Action::new("test.save").on_invoke(move |_, _| {
calls.borrow_mut().push("panel");
}));
let field = ctx.add(FillWidget::new().focusable().context_menu(|_, ctx| {
ctx.send_intent(Intent::new("test.save"));
None
}));
*self.field.borrow_mut() = Some(field);
vec![field]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(100.0, 20.0).into()
}
}
#[derive(Debug)]
struct PanelRoot {
calls: Calls,
field: Rc<RefCell<Option<WidgetId>>>,
}
impl Widget for PanelRoot {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let calls = self.calls.clone();
ctx.register_action(Action::new("test.save").on_invoke(move |_, _| {
calls.borrow_mut().push("root");
}));
vec![ctx.add(Panel {
calls: self.calls.clone(),
field: self.field.clone(),
})]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(400.0, 300.0).into()
}
}
#[test]
fn the_intent_of_a_declining_context_menu_factory_starts_where_the_menu_was_asked_for() {
let calls = Calls::default();
let field = Rc::new(RefCell::new(None));
let mut tree = WidgetTree::new();
tree.add(PanelRoot {
calls: calls.clone(),
field: field.clone(),
});
tree.layout(SizeProposal::exact(400.0, 300.0));
tree.focus(field.borrow().expect("the panel built its field"));
tree.press_key(Key::F10, Modifiers::SHIFT);
assert_eq!(
*calls.borrow(),
["panel"],
"the walk starts at the field, so the panel around it answers first"
);
}
#[derive(Debug)]
struct GlobalCommands {
calls: Calls,
}
impl Widget for GlobalCommands {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let calls = self.calls.clone();
ctx.register_action_global(Action::new("test.global").on_invoke(move |_, _| {
calls.borrow_mut().push("global");
}));
ctx.register_shortcut_global(
Shortcut::new("test.global")
.primary(KeyStroke::ctrl(Key::G))
.build(),
);
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(0.0, 0.0).into()
}
}
impl Window {
fn focused_modal(&mut self, anchor: WidgetId) -> (WidgetId, WidgetId) {
let field = self.tree.add(FillWidget::new().focusable());
let content = self.tree.add(StackWidget::new().child(field));
self.tree.show_overlay(OverlayRequest::new(
content,
anchor,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
self.tree.layout(SizeProposal::exact(400.0, 300.0));
self.tree.focus(field);
(content, field)
}
fn with_global_commands(mut self) -> Self {
self.tree.add(GlobalCommands {
calls: self.calls.clone(),
});
self.tree.layout(SizeProposal::exact(400.0, 300.0));
self
}
}
#[test]
fn an_intent_a_modal_leaves_walks_on_to_the_page_from_its_anchor() {
let mut window = Window::new();
let button = window.button;
window.focused_modal(button);
window.send_save_from_focus();
assert_eq!(
window.calls(),
["root"],
"the root above the anchor, not the editor focus was on before"
);
}
#[test]
fn a_popover_inside_a_modal_walks_on_through_the_modal_to_the_page() {
let mut window = Window::new();
let button = window.button;
let (_, field) = window.focused_modal(button);
window.focused_popup(field);
window.send_save_from_focus();
assert_eq!(window.calls(), ["root"]);
}
#[test]
fn a_modals_own_action_takes_its_intent_before_the_page() {
let mut window = Window::new();
let button = window.button;
let editor = window.tree.add(Editor {
calls: window.calls.clone(),
});
let content = window.tree.add(StackWidget::new().child(editor));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.focus(editor);
window.send_save_from_focus();
assert_eq!(
window.calls(),
["editor"],
"the dialog's editor, not the root"
);
}
#[test]
fn an_intent_walks_on_through_nested_modals() {
let mut window = Window::new().with_global_commands();
let button = window.button;
let editor = window.tree.add(Editor {
calls: window.calls.clone(),
});
let content = window.tree.add(StackWidget::new().child(editor));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.focused_modal(editor);
window.send_save_from_focus();
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.global"));
});
assert_eq!(
window.calls(),
["editor", "global"],
"the lower dialog's editor, then the page's global action"
);
}
#[test]
fn a_modal_whose_anchor_is_gone_walks_on_from_the_focus_it_gives_back() {
let mut window = Window::new();
let editor = window.editor;
let gone = window.tree.add(FillWidget::new());
window.tree.set_dormant(gone);
let field = window.tree.add(FillWidget::new().focusable());
let content = window.tree.add(StackWidget::new().child(field));
let modal = window.tree.show_overlay(OverlayRequest::new(
content,
gone,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window
.tree
.overlay_manager_mut()
.set_top_focus_restore(editor);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.focus(field);
window.send_save_from_focus();
assert_eq!(window.calls(), ["editor"], "from the focus it gives back");
window.tree.dismiss_overlay(modal);
window.tree.clear_focus();
window.calls.borrow_mut().clear();
let closing = window.tree.add(ClosingButton {
send: |ctx| ctx.send_intent(Intent::new("test.save")),
});
let content = window.tree.add(StackWidget::new().child(closing));
window.tree.show_overlay(OverlayRequest::new(
content,
gone,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.click(closing);
assert_eq!(
window.calls(),
["root"],
"closing, with no focus to give back: from the content root"
);
}
#[test]
fn a_scoped_shortcut_outside_a_modal_does_not_apply_to_focus_in_it() {
let mut window = Window::new();
let button = window.button;
window.focused_modal(button);
window.tree.press_key(Key::S, Modifiers::COMMAND);
assert!(window.calls().is_empty());
}
#[test]
fn a_modals_leftover_intent_reaches_the_pages_global_action() {
let mut window = Window::new().with_global_commands();
let button = window.button;
window.focused_modal(button);
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.global"));
});
assert_eq!(window.calls(), ["global"]);
}
#[test]
fn a_global_shortcut_of_the_page_does_not_fire_with_focus_in_a_modal() {
let mut window = Window::new().with_global_commands();
let button = window.button;
window.focused_modal(button);
window.tree.press_key(Key::G, Modifiers::COMMAND);
assert!(window.calls().is_empty());
}
#[test]
fn a_global_shortcut_of_the_page_does_not_fire_under_a_modal_with_nothing_focused() {
let mut window = Window::new().with_global_commands();
let button = window.button;
window.focused_modal(button);
window.tree.clear_focus();
assert_eq!(window.tree.focused(), None);
window.tree.press_key(Key::G, Modifiers::COMMAND);
assert!(window.calls().is_empty());
}
#[test]
fn an_intent_from_focus_with_nothing_focused_under_a_modal_starts_in_it() {
let mut window = Window::new().with_global_commands();
let button = window.button;
let content = window.tree.add(Editor {
calls: window.calls.clone(),
});
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
assert_eq!(window.tree.focused(), None);
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.save"));
ctx.send_intent_from_focus(Intent::new("test.global"));
});
assert_eq!(
window.calls(),
["editor", "global"],
"the modal's own action, not the root's; then the page's global"
);
}
#[derive(Debug)]
struct PageShortcut;
impl Widget for PageShortcut {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
ctx.register_shortcut_global(
Shortcut::new("test.page")
.primary(KeyStroke::ctrl(Key::K))
.build(),
);
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(0.0, 0.0).into()
}
}
#[derive(Debug)]
struct PageCommand {
calls: Calls,
}
impl Widget for PageCommand {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let calls = self.calls.clone();
ctx.register_action_global(Action::new("test.page").on_invoke(move |_, _| {
calls.borrow_mut().push("page");
}));
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(0.0, 0.0).into()
}
}
#[test]
fn a_modals_shortcut_runs_nothing_behind_it() {
let mut window = Window::new();
window.tree.add(PageCommand {
calls: window.calls.clone(),
});
let button = window.button;
use crate::widget_builder::WidgetBuilder;
let keys = Calls::default();
let heard = keys.clone();
let field = window
.tree
.add(FillWidget::new().focusable().on_key(move |event, _| {
if let WidgetEvent::KeyDown { key: Key::K, .. } = event {
heard.borrow_mut().push("field");
}
EventResponse::Handled
}));
let shortcut = window.tree.add(PageShortcut);
let content = window
.tree
.add(StackWidget::new().child(field).child(shortcut));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.focus(field);
window.tree.press_key(Key::K, Modifiers::COMMAND);
assert!(window.calls().is_empty(), "the page's action did not run");
assert_eq!(*keys.borrow(), ["field"], "the key went to the field");
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.page"));
});
assert_eq!(window.calls(), ["page"], "an intent sent there does");
}
#[test]
fn a_modals_own_global_shortcut_fires_in_it_with_nothing_focused() {
let mut window = Window::new();
let button = window.button;
let commands = window.tree.add(GlobalCommands {
calls: window.calls.clone(),
});
let content = window.tree.add(StackWidget::new().child(commands));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
assert_eq!(window.tree.focused(), None);
window.tree.press_key(Key::G, Modifiers::COMMAND);
assert_eq!(window.calls(), ["global"]);
}
struct ClosingButton {
send: fn(&mut crate::widget::EventContext),
}
impl std::fmt::Debug for ClosingButton {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ClosingButton")
}
}
impl Widget for ClosingButton {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
let send = self.send;
ctx.apply_self_handlers(
crate::widget_builder::HandlerSet::new().on_tap(move |_, ctx| {
ctx.dismiss_modal();
send(ctx);
}),
);
vec![]
}
fn layout_response(&self, proposal: SizeProposal, _: &LayoutContext) -> LayoutResponse {
proposal.resolve(100.0, 20.0).into()
}
}
impl Window {
fn click_closing_button_in_a_modal(
&mut self,
anchor: WidgetId,
send: fn(&mut crate::widget::EventContext),
) {
let closing = self.tree.add(ClosingButton { send });
let content = self.tree.add(StackWidget::new().child(closing));
self.tree.show_overlay(OverlayRequest::new(
content,
anchor,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
self.tree.layout(SizeProposal::exact(400.0, 300.0));
self.tree.click(closing);
assert!(self.tree.active_overlays().is_empty(), "the modal closed");
}
}
#[test]
fn an_intent_sent_as_its_modal_closes_walks_on_from_its_anchor() {
let mut window = Window::new().with_global_commands();
let button = window.button;
window.click_closing_button_in_a_modal(button, |ctx| {
ctx.send_intent(Intent::new("test.save"));
ctx.send_intent(Intent::new("test.global"));
});
assert_eq!(
window.calls(),
["root", "global"],
"the root above the anchor, and the page's global action"
);
}
#[test]
fn an_intent_from_focus_sent_as_its_modal_closes_with_nothing_focused_starts_at_the_root() {
let mut window = Window::new().with_global_commands();
let editor = window.editor;
assert_eq!(window.tree.focused(), None);
window.click_closing_button_in_a_modal(editor, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.save"));
ctx.send_intent_from_focus(Intent::new("test.global"));
});
assert_eq!(window.tree.focused(), None);
assert_eq!(window.calls(), ["root", "global"]);
}
#[test]
fn an_intent_from_focus_sent_as_its_modal_closes_starts_at_the_focus_given_back() {
let mut window = Window::new().with_global_commands();
let (button, editor) = (window.button, window.editor);
window.tree.focus(editor);
let closing = window.tree.add(ClosingButton {
send: |ctx| ctx.send_intent_from_focus(Intent::new("test.save")),
});
let content = window.tree.add(StackWidget::new().child(closing));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window
.tree
.overlay_manager_mut()
.set_top_focus_restore(editor);
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.click(closing);
assert_eq!(window.tree.focused(), Some(editor));
assert_eq!(window.calls(), ["editor"]);
}
#[test]
fn global_actions_and_shortcuts_registered_inside_a_modal_apply_in_it() {
let mut window = Window::new();
let button = window.button;
let field = window.tree.add(FillWidget::new().focusable());
let commands = window.tree.add(GlobalCommands {
calls: window.calls.clone(),
});
let content = window
.tree
.add(StackWidget::new().child(field).child(commands));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.tree.focus(field);
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.global"));
});
window.tree.press_key(Key::G, Modifiers::COMMAND);
assert_eq!(window.calls(), ["global", "global"]);
}
#[test]
fn a_modals_intent_nothing_in_the_window_takes_goes_to_the_application() {
let mut window = Window::new().with_global_commands();
let scope = crate::action::ApplicationScope::new();
scope.replace(vec![
("test.app", None, None, None),
("test.global", None, None, None),
]);
window.tree.set_application_scope(Some(scope));
window.tree.set_records_unclaimed_intents(true);
let button = window.button;
window.focused_modal(button);
window
.tree
.run_with_event_context(&mut NoopWindowOps, |ctx| {
ctx.send_intent_from_focus(Intent::new("test.app"));
ctx.send_intent_from_focus(Intent::new("test.global"));
});
assert_eq!(window.calls(), ["global"], "the page's global action");
let kept: Vec<_> = window
.tree
.take_unclaimed_intents()
.into_iter()
.map(|intent| intent.name)
.collect();
assert_eq!(kept, ["test.app"]);
}
#[test]
fn an_intent_sent_from_outside_a_modal_still_reaches_the_page_globals() {
let mut window = Window::new().with_global_commands();
let button = window.button;
window.focused_modal(button);
let page = window.tree.roots()[0];
window
.tree
.run_with_event_context_from(Some(page), &mut NoopWindowOps, |ctx| {
ctx.send_intent(Intent::new("test.global"));
});
assert_eq!(window.calls(), ["global"]);
}
#[test]
fn a_popover_attached_inside_a_modal_reaches_the_modals_own_actions() {
let mut window = Window::new();
let button = window.button;
let editor = window.tree.add(Editor {
calls: window.calls.clone(),
});
let content = window.tree.add(StackWidget::new().child(editor));
window.tree.show_overlay(OverlayRequest::new(
content,
button,
OverlayPlacement::Centered,
DismissBehavior::Manual,
));
window.tree.layout(SizeProposal::exact(400.0, 300.0));
window.focused_popup(editor);
window.send_save_from_focus();
assert_eq!(
window.calls(),
["editor"],
"the dialog's editor, not the root"
);
}