1use std::io::Stdout;
22
23use crate::console::ConsoleOptions;
24use crate::measure::Measurement;
25use crate::segment::{Segment, Segments};
26use crate::style::Style;
27use crate::{Console, Renderable};
28
29#[derive(Debug, Clone, Copy)]
41pub struct TreeGuides {
42 pub space: &'static str,
44 pub vertical: &'static str,
46 pub branch: &'static str,
48 pub end: &'static str,
50}
51
52pub const TREE_GUIDES: TreeGuides = TreeGuides {
54 space: " ",
55 vertical: "\u{2502} ", branch: "\u{251c}\u{2500}\u{2500} ", end: "\u{2514}\u{2500}\u{2500} ", };
59
60pub const BOLD_TREE_GUIDES: TreeGuides = TreeGuides {
65 space: " ",
66 vertical: "\u{2503} ", branch: "\u{2523}\u{2501}\u{2501} ", end: "\u{2517}\u{2501}\u{2501} ", };
70
71pub const DOUBLE_TREE_GUIDES: TreeGuides = TreeGuides {
76 space: " ",
77 vertical: "\u{2551} ", branch: "\u{2560}\u{2550}\u{2550} ", end: "\u{255a}\u{2550}\u{2550} ", };
81
82pub const ASCII_GUIDES: TreeGuides = TreeGuides {
84 space: " ",
85 vertical: "| ",
86 branch: "+-- ",
87 end: "`-- ",
88};
89
90#[derive(Debug, Clone, Default)]
115pub struct TreeNodeOptions {
116 pub style: Option<Style>,
118 pub guide_style: Option<Style>,
120 pub expanded: Option<bool>,
122 pub highlight: Option<bool>,
124}
125
126impl TreeNodeOptions {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn with_style(mut self, style: Style) -> Self {
134 self.style = Some(style);
135 self
136 }
137
138 pub fn with_guide_style(mut self, style: Style) -> Self {
140 self.guide_style = Some(style);
141 self
142 }
143
144 pub fn with_expanded(mut self, expanded: bool) -> Self {
146 self.expanded = Some(expanded);
147 self
148 }
149
150 pub fn with_highlight(mut self, highlight: bool) -> Self {
152 self.highlight = Some(highlight);
153 self
154 }
155}
156
157pub struct Tree {
158 label: Box<dyn Renderable + Send + Sync>,
160 children: Vec<Tree>,
162 style: Style,
164 guide_style: Style,
166 expanded: bool,
168 highlight: bool,
170 hide_root: bool,
172}
173
174impl std::fmt::Debug for Tree {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct("Tree")
177 .field("children_count", &self.children.len())
178 .field("style", &self.style)
179 .field("guide_style", &self.guide_style)
180 .field("expanded", &self.expanded)
181 .field("highlight", &self.highlight)
182 .field("hide_root", &self.hide_root)
183 .finish_non_exhaustive()
184 }
185}
186
187impl Tree {
188 fn guides_for(options: &ConsoleOptions, guide_style: Style) -> &'static TreeGuides {
189 if options.ascii_only() {
190 &ASCII_GUIDES
191 } else if guide_style.bold == Some(true) {
192 &BOLD_TREE_GUIDES
193 } else if guide_style.underline == Some(true) {
194 &DOUBLE_TREE_GUIDES
197 } else {
198 &TREE_GUIDES
199 }
200 }
201
202 pub fn new(label: Box<dyn Renderable + Send + Sync>) -> Self {
216 Tree {
217 label,
218 children: Vec::new(),
219 style: Style::default(),
220 guide_style: Style::default(),
221 expanded: true,
222 highlight: false,
223 hide_root: false,
224 }
225 }
226
227 pub fn add(&mut self, label: Box<dyn Renderable + Send + Sync>) -> &mut Tree {
250 let child = Tree {
251 label,
252 children: Vec::new(),
253 style: self.style,
254 guide_style: self.guide_style,
255 expanded: true,
256 highlight: self.highlight,
257 hide_root: false,
258 };
259 self.children.push(child);
260 self.children.last_mut().unwrap()
261 }
262
263 pub fn add_with_options(
277 &mut self,
278 label: Box<dyn Renderable + Send + Sync>,
279 options: TreeNodeOptions,
280 ) -> &mut Tree {
281 let child = Tree {
282 label,
283 children: Vec::new(),
284 style: options.style.unwrap_or(self.style),
285 guide_style: options.guide_style.unwrap_or(self.guide_style),
286 expanded: options.expanded.unwrap_or(true),
287 highlight: options.highlight.unwrap_or(self.highlight),
288 hide_root: false,
289 };
290 self.children.push(child);
291 self.children.last_mut().unwrap()
292 }
293
294 pub fn add_tree(&mut self, tree: Tree) {
314 self.children.push(tree);
315 }
316
317 pub fn with_style(mut self, style: Style) -> Self {
323 self.style = style;
324 self
325 }
326
327 pub fn with_guide_style(mut self, style: Style) -> Self {
333 self.guide_style = style;
334 self
335 }
336
337 pub fn with_expanded(mut self, expanded: bool) -> Self {
343 self.expanded = expanded;
344 self
345 }
346
347 pub fn with_highlight(mut self, highlight: bool) -> Self {
353 self.highlight = highlight;
354 self
355 }
356
357 pub fn with_hide_root(mut self, hide: bool) -> Self {
365 self.hide_root = hide;
366 self
367 }
368
369 pub fn children_count(&self) -> usize {
371 self.children.len()
372 }
373
374 pub fn has_children(&self) -> bool {
376 !self.children.is_empty()
377 }
378
379 pub fn is_expanded(&self) -> bool {
381 self.expanded
382 }
383
384 pub fn style(&self) -> Style {
386 self.style
387 }
388
389 pub fn guide_style(&self) -> Style {
391 self.guide_style
392 }
393
394 pub fn children(&self) -> &[Tree] {
396 &self.children
397 }
398
399 pub fn children_mut(&mut self) -> &mut Vec<Tree> {
401 &mut self.children
402 }
403}
404
405struct TraversalState<'a> {
407 node: &'a Tree,
409 child_index: usize,
411 is_last: bool,
413 depth: usize,
415}
416
417impl Renderable for Tree {
418 fn render(&self, _console: &Console<Stdout>, options: &ConsoleOptions) -> Segments {
419 let mut result = Segments::new();
420
421 let mut stack: Vec<TraversalState> = if self.hide_root {
424 self.children
426 .iter()
427 .enumerate()
428 .rev()
429 .map(|(i, child)| TraversalState {
430 node: child,
431 child_index: 0,
432 is_last: i == self.children.len() - 1,
433 depth: 0,
434 })
435 .collect()
436 } else {
437 vec![TraversalState {
438 node: self,
439 child_index: 0,
440 is_last: true,
441 depth: 0,
442 }]
443 };
444
445 let mut levels: Vec<bool> = Vec::new();
448
449 let temp_console = Console::<Stdout>::with_options(options.clone());
451
452 while let Some(state) = stack.last_mut() {
453 let TraversalState {
454 node,
455 child_index,
456 is_last,
457 depth,
458 } = state;
459
460 if *child_index == 0 {
461 let node_guides = Self::guides_for(options, node.guide_style);
463
464 while levels.len() < *depth {
466 levels.push(false);
467 }
468 if *depth > 0 {
469 if *depth <= levels.len() {
470 levels[*depth - 1] = *is_last;
471 } else {
472 levels.push(*is_last);
473 }
474 }
475
476 if *depth > 0 {
479 let mut prefix = String::new();
480
481 for i in 0..(*depth - 1) {
483 let ancestor_is_last = levels.get(i).copied().unwrap_or(false);
484 if ancestor_is_last {
485 prefix.push_str(node_guides.space);
486 } else {
487 prefix.push_str(node_guides.vertical);
488 }
489 }
490
491 if *is_last {
493 prefix.push_str(node_guides.end);
494 } else {
495 prefix.push_str(node_guides.branch);
496 }
497
498 if !prefix.is_empty() {
500 result.push(Segment::styled(prefix, node.guide_style));
501 }
502 }
503
504 let guide_width = if *depth > 0 { *depth * 4 } else { 0 };
506 let label_width = options.max_width.saturating_sub(guide_width);
507 let mut label_options = options.update_width(label_width);
508 label_options.highlight = Some(node.highlight);
509
510 let label_segments = node.label.render(&temp_console, &label_options);
512
513 let lines = Segment::split_lines(label_segments);
515
516 for (line_idx, line) in lines.iter().enumerate() {
517 if line_idx > 0 {
518 if *depth > 0 {
520 let mut prefix = String::new();
521 for i in 0..*depth {
522 let ancestor_is_last = levels.get(i).copied().unwrap_or(false);
523 if ancestor_is_last {
524 prefix.push_str(node_guides.space);
525 } else {
526 prefix.push_str(node_guides.vertical);
527 }
528 }
529 result.push(Segment::styled(prefix, node.guide_style));
530 }
531 }
532
533 for seg in line {
535 if !node.style.is_null() && seg.style.is_none() {
537 result.push(Segment::styled(seg.text.clone(), node.style));
538 } else if !node.style.is_null() {
539 let combined = node.style.combine(&seg.style.unwrap_or_default());
541 result.push(Segment::styled(seg.text.clone(), combined));
542 } else {
543 result.push(seg.clone());
544 }
545 }
546
547 result.push(Segment::line());
549 }
550
551 if lines.is_empty() {
553 result.push(Segment::line());
554 }
555 }
556
557 if node.expanded && *child_index < node.children.len() {
559 let child_node = &node.children[*child_index];
560 let child_is_last = *child_index == node.children.len() - 1;
561 let child_depth = *depth + 1;
562 *child_index += 1;
563
564 stack.push(TraversalState {
565 node: child_node,
566 child_index: 0,
567 is_last: child_is_last,
568 depth: child_depth,
569 });
570 } else {
571 stack.pop();
573 }
574 }
575
576 result
577 }
578
579 fn measure(&self, console: &Console<Stdout>, options: &ConsoleOptions) -> Measurement {
580 let mut stack: Vec<(&Tree, usize)> = vec![(self, 0)];
582 let mut minimum: usize = 0;
583 let mut maximum: usize = 0;
584
585 while let Some((node, depth)) = stack.pop() {
586 let label_measurement = node.label.measure(console, options);
588 let indent = depth * 4;
589
590 minimum = minimum.max(label_measurement.minimum + indent);
591 maximum = maximum.max(label_measurement.maximum + indent);
592
593 if node.expanded {
595 for child in node.children.iter().rev() {
596 stack.push((child, depth + 1));
597 }
598 }
599 }
600
601 Measurement::new(minimum, maximum)
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608 use crate::Renderable;
609 use crate::text::Text;
610 use std::io::Stdout;
611
612 struct HighlightAwareLabel;
613
614 impl Renderable for HighlightAwareLabel {
615 fn render(&self, _console: &Console<Stdout>, options: &ConsoleOptions) -> Segments {
616 if options.highlight == Some(true) {
617 Segments::one(Segment::styled(
618 "highlighted",
619 Style::parse("yellow").unwrap(),
620 ))
621 } else {
622 Segments::one(Segment::new("plain".to_string()))
623 }
624 }
625 }
626
627 #[test]
630 fn test_tree_guides_unicode() {
631 assert_eq!(TREE_GUIDES.space, " ");
632 assert_eq!(TREE_GUIDES.vertical, "│ ");
633 assert_eq!(TREE_GUIDES.branch, "├── ");
634 assert_eq!(TREE_GUIDES.end, "└── ");
635 }
636
637 #[test]
638 fn test_tree_guides_ascii() {
639 assert_eq!(ASCII_GUIDES.space, " ");
640 assert_eq!(ASCII_GUIDES.vertical, "| ");
641 assert_eq!(ASCII_GUIDES.branch, "+-- ");
642 assert_eq!(ASCII_GUIDES.end, "`-- ");
643 }
644
645 #[test]
648 fn test_tree_new() {
649 let tree = Tree::new(Box::new(Text::plain("Root")));
650 assert_eq!(tree.children_count(), 0);
651 assert!(!tree.has_children());
652 assert!(tree.is_expanded());
653 }
654
655 #[test]
656 fn test_tree_add_child() {
657 let mut tree = Tree::new(Box::new(Text::plain("Root")));
658 tree.add(Box::new(Text::plain("Child")));
659 assert_eq!(tree.children_count(), 1);
660 assert!(tree.has_children());
661 }
662
663 #[test]
664 fn test_tree_add_returns_child() {
665 let mut tree = Tree::new(Box::new(Text::plain("Root")));
666 let child = tree.add(Box::new(Text::plain("Child")));
667 child.add(Box::new(Text::plain("Grandchild")));
668
669 assert_eq!(tree.children_count(), 1);
670 assert_eq!(tree.children()[0].children_count(), 1);
671 }
672
673 #[test]
674 fn test_tree_add_tree() {
675 let mut subtree = Tree::new(Box::new(Text::plain("Subtree")));
676 subtree.add(Box::new(Text::plain("Leaf")));
677
678 let mut root = Tree::new(Box::new(Text::plain("Root")));
679 root.add_tree(subtree);
680
681 assert_eq!(root.children_count(), 1);
682 assert_eq!(root.children()[0].children_count(), 1);
683 }
684
685 #[test]
686 fn test_tree_chained_add() {
687 let mut root = Tree::new(Box::new(Text::plain("Root")));
688 root.add(Box::new(Text::plain("A")))
689 .add(Box::new(Text::plain("A1")))
690 .add(Box::new(Text::plain("A1a")));
691
692 assert_eq!(root.children_count(), 1);
694 assert_eq!(root.children()[0].children_count(), 1);
695 assert_eq!(root.children()[0].children()[0].children_count(), 1);
696 }
697
698 #[test]
701 fn test_tree_with_style() {
702 let style = Style::new().with_bold(true);
703 let tree = Tree::new(Box::new(Text::plain("Root"))).with_style(style);
704 assert_eq!(tree.style().bold, Some(true));
705 }
706
707 #[test]
708 fn test_tree_with_guide_style() {
709 let style = Style::new().with_dim(true);
710 let tree = Tree::new(Box::new(Text::plain("Root"))).with_guide_style(style);
711 assert_eq!(tree.guide_style().dim, Some(true));
712 }
713
714 #[test]
715 fn test_tree_with_expanded() {
716 let tree = Tree::new(Box::new(Text::plain("Root"))).with_expanded(false);
717 assert!(!tree.is_expanded());
718 }
719
720 #[test]
721 fn test_tree_with_highlight() {
722 let tree = Tree::new(Box::new(Text::plain("Root"))).with_highlight(true);
723 assert!(tree.highlight);
724 }
725
726 #[test]
729 fn test_tree_hide_root() {
730 let mut tree = Tree::new(Box::new(Text::plain("Root"))).with_hide_root(true);
731 tree.add(Box::new(Text::plain("Child 1")));
732 tree.add(Box::new(Text::plain("Child 2")));
733
734 let console = Console::with_options(ConsoleOptions::default());
735 let options = console.options().clone();
736 let segments = tree.render(&console, &options);
737 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
738
739 assert!(!output.contains("Root"));
741 assert!(output.contains("Child 1"));
743 assert!(output.contains("Child 2"));
744 }
745
746 #[test]
747 fn test_tree_hide_root_no_children() {
748 let tree = Tree::new(Box::new(Text::plain("Root"))).with_hide_root(true);
749
750 let console = Console::with_options(ConsoleOptions::default());
751 let options = console.options().clone();
752 let segments = tree.render(&console, &options);
753 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
754
755 assert!(!output.contains("Root"));
757 }
758
759 #[test]
762 fn test_tree_add_with_options() {
763 let mut tree = Tree::new(Box::new(Text::plain("Root")));
764 let opts = TreeNodeOptions::new()
765 .with_style(Style::new().with_bold(true))
766 .with_expanded(false);
767 let child = tree.add_with_options(Box::new(Text::plain("Child")), opts);
768 assert!(!child.is_expanded());
769 assert_eq!(child.style().bold, Some(true));
770 }
771
772 #[test]
773 fn test_tree_add_with_options_inherits() {
774 let style = Style::new().with_dim(true);
775 let mut tree = Tree::new(Box::new(Text::plain("Root"))).with_style(style);
776 let child = tree.add_with_options(Box::new(Text::plain("Child")), TreeNodeOptions::new());
777 assert_eq!(child.style().dim, Some(true));
779 }
780
781 #[test]
782 fn test_tree_add_with_options_highlight_affects_render() {
783 let mut tree = Tree::new(Box::new(Text::plain("Root")));
784 tree.add_with_options(
785 Box::new(HighlightAwareLabel),
786 TreeNodeOptions::new().with_highlight(true),
787 );
788
789 let console = Console::with_options(ConsoleOptions::default());
790 let options = console.options().clone();
791 let segments = tree.render(&console, &options);
792
793 assert!(
794 segments.iter().any(|seg| seg.text == "highlighted"),
795 "child label should render with highlight=true",
796 );
797 assert!(!segments.iter().any(|seg| seg.text == "plain"));
798 assert!(
799 segments
800 .iter()
801 .any(|seg| seg.text == "highlighted" && seg.style.and_then(|s| s.color).is_some()),
802 "highlighted label should include style from highlight-aware renderable",
803 );
804 }
805
806 #[test]
807 fn test_tree_add_with_options_guide_style_affects_guide_chars() {
808 let mut tree = Tree::new(Box::new(Text::plain("Root")));
809 tree.add_with_options(
810 Box::new(Text::plain("Bold child")),
811 TreeNodeOptions::new().with_guide_style(Style::new().with_bold(true)),
812 );
813
814 let console = Console::with_options(ConsoleOptions::default());
815 let options = console.options().clone();
816 let segments = tree.render(&console, &options);
817 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
818
819 assert!(
820 output.contains("┗━━ "),
821 "child with bold guide_style should use bold guide connector",
822 );
823 }
824
825 #[test]
828 fn test_bold_tree_guides() {
829 assert_eq!(BOLD_TREE_GUIDES.vertical, "┃ ");
830 assert_eq!(BOLD_TREE_GUIDES.branch, "┣━━ ");
831 assert_eq!(BOLD_TREE_GUIDES.end, "┗━━ ");
832 }
833
834 #[test]
835 fn test_double_tree_guides() {
836 assert_eq!(DOUBLE_TREE_GUIDES.vertical, "║ ");
837 assert_eq!(DOUBLE_TREE_GUIDES.branch, "╠══ ");
838 assert_eq!(DOUBLE_TREE_GUIDES.end, "╚══ ");
839 }
840
841 #[test]
842 fn test_tree_renders_bold_guides() {
843 let mut tree =
844 Tree::new(Box::new(Text::plain("Root"))).with_guide_style(Style::new().with_bold(true));
845 tree.add(Box::new(Text::plain("Child")));
846
847 let console = Console::with_options(ConsoleOptions::default());
848 let options = console.options().clone();
849 let segments = tree.render(&console, &options);
850 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
851
852 assert!(output.contains("┗━━ "), "Should use bold guides");
853 }
854
855 #[test]
858 fn test_tree_render_single_node() {
859 let tree = Tree::new(Box::new(Text::plain("Root")));
860 let console = Console::with_options(ConsoleOptions::default());
861 let options = console.options().clone();
862
863 let segments = tree.render(&console, &options);
864 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
865
866 assert!(output.contains("Root"));
867 assert!(output.ends_with('\n'));
868 }
869
870 #[test]
871 fn test_tree_render_with_children() {
872 let mut tree = Tree::new(Box::new(Text::plain("Root")));
873 tree.add(Box::new(Text::plain("Child 1")));
874 tree.add(Box::new(Text::plain("Child 2")));
875
876 let console = Console::with_options(ConsoleOptions::default());
877 let options = console.options().clone();
878
879 let segments = tree.render(&console, &options);
880 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
881
882 assert!(output.contains("Root"));
883 assert!(output.contains("Child 1"));
884 assert!(output.contains("Child 2"));
885 assert!(output.contains("├── ") || output.contains("+-- ")); assert!(output.contains("└── ") || output.contains("`-- ")); }
889
890 #[test]
891 fn test_tree_render_nested() {
892 let mut tree = Tree::new(Box::new(Text::plain("Root")));
893 let child = tree.add(Box::new(Text::plain("Child")));
894 child.add(Box::new(Text::plain("Grandchild")));
895
896 let console = Console::with_options(ConsoleOptions::default());
897 let options = console.options().clone();
898
899 let segments = tree.render(&console, &options);
900 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
901
902 assert!(output.contains("Root"));
903 assert!(output.contains("Child"));
904 assert!(output.contains("Grandchild"));
905 }
906
907 #[test]
908 fn test_tree_render_ascii_guides() {
909 let mut tree = Tree::new(Box::new(Text::plain("Root")));
910 tree.add(Box::new(Text::plain("Child")));
911
912 let console = Console::with_options(ConsoleOptions {
913 encoding: "ascii".to_string(),
914 ..Default::default()
915 });
916 let options = console.options().clone();
917
918 let segments = tree.render(&console, &options);
919 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
920
921 assert!(output.contains("`-- ")); assert!(!output.contains("└")); }
925
926 #[test]
927 fn test_tree_render_unicode_guides() {
928 let mut tree = Tree::new(Box::new(Text::plain("Root")));
929 tree.add(Box::new(Text::plain("Child")));
930
931 let console = Console::with_options(ConsoleOptions {
932 encoding: "utf-8".to_string(),
933 ..Default::default()
934 });
935 let options = console.options().clone();
936
937 let segments = tree.render(&console, &options);
938 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
939
940 assert!(output.contains("└── ")); }
943
944 #[test]
945 fn test_tree_render_collapsed() {
946 let mut tree = Tree::new(Box::new(Text::plain("Root"))).with_expanded(false);
947 tree.add(Box::new(Text::plain("Child")));
948
949 let console = Console::with_options(ConsoleOptions::default());
950 let options = console.options().clone();
951
952 let segments = tree.render(&console, &options);
953 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
954
955 assert!(output.contains("Root"));
956 assert!(!output.contains("Child")); }
958
959 #[test]
960 fn test_tree_render_complex_structure() {
961 let mut root = Tree::new(Box::new(Text::plain("Documents")));
963
964 let projects = root.add(Box::new(Text::plain("Projects")));
965 projects.add(Box::new(Text::plain("project1")));
966 projects.add(Box::new(Text::plain("project2")));
967
968 root.add(Box::new(Text::plain("notes.txt")));
969
970 let console = Console::with_options(ConsoleOptions::default());
971 let options = console.options().clone();
972
973 let segments = root.render(&console, &options);
974 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
975
976 assert!(output.contains("Documents"));
977 assert!(output.contains("Projects"));
978 assert!(output.contains("project1"));
979 assert!(output.contains("project2"));
980 assert!(output.contains("notes.txt"));
981 }
982
983 #[test]
986 fn test_tree_measure_single_node() {
987 let tree = Tree::new(Box::new(Text::plain("Root")));
988 let console = Console::with_options(ConsoleOptions::default());
989 let options = console.options().clone();
990
991 let measurement = tree.measure(&console, &options);
992 assert!(measurement.minimum >= 4);
994 assert!(measurement.maximum >= measurement.minimum);
995 }
996
997 #[test]
998 fn test_tree_measure_with_children() {
999 let mut tree = Tree::new(Box::new(Text::plain("R"))); tree.add(Box::new(Text::plain("Child"))); let console = Console::with_options(ConsoleOptions::default());
1003 let options = console.options().clone();
1004
1005 let measurement = tree.measure(&console, &options);
1006 assert!(measurement.maximum >= 9);
1008 }
1009
1010 #[test]
1013 fn test_tree_is_send_sync() {
1014 fn assert_send<T: Send>() {}
1015 fn assert_sync<T: Sync>() {}
1016 assert_send::<Tree>();
1017 assert_sync::<Tree>();
1018 }
1019
1020 #[test]
1021 fn test_tree_guides_is_send_sync() {
1022 fn assert_send<T: Send>() {}
1023 fn assert_sync<T: Sync>() {}
1024 assert_send::<TreeGuides>();
1025 assert_sync::<TreeGuides>();
1026 }
1027
1028 #[test]
1031 fn test_tree_debug() {
1032 let tree = Tree::new(Box::new(Text::plain("Root")));
1033 let debug_str = format!("{:?}", tree);
1034 assert!(debug_str.contains("Tree"));
1035 assert!(debug_str.contains("children_count"));
1036 }
1037}