use std::collections::HashSet;
use super::*;
use insta::assert_snapshot;
use ratatui::{backend::TestBackend, Terminal};
fn render_stateful(name: &str, tree: Tree<'_>, state: &mut TreeState) {
let backend = TestBackend::new(60, 12);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| f.render_stateful_widget(tree, f.area(), state))
.unwrap();
assert_snapshot!(name, terminal.backend());
}
fn nested_nodes() -> Vec<TreeNode<'static>> {
vec![TreeNode {
text: Line::from("a"),
children: vec![TreeNode {
text: Line::from("b"),
children: vec![TreeNode {
text: Line::from("c"),
children: vec![],
id: 12,
}],
id: 11,
}],
id: 10,
}]
}
fn sample_nodes() -> Vec<TreeNode<'static>> {
vec![
TreeNode {
text: Line::from("src"),
children: vec![
TreeNode {
text: Line::from("main.rs"),
children: vec![],
id: 2,
},
TreeNode {
text: Line::from("lib.rs"),
children: vec![],
id: 3,
},
],
id: 1,
},
TreeNode {
text: Line::from("Cargo.toml"),
children: vec![],
id: 4,
},
TreeNode {
text: Line::from("docs"),
children: vec![
TreeNode {
text: Line::from("guide.md"),
children: vec![],
id: 6,
},
],
id: 5,
},
]
}
#[test]
fn visible_count_zero_when_empty() {
let tree = Tree::new(vec![]);
assert_eq!(tree.visible_count(&HashSet::new()), 0);
}
#[test]
fn visible_count_root_nodes() {
let tree = Tree::new(sample_nodes());
assert_eq!(tree.visible_count(&HashSet::new()), 3);
}
#[test]
fn visible_count_expanded_shows_children() {
let tree = Tree::new(sample_nodes());
let mut expanded = HashSet::new();
expanded.insert(1);
assert_eq!(tree.visible_count(&expanded), 5);
}
#[test]
fn visible_count_all_expanded() {
let tree = Tree::new(sample_nodes());
let expanded = HashSet::from([1, 5]);
assert_eq!(tree.visible_count(&expanded), 6);
}
#[test]
fn toggle_adds_when_absent() {
let mut state = TreeState::default();
state.toggle(1);
assert!(state.expanded.contains(&1));
}
#[test]
fn toggle_removes_when_present() {
let mut state = TreeState { expanded: HashSet::from([1]), ..TreeState::default() };
state.toggle(1);
assert!(!state.expanded.contains(&1));
}
#[test]
fn expand_all_inserts_nodes_with_children() {
let nodes = sample_nodes();
let mut state = TreeState::default();
state.expand_all(&nodes);
assert!(state.expanded.contains(&1));
assert!(state.expanded.contains(&5));
assert!(!state.expanded.contains(&2));
assert!(!state.expanded.contains(&3));
assert!(!state.expanded.contains(&4));
}
#[test]
fn expand_all_empty_when_no_children() {
let nodes = vec![
TreeNode { text: Line::from("x"), children: vec![], id: 1 },
TreeNode { text: Line::from("y"), children: vec![], id: 2 },
];
let mut state = TreeState::default();
state.expand_all(&nodes);
assert!(state.expanded.is_empty());
}
#[test]
fn expand_all_recursive_inserts_nested() {
let nodes = nested_nodes();
let mut state = TreeState::default();
state.expand_all(&nodes);
assert!(state.expanded.contains(&10));
assert!(state.expanded.contains(&11));
assert!(!state.expanded.contains(&12));
}
#[test]
fn expand_subtree_expands_all_descendants() {
let nodes = nested_nodes();
let mut state = TreeState::default();
state.expand_subtree(&nodes, 10);
assert!(state.expanded.contains(&10));
assert!(state.expanded.contains(&11));
assert!(state.expanded.contains(&12));
}
#[test]
fn expand_subtree_leaf_only_inserts_self() {
let nodes = sample_nodes();
let mut state = TreeState::default();
state.expand_subtree(&nodes, 2);
assert!(state.expanded.contains(&2));
assert_eq!(state.expanded.len(), 1);
}
#[test]
fn collapse_at_expanded_node_collapses_self() {
let nodes = sample_nodes();
let mut state = TreeState { expanded: HashSet::from([1, 5]), ..TreeState::default() };
state.collapse_at(&nodes, 1);
assert!(!state.expanded.contains(&1));
assert!(state.expanded.contains(&5));
}
#[test]
fn collapse_at_leaf_finds_expanded_ancestor() {
let nodes = sample_nodes();
let mut state = TreeState { expanded: HashSet::from([1, 5]), ..TreeState::default() };
state.collapse_at(&nodes, 2);
assert!(!state.expanded.contains(&1));
assert!(state.expanded.contains(&5));
}
#[test]
fn collapse_at_collapsed_node_finds_expanded_ancestor() {
let nodes = sample_nodes();
let mut state = TreeState { expanded: HashSet::from([1, 5]), ..TreeState::default() };
state.expanded.remove(&1);
state.collapse_at(&nodes, 3);
assert!(!state.expanded.contains(&1));
assert!(state.expanded.contains(&5));
}
#[test]
fn collapse_at_no_ancestor_does_nothing() {
let nodes = sample_nodes();
let mut state = TreeState::default();
state.collapse_at(&nodes, 2);
assert!(state.expanded.is_empty());
}
#[test]
fn expand_at_collapsed_node_inserts_one_level() {
let nodes = nested_nodes();
let mut state = TreeState::default();
state.expand_at(&nodes, 10);
assert!(state.expanded.contains(&10));
assert!(!state.expanded.contains(&11));
}
#[test]
fn expand_at_already_expanded_expands_recursively() {
let nodes = nested_nodes();
let mut state = TreeState { expanded: HashSet::from([10]), ..TreeState::default() };
state.expand_at(&nodes, 10);
assert!(state.expanded.contains(&10));
assert!(state.expanded.contains(&11));
assert!(state.expanded.contains(&12));
}
#[test]
fn nearest_expanded_ancestor_returns_closest() {
let nodes = nested_nodes();
let expanded = HashSet::from([10, 11]);
let state = TreeState { expanded, ..TreeState::default() };
assert_eq!(state.nearest_expanded_ancestor(&nodes, 12), Some(11));
}
#[test]
fn nearest_expanded_ancestor_returns_root() {
let nodes = nested_nodes();
let expanded = HashSet::from([10]);
let state = TreeState { expanded, ..TreeState::default() };
assert_eq!(state.nearest_expanded_ancestor(&nodes, 12), Some(10));
}
#[test]
fn nearest_expanded_ancestor_none_when_all_collapsed() {
let nodes = sample_nodes();
let state = TreeState::default();
assert_eq!(state.nearest_expanded_ancestor(&nodes, 2), None);
}
#[test]
fn nearest_expanded_ancestor_none_for_missing_id() {
let nodes = sample_nodes();
let state = TreeState { expanded: HashSet::from([1]), ..TreeState::default() };
assert_eq!(state.nearest_expanded_ancestor(&nodes, 99), None);
}
#[test]
fn visible_index_of_root() {
let nodes = sample_nodes();
assert_eq!(TreeState::visible_index_of(&TreeState::default(), &nodes, 1), Some(0));
}
#[test]
fn visible_index_of_second_root() {
let nodes = sample_nodes();
assert_eq!(TreeState::visible_index_of(&TreeState::default(), &nodes, 4), Some(1));
}
#[test]
fn visible_index_of_child_when_collapsed() {
let nodes = sample_nodes();
assert_eq!(TreeState::visible_index_of(&TreeState::default(), &nodes, 2), None);
}
#[test]
fn visible_index_of_child_when_expanded() {
let nodes = sample_nodes();
let expanded = HashSet::from([1]);
let state = TreeState { expanded, ..TreeState::default() };
assert_eq!(TreeState::visible_index_of(&state, &nodes, 2), Some(1));
}
#[test]
fn visible_index_of_missing_id() {
let nodes = sample_nodes();
assert_eq!(TreeState::visible_index_of(&TreeState::default(), &nodes, 99), None);
}
#[test]
fn snapshot_collapsed() {
let tree = Tree::new(sample_nodes());
let mut state = TreeState::default();
render_stateful("tree_collapsed", tree, &mut state);
}
#[test]
fn snapshot_expanded_root() {
let tree = Tree::new(sample_nodes());
let mut state = TreeState { expanded: HashSet::from([1]), ..TreeState::default() };
render_stateful("tree_expanded_root", tree, &mut state);
}
#[test]
fn snapshot_all_expanded() {
let tree = Tree::new(sample_nodes());
let mut state = TreeState { expanded: HashSet::from([1, 5]), ..TreeState::default() };
render_stateful("tree_all_expanded", tree, &mut state);
}
#[test]
fn snapshot_with_borders() {
let tree = Tree::new(sample_nodes())
.borders(ratatui::widgets::Borders::ALL);
let mut state = TreeState { expanded: HashSet::from([1]), ..TreeState::default() };
render_stateful("tree_with_borders", tree, &mut state);
}
#[test]
fn snapshot_custom_icons() {
let tree = Tree::new(sample_nodes())
.expand_icon("[+] ")
.collapse_icon("[-] ")
.leaf_icon(" * ");
let mut state = TreeState { expanded: HashSet::from([1, 5]), ..TreeState::default() };
render_stateful("tree_custom_icons", tree, &mut state);
}