pub(crate) mod harness;
mod helpers;
mod layout_tests;
use std::cell::Cell;
use std::env;
use std::fs;
use std::rc::Rc;
use crate::widget::*;
use crate::*;
use harness::*;
use helpers::*;
use kurbo::Vec2;
#[allow(dead_code)]
pub fn temp_dir_for_test() -> std::path::PathBuf {
let current_exe_path = env::current_exe().unwrap();
let mut exe_dir = current_exe_path.parent().unwrap();
if exe_dir.ends_with("deps") {
exe_dir = exe_dir.parent().unwrap();
}
let test_dir = exe_dir.parent().unwrap().join("tests");
fs::create_dir_all(&test_dir).unwrap();
tempfile::Builder::new()
.prefix("TempDir")
.tempdir_in(test_dir)
.unwrap()
.into_path()
}
#[test]
fn propogate_hot() {
let (button, pad, root, empty) = widget_id4();
let root_rec = Recording::default();
let padding_rec = Recording::default();
let button_rec = Recording::default();
let widget = Split::columns(
SizedBox::empty().with_id(empty),
Button::new("hot")
.record(&button_rec)
.with_id(button)
.padding(50.)
.record(&padding_rec)
.with_id(pad),
)
.record(&root_rec)
.with_id(root);
fn move_mouse(x: f64, y: f64) -> MouseEvent {
let pos = Point::new(x, y);
MouseEvent {
pos,
window_pos: pos,
buttons: MouseButtons::default(),
mods: KeyModifiers::default(),
count: 0,
focus: false,
button: MouseButton::None,
wheel_delta: Vec2::ZERO,
}
}
#[allow(clippy::cognitive_complexity)]
Harness::create_simple((), widget, |harness| {
harness.send_initial_events();
harness.just_layout();
root_rec.clear();
padding_rec.clear();
button_rec.clear();
harness.inspect_state(|state| assert!(!state.is_hot));
harness.event(Event::MouseMove(move_mouse(10., 10.)));
assert!(harness.get_state(root).is_hot);
assert!(harness.get_state(empty).is_hot);
assert!(!harness.get_state(pad).is_hot);
assert_matches!(root_rec.next(), Record::L(LifeCycle::HotChanged(true)));
assert_matches!(root_rec.next(), Record::E(Event::MouseMove(_)));
assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty());
harness.event(Event::MouseMove(move_mouse(210., 10.)));
assert!(harness.get_state(root).is_hot);
assert!(!harness.get_state(empty).is_hot);
assert!(!harness.get_state(button).is_hot);
assert!(harness.get_state(pad).is_hot);
assert_matches!(root_rec.next(), Record::E(Event::MouseMove(_)));
assert_matches!(padding_rec.next(), Record::L(LifeCycle::HotChanged(true)));
assert_matches!(padding_rec.next(), Record::E(Event::MouseMove(_)));
assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty());
harness.event(Event::MouseMove(move_mouse(260., 60.)));
assert!(harness.get_state(root).is_hot);
assert!(!harness.get_state(empty).is_hot);
assert!(harness.get_state(button).is_hot);
assert!(harness.get_state(pad).is_hot);
assert_matches!(root_rec.next(), Record::E(Event::MouseMove(_)));
assert_matches!(padding_rec.next(), Record::E(Event::MouseMove(_)));
assert_matches!(button_rec.next(), Record::L(LifeCycle::HotChanged(true)));
assert_matches!(button_rec.next(), Record::E(Event::MouseMove(_)));
assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty());
harness.event(Event::MouseMove(move_mouse(10., 10.)));
assert!(harness.get_state(root).is_hot);
assert!(harness.get_state(empty).is_hot);
assert!(!harness.get_state(button).is_hot);
assert!(!harness.get_state(pad).is_hot);
assert_matches!(root_rec.next(), Record::E(Event::MouseMove(_)));
assert_matches!(padding_rec.next(), Record::L(LifeCycle::HotChanged(false)));
assert_matches!(padding_rec.next(), Record::E(Event::MouseMove(_)));
assert_matches!(button_rec.next(), Record::L(LifeCycle::HotChanged(false)));
assert_matches!(button_rec.next(), Record::E(Event::MouseMove(_)));
assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty());
});
}
#[test]
fn take_focus() {
const TAKE_FOCUS: Selector = Selector::new("druid-tests.take-focus");
fn make_focus_taker(inner: Rc<Cell<Option<bool>>>) -> impl Widget<bool> {
ModularWidget::new(inner)
.event_fn(|_, ctx, event, _data, _env| {
if let Event::Command(cmd) = event {
if cmd.is(TAKE_FOCUS) {
ctx.request_focus();
}
}
})
.lifecycle_fn(|is_focused, _, event, _data, _env| {
if let LifeCycle::FocusChanged(focus) = event {
is_focused.set(Some(*focus));
}
})
}
let (id_1, id_2, _id_3) = widget_id3();
let left_focus: Rc<Cell<Option<bool>>> = Default::default();
let right_focus: Rc<Cell<Option<bool>>> = Default::default();
assert!(left_focus.get().is_none());
let left = make_focus_taker(left_focus.clone()).with_id(id_1);
let right = make_focus_taker(right_focus.clone()).with_id(id_2);
let app = Split::columns(left, right).padding(5.0);
let data = true;
Harness::create_simple(data, app, |harness| {
harness.send_initial_events();
assert!(left_focus.get().is_none());
assert!(right_focus.get().is_none());
harness.submit_command(TAKE_FOCUS, None);
assert_eq!(harness.window().focus, Some(id_2));
assert_eq!(left_focus.get(), None);
assert_eq!(right_focus.get(), Some(true));
harness.submit_command(TAKE_FOCUS, None);
assert_eq!(harness.window().focus, Some(id_2));
assert_eq!(left_focus.get(), None);
assert_eq!(right_focus.get(), Some(true));
harness.submit_command(TAKE_FOCUS, id_1);
assert_eq!(harness.window().focus, Some(id_1));
assert_eq!(left_focus.get(), Some(true));
assert_eq!(right_focus.get(), Some(false));
harness.submit_command(TAKE_FOCUS, id_2);
assert_eq!(harness.window().focus, Some(id_2));
assert_eq!(left_focus.get(), Some(false));
assert_eq!(right_focus.get(), Some(true));
})
}
#[test]
fn focus_changed() {
const TAKE_FOCUS: Selector = Selector::new("druid-tests.take-focus");
const ALL_TAKE_FOCUS_BEFORE: Selector = Selector::new("druid-tests.take-focus-before");
const ALL_TAKE_FOCUS_AFTER: Selector = Selector::new("druid-tests.take-focus-after");
fn make_focus_container(children: Vec<WidgetPod<(), Box<dyn Widget<()>>>>) -> impl Widget<()> {
ModularWidget::new(children)
.event_fn(|children, ctx, event, data, env| {
if let Event::Command(cmd) = event {
if cmd.is(TAKE_FOCUS) {
ctx.request_focus();
ctx.set_handled();
} else if cmd.is(ALL_TAKE_FOCUS_BEFORE) {
ctx.request_focus();
}
}
children
.iter_mut()
.for_each(|a| a.event(ctx, event, data, env));
if let Event::Command(cmd) = event {
if cmd.is(ALL_TAKE_FOCUS_AFTER) {
ctx.request_focus();
}
}
})
.lifecycle_fn(|children, ctx, event, data, env| {
children
.iter_mut()
.for_each(|a| a.lifecycle(ctx, event, data, env));
})
}
let a_rec = Recording::default();
let b_rec = Recording::default();
let c_rec = Recording::default();
let (id_a, id_b, id_c) = widget_id3();
let c = make_focus_container(vec![]).record(&c_rec).with_id(id_c);
let b = make_focus_container(vec![WidgetPod::new(c).boxed()])
.record(&b_rec)
.with_id(id_b);
let a = make_focus_container(vec![WidgetPod::new(b).boxed()])
.record(&a_rec)
.with_id(id_a);
let f = |a| match a {
Record::L(LifeCycle::FocusChanged(c)) => Some(c),
_ => None,
};
let no_change = |a: &Recording| a.drain().filter_map(f).count() == 0;
let changed = |a: &Recording, b| a.drain().filter_map(f).eq(std::iter::once(b));
Harness::create_simple((), a, |harness| {
harness.send_initial_events();
harness.submit_command(TAKE_FOCUS, id_a);
assert_eq!(harness.window().focus, Some(id_a));
assert!(changed(&a_rec, true));
assert!(no_change(&b_rec));
assert!(no_change(&c_rec));
harness.submit_command(TAKE_FOCUS, id_b);
assert_eq!(harness.window().focus, Some(id_b));
assert!(changed(&a_rec, false));
assert!(changed(&b_rec, true));
assert!(no_change(&c_rec));
harness.submit_command(TAKE_FOCUS, id_c);
assert_eq!(harness.window().focus, Some(id_c));
assert!(no_change(&a_rec));
assert!(changed(&b_rec, false));
assert!(changed(&c_rec, true));
harness.submit_command(TAKE_FOCUS, id_a);
assert_eq!(harness.window().focus, Some(id_a));
assert!(changed(&a_rec, true));
assert!(no_change(&b_rec));
assert!(changed(&c_rec, false));
harness.submit_command(ALL_TAKE_FOCUS_BEFORE, None);
assert_eq!(harness.window().focus, Some(id_c));
assert!(changed(&a_rec, false));
assert!(no_change(&b_rec));
assert!(changed(&c_rec, true));
harness.submit_command(ALL_TAKE_FOCUS_AFTER, None);
assert_eq!(harness.window().focus, Some(id_a));
assert!(changed(&a_rec, true));
assert!(no_change(&b_rec));
assert!(changed(&c_rec, false));
})
}
#[test]
fn simple_lifecyle() {
let record = Recording::default();
let widget = SizedBox::empty().record(&record);
Harness::create_simple(true, widget, |harness| {
harness.send_initial_events();
assert_matches!(record.next(), Record::L(LifeCycle::WidgetAdded));
assert_matches!(record.next(), Record::E(Event::WindowConnected));
assert_matches!(record.next(), Record::E(Event::WindowSize(_)));
assert!(record.is_empty());
})
}
#[test]
fn adding_child_lifecycle() {
let record = Recording::default();
let record_new_child = Recording::default();
let record_new_child2 = record_new_child.clone();
let replacer = ReplaceChild::new(TextBox::new(), move || {
Split::columns(TextBox::new(), TextBox::new().record(&record_new_child2))
});
let widget = Split::columns(Label::new("hi").record(&record), replacer);
Harness::create_simple(String::new(), widget, |harness| {
harness.send_initial_events();
assert_matches!(record.next(), Record::L(LifeCycle::WidgetAdded));
assert_matches!(record.next(), Record::E(Event::WindowConnected));
assert!(record.is_empty());
assert!(record_new_child.is_empty());
harness.submit_command(REPLACE_CHILD, None);
assert_matches!(record.next(), Record::E(Event::Command(_)));
assert_matches!(record_new_child.next(), Record::L(LifeCycle::WidgetAdded));
assert!(record_new_child.is_empty());
})
}
#[test]
fn participate_in_autofocus() {
let (id_1, id_2, id_3, id_4, id_5, id_6) = widget_id6();
let replacer = ReplaceChild::new(TextBox::new().with_id(id_4), move || {
Split::columns(TextBox::new().with_id(id_5), TextBox::new().with_id(id_6))
});
let widget = Split::columns(
Flex::row()
.with_flex_child(TextBox::new().with_id(id_1), 1.0)
.with_flex_child(TextBox::new().with_id(id_2), 1.0)
.with_flex_child(TextBox::new().with_id(id_3), 1.0),
replacer,
);
Harness::create_simple("my test text".to_string(), widget, |harness| {
harness.inspect_state(|state| assert!(state.children_changed));
harness.send_initial_events();
assert_eq!(harness.window().focus_chain(), &[id_1, id_2, id_3, id_4]);
harness.submit_command(REPLACE_CHILD, None);
assert_eq!(
harness.window().focus_chain(),
&[id_1, id_2, id_3, id_5, id_6]
);
harness.inspect_state(|state| assert!(!state.children_changed))
})
}
#[test]
fn child_tracking() {
let (id_1, id_2, id_3, id_4) = widget_id4();
let widget = Split::columns(
SizedBox::empty().with_id(id_1),
SizedBox::empty().with_id(id_2),
)
.with_id(id_3)
.padding(5.0)
.with_id(id_4);
Harness::create_simple(true, widget, |harness| {
harness.send_initial_events();
let root = harness.get_state(id_4);
assert_eq!(root.children.entry_count(), 3);
assert!(root.children.may_contain(&id_1));
assert!(root.children.may_contain(&id_2));
assert!(root.children.may_contain(&id_3));
let split = harness.get_state(id_3);
assert!(split.children.may_contain(&id_1));
assert!(split.children.may_contain(&id_2));
assert_eq!(split.children.entry_count(), 2);
});
}
#[test]
fn register_after_adding_child() {
let (id_1, id_2, id_3, id_4, id_5, id_6) = widget_id6();
let id_7 = WidgetId::next();
let replacer = ReplaceChild::new(TextBox::new().with_id(id_1), move || {
Split::columns(TextBox::new().with_id(id_2), TextBox::new().with_id(id_3)).with_id(id_7)
})
.with_id(id_6);
let widget = Split::columns(Label::new("hi").with_id(id_4), replacer).with_id(id_5);
Harness::create_simple(String::new(), widget, |harness| {
harness.send_initial_events();
assert!(harness.get_state(id_5).children.may_contain(&id_6));
assert!(harness.get_state(id_5).children.may_contain(&id_1));
assert!(harness.get_state(id_5).children.may_contain(&id_4));
assert_eq!(harness.get_state(id_5).children.entry_count(), 3);
harness.submit_command(REPLACE_CHILD, None);
assert!(harness.get_state(id_5).children.may_contain(&id_6));
assert!(harness.get_state(id_5).children.may_contain(&id_4));
assert!(harness.get_state(id_5).children.may_contain(&id_7));
assert!(harness.get_state(id_5).children.may_contain(&id_2));
assert!(harness.get_state(id_5).children.may_contain(&id_3));
assert_eq!(harness.get_state(id_5).children.entry_count(), 5);
})
}