1use crate::stateful::Stateful;
22use crate::undo_support::{TreeUndoExt, UndoSupport, UndoWidgetId};
23use crate::{Widget, draw_text_span};
24use ftui_core::geometry::Rect;
25use ftui_render::frame::{Frame, HitId, HitRegion};
26use ftui_style::Style;
27use std::any::Any;
28use std::collections::HashSet;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum TreeGuides {
33 Ascii,
35 #[default]
37 Unicode,
38 Bold,
40 Double,
42 Rounded,
44}
45
46impl TreeGuides {
47 #[must_use]
49 pub const fn vertical(&self) -> &str {
50 match self {
51 Self::Ascii => "| ",
52 Self::Unicode | Self::Rounded => "\u{2502} ",
53 Self::Bold => "\u{2503} ",
54 Self::Double => "\u{2551} ",
55 }
56 }
57
58 #[must_use]
60 pub const fn branch(&self) -> &str {
61 match self {
62 Self::Ascii => "+-- ",
63 Self::Unicode => "\u{251C}\u{2500}\u{2500} ",
64 Self::Bold => "\u{2523}\u{2501}\u{2501} ",
65 Self::Double => "\u{2560}\u{2550}\u{2550} ",
66 Self::Rounded => "\u{251C}\u{2500}\u{2500} ",
67 }
68 }
69
70 #[must_use]
72 pub const fn last(&self) -> &str {
73 match self {
74 Self::Ascii => "`-- ",
75 Self::Unicode => "\u{2514}\u{2500}\u{2500} ",
76 Self::Bold => "\u{2517}\u{2501}\u{2501} ",
77 Self::Double => "\u{255A}\u{2550}\u{2550} ",
78 Self::Rounded => "\u{2570}\u{2500}\u{2500} ",
79 }
80 }
81
82 #[must_use]
84 pub const fn space(&self) -> &str {
85 " "
86 }
87
88 #[must_use]
90 pub fn width(&self) -> usize {
91 4
92 }
93}
94
95#[derive(Debug, Clone)]
97pub struct TreeNode {
98 label: String,
99 pub(crate) children: Vec<TreeNode>,
101 pub(crate) expanded: bool,
103}
104
105impl TreeNode {
106 #[must_use]
108 pub fn new(label: impl Into<String>) -> Self {
109 Self {
110 label: label.into(),
111 children: Vec::new(),
112 expanded: true,
113 }
114 }
115
116 #[must_use]
118 pub fn child(mut self, node: TreeNode) -> Self {
119 self.children.push(node);
120 self
121 }
122
123 #[must_use]
125 pub fn with_children(mut self, nodes: Vec<TreeNode>) -> Self {
126 self.children = nodes;
127 self
128 }
129
130 #[must_use]
132 pub fn with_expanded(mut self, expanded: bool) -> Self {
133 self.expanded = expanded;
134 self
135 }
136
137 #[must_use]
139 pub fn label(&self) -> &str {
140 &self.label
141 }
142
143 #[must_use]
145 pub fn children(&self) -> &[TreeNode] {
146 &self.children
147 }
148
149 #[must_use]
151 pub fn is_expanded(&self) -> bool {
152 self.expanded
153 }
154
155 pub fn toggle_expanded(&mut self) {
157 self.expanded = !self.expanded;
158 }
159
160 #[must_use]
162 pub fn visible_count(&self) -> usize {
163 let mut count = 1;
164 if self.expanded {
165 for child in &self.children {
166 count += child.visible_count();
167 }
168 }
169 count
170 }
171
172 #[allow(dead_code)]
174 pub(crate) fn collect_expanded(&self, prefix: &str, out: &mut HashSet<String>) {
175 let path = if prefix.is_empty() {
176 self.label.clone()
177 } else {
178 format!("{}/{}", prefix, self.label)
179 };
180
181 if self.expanded && !self.children.is_empty() {
182 out.insert(path.clone());
183 }
184
185 for child in &self.children {
186 child.collect_expanded(&path, out);
187 }
188 }
189
190 #[allow(dead_code)]
192 pub(crate) fn apply_expanded(&mut self, prefix: &str, expanded_paths: &HashSet<String>) {
193 let path = if prefix.is_empty() {
194 self.label.clone()
195 } else {
196 format!("{}/{}", prefix, self.label)
197 };
198
199 if !self.children.is_empty() {
200 self.expanded = expanded_paths.contains(&path);
201 }
202
203 for child in &mut self.children {
204 child.apply_expanded(&path, expanded_paths);
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct Tree {
212 undo_id: UndoWidgetId,
214 root: TreeNode,
215 show_root: bool,
217 guides: TreeGuides,
219 guide_style: Style,
221 label_style: Style,
223 root_style: Style,
225 persistence_id: Option<String>,
227 hit_id: Option<HitId>,
229}
230
231impl Tree {
232 #[must_use]
234 pub fn new(root: TreeNode) -> Self {
235 Self {
236 undo_id: UndoWidgetId::new(),
237 root,
238 show_root: true,
239 guides: TreeGuides::default(),
240 guide_style: Style::default(),
241 label_style: Style::default(),
242 root_style: Style::default(),
243 persistence_id: None,
244 hit_id: None,
245 }
246 }
247
248 #[must_use]
250 pub fn with_show_root(mut self, show: bool) -> Self {
251 self.show_root = show;
252 self
253 }
254
255 #[must_use]
257 pub fn with_guides(mut self, guides: TreeGuides) -> Self {
258 self.guides = guides;
259 self
260 }
261
262 #[must_use]
264 pub fn with_guide_style(mut self, style: Style) -> Self {
265 self.guide_style = style;
266 self
267 }
268
269 #[must_use]
271 pub fn with_label_style(mut self, style: Style) -> Self {
272 self.label_style = style;
273 self
274 }
275
276 #[must_use]
278 pub fn with_root_style(mut self, style: Style) -> Self {
279 self.root_style = style;
280 self
281 }
282
283 #[must_use]
285 pub fn with_persistence_id(mut self, id: impl Into<String>) -> Self {
286 self.persistence_id = Some(id.into());
287 self
288 }
289
290 #[must_use]
292 pub fn persistence_id(&self) -> Option<&str> {
293 self.persistence_id.as_deref()
294 }
295
296 #[must_use]
298 pub fn hit_id(mut self, id: HitId) -> Self {
299 self.hit_id = Some(id);
300 self
301 }
302
303 #[must_use]
305 pub fn root(&self) -> &TreeNode {
306 &self.root
307 }
308
309 pub fn root_mut(&mut self) -> &mut TreeNode {
311 &mut self.root
312 }
313
314 #[allow(clippy::too_many_arguments)]
315 fn render_node(
316 &self,
317 node: &TreeNode,
318 depth: usize,
319 is_last: &mut Vec<bool>,
320 area: Rect,
321 frame: &mut Frame,
322 current_row: &mut usize,
323 deg: ftui_render::budget::DegradationLevel,
324 ) {
325 if *current_row >= area.height as usize {
326 return;
327 }
328
329 let y = area.y.saturating_add(*current_row as u16);
330 let mut x = area.x;
331 let max_x = area.right();
332
333 if depth > 0 && deg.apply_styling() {
335 for d in 0..depth {
336 let is_last_at_depth = is_last.get(d).copied().unwrap_or(false);
337 let guide = if d == depth - 1 {
338 if is_last_at_depth {
340 self.guides.last()
341 } else {
342 self.guides.branch()
343 }
344 } else {
345 if is_last_at_depth {
347 self.guides.space()
348 } else {
349 self.guides.vertical()
350 }
351 };
352
353 x = draw_text_span(frame, x, y, guide, self.guide_style, max_x);
354 }
355 } else if depth > 0 {
356 let indent = " ".repeat(depth);
358 x = draw_text_span(frame, x, y, &indent, Style::default(), max_x);
359 }
360
361 let style = if depth == 0 && self.show_root {
363 self.root_style
364 } else {
365 self.label_style
366 };
367
368 if deg.apply_styling() {
369 draw_text_span(frame, x, y, &node.label, style, max_x);
370 } else {
371 draw_text_span(frame, x, y, &node.label, Style::default(), max_x);
372 }
373
374 if let Some(id) = self.hit_id {
376 let row_area = Rect::new(area.x, y, area.width, 1);
377 frame.register_hit(row_area, id, HitRegion::Content, *current_row as u64);
378 }
379
380 *current_row += 1;
381
382 if !node.expanded {
383 return;
384 }
385
386 let child_count = node.children.len();
387 for (i, child) in node.children.iter().enumerate() {
388 is_last.push(i == child_count - 1);
389 self.render_node(child, depth + 1, is_last, area, frame, current_row, deg);
390 is_last.pop();
391 }
392 }
393}
394
395impl Widget for Tree {
396 fn render(&self, area: Rect, frame: &mut Frame) {
397 if area.width == 0 || area.height == 0 {
398 return;
399 }
400
401 let deg = frame.buffer.degradation;
402 let mut current_row = 0;
403 let mut is_last = Vec::with_capacity(8);
404
405 if self.show_root {
406 self.render_node(
407 &self.root,
408 0,
409 &mut is_last,
410 area,
411 frame,
412 &mut current_row,
413 deg,
414 );
415 } else if self.root.expanded {
416 let child_count = self.root.children.len();
420 for (i, child) in self.root.children.iter().enumerate() {
421 is_last.push(i == child_count - 1);
422 self.render_node(
423 child,
424 0, &mut is_last,
426 area,
427 frame,
428 &mut current_row,
429 deg,
430 );
431 is_last.pop();
432 }
433 }
434 }
435
436 fn is_essential(&self) -> bool {
437 false
438 }
439}
440
441#[derive(Clone, Debug, Default, PartialEq)]
449#[cfg_attr(
450 feature = "state-persistence",
451 derive(serde::Serialize, serde::Deserialize)
452)]
453pub struct TreePersistState {
454 pub expanded_paths: HashSet<String>,
456}
457
458impl crate::stateful::Stateful for Tree {
459 type State = TreePersistState;
460
461 fn state_key(&self) -> crate::stateful::StateKey {
462 crate::stateful::StateKey::new("Tree", self.persistence_id.as_deref().unwrap_or("default"))
463 }
464
465 fn save_state(&self) -> TreePersistState {
466 let mut expanded_paths = HashSet::new();
467 self.root.collect_expanded("", &mut expanded_paths);
468 TreePersistState { expanded_paths }
469 }
470
471 fn restore_state(&mut self, state: TreePersistState) {
472 self.root.apply_expanded("", &state.expanded_paths);
473 }
474}
475
476impl UndoSupport for Tree {
481 fn undo_widget_id(&self) -> UndoWidgetId {
482 self.undo_id
483 }
484
485 fn create_snapshot(&self) -> Box<dyn Any + Send> {
486 Box::new(self.save_state())
487 }
488
489 fn restore_snapshot(&mut self, snapshot: &dyn Any) -> bool {
490 if let Some(snap) = snapshot.downcast_ref::<TreePersistState>() {
491 self.restore_state(snap.clone());
492 true
493 } else {
494 false
495 }
496 }
497}
498
499impl TreeUndoExt for Tree {
500 fn is_node_expanded(&self, path: &[usize]) -> bool {
501 self.get_node_at_path(path)
502 .map(|node| node.is_expanded())
503 .unwrap_or(false)
504 }
505
506 fn expand_node(&mut self, path: &[usize]) {
507 if let Some(node) = self.get_node_at_path_mut(path) {
508 node.expanded = true;
509 }
510 }
511
512 fn collapse_node(&mut self, path: &[usize]) {
513 if let Some(node) = self.get_node_at_path_mut(path) {
514 node.expanded = false;
515 }
516 }
517}
518
519impl Tree {
520 #[must_use]
522 pub fn undo_id(&self) -> UndoWidgetId {
523 self.undo_id
524 }
525
526 fn get_node_at_path(&self, path: &[usize]) -> Option<&TreeNode> {
528 let mut current = &self.root;
529 for &idx in path {
530 current = current.children.get(idx)?;
531 }
532 Some(current)
533 }
534
535 fn get_node_at_path_mut(&mut self, path: &[usize]) -> Option<&mut TreeNode> {
537 let mut current = &mut self.root;
538 for &idx in path {
539 current = current.children.get_mut(idx)?;
540 }
541 Some(current)
542 }
543}
544
545#[cfg(test)]
550#[derive(Debug, Clone, PartialEq, Eq)]
551struct FlatNode {
552 label: String,
553 depth: usize,
554}
555
556#[cfg(test)]
557fn flatten_visible(node: &TreeNode, depth: usize, out: &mut Vec<FlatNode>) {
558 out.push(FlatNode {
559 label: node.label.clone(),
560 depth,
561 });
562 if node.expanded {
563 for child in &node.children {
564 flatten_visible(child, depth + 1, out);
565 }
566 }
567}
568
569#[cfg(test)]
570impl Tree {
571 fn flatten(&self) -> Vec<FlatNode> {
572 let mut out = Vec::new();
573 if self.show_root {
574 flatten_visible(&self.root, 0, &mut out);
575 } else if self.root.expanded {
576 for child in &self.root.children {
577 flatten_visible(child, 0, &mut out);
578 }
579 }
580 out
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587 use ftui_render::frame::Frame;
588 use ftui_render::grapheme_pool::GraphemePool;
589
590 fn simple_tree() -> TreeNode {
591 TreeNode::new("root")
592 .child(
593 TreeNode::new("a")
594 .child(TreeNode::new("a1"))
595 .child(TreeNode::new("a2")),
596 )
597 .child(TreeNode::new("b"))
598 }
599
600 #[test]
601 fn tree_node_basics() {
602 let node = TreeNode::new("hello");
603 assert_eq!(node.label(), "hello");
604 assert!(node.children().is_empty());
605 assert!(node.is_expanded());
606 }
607
608 #[test]
609 fn tree_node_children() {
610 let root = simple_tree();
611 assert_eq!(root.children().len(), 2);
612 assert_eq!(root.children()[0].label(), "a");
613 assert_eq!(root.children()[0].children().len(), 2);
614 }
615
616 #[test]
617 fn tree_node_visible_count() {
618 let root = simple_tree();
619 assert_eq!(root.visible_count(), 5);
621 }
622
623 #[test]
624 fn tree_node_collapsed() {
625 let root = TreeNode::new("root")
626 .child(
627 TreeNode::new("a")
628 .with_expanded(false)
629 .child(TreeNode::new("a1"))
630 .child(TreeNode::new("a2")),
631 )
632 .child(TreeNode::new("b"));
633 assert_eq!(root.visible_count(), 3);
635 }
636
637 #[test]
638 fn tree_node_toggle() {
639 let mut node = TreeNode::new("x");
640 assert!(node.is_expanded());
641 node.toggle_expanded();
642 assert!(!node.is_expanded());
643 node.toggle_expanded();
644 assert!(node.is_expanded());
645 }
646
647 #[test]
648 fn tree_guides_unicode() {
649 let g = TreeGuides::Unicode;
650 assert!(g.branch().contains('├'));
651 assert!(g.last().contains('└'));
652 assert!(g.vertical().contains('│'));
653 }
654
655 #[test]
656 fn tree_guides_ascii() {
657 let g = TreeGuides::Ascii;
658 assert!(g.branch().contains('+'));
659 assert!(g.vertical().contains('|'));
660 }
661
662 #[test]
663 fn tree_guides_width() {
664 for g in [
665 TreeGuides::Ascii,
666 TreeGuides::Unicode,
667 TreeGuides::Bold,
668 TreeGuides::Double,
669 TreeGuides::Rounded,
670 ] {
671 assert_eq!(g.width(), 4);
672 }
673 }
674
675 #[test]
676 fn tree_render_basic() {
677 let tree = Tree::new(simple_tree());
678
679 let mut pool = GraphemePool::new();
680 let mut frame = Frame::new(40, 10, &mut pool);
681 let area = Rect::new(0, 0, 40, 10);
682 tree.render(area, &mut frame);
683
684 let cell = frame.buffer.get(0, 0).unwrap();
686 assert_eq!(cell.content.as_char(), Some('r'));
687 }
688
689 #[test]
690 fn tree_render_guides_present() {
691 let tree = Tree::new(simple_tree()).with_guides(TreeGuides::Ascii);
692
693 let mut pool = GraphemePool::new();
694 let mut frame = Frame::new(40, 10, &mut pool);
695 let area = Rect::new(0, 0, 40, 10);
696 tree.render(area, &mut frame);
697
698 let cell = frame.buffer.get(0, 1).unwrap();
701 assert_eq!(cell.content.as_char(), Some('+'));
702 }
703
704 #[test]
705 fn tree_render_last_guide() {
706 let tree = Tree::new(
707 TreeNode::new("root")
708 .child(TreeNode::new("a"))
709 .child(TreeNode::new("b")),
710 )
711 .with_guides(TreeGuides::Ascii);
712
713 let mut pool = GraphemePool::new();
714 let mut frame = Frame::new(40, 10, &mut pool);
715 let area = Rect::new(0, 0, 40, 10);
716 tree.render(area, &mut frame);
717
718 let cell = frame.buffer.get(0, 1).unwrap();
720 assert_eq!(cell.content.as_char(), Some('+'));
721
722 let cell = frame.buffer.get(0, 2).unwrap();
724 assert_eq!(cell.content.as_char(), Some('`'));
725 }
726
727 #[test]
728 fn tree_render_zero_area() {
729 let tree = Tree::new(simple_tree());
730 let mut pool = GraphemePool::new();
731 let mut frame = Frame::new(40, 10, &mut pool);
732 tree.render(Rect::new(0, 0, 0, 0), &mut frame); }
734
735 #[test]
736 fn tree_render_truncated_height() {
737 let tree = Tree::new(simple_tree());
738 let mut pool = GraphemePool::new();
739 let mut frame = Frame::new(40, 2, &mut pool);
740 let area = Rect::new(0, 0, 40, 2);
741 tree.render(area, &mut frame); }
743
744 #[test]
745 fn is_not_essential() {
746 let tree = Tree::new(TreeNode::new("x"));
747 assert!(!tree.is_essential());
748 }
749
750 #[test]
751 fn tree_root_access() {
752 let mut tree = Tree::new(TreeNode::new("root"));
753 assert_eq!(tree.root().label(), "root");
754 tree.root_mut().toggle_expanded();
755 assert!(!tree.root().is_expanded());
756 }
757
758 #[test]
759 fn tree_guides_default() {
760 let g = TreeGuides::default();
761 assert_eq!(g, TreeGuides::Unicode);
762 }
763
764 #[test]
765 fn tree_guides_rounded() {
766 let g = TreeGuides::Rounded;
767 assert!(g.last().contains('╰'));
768 }
769
770 #[test]
771 fn tree_deep_nesting() {
772 let node = TreeNode::new("d3");
773 let node = TreeNode::new("d2").child(node);
774 let node = TreeNode::new("d1").child(node);
775 let root = TreeNode::new("root").child(node);
776
777 let tree = Tree::new(root);
778 let flat = tree.flatten();
779 assert_eq!(flat.len(), 4);
780 assert_eq!(flat[3].depth, 3);
781 }
782
783 #[test]
784 fn tree_node_with_children_vec() {
785 let root = TreeNode::new("root").with_children(vec![
786 TreeNode::new("a"),
787 TreeNode::new("b"),
788 TreeNode::new("c"),
789 ]);
790 assert_eq!(root.children().len(), 3);
791 }
792
793 use crate::stateful::Stateful;
796
797 #[test]
798 fn tree_with_persistence_id() {
799 let tree = Tree::new(TreeNode::new("root")).with_persistence_id("file-tree");
800 assert_eq!(tree.persistence_id(), Some("file-tree"));
801 }
802
803 #[test]
804 fn tree_default_no_persistence_id() {
805 let tree = Tree::new(TreeNode::new("root"));
806 assert_eq!(tree.persistence_id(), None);
807 }
808
809 #[test]
810 fn tree_save_restore_round_trip() {
811 let mut tree = Tree::new(
813 TreeNode::new("root")
814 .child(
815 TreeNode::new("src")
816 .child(TreeNode::new("main.rs"))
817 .child(TreeNode::new("lib.rs")),
818 )
819 .child(TreeNode::new("tests").with_expanded(false)),
820 )
821 .with_persistence_id("test");
822
823 assert!(tree.root().is_expanded());
825 assert!(tree.root().children()[0].is_expanded()); assert!(!tree.root().children()[1].is_expanded()); let saved = tree.save_state();
829
830 assert!(saved.expanded_paths.contains("root"));
832 assert!(saved.expanded_paths.contains("root/src"));
833 assert!(!saved.expanded_paths.contains("root/tests"));
834
835 tree.root_mut().children[0].toggle_expanded();
837 assert!(!tree.root().children()[0].is_expanded());
838
839 tree.restore_state(saved);
841
842 assert!(tree.root().is_expanded());
844 assert!(tree.root().children()[0].is_expanded()); assert!(!tree.root().children()[1].is_expanded()); }
847
848 #[test]
849 fn tree_state_key_uses_persistence_id() {
850 let tree = Tree::new(TreeNode::new("root")).with_persistence_id("project-explorer");
851 let key = tree.state_key();
852 assert_eq!(key.widget_type, "Tree");
853 assert_eq!(key.instance_id, "project-explorer");
854 }
855
856 #[test]
857 fn tree_state_key_default_when_no_id() {
858 let tree = Tree::new(TreeNode::new("root"));
859 let key = tree.state_key();
860 assert_eq!(key.widget_type, "Tree");
861 assert_eq!(key.instance_id, "default");
862 }
863
864 #[test]
865 fn tree_persist_state_default() {
866 let persist = TreePersistState::default();
867 assert!(persist.expanded_paths.is_empty());
868 }
869
870 #[test]
871 fn tree_collect_expanded_only_includes_nodes_with_children() {
872 let tree = Tree::new(
873 TreeNode::new("root").child(TreeNode::new("leaf")), );
875
876 let saved = tree.save_state();
877
878 assert!(saved.expanded_paths.contains("root"));
880 assert!(!saved.expanded_paths.contains("root/leaf"));
882 }
883
884 #[test]
889 fn tree_undo_widget_id_unique() {
890 let tree1 = Tree::new(TreeNode::new("root1"));
891 let tree2 = Tree::new(TreeNode::new("root2"));
892 assert_ne!(tree1.undo_id(), tree2.undo_id());
893 }
894
895 #[test]
896 fn tree_undo_snapshot_and_restore() {
897 let mut tree = Tree::new(
899 TreeNode::new("root")
900 .child(
901 TreeNode::new("a")
902 .with_expanded(true)
903 .child(TreeNode::new("a_child")),
904 )
905 .child(
906 TreeNode::new("b")
907 .with_expanded(false)
908 .child(TreeNode::new("b_child")),
909 ),
910 );
911
912 let snapshot = tree.create_snapshot();
914
915 assert!(tree.is_node_expanded(&[0])); assert!(!tree.is_node_expanded(&[1])); tree.collapse_node(&[0]); tree.expand_node(&[1]); assert!(!tree.is_node_expanded(&[0]));
923 assert!(tree.is_node_expanded(&[1]));
924
925 assert!(tree.restore_snapshot(&*snapshot));
927
928 assert!(tree.is_node_expanded(&[0])); assert!(!tree.is_node_expanded(&[1])); }
932
933 #[test]
934 fn tree_expand_collapse_node() {
935 let mut tree =
936 Tree::new(TreeNode::new("root").child(TreeNode::new("child").with_expanded(true)));
937
938 assert!(tree.is_node_expanded(&[0]));
940
941 tree.collapse_node(&[0]);
943 assert!(!tree.is_node_expanded(&[0]));
944
945 tree.expand_node(&[0]);
947 assert!(tree.is_node_expanded(&[0]));
948 }
949
950 #[test]
951 fn tree_node_path_navigation() {
952 let tree = Tree::new(
953 TreeNode::new("root")
954 .child(
955 TreeNode::new("a")
956 .child(TreeNode::new("a1"))
957 .child(TreeNode::new("a2")),
958 )
959 .child(TreeNode::new("b")),
960 );
961
962 assert_eq!(tree.get_node_at_path(&[]).map(|n| n.label()), Some("root"));
964 assert_eq!(tree.get_node_at_path(&[0]).map(|n| n.label()), Some("a"));
965 assert_eq!(tree.get_node_at_path(&[1]).map(|n| n.label()), Some("b"));
966 assert_eq!(
967 tree.get_node_at_path(&[0, 0]).map(|n| n.label()),
968 Some("a1")
969 );
970 assert_eq!(
971 tree.get_node_at_path(&[0, 1]).map(|n| n.label()),
972 Some("a2")
973 );
974 assert!(tree.get_node_at_path(&[5]).is_none()); }
976
977 #[test]
978 fn tree_restore_wrong_snapshot_type_fails() {
979 use std::any::Any;
980 let mut tree = Tree::new(TreeNode::new("root"));
981 let wrong_snapshot: Box<dyn Any + Send> = Box::new(42i32);
982 assert!(!tree.restore_snapshot(&*wrong_snapshot));
983 }
984}