use super::TestCase;
use rich_rust::prelude::*;
use rich_rust::renderables::tree::{Tree, TreeGuides, TreeNode};
use rich_rust::segment::Segment;
use rich_rust::text::Text;
#[derive(Debug)]
pub struct TreeTest {
pub name: &'static str,
pub root_label: &'static str,
pub children: Vec<&'static str>,
pub guides: TreeGuides,
}
impl TestCase for TreeTest {
fn name(&self) -> &str {
self.name
}
fn render(&self) -> Vec<Segment<'static>> {
let mut tree = Tree::with_label(self.root_label).guides(self.guides);
for child in &self.children {
tree = tree.child(TreeNode::new(*child));
}
tree.render().into_iter().map(Segment::into_owned).collect()
}
fn python_rich_code(&self) -> Option<String> {
let children_code = self
.children
.iter()
.map(|c| format!("tree.add(\"{}\")", c))
.collect::<Vec<_>>()
.join("\n");
Some(format!(
r#"from rich.console import Console
from rich.tree import Tree
console = Console(force_terminal=True, width=80)
tree = Tree("{}")
{}
console.print(tree, end="")"#,
self.root_label, children_code
))
}
}
pub fn standard_tree_tests() -> Vec<Box<dyn TestCase>> {
vec![
Box::new(TreeTest {
name: "tree_simple",
root_label: "root",
children: vec!["child1", "child2"],
guides: TreeGuides::Unicode,
}),
Box::new(TreeTest {
name: "tree_ascii",
root_label: "root",
children: vec!["child1", "child2"],
guides: TreeGuides::Ascii,
}),
Box::new(TreeTest {
name: "tree_rounded",
root_label: "root",
children: vec!["child"],
guides: TreeGuides::Rounded,
}),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conformance::run_test;
#[test]
fn test_tree_simple() {
let test = TreeTest {
name: "tree_simple",
root_label: "root",
children: vec!["child1", "child2"],
guides: TreeGuides::Unicode,
};
let output = run_test(&test);
assert!(output.contains("root"), "Root should be present");
assert!(output.contains("child1"), "Child1 should be present");
assert!(output.contains("child2"), "Child2 should be present");
}
#[test]
fn test_tree_with_nested_children() {
let tree =
Tree::with_label("root").child(TreeNode::new("parent").child(TreeNode::new("child")));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(output.contains("root"));
assert!(output.contains("parent"));
assert!(output.contains("child"));
}
#[test]
fn test_tree_node_does_not_parse_markup() {
let tree = Tree::new(TreeNode::new("[bold]Styled Root[/]"))
.child(TreeNode::new("[italic]Styled Child[/]"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(
output.contains("[bold]"),
"Raw [bold] tag should appear literally (not parsed)"
);
assert!(
output.contains("[/]"),
"Raw [/] tag should appear literally (not parsed)"
);
assert!(
output.contains("[italic]"),
"Raw [italic] tag should appear literally"
);
assert!(
output.contains("Styled Root"),
"Root label text should be present"
);
assert!(
output.contains("Styled Child"),
"Child label text should be present"
);
}
#[test]
fn test_tree_with_label_does_not_parse_markup() {
let tree = Tree::with_label("[red]Root[/]").child(TreeNode::new("child"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(
output.contains("[red]"),
"Raw [red] tag should appear literally"
);
assert!(
output.contains("[/]"),
"Raw [/] tag should appear literally"
);
}
#[test]
fn test_tree_node_with_prestyled_text_preserves_styles() {
let mut styled_label = Text::new("Styled Root");
styled_label.stylize(0, 6, Style::new().bold());
let tree = Tree::new(TreeNode::new(styled_label));
let segments: Vec<Segment<'_>> = tree.render();
let label_segment = segments
.iter()
.find(|seg| seg.text.contains("Styled"))
.expect("Label segment should exist");
let style = label_segment.style.as_ref().expect("Should have style");
assert!(
style.attributes.contains(Attributes::BOLD),
"Label should have bold attribute from explicit styling"
);
let output: String = segments.iter().map(|s| s.text.as_ref()).collect();
assert!(
!output.contains("[bold]"),
"Should not contain raw [bold] tag"
);
}
#[test]
fn test_tree_guide_style_applied() {
let tree = Tree::with_label("root")
.guide_style(Style::new().color(Color::parse("green").unwrap()))
.child(TreeNode::new("child"));
let segments = tree.render();
let has_colored_guide = segments.iter().any(|seg| {
let is_guide = seg.text.contains('─')
|| seg.text.contains('│')
|| seg.text.contains('├')
|| seg.text.contains('└');
if is_guide {
if let Some(ref style) = seg.style {
style.color.is_some()
} else {
false
}
} else {
false
}
});
assert!(
has_colored_guide,
"Guide segments should have color style applied"
);
}
#[test]
fn test_tree_ascii_guides() {
let tree = Tree::with_label("root")
.guides(TreeGuides::Ascii)
.child(TreeNode::new("child1"))
.child(TreeNode::new("child2"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(
output.contains("+--") || output.contains("`--"),
"Should have ASCII guide characters"
);
}
#[test]
fn test_tree_unicode_guides() {
let tree = Tree::with_label("root")
.guides(TreeGuides::Unicode)
.child(TreeNode::new("child1"))
.child(TreeNode::new("child2"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(
output.contains("├──") || output.contains("└──"),
"Should have Unicode guide characters"
);
}
#[test]
fn test_tree_highlight_style_applied() {
let tree = Tree::with_label("root")
.highlight_style(Style::new().bold())
.child(TreeNode::new("child"));
let segments = tree.render();
let has_bold = segments.iter().any(|seg| {
!seg.is_control()
&& seg
.style
.as_ref()
.is_some_and(|s| s.attributes.contains(Attributes::BOLD))
});
assert!(has_bold, "Should have bold style from highlight_style");
}
#[test]
fn test_tree_highlight_combines_with_label_style() {
let mut styled_label = Text::new("Root");
styled_label.stylize(0, 4, Style::new().italic());
let tree = Tree::new(TreeNode::new(styled_label)).highlight_style(Style::new().bold());
let segments = tree.render();
let root_segment = segments
.iter()
.find(|seg| seg.text.contains("Root"))
.expect("Root segment should exist");
let style = root_segment.style.as_ref().expect("Should have style");
assert!(
style.attributes.contains(Attributes::ITALIC),
"Should preserve italic from label"
);
assert!(
style.attributes.contains(Attributes::BOLD),
"Should have bold from highlight"
);
}
#[test]
fn test_tree_node_with_icon() {
let tree =
Tree::new(TreeNode::with_icon("📁", "folder")).child(TreeNode::with_icon("📄", "file"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(output.contains("📁"), "Should have folder icon");
assert!(output.contains("📄"), "Should have file icon");
assert!(output.contains("folder"), "Should have folder label");
assert!(output.contains("file"), "Should have file label");
}
#[test]
fn test_tree_icon_style_applied() {
let tree = Tree::new(
TreeNode::with_icon("*", "root")
.icon_style(Style::new().color(Color::parse("yellow").unwrap())),
);
let segments = tree.render();
let icon_segment = segments
.iter()
.find(|seg| seg.text.contains('*'))
.expect("Icon segment should exist");
let style = icon_segment.style.as_ref().expect("Icon should have style");
assert!(style.color.is_some(), "Icon should have color style");
}
#[test]
fn test_tree_with_styled_label_has_ansi_codes() {
let mut styled_label = Text::new("Label");
styled_label.stylize(0, 5, Style::new().bold());
let tree = Tree::new(TreeNode::new(styled_label));
let segments = tree.render();
let has_bold = segments.iter().any(|seg| {
seg.style
.as_ref()
.is_some_and(|s| s.attributes.contains(Attributes::BOLD))
});
assert!(has_bold, "Should have bold style in segments");
}
#[test]
fn test_tree_with_colored_guides_has_color_in_segments() {
let tree = Tree::with_label("root")
.guide_style(Style::new().color(Color::parse("blue").unwrap()))
.child(TreeNode::new("child"));
let segments = tree.render();
let has_color = segments
.iter()
.any(|seg| seg.style.as_ref().is_some_and(|s| s.color.is_some()));
assert!(has_color, "Should have color in guide segments");
}
#[test]
fn test_tree_parsed_labels_have_no_raw_markup() {
let mut text = Text::new("Styled Label");
text.stylize(0, 6, Style::new().bold());
let tree = Tree::new(TreeNode::new(text));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
let markup_patterns = ["[bold]", "[/bold]", "[italic]", "[/italic]", "[red]", "[/]"];
for pattern in markup_patterns {
assert!(
!output.contains(pattern),
"Pre-styled content should not contain raw markup: {}",
pattern
);
}
}
#[test]
fn test_tree_nested_styles() {
let mut root_text = Text::new("Bold Root");
root_text.stylize(0, 4, Style::new().bold());
let mut child_text = Text::new("Italic Child");
child_text.stylize(0, 6, Style::new().italic());
let tree = Tree::new(TreeNode::new(root_text)).child(TreeNode::new(child_text));
let segments = tree.render();
let has_bold = segments.iter().any(|seg| {
seg.text.contains("Bold")
&& seg
.style
.as_ref()
.is_some_and(|s| s.attributes.contains(Attributes::BOLD))
});
let has_italic = segments.iter().any(|seg| {
seg.text.contains("Italic")
&& seg
.style
.as_ref()
.is_some_and(|s| s.attributes.contains(Attributes::ITALIC))
});
assert!(has_bold, "Should have bold style on 'Bold'");
assert!(has_italic, "Should have italic style on 'Italic'");
}
#[test]
fn test_tree_collapsed_node_indicator() {
let tree = Tree::with_label("root").child(
TreeNode::new("collapsed")
.collapsed()
.child(TreeNode::new("hidden")),
);
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(
output.contains("[...]"),
"Collapsed indicator should appear"
);
assert!(!output.contains("hidden"), "Hidden child should not appear");
}
#[test]
fn test_tree_max_depth() {
let tree = Tree::with_label("root")
.max_depth(1)
.child(TreeNode::new("visible").child(TreeNode::new("hidden")));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(output.contains("root"), "Root should be visible");
assert!(output.contains("visible"), "Level 1 should be visible");
assert!(
!output.contains("hidden"),
"Level 2 should be hidden by max_depth"
);
}
#[test]
fn test_tree_hide_root() {
let tree = Tree::with_label("hidden_root")
.hide_root()
.child(TreeNode::new("visible_child"));
let output: String = tree
.render()
.into_iter()
.map(|s| s.text.into_owned())
.collect();
assert!(!output.contains("hidden_root"), "Root should be hidden");
assert!(output.contains("visible_child"), "Child should be visible");
}
#[test]
fn test_all_standard_tree_tests() {
for test in standard_tree_tests() {
let output = run_test(test.as_ref());
assert!(
!output.is_empty(),
"Test '{}' produced empty output",
test.name()
);
}
}
}