#![cfg(feature = "dialog")]
use iced::keyboard::{self, Key, Modifiers, key::Named};
use iced::widget::{self, column, text_input};
use iced::{Element, Event, Point, event, mouse, touch};
use iced_cube::button;
use iced_cube::overlay::dialog::{self, CLOSE_BUTTON_ID, alert_dialog, dialog};
use iced_test::selector::Candidate;
use iced_test::simulator::{Simulator, click, simulator};
#[derive(Debug, Clone, PartialEq)]
enum Message {
Open,
Behind,
Dismiss,
Save,
Name(String),
Email(String),
Search(String),
Delete,
}
#[derive(Debug, Clone, Copy)]
struct Options {
open: bool,
escape: bool,
scrim: bool,
close: bool,
}
const OPEN: Options = Options {
open: true,
escape: true,
scrim: true,
close: true,
};
fn base() -> Element<'static, Message> {
column![
button("Open").on_press(Message::Open),
button("Behind").on_press(Message::Behind),
]
.into()
}
fn view(options: Options) -> Element<'static, Message> {
dialog(base())
.open(options.open)
.title("Edit profile")
.description("Make changes to your profile.")
.action(button("Save").on_press(Message::Save))
.on_dismiss(Message::Dismiss)
.dismiss_on_escape(options.escape)
.dismiss_on_scrim(options.scrim)
.close_button(options.close)
.into()
}
fn messages(ui: Simulator<'_, Message>) -> Vec<Message> {
ui.into_messages().collect()
}
fn click_at(ui: &mut Simulator<'_, Message>, position: Point) {
ui.point_at(position);
let _ = ui.simulate([Event::Mouse(mouse::Event::CursorMoved { position })]);
let _ = ui.simulate(click());
}
const SCRIM: Point = Point::new(8.0, 700.0);
#[test]
fn trigger_opens_and_the_open_dialog_renders_its_content() -> Result<(), iced_test::Error> {
let closed = Options {
open: false,
..OPEN
};
let mut ui = simulator(view(closed));
assert!(ui.find("Edit profile").is_err());
assert!(ui.find(CLOSE_BUTTON_ID).is_err());
ui.click("Open")?;
assert_eq!(messages(ui), vec![Message::Open]);
let mut ui = simulator(view(OPEN));
assert!(ui.find("Edit profile").is_ok());
assert!(ui.find("Make changes to your profile.").is_ok());
assert!(ui.find(CLOSE_BUTTON_ID).is_ok());
ui.click("Save")?;
assert_eq!(messages(ui), vec![Message::Save]);
Ok(())
}
#[test]
fn escape_dismisses_unless_turned_off() {
let mut ui = simulator(view(OPEN));
let _ = ui.tap_key(Named::Escape);
assert_eq!(messages(ui), vec![Message::Dismiss]);
let mut ui = simulator(view(Options {
escape: false,
..OPEN
}));
let _ = ui.tap_key(Named::Escape);
assert!(messages(ui).is_empty());
}
#[test]
fn escape_does_nothing_while_closed() {
let mut ui = simulator(view(Options {
open: false,
..OPEN
}));
let _ = ui.tap_key(Named::Escape);
assert!(messages(ui).is_empty());
}
#[test]
fn scrim_click_dismisses_unless_turned_off() {
let mut ui = simulator(view(OPEN));
click_at(&mut ui, SCRIM);
assert_eq!(messages(ui), vec![Message::Dismiss]);
let mut ui = simulator(view(Options {
scrim: false,
..OPEN
}));
click_at(&mut ui, SCRIM);
assert!(messages(ui).is_empty());
}
#[test]
fn clicks_inside_the_surface_do_not_dismiss() -> Result<(), iced_test::Error> {
let mut ui = simulator(view(OPEN));
ui.click("Edit profile")?;
assert!(messages(ui).is_empty());
Ok(())
}
fn tap_at(ui: &mut Simulator<'_, Message>, position: Point) {
let id = touch::Finger(1);
ui.point_at(position);
let _ = ui.simulate([
Event::Touch(touch::Event::FingerPressed { id, position }),
Event::Touch(touch::Event::FingerLifted { id, position }),
]);
}
#[test]
fn taps_inside_the_surface_do_not_dismiss_but_taps_on_the_scrim_do() -> Result<(), iced_test::Error>
{
let mut ui = simulator(view(OPEN));
let title = ui.find("Edit profile")?.bounds().center();
tap_at(&mut ui, title);
let description = ui.find("Make changes to your profile.")?.bounds().center();
tap_at(&mut ui, description);
assert!(messages(ui).is_empty(), "the panel stops the finger");
let mut ui = simulator(view(OPEN));
tap_at(&mut ui, SCRIM);
assert_eq!(messages(ui), vec![Message::Dismiss]);
Ok(())
}
#[test]
fn close_button_dismisses_and_can_be_hidden() -> Result<(), iced_test::Error> {
let mut ui = simulator(view(OPEN));
ui.click(CLOSE_BUTTON_ID)?;
assert_eq!(messages(ui), vec![Message::Dismiss]);
let mut ui = simulator(view(Options {
close: false,
..OPEN
}));
assert!(ui.find(CLOSE_BUTTON_ID).is_err());
Ok(())
}
#[test]
fn scrim_blocks_clicks_on_the_content_underneath() -> Result<(), iced_test::Error> {
let mut ui = simulator(view(OPEN));
ui.click("Behind")?;
assert_eq!(messages(ui), vec![Message::Dismiss]);
let mut ui = simulator(view(Options {
scrim: false,
..OPEN
}));
ui.click("Behind")?;
ui.click("Open")?;
assert!(messages(ui).is_empty());
let mut ui = simulator(view(Options {
open: false,
..OPEN
}));
ui.click("Behind")?;
assert_eq!(messages(ui), vec![Message::Behind]);
Ok(())
}
fn name() -> widget::Id {
widget::Id::new("name")
}
fn email() -> widget::Id {
widget::Id::new("email")
}
fn search() -> widget::Id {
widget::Id::new("search")
}
fn form() -> Element<'static, Message> {
let base = text_input("Search", "")
.id(search())
.on_input(Message::Search);
let body = column![
text_input("Name", "").id(name()).on_input(Message::Name),
text_input("Email", "").id(email()).on_input(Message::Email),
];
dialog(base)
.open(true)
.title("Edit profile")
.body(body)
.on_dismiss(Message::Dismiss)
.into()
}
fn focused(ui: &mut Simulator<'_, Message>) -> Option<widget::Id> {
ui.find(|candidate: Candidate<'_>| match candidate {
Candidate::Focusable { id, state, .. } if state.is_focused() => id.cloned(),
_ => None,
})
.ok()
}
fn press(ui: &mut Simulator<'_, Message>, key: Named, modifiers: Modifiers) {
let key = Key::Named(key);
let _ = ui.simulate([Event::Keyboard(keyboard::Event::KeyPressed {
key: key.clone(),
modified_key: key,
physical_key: keyboard::key::Physical::Unidentified(
keyboard::key::NativeCode::Unidentified,
),
location: keyboard::Location::Standard,
modifiers,
repeat: false,
text: None,
})]);
}
fn nudge(ui: &mut Simulator<'_, Message>) {
let position = Point::new(1.0, 1.0);
let _ = ui.simulate([Event::Mouse(mouse::Event::CursorMoved { position })]);
}
#[test]
fn opening_focuses_the_first_field_in_the_dialog() {
let mut ui = simulator(form());
assert_eq!(focused(&mut ui), None);
nudge(&mut ui);
assert_eq!(focused(&mut ui), Some(name()));
}
#[test]
fn tab_and_shift_tab_cycle_inside_the_dialog() {
let mut ui = simulator(form());
nudge(&mut ui);
let mut order = Vec::new();
for _ in 0..3 {
press(&mut ui, Named::Tab, Modifiers::empty());
order.extend(focused(&mut ui));
}
assert_eq!(order, vec![email(), name(), email()]);
press(&mut ui, Named::Tab, Modifiers::SHIFT);
assert_eq!(focused(&mut ui), Some(name()));
press(&mut ui, Named::Tab, Modifiers::SHIFT);
assert_eq!(focused(&mut ui), Some(email()));
assert!(ui.into_messages().next().is_none());
}
#[test]
fn typing_reaches_the_dialog_and_never_the_content_underneath() {
let mut ui = simulator(form());
nudge(&mut ui);
let _ = ui.typewrite("Al");
assert_eq!(
messages(ui),
vec![Message::Name("A".into()), Message::Name("Al".into())]
);
}
#[test]
fn a_custom_keymap_replaces_the_defaults() {
let element: Element<'_, Message> = dialog(base())
.open(true)
.title("Custom keys")
.on_dismiss(Message::Dismiss)
.keymap(dialog::default_keymap().unbind(&iced_cube::Chord::named(Named::Escape)))
.into();
let mut ui = simulator(element);
let _ = ui.tap_key(Named::Escape);
assert!(messages(ui).is_empty());
}
#[test]
fn an_open_dialog_captures_every_key_so_app_shortcuts_stop_there() {
let mut ui = simulator(view(OPEN));
for key in [Named::ArrowDown, Named::Enter, Named::Space, Named::F2] {
assert_eq!(ui.tap_key(key), event::Status::Captured, "{key:?}");
}
let _ = ui.tap_key(Key::Character("k".into()));
assert!(messages(ui).is_empty());
let closed = Options {
open: false,
..OPEN
};
let mut ui = simulator(view(closed));
assert_eq!(ui.tap_key(Named::ArrowDown), event::Status::Ignored);
}
#[test]
fn pass_through_chords_reach_the_app() {
let toggle = iced_cube::Chord::named(Named::F2);
let element: Element<'_, Message> = dialog(base())
.open(true)
.title("Palette")
.on_dismiss(Message::Dismiss)
.pass_through([toggle])
.into();
let mut ui = simulator(element);
assert_eq!(ui.tap_key(Named::F2), event::Status::Ignored);
assert_eq!(ui.tap_key(Named::F3), event::Status::Captured);
}
fn confirmation(escape: bool) -> Element<'static, Message> {
alert_dialog(base(), "Delete project?", "This cannot be undone.")
.open(true)
.confirm("Delete")
.on_cancel(Message::Dismiss)
.on_confirm(Message::Delete)
.dismiss_on_escape(escape)
.into()
}
#[test]
fn alert_dialog_answers_with_cancel_or_the_destructive_action() -> Result<(), iced_test::Error> {
let mut ui = simulator(confirmation(true));
assert!(ui.find(CLOSE_BUTTON_ID).is_err());
ui.click("Cancel")?;
ui.click("Delete")?;
assert_eq!(messages(ui), vec![Message::Dismiss, Message::Delete]);
Ok(())
}
#[test]
fn alert_dialog_ignores_the_scrim_and_cancels_on_escape() {
let mut ui = simulator(confirmation(true));
click_at(&mut ui, SCRIM);
let _ = ui.tap_key(Named::Escape);
assert_eq!(messages(ui), vec![Message::Dismiss]);
let mut ui = simulator(confirmation(false));
let _ = ui.tap_key(Named::Escape);
assert!(messages(ui).is_empty());
}
#[test]
fn enter_does_not_run_the_destructive_action_by_default() {
let mut ui = simulator(confirmation(true));
assert_eq!(ui.tap_key(Named::Enter), event::Status::Captured);
assert!(messages(ui).is_empty());
}
#[test]
fn tab_reaches_the_alert_buttons_and_enter_or_space_presses_them() {
let mut ui = simulator(confirmation(true));
nudge(&mut ui);
assert_eq!(focused(&mut ui), None, "nothing focused on open");
press(&mut ui, Named::Tab, Modifiers::empty());
assert!(focused(&mut ui).is_some(), "Tab focuses Cancel");
let _ = ui.tap_key(Named::Enter);
press(&mut ui, Named::Tab, Modifiers::empty());
let _ = ui.tap_key(Named::Space);
press(&mut ui, Named::Tab, Modifiers::SHIFT);
let _ = ui.tap_key(Named::Space);
assert_eq!(
messages(ui),
vec![Message::Dismiss, Message::Delete, Message::Dismiss]
);
}
#[test]
fn a_pointer_press_clears_the_focused_alert_button() {
let mut ui = simulator(confirmation(true));
nudge(&mut ui);
press(&mut ui, Named::Tab, Modifiers::empty());
press(&mut ui, Named::Tab, Modifiers::empty());
click_at(&mut ui, SCRIM);
assert_eq!(focused(&mut ui), None);
let _ = ui.tap_key(Named::Enter);
assert!(messages(ui).is_empty());
}
#[test]
fn enter_confirms_an_alert_dialog_that_opts_in() {
let element: Element<'_, Message> =
alert_dialog(base(), "Delete project?", "This cannot be undone.")
.open(true)
.on_cancel(Message::Dismiss)
.on_confirm(Message::Delete)
.confirm_on_enter(true)
.into();
let mut ui = simulator(element);
assert_eq!(ui.tap_key(Named::Enter), event::Status::Captured);
assert_eq!(messages(ui), vec![Message::Delete]);
}
#[test]
fn enter_follows_the_keymap() {
let element: Element<'_, Message> =
alert_dialog(base(), "Delete project?", "This cannot be undone.")
.open(true)
.on_cancel(Message::Dismiss)
.on_confirm(Message::Delete)
.confirm_on_enter(true)
.keymap(
dialog::default_keymap()
.unbind(&iced_cube::Chord::named(Named::Enter))
.bind(iced_cube::Chord::named(Named::F2), dialog::Action::Confirm),
)
.into();
let mut ui = simulator(element);
let _ = ui.tap_key(Named::Enter);
let _ = ui.tap_key(Named::F2);
assert_eq!(messages(ui), vec![Message::Delete]);
}
#[test]
fn a_field_that_submits_on_enter_keeps_the_key() {
let submit = || -> Element<'static, Message> {
dialog(base())
.open(true)
.title("Rename")
.body(
text_input("Name", "")
.on_input(Message::Name)
.on_submit(Message::Save),
)
.on_confirm(Message::Delete)
.into()
};
let mut ui = simulator(submit());
nudge(&mut ui);
let _ = ui.tap_key(Named::Enter);
assert_eq!(messages(ui), vec![Message::Save]);
let plain: Element<'_, Message> = dialog(base()).open(true).title("Plain").into();
let mut ui = simulator(plain);
let _ = ui.tap_key(Named::Enter);
assert!(messages(ui).is_empty(), "no on_confirm, no message");
}