use super::*;
use crate::base::Size;
use crate::reactive::Signal;
use crate::ui::{text, BufferCanvas, MouseButton, MouseKind, UiTree};
use crate::widgets::itest_util::{key, key_mod, mount_widget, mouse, settle};
use crate::widgets::Scroll;
const VIEW: Size = Size { w: 20, h: 4 };
fn member_rows(items: Vec<String>, sel: Signal<usize>) -> View {
crate::ui::dyn_view(
LayoutStyle::column().width(Dimension::Percent(1.0)),
move || {
let s = sel.get();
let mut col =
Element::new().style(LayoutStyle::column().width(Dimension::Percent(1.0)));
for (i, name) in items.iter().enumerate() {
let mark = if i == s { ">" } else { " " };
col = col.child(
Element::new()
.style(
LayoutStyle::column()
.width(Dimension::Percent(1.0))
.height(Dimension::Cells(2)),
)
.child(text(format!("{mark} {name}")))
.child(text(format!(" mission {i}")))
.build(),
);
}
col.build()
},
)
}
struct Harness {
tree: UiTree,
sel: Signal<usize>,
sel_key: Signal<String>,
data: Signal<Vec<String>>,
_root: crate::reactive::RootScope,
}
impl Harness {
fn canvas(&mut self) -> BufferCanvas {
settle(&mut self.tree, VIEW)
}
fn rows(&mut self) -> Vec<String> {
let canvas = self.canvas();
(0..VIEW.h).map(|y| canvas.row_text(y)).collect()
}
}
fn harness(items: &[&str], sticky: bool) -> Harness {
let mut probes = None;
let (root, tree) = mount_widget(VIEW, |cx| {
let sel = cx.signal(0usize);
let sel_key = cx.signal(String::new());
let offset = cx.signal(0i32);
let data = cx.signal(items.iter().map(|s| s.to_string()).collect::<Vec<_>>());
probes = Some((sel, sel_key, data));
Element::new()
.style(LayoutStyle::fill())
.child(crate::ui::dyn_view_scoped(LayoutStyle::fill(), move |cx| {
let items = data.get();
let scroll = Scroll::new(member_rows(items.clone(), sel))
.offset_y(offset)
.view(cx);
let mut rs = RowSelect::new(items)
.row_heights(|_| 2)
.selection(sel)
.offset_y(offset);
if sticky {
rs = rs.selection_key(sel_key);
}
rs.wrap(cx, scroll).build()
}))
.build()
});
let (sel, sel_key, data) = probes.unwrap();
Harness {
tree,
sel,
sel_key,
data,
_root: root,
}
}
#[test]
fn arrows_move_selection_across_multi_line_rows_and_the_selected_element_reports_it() {
let mut h = harness(&["alpha", "beta", "gamma"], false);
key(&mut h.tree, Key::Tab);
let rows = h.rows();
assert!(rows[0].contains("> alpha"), "{rows:?}");
assert!(rows[1].contains("mission 0"), "{rows:?}");
key(&mut h.tree, Key::Down);
let rows = h.rows();
assert_eq!(h.sel.get_untracked(), 1);
assert!(!rows[0].contains(">"), "marker left behind: {rows:?}");
assert!(rows[2].contains("> beta"), "{rows:?}");
assert!(rows[3].contains("mission 1"), "{rows:?}");
key(&mut h.tree, Key::Down);
let rows = h.rows();
assert_eq!(h.sel.get_untracked(), 2);
let marked = rows
.iter()
.position(|r| r.contains("> gamma"))
.unwrap_or_else(|| panic!("gamma never scrolled into view: {rows:?}"));
assert!(
rows[marked + 1].contains("mission 2"),
"the selected row's own second line did not come with it: {rows:?}"
);
key(&mut h.tree, Key::Home);
let rows = h.rows();
assert_eq!(h.sel.get_untracked(), 0);
assert!(rows[0].contains("> alpha"), "{rows:?}");
key(&mut h.tree, Key::End);
let rows = h.rows();
assert_eq!(h.sel.get_untracked(), 2);
assert!(rows.iter().any(|r| r.contains("> gamma")), "{rows:?}");
}
#[test]
fn sticky_selection_survives_a_mutation_that_moves_the_selected_row() {
let mut h = harness(&["alpha", "beta", "gamma"], true);
key(&mut h.tree, Key::Tab);
key(&mut h.tree, Key::Down);
h.canvas();
assert_eq!(h.sel.get_untracked(), 1);
assert_eq!(h.sel_key.get_untracked(), "beta");
h.data.set(
["zeta", "yeti", "alpha", "beta", "gamma"]
.iter()
.map(|s| s.to_string())
.collect(),
);
h.canvas();
assert_eq!(
h.sel.get_untracked(),
3,
"selection did not follow the KEY through the mutation"
);
assert_eq!(h.sel_key.get_untracked(), "beta");
key(&mut h.tree, Key::Tab);
key(&mut h.tree, Key::Up);
let rows = h.rows();
assert_eq!(h.sel.get_untracked(), 2);
assert!(
rows.iter().any(|r| r.contains("> alpha")),
"moved off beta to the wrong neighbour: {rows:?}"
);
}
#[test]
fn a_removed_selected_row_falls_to_the_slot_it_left() {
let mut h = harness(&["alpha", "beta", "gamma"], true);
key(&mut h.tree, Key::Tab);
key(&mut h.tree, Key::Down);
h.canvas();
assert_eq!(h.sel_key.get_untracked(), "beta");
h.data
.set(["alpha", "gamma"].iter().map(|s| s.to_string()).collect());
h.canvas();
assert_eq!(h.sel.get_untracked(), 1);
assert_eq!(h.sel_key.get_untracked(), "gamma");
}
#[test]
fn a_press_selects_the_multi_line_row_under_the_pointer() {
let mut h = harness(&["alpha", "beta", "gamma"], false);
h.canvas();
mouse(&mut h.tree, MouseKind::Down(MouseButton::Left), 3, 3);
h.canvas();
assert_eq!(h.sel.get_untracked(), 1, "row 3 is beta's second line");
let rows = h.rows();
assert!(rows[2].contains("> beta"), "{rows:?}");
}
#[test]
fn enter_activates_and_movement_does_not() {
let fired: Rc<RefCell<Vec<usize>>> = Rc::new(RefCell::new(Vec::new()));
let f = fired.clone();
let mut probe = None;
let (_root, mut tree) = mount_widget(VIEW, |cx| {
let sel = cx.signal(0usize);
let offset = cx.signal(0i32);
probe = Some(sel);
let items: Vec<String> = ["alpha", "beta"].iter().map(|s| s.to_string()).collect();
let scroll = Scroll::new(member_rows(items.clone(), sel))
.offset_y(offset)
.view(cx);
Element::new()
.style(LayoutStyle::fill())
.child(
RowSelect::new(items)
.row_heights(|_| 2)
.selection(sel)
.offset_y(offset)
.on_activate(move |i| f.borrow_mut().push(i))
.wrap(cx, scroll)
.build(),
)
.build()
});
key(&mut tree, Key::Tab);
key(&mut tree, Key::Down);
assert_eq!(probe.unwrap().get_untracked(), 1);
assert!(fired.borrow().is_empty(), "movement is not a commitment");
key(&mut tree, Key::Enter);
assert_eq!(*fired.borrow(), vec![1]);
}
#[test]
fn a_modified_chord_is_not_a_navigation_key() {
let mut h = harness(&["alpha", "beta", "gamma"], false);
key(&mut h.tree, Key::Tab);
key_mod(&mut h.tree, Key::Down, Mods::CTRL);
h.canvas();
assert_eq!(h.sel.get_untracked(), 0, "Ctrl+Down is not ours");
key_mod(&mut h.tree, Key::End, Mods::SHIFT);
h.canvas();
assert_eq!(h.sel.get_untracked(), 0, "Shift+End is not ours");
}
#[test]
fn an_empty_row_select_selects_nothing_and_survives_the_keys() {
let mut h = harness(&[], false);
key(&mut h.tree, Key::Tab);
key(&mut h.tree, Key::Down);
key(&mut h.tree, Key::End);
h.canvas();
assert_eq!(h.sel.get_untracked(), 0);
}