test(tree): search prompt and filter prompt

pull/9/head
wongjiahau 2 years ago
parent 7e4feb02ef
commit fae4990444

@ -34,6 +34,47 @@ impl<'a> Context<'a> {
tokio::task::block_in_place(|| helix_lsp::block_on(self.editor.flush_writes()))?; tokio::task::block_in_place(|| helix_lsp::block_on(self.editor.flush_writes()))?;
Ok(()) Ok(())
} }
/// Purpose: to test `handle_event` without escalating the test case to integration test
/// Usage:
/// ```
/// let mut editor = Context::dummy_editor();
/// let mut jobs = Context::dummy_jobs();
/// let mut cx = Context::dummy(&mut jobs, &mut editor);
/// ```
#[cfg(test)]
pub fn dummy(jobs: &'a mut Jobs, editor: &'a mut helix_view::Editor) -> Context<'a> {
Context {
jobs,
scroll: None,
editor,
}
}
#[cfg(test)]
pub fn dummy_jobs() -> Jobs {
Jobs::new()
}
#[cfg(test)]
pub fn dummy_editor() -> Editor {
use crate::config::Config;
use arc_swap::{access::Map, ArcSwap};
use helix_core::syntax::{self, Configuration};
use helix_view::theme;
use std::sync::Arc;
let config = Arc::new(ArcSwap::from_pointee(Config::default()));
Editor::new(
Rect::new(0, 0, 60, 120),
Arc::new(theme::Loader::new("", "")),
Arc::new(syntax::Loader::new(Configuration { language: vec![] })),
Arc::new(Arc::new(Map::new(
Arc::clone(&config),
|config: &Config| &config.editor,
))),
)
}
} }
pub trait Component: Any + AnyComponent { pub trait Component: Any + AnyComponent {

@ -317,8 +317,10 @@ pub struct TreeView<T: TreeViewItem> {
} }
impl<T: TreeViewItem> TreeView<T> { impl<T: TreeViewItem> TreeView<T> {
pub fn new(root: T, items: Vec<Tree<T>>) -> Self { pub fn build_tree(root: T) -> Result<Self> {
Self { let children = root.get_children()?;
let items = vec_to_tree(children);
Ok(Self {
tree: Tree::new(root, items), tree: Tree::new(root, items),
selected: 0, selected: 0,
backward_jumps: vec![], backward_jumps: vec![],
@ -337,12 +339,7 @@ impl<T: TreeViewItem> TreeView<T> {
filter_prompt: None, filter_prompt: None,
search_str: "".into(), search_str: "".into(),
filter: "".into(), filter: "".into(),
} })
}
pub fn build_tree(root: T) -> Result<Self> {
let children = root.get_children()?;
Ok(Self::new(root, vec_to_tree(children)))
} }
pub fn with_enter_fn<F>(mut self, f: F) -> Self pub fn with_enter_fn<F>(mut self, f: F) -> Self
@ -1011,6 +1008,21 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
.collect() .collect()
} }
#[cfg(test)]
pub fn handle_events(
&mut self,
events: &str,
cx: &mut Context,
params: &mut T::Params,
) -> Result<()> {
use helix_view::input::parse_macro;
for event in parse_macro(events)? {
self.handle_event(&Event::Key(event), cx, params);
}
Ok(())
}
pub fn handle_event( pub fn handle_event(
&mut self, &mut self,
event: &Event, event: &Event,
@ -1234,21 +1246,26 @@ fn index_elems<T>(parent_index: usize, elems: Vec<Tree<T>>) -> Vec<Tree<T>> {
#[cfg(test)] #[cfg(test)]
mod test_tree_view { mod test_tree_view {
use helix_view::graphics::Rect; use helix_view::graphics::Rect;
use super::{vec_to_tree, TreeView, TreeViewItem}; use crate::compositor::Context;
use super::{TreeView, TreeViewItem};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
struct Item<'a> { /// The children of DivisibleItem is the division of itself.
/// This is used to ease the creation of a dummy tree without having to specify so many things.
struct DivisibleItem<'a> {
name: &'a str, name: &'a str,
} }
fn item(name: &str) -> Item { fn item(name: &str) -> DivisibleItem {
Item { name } DivisibleItem { name }
} }
impl<'a> TreeViewItem for Item<'a> { impl<'a> TreeViewItem for DivisibleItem<'a> {
type Params = (); type Params = ();
fn name(&self) -> String { fn name(&self) -> String {
@ -1260,7 +1277,20 @@ mod test_tree_view {
} }
fn get_children(&self) -> anyhow::Result<Vec<Self>> { fn get_children(&self) -> anyhow::Result<Vec<Self>> {
if self.is_parent() { if self.name.eq("who_lives_in_a_pineapple_under_the_sea") {
Ok(vec![
item("gary_the_snail"),
item("krabby_patty"),
item("larry_the_lobster"),
item("patrick_star"),
item("sandy_cheeks"),
item("spongebob_squarepants"),
item("mrs_puff"),
item("king_neptune"),
item("karen"),
item("plankton"),
])
} else if self.is_parent() {
let (left, right) = self.name.split_at(self.name.len() / 2); let (left, right) = self.name.split_at(self.name.len() / 2);
Ok(vec![item(left), item(right)]) Ok(vec![item(left), item(right)])
} else { } else {
@ -1273,29 +1303,15 @@ mod test_tree_view {
} }
} }
fn dummy_tree_view<'a>() -> TreeView<Item<'a>> { fn dummy_tree_view<'a>() -> TreeView<DivisibleItem<'a>> {
TreeView::new( TreeView::build_tree(item("who_lives_in_a_pineapple_under_the_sea")).unwrap()
item("who_lives_in_a_pineapple_under_the_sea"),
vec_to_tree(vec![
item("gary_the_snail"),
item("krabby_patty"),
item("larry_the_lobster"),
item("patrick_star"),
item("sandy_cheeks"),
item("spongebob_squarepants"),
item("mrs_puff"),
item("king_neptune"),
item("karen"),
item("plankton"),
]),
)
} }
fn dummy_area() -> Rect { fn dummy_area() -> Rect {
Rect::new(0, 0, 50, 5) Rect::new(0, 0, 50, 5)
} }
fn render(view: &mut TreeView<Item>) -> String { fn render(view: &mut TreeView<DivisibleItem>) -> String {
view.render_to_string(dummy_area()) view.render_to_string(dummy_area())
} }
@ -1646,7 +1662,7 @@ mod test_tree_view {
fn test_move_left_right() { fn test_move_left_right() {
let mut view = dummy_tree_view(); let mut view = dummy_tree_view();
fn render(view: &mut TreeView<Item>) -> String { fn render(view: &mut TreeView<DivisibleItem>) -> String {
view.render_to_string(dummy_area().with_width(20)) view.render_to_string(dummy_area().with_width(20))
} }
@ -2028,7 +2044,7 @@ krabby_patty
let item = view.current_item().unwrap(); let item = view.current_item().unwrap();
// 3a. Expects no failure // 3a. Expects no failure
assert_eq!(item.name, "who_lives_in_a_pine") assert_eq!(item.name, "ar")
} }
#[test] #[test]
@ -2116,33 +2132,33 @@ krabby_patty
); );
} }
#[test] mod static_tree {
fn test_sticky_ancestors() { use crate::ui::{TreeView, TreeViewItem};
// The ancestors of the current item should always be visible
// However, if there's not enough space, the current item will take precedence, use super::dummy_area;
// and the nearest ancestor has higher precedence than further ancestors
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
struct Item<'a> { /// This is used for test cases where the structure of the tree has to be known upfront
name: &'a str, pub struct StaticItem<'a> {
children: Option<Vec<Item<'a>>>, pub name: &'a str,
pub children: Option<Vec<StaticItem<'a>>>,
} }
fn parent<'a>(name: &'a str, children: Vec<Item<'a>>) -> Item<'a> { pub fn parent<'a>(name: &'a str, children: Vec<StaticItem<'a>>) -> StaticItem<'a> {
Item { StaticItem {
name, name,
children: Some(children), children: Some(children),
} }
} }
fn child(name: &str) -> Item { pub fn child(name: &str) -> StaticItem {
Item { StaticItem {
name, name,
children: None, children: None,
} }
} }
impl<'a> TreeViewItem for Item<'a> { impl<'a> TreeViewItem for StaticItem<'a> {
type Params = (); type Params = ();
fn name(&self) -> String { fn name(&self) -> String {
@ -2165,13 +2181,21 @@ krabby_patty
} }
} }
fn render(view: &mut TreeView<Item<'_>>) -> String { pub fn render(view: &mut TreeView<StaticItem<'_>>) -> String {
view.render_to_string(dummy_area().with_height(3)) view.render_to_string(dummy_area().with_height(3))
} }
}
#[test]
fn test_sticky_ancestors() {
// The ancestors of the current item should always be visible
// However, if there's not enough space, the current item will take precedence,
// and the nearest ancestor has higher precedence than further ancestors
use static_tree::*;
let mut view = TreeView::new( let mut view = TreeView::build_tree(parent(
parent("root", vec![]), "root",
vec_to_tree(vec![ vec![
parent("a", vec![child("aa"), child("ab")]), parent("a", vec![child("aa"), child("ab")]),
parent( parent(
"b", "b",
@ -2180,8 +2204,9 @@ krabby_patty
vec![parent("baa", vec![child("baaa"), child("baab")])], vec![parent("baa", vec![child("baaa"), child("baab")])],
)], )],
), ),
]), ],
); ))
.unwrap();
assert_eq!( assert_eq!(
render(&mut view), render(&mut view),
@ -2364,6 +2389,152 @@ krabby_patty
.trim() .trim()
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_search_prompt() {
let mut editor = Context::dummy_editor();
let mut jobs = Context::dummy_jobs();
let mut cx = Context::dummy(&mut jobs, &mut editor);
let mut view = dummy_tree_view();
view.handle_events("/an", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
larry_the_lobster
mrs_puff
patrick_star
(plankton)
"
.trim()
);
view.handle_events("t<ret>", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
patrick_star
plankton
sandy_cheeks
(spongebob_squarepants)
"
.trim()
);
view.handle_events("/larry", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
karen
king_neptune
krabby_patty
(larry_the_lobster)
"
.trim()
);
view.handle_events("<esc>", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
patrick_star
plankton
sandy_cheeks
(spongebob_squarepants)
"
.trim()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_filter_prompt() {
use static_tree::*;
let mut editor = Context::dummy_editor();
let mut jobs = Context::dummy_jobs();
let mut cx = Context::dummy(&mut jobs, &mut editor);
let mut view = TreeView::build_tree(parent(
"root",
vec![
parent("src", vec![child("bar.rs"), child("foo.toml")]),
parent("tests", vec![child("hello.toml"), child("spam.rs")]),
],
))
.unwrap();
fn render(view: &mut TreeView<StaticItem<'_>>) -> String {
view.render_to_string(dummy_area().with_height(5))
}
// Open all the children
view.handle_events("lljjl", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[root]
bar.rs
foo.toml
[tests]
(hello.toml)
"
.trim()
);
view.handle_events("frs<ret>", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[root]
bar.rs
[tests]
(spam.rs)
"
.trim()
);
view.handle_events("f<C-w>toml", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[root]
foo.toml
[tests]
(hello.toml)
"
.trim()
);
// Escape should causes the filter to be reverted
view.handle_events("<esc>", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[root]
bar.rs
[tests]
(spam.rs)
"
.trim()
);
// C-c should clear the filter
view.handle_events("f<C-c>", &mut cx, &mut ()).unwrap();
assert_eq!(
render(&mut view),
"
[root]
bar.rs
foo.toml
(tests)
hello.toml
"
.trim()
);
}
} }
#[cfg(test)] #[cfg(test)]

Loading…
Cancel
Save