Skip to main content

azul_core/
id.rs

1//! Node tree data structures and hierarchy management.
2//!
3//! This module provides the core data structures for managing DOM-like tree hierarchies:
4//!
5//! - `NodeId`: Type-safe node identifiers with Option<NodeId> optimization
6//! - `NodeHierarchy`: Parent-child relationships between nodes
7//! - `NodeDataContainer`: Generic storage for node data with efficient indexing
8//!
9//! # Memory Layout
10//!
11//! `NodeId` stores a plain `usize` index internally. For FFI structs that need
12//! `Option<NodeId>`, a manual 1-based encoding is used (0 = None, n > 0 = Some(n-1)).
13//!
14//! # Performance
15//!
16//! - Node lookups are O(1) via direct array indexing
17//! - Parent/child traversal is O(1) via pre-computed indices
18//! - No heap allocations after initial tree construction
19
20use alloc::vec::Vec;
21use core::{
22    ops::{Index, IndexMut},
23    slice::Iter,
24};
25
26pub use self::node_id::NodeId;
27use crate::styled_dom::NodeHierarchyItem;
28
29/// Type alias for depth-first traversal results: (depth, `node_id`) pairs
30pub type NodeDepths = Vec<(usize, NodeId)>;
31
32// Simple FFI-safe NodeId - just a wrapper around usize
33pub mod node_id {
34
35    use alloc::vec::Vec;
36    use core::{
37        fmt,
38        ops::{Add, AddAssign},
39    };
40
41    /// A type-safe identifier for a node within a DOM tree.
42    ///
43    /// `NodeId` is FFI-safe (`#[repr(C)]`) and stores a **zero-based** index internally.
44    /// Use `NodeId::index()` to get the array index for direct node access.
45    ///
46    /// # Zero-based indexing
47    ///
48    /// - `NodeId::new(0)` → first node (index 0)
49    /// - `NodeId::new(5)` → sixth node (index 5)
50    /// - Use `node_id.index()` to get the array index
51    ///
52    /// # FFI Encoding (for `Option<NodeId>`)
53    ///
54    /// When storing `Option<NodeId>` in FFI structs (like `NodeHierarchyItem`),
55    /// we use a **1-based encoding** to represent None:
56    ///
57    /// - `0` means `None` (no node)
58    /// - `n > 0` means `Some(NodeId(n - 1))`
59    ///
60    /// Use [`NodeId::from_usize`] to decode and [`NodeId::into_raw`] to encode.
61    /// See also: [`crate::styled_dom::NodeHierarchyItemId`] for the FFI wrapper type.
62    ///
63    /// # Warning
64    ///
65    /// **Never manually construct raw usize values for node hierarchy fields!**
66    /// Always use the provided `from_usize`/`into_raw` functions to avoid
67    /// off-by-one errors that can cause index-out-of-bounds panics.
68    ///
69    #[repr(C)]
70    #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
71    pub struct NodeId {
72        // Private field to prevent direct manipulation.
73        // Use NodeId::new() to create, NodeId::index() to read.
74        inner: usize,
75    }
76
77    impl NodeId {
78        /// The zero/first node ID (index 0).
79        pub const ZERO: Self = Self { inner: 0 };
80
81        /// Creates a new `NodeId` from a zero-based index.
82        #[inline]
83        #[must_use] pub const fn new(value: usize) -> Self {
84            Self { inner: value }
85        }
86
87        /// Decodes a raw `usize` to `Option<NodeId>` using 1-based encoding.
88        ///
89        /// This is the inverse of [`NodeId::into_usize`].
90        ///
91        /// - `0` → `None` (no node)
92        /// - `n > 0` → `Some(NodeId(n - 1))`
93        ///
94        /// # Warning
95        ///
96        /// This function is for decoding values stored in FFI structs like
97        /// `NodeHierarchyItem`. Do not use raw usize values directly - always
98        /// decode them first!
99        #[inline]
100        #[must_use] pub const fn from_usize(value: usize) -> Option<Self> {
101            match value {
102                0 => None,
103                i => Some(Self { inner: i - 1 }),
104            }
105        }
106
107        /// Encodes `Option<NodeId>` to a raw `usize` for storage in FFI structs.
108        ///
109        /// - `None` → `0`
110        /// - `Some(NodeId(n))` → `n + 1`
111        ///
112        /// The returned value uses **1-based encoding**! A value of `0` means "no node",
113        /// NOT "node at index 0". Use [`NodeId::from_usize`] to decode.
114        ///
115        #[inline]
116        #[must_use] pub const fn into_raw(val: &Option<Self>) -> usize {
117            match val {
118                None => 0,
119                Some(s) => s.inner + 1,
120            }
121        }
122
123        /// Returns the **zero-based** index of this node.
124        ///
125        /// This is the actual array index where the node data is stored.
126        #[inline]
127        #[must_use] pub const fn index(&self) -> usize {
128            self.inner
129        }
130    }
131
132    impl From<usize> for NodeId {
133        fn from(val: usize) -> Self {
134            Self::new(val)
135        }
136    }
137
138    impl From<NodeId> for usize {
139        fn from(val: NodeId) -> Self {
140            val.inner
141        }
142    }
143
144    impl Add<usize> for NodeId {
145        type Output = Self;
146        /// AUDIT: saturating add. A raw `self.inner + other` could overflow
147        /// (debug panic / release wrap to a bogus small index that then aliases
148        /// a real node). `NodeId` indices are bounded by the arena length, so a
149        /// saturation to `usize::MAX` is an obviously-invalid index that fails
150        /// loudly at the next bounds-checked access rather than silently aliasing.
151        #[inline]
152        fn add(self, other: usize) -> Self {
153            Self::new(self.inner.saturating_add(other))
154        }
155    }
156
157    impl AddAssign<usize> for NodeId {
158        /// AUDIT: saturating add — see [`Add`] impl above.
159        #[inline]
160        fn add_assign(&mut self, other: usize) {
161            *self = *self + other;
162        }
163    }
164
165    impl fmt::Display for NodeId {
166        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167            write!(f, "{}", self.inner)
168        }
169    }
170
171    impl fmt::Debug for NodeId {
172        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173            write!(f, "NodeId({})", self.inner)
174        }
175    }
176}
177
178/// Hierarchical information about a node (stores the indices of the parent / child nodes).
179#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
180pub struct Node {
181    pub parent: Option<NodeId>,
182    pub previous_sibling: Option<NodeId>,
183    pub next_sibling: Option<NodeId>,
184    pub last_child: Option<NodeId>,
185    // NOTE: first_child can be calculated on the fly:
186    //
187    //   - if last_child is None, first_child is None
188    //   - if last_child is Some, first_child is parent_index + 1
189    //
190    // This makes the "Node" struct take up 4 registers instead of 5
191    //
192    // pub first_child: Option<NodeId>,
193}
194
195impl Node {
196    pub const ROOT: Self = Self {
197        parent: None,
198        previous_sibling: None,
199        next_sibling: None,
200        last_child: None,
201    };
202
203    #[inline]
204    #[must_use] pub const fn has_parent(&self) -> bool {
205        self.parent.is_some()
206    }
207    #[inline]
208    #[must_use] pub const fn has_previous_sibling(&self) -> bool {
209        self.previous_sibling.is_some()
210    }
211    #[inline]
212    #[must_use] pub const fn has_next_sibling(&self) -> bool {
213        self.next_sibling.is_some()
214    }
215    #[inline]
216    #[must_use] pub const fn has_first_child(&self) -> bool {
217        self.last_child.is_some() /* last_child and first_child are always set together */
218    }
219    #[inline]
220    #[must_use] pub const fn has_last_child(&self) -> bool {
221        self.last_child.is_some()
222    }
223
224    #[inline]
225    #[must_use] pub fn get_first_child(&self, current_node_id: NodeId) -> Option<NodeId> {
226        // last_child and first_child are always set together
227        self.last_child.map(|_| current_node_id + 1)
228    }
229}
230
231/// The hierarchy of nodes is stored separately from the actual node content in order
232/// to save on memory, since the hierarchy can be re-used across several DOM trees even
233/// if the content changes.
234#[derive(Debug, Default, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
235pub struct NodeHierarchy {
236    pub internal: Vec<Node>,
237}
238
239impl NodeHierarchy {
240    #[inline]
241    #[must_use] pub const fn new(data: Vec<Node>) -> Self {
242        Self { internal: data }
243    }
244
245    #[inline]
246    #[must_use] pub fn as_ref(&self) -> NodeHierarchyRef<'_> {
247        NodeHierarchyRef {
248            internal: &self.internal[..],
249        }
250    }
251
252}
253
254/// The hierarchy of nodes is stored separately from the actual node content in order
255/// to save on memory, since the hierarchy can be re-used across several DOM trees even
256/// if the content changes.
257#[derive(Debug, PartialEq, Hash, Eq)]
258pub struct NodeHierarchyRef<'a> {
259    pub internal: &'a [Node],
260}
261
262impl<'a> NodeHierarchyRef<'a> {
263    #[inline]
264    #[must_use] pub const fn from_slice(data: &'a [Node]) -> Self {
265        NodeHierarchyRef { internal: data }
266    }
267
268    #[inline]
269    #[must_use] pub const fn len(&self) -> usize {
270        self.internal.len()
271    }
272
273    #[inline]
274    #[must_use] pub const fn is_empty(&self) -> bool {
275        self.internal.is_empty()
276    }
277
278    #[inline]
279    #[must_use] pub fn get(&self, id: NodeId) -> Option<&Node> {
280        self.internal.get(id.index())
281    }
282
283    #[inline]
284    #[must_use] pub const fn linear_iter(&self) -> LinearIterator {
285        LinearIterator {
286            arena_len: self.len(),
287            position: 0,
288        }
289    }
290
291    /// Returns the `(depth, NodeId)` of all parent nodes (i.e. nodes that have a
292    /// `first_child`), in depth sorted order, (i.e. `NodeId(0)` with a depth of 0) is
293    /// the first element.
294    ///
295    /// Runtime: O(n) max
296    // the `.drain(..)` calls intentionally empty current/next_children to REUSE
297    // their allocations across the BFS levels; `into_iter()` would move them.
298    #[allow(clippy::iter_with_drain)]
299    #[must_use] pub fn get_parents_sorted_by_depth(&self) -> NodeDepths {
300        // AUDIT: an empty hierarchy has no root node — indexing `internal[0]`
301        // (via `self[root]` below) would panic. Bail out early.
302        if self.is_empty() {
303            return Vec::new();
304        }
305
306        let root = NodeId::new(0);
307        let mut non_leaf_nodes = Vec::new();
308
309        // AUDIT: a childless root (e.g. a single-node DOM) is a LEAF, not a
310        // parent. The old code seeded `current_children` with the root and
311        // unconditionally pushed it into `non_leaf_nodes`, mislabeling it as a
312        // parent. Only descend (and only emit the root) when it actually has a
313        // first child.
314        if !self[root].has_first_child() {
315            return non_leaf_nodes;
316        }
317
318        let mut current_children = vec![(0, root)];
319        let mut next_children = Vec::new();
320        let mut depth = 1_usize;
321
322        loop {
323            for id in &current_children {
324                for child_id in id.1.children(self).filter(|id| self[*id].has_first_child()) {
325                    next_children.push((depth, child_id));
326                }
327            }
328
329            non_leaf_nodes.extend(&mut current_children.drain(..));
330
331            if next_children.is_empty() {
332                break;
333            }
334            current_children.extend(&mut next_children.drain(..));
335            depth += 1;
336        }
337
338        non_leaf_nodes
339    }
340
341}
342
343#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
344pub struct NodeDataContainer<T> {
345    pub internal: Vec<T>,
346}
347
348impl<T> From<Vec<T>> for NodeDataContainer<T> {
349    fn from(v: Vec<T>) -> Self {
350        Self { internal: v }
351    }
352}
353
354#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
355pub struct NodeDataContainerRef<'a, T> {
356    pub internal: &'a [T],
357}
358
359#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
360pub struct NodeDataContainerRefMut<'a, T> {
361    pub internal: &'a mut [T],
362}
363
364impl<T> Default for NodeDataContainer<T> {
365    fn default() -> Self {
366        Self {
367            internal: Vec::new(),
368        }
369    }
370}
371
372impl Index<NodeId> for NodeHierarchyRef<'_> {
373    type Output = Node;
374
375    #[inline]
376    fn index(&self, node_id: NodeId) -> &Node {
377        &self.internal[node_id.index()]
378    }
379}
380
381impl<T> NodeDataContainer<T> {
382    #[inline]
383    #[must_use] pub const fn new(data: Vec<T>) -> Self {
384        Self { internal: data }
385    }
386
387    #[inline]
388    #[must_use] pub const fn is_empty(&self) -> bool {
389        self.internal.is_empty()
390    }
391
392    #[inline]
393    #[must_use] pub fn as_ref(&self) -> NodeDataContainerRef<'_, T> {
394        NodeDataContainerRef {
395            internal: &self.internal[..],
396        }
397    }
398
399    #[inline]
400    pub fn as_ref_mut(&mut self) -> NodeDataContainerRefMut<'_, T> {
401        NodeDataContainerRefMut {
402            internal: &mut self.internal[..],
403        }
404    }
405
406    #[inline]
407    #[must_use] pub const fn len(&self) -> usize {
408        self.internal.len()
409    }
410}
411
412impl<'a, T: 'a> NodeDataContainerRefMut<'a, T> {
413    #[inline]
414    pub const fn from_slice(data: &'a mut [T]) -> Self {
415        NodeDataContainerRefMut { internal: data }
416    }
417}
418
419impl<'a, T: 'a> NodeDataContainerRefMut<'a, T> {
420    #[inline]
421    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
422        self.internal.get_mut(id.index())
423    }
424}
425
426impl<'a, T: Send + 'a> NodeDataContainerRef<'a, T> {
427    pub fn transform_nodeid_optional<U: Send, F>(
428        &self,
429        closure: F,
430    ) -> NodeDataContainer<U>
431    where
432        F: Send + Sync + Fn(NodeId) -> Option<U>,
433    {
434        let len = self.len();
435        NodeDataContainer {
436            internal: (0..len)
437                .filter_map(|node_id| closure(NodeId::new(node_id)))
438                .collect::<Vec<U>>(),
439        }
440    }
441}
442
443impl<'a, T> IntoIterator for &NodeDataContainerRef<'a, T> {
444    type Item = &'a T;
445    type IntoIter = Iter<'a, T>;
446    #[inline]
447    fn into_iter(self) -> Self::IntoIter {
448        self.internal.iter()
449    }
450}
451
452impl<'a, T: 'a> NodeDataContainerRef<'a, T> {
453    #[inline]
454    pub const fn from_slice(data: &'a [T]) -> Self {
455        NodeDataContainerRef { internal: data }
456    }
457
458    #[inline]
459    #[must_use] pub const fn len(&self) -> usize {
460        self.internal.len()
461    }
462
463    #[inline]
464    #[must_use] pub const fn is_empty(&self) -> bool {
465        self.internal.is_empty()
466    }
467
468    #[inline]
469    #[must_use] pub fn get(&self, id: NodeId) -> Option<&T> {
470        self.internal.get(id.index())
471    }
472
473    #[inline]
474    pub fn iter(&self) -> Iter<'_, T> {
475        self.internal.iter()
476    }
477
478    #[inline]
479    #[must_use] pub const fn linear_iter(&self) -> LinearIterator {
480        LinearIterator {
481            arena_len: self.len(),
482            position: 0,
483        }
484    }
485}
486
487impl<T> Index<NodeId> for NodeDataContainerRef<'_, T> {
488    type Output = T;
489
490    #[inline]
491    fn index(&self, node_id: NodeId) -> &T {
492        &self.internal[node_id.index()]
493    }
494}
495
496impl<T> Index<NodeId> for NodeDataContainerRefMut<'_, T> {
497    type Output = T;
498
499    #[inline]
500    fn index(&self, node_id: NodeId) -> &T {
501        &self.internal[node_id.index()]
502    }
503}
504
505impl<T> IndexMut<NodeId> for NodeDataContainerRefMut<'_, T> {
506    #[inline]
507    fn index_mut(&mut self, node_id: NodeId) -> &mut T {
508        &mut self.internal[node_id.index()]
509    }
510}
511
512impl NodeId {
513    /// Return an iterator of references to this node and the siblings before it.
514    ///
515    /// Call `.next().unwrap()` once on the iterator to skip the node itself.
516    #[inline]
517    #[must_use] pub const fn preceding_siblings<'a>(
518        self,
519        node_hierarchy: &'a NodeHierarchyRef<'a>,
520    ) -> PrecedingSiblings<'a> {
521        PrecedingSiblings {
522            node_hierarchy,
523            node: Some(self),
524        }
525    }
526
527    /// Return an iterator of references to this node's children.
528    #[inline]
529    #[must_use] pub fn children<'a>(self, node_hierarchy: &'a NodeHierarchyRef<'a>) -> Children<'a> {
530        Children {
531            node_hierarchy,
532            node: node_hierarchy[self].get_first_child(self),
533        }
534    }
535}
536
537macro_rules! impl_node_iterator {
538    ($name:ident, $next:expr) => {
539        impl Iterator for $name<'_> {
540            type Item = NodeId;
541
542            fn next(&mut self) -> Option<NodeId> {
543                match self.node.take() {
544                    Some(node) => {
545                        self.node = $next(&self.node_hierarchy[node]);
546                        Some(node)
547                    }
548                    None => None,
549                }
550            }
551        }
552    };
553}
554
555/// An linear iterator, does not respect the DOM in any way,
556/// it just iterates over the nodes like a Vec
557#[derive(Debug, Clone)]
558pub struct LinearIterator {
559    arena_len: usize,
560    position: usize,
561}
562
563impl Iterator for LinearIterator {
564    type Item = NodeId;
565
566    fn next(&mut self) -> Option<NodeId> {
567        if self.arena_len < 1 || self.position > (self.arena_len - 1) {
568            None
569        } else {
570            let new_id = Some(NodeId::new(self.position));
571            self.position += 1;
572            new_id
573        }
574    }
575}
576
577/// An iterator of references to the siblings before a given node.
578#[derive(Debug)]
579pub struct PrecedingSiblings<'a> {
580    node_hierarchy: &'a NodeHierarchyRef<'a>,
581    node: Option<NodeId>,
582}
583
584impl_node_iterator!(PrecedingSiblings, |node: &Node| node.previous_sibling);
585
586/// Special iterator for using `NodeDataContainerRef`<AzNode> instead of `NodeHierarchy`
587#[derive(Debug)]
588pub struct AzChildren<'a> {
589    node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
590    node: Option<NodeId>,
591}
592
593impl Iterator for AzChildren<'_> {
594    type Item = NodeId;
595
596    fn next(&mut self) -> Option<NodeId> {
597        match self.node.take() {
598            Some(node) => {
599                self.node = self.node_hierarchy[node].next_sibling_id();
600                Some(node)
601            }
602            None => None,
603        }
604    }
605}
606
607/// Special iterator for using `NodeDataContainerRef`<AzNode> instead of `NodeHierarchy`
608#[derive(Debug)]
609pub struct AzReverseChildren<'a> {
610    node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
611    node: Option<NodeId>,
612}
613
614impl Iterator for AzReverseChildren<'_> {
615    type Item = NodeId;
616
617    fn next(&mut self) -> Option<NodeId> {
618        match self.node.take() {
619            Some(node) => {
620                self.node = self.node_hierarchy[node].previous_sibling_id();
621                Some(node)
622            }
623            None => None,
624        }
625    }
626}
627
628impl NodeId {
629    /// Traverse up through the hierarchy until a node matching the predicate is found.
630    ///
631    /// Necessary to resolve the last positioned (= relative)
632    /// element of an absolute node.
633    pub fn get_nearest_matching_parent<'a, F>(
634        self,
635        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
636        predicate: F,
637    ) -> Option<Self>
638    where
639        F: Fn(Self) -> bool,
640    {
641        // AUDIT: guard against (a) an out-of-bounds `self` and (b) a cycle in a
642        // corrupt hierarchy (a `parent_id` that points back down into a
643        // descendant). Use checked `get` and cap the walk at the node count —
644        // a valid parent chain can never be longer than the number of nodes.
645        let node_count = node_hierarchy.internal.len();
646        let mut current_node = node_hierarchy.internal.get(self.index())?.parent_id()?;
647        for _ in 0..node_count {
648            if predicate(current_node) {
649                return Some(current_node);
650            }
651            current_node = node_hierarchy.internal.get(current_node.index())?.parent_id()?;
652        }
653        None
654    }
655
656    /// Return the children of this node (necessary for parallel iteration over children)
657    #[inline]
658    #[must_use] pub fn az_children_collect<'a>(
659        self,
660        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
661    ) -> Vec<Self> {
662        self.az_children(node_hierarchy).collect()
663    }
664
665    /// Return an iterator of references to this node's children.
666    #[inline]
667    #[must_use] pub fn az_children<'a>(
668        self,
669        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
670    ) -> AzChildren<'a> {
671        AzChildren {
672            node_hierarchy,
673            node: node_hierarchy[self].first_child_id(self),
674        }
675    }
676
677    /// Return an iterator of references to this node's children.
678    #[inline]
679    #[must_use] pub fn az_reverse_children<'a>(
680        self,
681        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
682    ) -> AzReverseChildren<'a> {
683        AzReverseChildren {
684            node_hierarchy,
685            node: node_hierarchy[self].last_child_id(),
686        }
687    }
688}
689
690/// An iterator of references to the children of a given node.
691#[derive(Debug)]
692pub struct Children<'a> {
693    node_hierarchy: &'a NodeHierarchyRef<'a>,
694    node: Option<NodeId>,
695}
696
697impl_node_iterator!(Children, |node: &Node| node.next_sibling);
698
699#[cfg(test)]
700mod audit_tests {
701    use super::*;
702    use crate::styled_dom::NodeHierarchyItem;
703
704    #[test]
705    fn parents_by_depth_empty_hierarchy() {
706        let h = NodeHierarchy::new(Vec::new());
707        assert!(h.as_ref().get_parents_sorted_by_depth().is_empty());
708    }
709
710    #[test]
711    fn parents_by_depth_single_childless_root() {
712        // A single-node DOM: the root is a LEAF, not a parent.
713        let h = NodeHierarchy::new(vec![Node::ROOT]);
714        assert!(h.as_ref().get_parents_sorted_by_depth().is_empty());
715    }
716
717    #[test]
718    fn parents_by_depth_root_with_child() {
719        let root = Node {
720            parent: None,
721            previous_sibling: None,
722            next_sibling: None,
723            last_child: Some(NodeId::new(1)),
724        };
725        let child = Node {
726            parent: Some(NodeId::new(0)),
727            ..Node::ROOT
728        };
729        let h = NodeHierarchy::new(vec![root, child]);
730        let parents = h.as_ref().get_parents_sorted_by_depth();
731        assert_eq!(parents, vec![(0, NodeId::new(0))]);
732    }
733
734    fn item(parent: Option<usize>) -> NodeHierarchyItem {
735        NodeHierarchyItem {
736            parent: parent.map_or(0, |p| p + 1),
737            previous_sibling: 0,
738            next_sibling: 0,
739            last_child: 0,
740        }
741    }
742
743    #[test]
744    fn nearest_matching_parent_cycle_terminates() {
745        // node1.parent = 2, node2.parent = 1 — cyclic, must not hang.
746        let items = vec![item(None), item(Some(2)), item(Some(1))];
747        let cont = NodeDataContainerRef { internal: &items };
748        let r = NodeId::new(1).get_nearest_matching_parent(&cont, |_| false);
749        assert_eq!(r, None);
750    }
751
752    #[test]
753    fn nearest_matching_parent_finds_match() {
754        // 0 <- 1 <- 2 ; from 2, find the root (index 0).
755        let items = vec![item(None), item(Some(0)), item(Some(1))];
756        let cont = NodeDataContainerRef { internal: &items };
757        let r = NodeId::new(2).get_nearest_matching_parent(&cont, |n| n == NodeId::new(0));
758        assert_eq!(r, Some(NodeId::new(0)));
759    }
760
761    #[test]
762    fn node_id_add_saturates() {
763        assert_eq!(NodeId::new(5) + 3, NodeId::new(8));
764        assert_eq!(NodeId::new(usize::MAX) + 1, NodeId::new(usize::MAX));
765        let mut n = NodeId::new(usize::MAX);
766        n += 10;
767        assert_eq!(n, NodeId::new(usize::MAX));
768    }
769}
770
771#[cfg(test)]
772#[allow(clippy::pedantic, clippy::nursery)]
773mod autotest_generated {
774    use core::sync::atomic::{AtomicUsize, Ordering};
775
776    use super::*;
777    use crate::styled_dom::NodeHierarchyItem;
778
779    // ---------------------------------------------------------------------
780    // helpers
781    // ---------------------------------------------------------------------
782
783    /// A well-formed 5-node tree (children are always contiguous after the
784    /// parent, as the `first_child = parent + 1` design requires):
785    ///
786    /// ```text
787    /// 0 ── 1 ── 2
788    ///  │    └── 3
789    ///  └── 4
790    /// ```
791    fn tree_5() -> Vec<Node> {
792        vec![
793            Node {
794                parent: None,
795                previous_sibling: None,
796                next_sibling: None,
797                last_child: Some(NodeId::new(4)),
798            },
799            Node {
800                parent: Some(NodeId::new(0)),
801                previous_sibling: None,
802                next_sibling: Some(NodeId::new(4)),
803                last_child: Some(NodeId::new(3)),
804            },
805            Node {
806                parent: Some(NodeId::new(1)),
807                previous_sibling: None,
808                next_sibling: Some(NodeId::new(3)),
809                last_child: None,
810            },
811            Node {
812                parent: Some(NodeId::new(1)),
813                previous_sibling: Some(NodeId::new(2)),
814                next_sibling: None,
815                last_child: None,
816            },
817            Node {
818                parent: Some(NodeId::new(0)),
819                previous_sibling: Some(NodeId::new(1)),
820                next_sibling: None,
821                last_child: None,
822            },
823        ]
824    }
825
826    fn items(nodes: &[Node]) -> Vec<NodeHierarchyItem> {
827        nodes.iter().copied().map(NodeHierarchyItem::from).collect()
828    }
829
830    // ---------------------------------------------------------------------
831    // NodeId::new / index / ZERO  (constructor + getter)
832    // ---------------------------------------------------------------------
833
834    #[test]
835    fn node_id_new_roundtrips_index_at_boundaries() {
836        for v in [0_usize, 1, 2, 42, usize::MAX - 1, usize::MAX] {
837            assert_eq!(NodeId::new(v).index(), v, "index() must echo new()");
838        }
839    }
840
841    #[test]
842    fn node_id_zero_matches_new_zero() {
843        assert_eq!(NodeId::ZERO, NodeId::new(0));
844        assert_eq!(NodeId::ZERO.index(), 0);
845    }
846
847    #[test]
848    fn node_id_usize_conversions_are_lossless() {
849        for v in [0_usize, 7, usize::MAX] {
850            let id: NodeId = v.into();
851            let back: usize = id.into();
852            assert_eq!(back, v);
853        }
854    }
855
856    #[test]
857    fn node_id_ordering_follows_index() {
858        assert!(NodeId::new(0) < NodeId::new(1));
859        assert!(NodeId::new(1) < NodeId::new(usize::MAX));
860        assert_eq!(NodeId::new(3), NodeId::new(3));
861    }
862
863    // ---------------------------------------------------------------------
864    // NodeId::from_usize / into_raw  (1-based FFI encoding round-trip)
865    // ---------------------------------------------------------------------
866
867    #[test]
868    fn from_usize_zero_is_none_and_shifts_by_one() {
869        assert_eq!(NodeId::from_usize(0), None);
870        assert_eq!(NodeId::from_usize(1), Some(NodeId::new(0)));
871        assert_eq!(NodeId::from_usize(2), Some(NodeId::new(1)));
872        // usize::MAX must NOT overflow the `i - 1` decode.
873        assert_eq!(
874            NodeId::from_usize(usize::MAX),
875            Some(NodeId::new(usize::MAX - 1))
876        );
877    }
878
879    #[test]
880    fn into_raw_encodes_none_as_zero() {
881        assert_eq!(NodeId::into_raw(&None), 0);
882        assert_eq!(NodeId::into_raw(&Some(NodeId::new(0))), 1);
883        assert_eq!(NodeId::into_raw(&Some(NodeId::new(41))), 42);
884        // Largest index that survives the +1 encode without overflowing.
885        assert_eq!(NodeId::into_raw(&Some(NodeId::new(usize::MAX - 1))), usize::MAX);
886    }
887
888    #[test]
889    fn encode_decode_roundtrip_is_identity() {
890        // decode(encode(x)) == x
891        for x in [
892            None,
893            Some(NodeId::new(0)),
894            Some(NodeId::new(1)),
895            Some(NodeId::new(9_999)),
896            Some(NodeId::new(usize::MAX - 1)),
897        ] {
898            assert_eq!(NodeId::from_usize(NodeId::into_raw(&x)), x, "decode(encode({x:?}))");
899        }
900        // encode(decode(n)) == n
901        for n in [0_usize, 1, 2, 12_345, usize::MAX] {
902            assert_eq!(NodeId::into_raw(&NodeId::from_usize(n)), n, "encode(decode({n}))");
903        }
904    }
905
906    /// BOUNDARY: `NodeId::new(usize::MAX)` is the one index that cannot be
907    /// encoded — `into_raw` computes `inner + 1`, which overflows. Unlike the
908    /// `Add`/`AddAssign` impls (which were deliberately made saturating), this
909    /// add is unchecked: debug builds panic, release builds wrap to `0`, i.e.
910    /// the `None` encoding. Either way the node is lost; assert that it never
911    /// silently produces some *other* valid-looking node id.
912    #[cfg(feature = "std")]
913    #[test]
914    fn into_raw_at_usize_max_never_yields_a_bogus_node() {
915        let encoded = std::panic::catch_unwind(|| NodeId::into_raw(&Some(NodeId::new(usize::MAX))));
916        match encoded {
917            // debug: overflow check fires — loud failure, acceptable.
918            Err(_) => {}
919            // release: wraps to 0 == the "no node" encoding.
920            Ok(raw) => {
921                assert_eq!(raw, 0, "wrapped encode must not alias a real node id");
922                assert_eq!(NodeId::from_usize(raw), None);
923            }
924        }
925    }
926
927    // ---------------------------------------------------------------------
928    // NodeId Display / Debug  (serializer)
929    // ---------------------------------------------------------------------
930
931    #[test]
932    fn node_id_display_and_debug_are_well_formed() {
933        assert_eq!(alloc::format!("{}", NodeId::new(0)), "0");
934        assert_eq!(alloc::format!("{}", NodeId::new(7)), "7");
935        assert_eq!(alloc::format!("{:?}", NodeId::new(7)), "NodeId(7)");
936        // Extreme values must render without panicking and stay non-empty.
937        let max = alloc::format!("{}", NodeId::new(usize::MAX));
938        assert_eq!(max, alloc::format!("{}", usize::MAX));
939        assert!(!max.is_empty());
940        assert_eq!(
941            alloc::format!("{:?}", NodeId::new(usize::MAX)),
942            alloc::format!("NodeId({})", usize::MAX)
943        );
944    }
945
946    // ---------------------------------------------------------------------
947    // Node predicates + get_first_child
948    // ---------------------------------------------------------------------
949
950    #[test]
951    fn node_root_and_default_have_no_relations() {
952        for n in [Node::ROOT, Node::default()] {
953            assert!(!n.has_parent());
954            assert!(!n.has_previous_sibling());
955            assert!(!n.has_next_sibling());
956            assert!(!n.has_first_child());
957            assert!(!n.has_last_child());
958            assert_eq!(n.get_first_child(NodeId::new(0)), None);
959        }
960        assert_eq!(Node::default(), Node::ROOT);
961    }
962
963    #[test]
964    fn node_predicates_report_each_populated_field() {
965        let full = Node {
966            parent: Some(NodeId::new(1)),
967            previous_sibling: Some(NodeId::new(2)),
968            next_sibling: Some(NodeId::new(3)),
969            last_child: Some(NodeId::new(4)),
970        };
971        assert!(full.has_parent());
972        assert!(full.has_previous_sibling());
973        assert!(full.has_next_sibling());
974        assert!(full.has_first_child());
975        assert!(full.has_last_child());
976    }
977
978    /// INVARIANT: `has_first_child()` and `has_last_child()` read the same
979    /// field, so they can never disagree — child-presence is all-or-nothing.
980    #[test]
981    fn has_first_child_always_agrees_with_has_last_child() {
982        for last_child in [None, Some(NodeId::new(0)), Some(NodeId::new(usize::MAX))] {
983            let n = Node {
984                last_child,
985                ..Node::ROOT
986            };
987            assert_eq!(n.has_first_child(), n.has_last_child());
988            assert_eq!(n.has_first_child(), last_child.is_some());
989        }
990    }
991
992    #[test]
993    fn get_first_child_is_self_plus_one_and_saturates() {
994        let parent = Node {
995            last_child: Some(NodeId::new(9)),
996            ..Node::ROOT
997        };
998        assert_eq!(parent.get_first_child(NodeId::new(0)), Some(NodeId::new(1)));
999        assert_eq!(parent.get_first_child(NodeId::new(5)), Some(NodeId::new(6)));
1000        // Extreme id: the `+ 1` is saturating, so this must not panic/wrap. It
1001        // yields an obviously-invalid id that fails loudly at the next lookup.
1002        assert_eq!(
1003            parent.get_first_child(NodeId::new(usize::MAX)),
1004            Some(NodeId::new(usize::MAX))
1005        );
1006        // A leaf has no first child regardless of how extreme the id is.
1007        assert_eq!(Node::ROOT.get_first_child(NodeId::new(usize::MAX)), None);
1008    }
1009
1010    // ---------------------------------------------------------------------
1011    // NodeHierarchy / NodeHierarchyRef
1012    // ---------------------------------------------------------------------
1013
1014    #[test]
1015    fn hierarchy_new_preserves_len_and_contents() {
1016        let h = NodeHierarchy::new(tree_5());
1017        assert_eq!(h.as_ref().len(), 5);
1018        assert!(!h.as_ref().is_empty());
1019        assert_eq!(h.as_ref().get(NodeId::new(0)), Some(&tree_5()[0]));
1020        assert_eq!(h.as_ref().internal, &tree_5()[..]);
1021    }
1022
1023    #[test]
1024    fn empty_hierarchy_is_empty_everywhere() {
1025        let h = NodeHierarchy::new(Vec::new());
1026        let r = h.as_ref();
1027        assert_eq!(r.len(), 0);
1028        assert!(r.is_empty());
1029        assert_eq!(r.get(NodeId::new(0)), None);
1030        assert_eq!(r.linear_iter().count(), 0);
1031        assert!(r.get_parents_sorted_by_depth().is_empty());
1032
1033        let default = NodeHierarchy::default();
1034        assert!(default.as_ref().is_empty());
1035    }
1036
1037    #[test]
1038    fn hierarchy_ref_from_slice_matches_len_and_emptiness() {
1039        let empty: [Node; 0] = [];
1040        assert_eq!(NodeHierarchyRef::from_slice(&empty).len(), 0);
1041        assert!(NodeHierarchyRef::from_slice(&empty).is_empty());
1042
1043        let nodes = tree_5();
1044        let r = NodeHierarchyRef::from_slice(&nodes);
1045        assert_eq!(r.len(), 5);
1046        assert!(!r.is_empty());
1047    }
1048
1049    /// `get()` is the bounds-checked accessor: an out-of-range id must return
1050    /// `None`, never panic and never read out of bounds.
1051    #[test]
1052    fn hierarchy_ref_get_out_of_bounds_returns_none() {
1053        let nodes = tree_5();
1054        let r = NodeHierarchyRef::from_slice(&nodes);
1055        assert!(r.get(NodeId::new(4)).is_some());
1056        assert_eq!(r.get(NodeId::new(5)), None);
1057        assert_eq!(r.get(NodeId::new(usize::MAX)), None);
1058    }
1059
1060    /// `Index` (unlike `get`) is unchecked-by-contract: it must fail loudly
1061    /// rather than silently hand back an unrelated node.
1062    #[test]
1063    #[should_panic]
1064    fn hierarchy_ref_index_out_of_bounds_panics() {
1065        let nodes = tree_5();
1066        let r = NodeHierarchyRef::from_slice(&nodes);
1067        let _ = &r[NodeId::new(5)];
1068    }
1069
1070    #[test]
1071    fn hierarchy_linear_iter_walks_every_index_in_order() {
1072        let nodes = tree_5();
1073        let r = NodeHierarchyRef::from_slice(&nodes);
1074        let ids: Vec<NodeId> = r.linear_iter().collect();
1075        assert_eq!(
1076            ids,
1077            (0..5).map(NodeId::new).collect::<Vec<_>>(),
1078            "linear_iter must yield 0..len exactly once, in order"
1079        );
1080    }
1081
1082    /// The `arena_len < 1` guard exists because `arena_len - 1` would underflow
1083    /// on an empty arena; check the 0- and 1-element boundaries explicitly.
1084    #[test]
1085    fn linear_iter_len_zero_and_one_boundaries() {
1086        let empty: [Node; 0] = [];
1087        assert_eq!(
1088            NodeHierarchyRef::from_slice(&empty).linear_iter().next(),
1089            None
1090        );
1091
1092        let one = [Node::ROOT];
1093        let mut it = NodeHierarchyRef::from_slice(&one).linear_iter();
1094        assert_eq!(it.next(), Some(NodeId::new(0)));
1095        assert_eq!(it.next(), None);
1096        // Exhausted iterators stay exhausted.
1097        assert_eq!(it.next(), None);
1098    }
1099
1100    #[test]
1101    fn get_parents_sorted_by_depth_is_depth_ordered_and_leaf_free() {
1102        let h = NodeHierarchy::new(tree_5());
1103        let parents = h.as_ref().get_parents_sorted_by_depth();
1104        // Only 0 and 1 have children; 2/3/4 are leaves and must not appear.
1105        assert_eq!(parents, vec![(0, NodeId::new(0)), (1, NodeId::new(1))]);
1106        // Depths must be non-decreasing.
1107        assert!(parents.windows(2).all(|w| w[0].0 <= w[1].0));
1108    }
1109
1110    // ---------------------------------------------------------------------
1111    // NodeId::children / preceding_siblings  (NodeHierarchyRef iterators)
1112    // ---------------------------------------------------------------------
1113
1114    #[test]
1115    fn children_yields_direct_children_only() {
1116        let nodes = tree_5();
1117        let r = NodeHierarchyRef::from_slice(&nodes);
1118        assert_eq!(
1119            NodeId::new(0).children(&r).collect::<Vec<_>>(),
1120            vec![NodeId::new(1), NodeId::new(4)]
1121        );
1122        assert_eq!(
1123            NodeId::new(1).children(&r).collect::<Vec<_>>(),
1124            vec![NodeId::new(2), NodeId::new(3)]
1125        );
1126        // Leaves have no children.
1127        assert_eq!(NodeId::new(2).children(&r).count(), 0);
1128        assert_eq!(NodeId::new(4).children(&r).count(), 0);
1129    }
1130
1131    #[test]
1132    #[should_panic]
1133    fn children_of_out_of_bounds_node_panics_loudly() {
1134        let nodes = tree_5();
1135        let r = NodeHierarchyRef::from_slice(&nodes);
1136        // `children()` indexes the hierarchy directly — an id past the end must
1137        // abort rather than fabricate a child list.
1138        let _ = NodeId::new(99).children(&r);
1139    }
1140
1141    #[test]
1142    fn preceding_siblings_starts_with_self_then_walks_backwards() {
1143        let nodes = tree_5();
1144        let r = NodeHierarchyRef::from_slice(&nodes);
1145        assert_eq!(
1146            NodeId::new(3).preceding_siblings(&r).collect::<Vec<_>>(),
1147            vec![NodeId::new(3), NodeId::new(2)],
1148            "the iterator includes the node itself first"
1149        );
1150        // A first-born has only itself.
1151        assert_eq!(
1152            NodeId::new(2).preceding_siblings(&r).collect::<Vec<_>>(),
1153            vec![NodeId::new(2)]
1154        );
1155    }
1156
1157    /// ADVERSARIAL: a corrupt hierarchy whose `previous_sibling` points at the
1158    /// node itself makes the iterator cycle forever. It must not panic — but a
1159    /// caller that `collect()`s it would hang, so only ever take a bounded
1160    /// prefix from an untrusted hierarchy.
1161    #[test]
1162    fn preceding_siblings_on_self_cycle_repeats_without_panicking() {
1163        let nodes = vec![
1164            Node::ROOT,
1165            Node {
1166                parent: Some(NodeId::new(0)),
1167                previous_sibling: Some(NodeId::new(1)), // points at itself
1168                next_sibling: None,
1169                last_child: None,
1170            },
1171        ];
1172        let r = NodeHierarchyRef::from_slice(&nodes);
1173        let first_4: Vec<NodeId> = NodeId::new(1).preceding_siblings(&r).take(4).collect();
1174        assert_eq!(first_4, vec![NodeId::new(1); 4]);
1175    }
1176
1177    // ---------------------------------------------------------------------
1178    // NodeDataContainer / Ref / RefMut
1179    // ---------------------------------------------------------------------
1180
1181    #[test]
1182    fn data_container_new_and_len_track_the_vec() {
1183        let c = NodeDataContainer::new(vec![10_u32, 20, 30]);
1184        assert_eq!(c.len(), 3);
1185        assert!(!c.is_empty());
1186        assert_eq!(c.as_ref().len(), 3);
1187        assert_eq!(c.as_ref().get(NodeId::new(2)), Some(&30));
1188
1189        let empty: NodeDataContainer<u32> = NodeDataContainer::new(Vec::new());
1190        assert_eq!(empty.len(), 0);
1191        assert!(empty.is_empty());
1192        assert!(empty.as_ref().is_empty());
1193        assert_eq!(empty.as_ref().get(NodeId::new(0)), None);
1194
1195        // Default and From<Vec<T>> agree with the explicit constructor.
1196        assert!(NodeDataContainer::<u32>::default().is_empty());
1197        assert_eq!(NodeDataContainer::from(vec![1_u32, 2]).len(), 2);
1198    }
1199
1200    #[test]
1201    fn data_container_ref_get_out_of_bounds_returns_none() {
1202        let data = [1_u8, 2, 3];
1203        let r = NodeDataContainerRef::from_slice(&data);
1204        assert_eq!(r.get(NodeId::new(0)), Some(&1));
1205        assert_eq!(r.get(NodeId::new(3)), None);
1206        assert_eq!(r.get(NodeId::new(usize::MAX)), None);
1207    }
1208
1209    #[test]
1210    #[should_panic]
1211    fn data_container_ref_index_out_of_bounds_panics() {
1212        let data = [1_u8, 2, 3];
1213        let r = NodeDataContainerRef::from_slice(&data);
1214        let _ = &r[NodeId::new(3)];
1215    }
1216
1217    #[test]
1218    fn data_container_ref_iterators_cover_all_elements() {
1219        let data = [1_u8, 2, 3];
1220        let r = NodeDataContainerRef::from_slice(&data);
1221        assert_eq!(r.iter().copied().collect::<Vec<_>>(), vec![1, 2, 3]);
1222        assert_eq!((&r).into_iter().copied().collect::<Vec<_>>(), vec![1, 2, 3]);
1223        assert_eq!(r.linear_iter().count(), 3);
1224
1225        let empty: [u8; 0] = [];
1226        let e = NodeDataContainerRef::from_slice(&empty);
1227        assert_eq!(e.len(), 0);
1228        assert!(e.is_empty());
1229        assert_eq!(e.iter().count(), 0);
1230        assert_eq!(e.linear_iter().next(), None);
1231    }
1232
1233    #[test]
1234    fn data_container_ref_mut_get_mut_is_bounds_checked() {
1235        let mut c = NodeDataContainer::new(vec![1_u32, 2, 3]);
1236        let mut m = c.as_ref_mut();
1237        assert_eq!(m.get_mut(NodeId::new(3)), None);
1238        assert_eq!(m.get_mut(NodeId::new(usize::MAX)), None);
1239        *m.get_mut(NodeId::new(1)).unwrap() = 99;
1240        m[NodeId::new(2)] = 7;
1241        assert_eq!(m[NodeId::new(2)], 7);
1242        assert_eq!(c.internal, vec![1, 99, 7]);
1243    }
1244
1245    #[test]
1246    fn data_container_ref_mut_from_slice_on_empty_slice() {
1247        let mut empty: [u32; 0] = [];
1248        let mut m = NodeDataContainerRefMut::from_slice(&mut empty);
1249        assert_eq!(m.get_mut(NodeId::new(0)), None);
1250        assert!(m.internal.is_empty());
1251    }
1252
1253    #[test]
1254    #[should_panic]
1255    fn data_container_ref_mut_index_out_of_bounds_panics() {
1256        let mut data = [1_u8, 2];
1257        let m = NodeDataContainerRefMut::from_slice(&mut data);
1258        let _ = &m[NodeId::new(2)];
1259    }
1260
1261    // ---------------------------------------------------------------------
1262    // transform_nodeid_optional
1263    // ---------------------------------------------------------------------
1264
1265    #[test]
1266    fn transform_nodeid_optional_maps_every_index() {
1267        let data = [0_u32; 4];
1268        let r = NodeDataContainerRef::from_slice(&data);
1269        let out = r.transform_nodeid_optional(|id| Some(id.index() as u32 * 10));
1270        assert_eq!(out.internal, vec![0, 10, 20, 30]);
1271        assert_eq!(out.len(), r.len());
1272    }
1273
1274    #[test]
1275    fn transform_nodeid_optional_empty_input_never_calls_the_closure() {
1276        let empty: [u32; 0] = [];
1277        let r = NodeDataContainerRef::from_slice(&empty);
1278        let calls = AtomicUsize::new(0);
1279        let out = r.transform_nodeid_optional(|_| {
1280            calls.fetch_add(1, Ordering::SeqCst);
1281            Some(1_u32)
1282        });
1283        assert_eq!(calls.load(Ordering::SeqCst), 0);
1284        assert!(out.is_empty());
1285    }
1286
1287    /// CONTRACT TRAP: `None` results are *filtered out*, so the output is
1288    /// COMPACTED — it is shorter than the input and its positions no longer
1289    /// line up with the `NodeId`s that produced them. Indexing the result by a
1290    /// `NodeId` therefore reads the wrong element (or goes out of bounds).
1291    /// Pinned here so the aliasing behaviour can't change silently.
1292    #[test]
1293    fn transform_nodeid_optional_compacts_and_breaks_nodeid_alignment() {
1294        let data = [0_u32; 5];
1295        let r = NodeDataContainerRef::from_slice(&data);
1296        // Keep only even node ids: 0, 2, 4.
1297        let out = r.transform_nodeid_optional(|id| {
1298            if id.index() % 2 == 0 {
1299                Some(id.index() as u32)
1300            } else {
1301                None
1302            }
1303        });
1304        assert_eq!(out.len(), 3, "output is compacted, NOT padded to the input len");
1305        assert_eq!(out.internal, vec![0, 2, 4]);
1306        // Position 1 holds node 2's value — the result is not index-aligned.
1307        assert_eq!(out.as_ref().get(NodeId::new(1)), Some(&2));
1308        assert_eq!(out.as_ref().get(NodeId::new(4)), None);
1309
1310        // All-None closure yields an empty container rather than panicking.
1311        let none_out = r.transform_nodeid_optional(|_| -> Option<u32> { None });
1312        assert!(none_out.is_empty());
1313    }
1314
1315    // ---------------------------------------------------------------------
1316    // NodeId::az_children / az_reverse_children / az_children_collect
1317    // ---------------------------------------------------------------------
1318
1319    #[test]
1320    fn az_children_walks_forwards_and_reverse_walks_backwards() {
1321        let nodes = tree_5();
1322        let it = items(&nodes);
1323        let h = NodeDataContainerRef::from_slice(&it);
1324
1325        assert_eq!(
1326            NodeId::new(0).az_children_collect(&h),
1327            vec![NodeId::new(1), NodeId::new(4)]
1328        );
1329        assert_eq!(
1330            NodeId::new(1).az_children(&h).collect::<Vec<_>>(),
1331            vec![NodeId::new(2), NodeId::new(3)]
1332        );
1333        assert_eq!(
1334            NodeId::new(1).az_reverse_children(&h).collect::<Vec<_>>(),
1335            vec![NodeId::new(3), NodeId::new(2)],
1336            "reverse iteration starts at last_child and walks previous_sibling"
1337        );
1338        // Leaves yield nothing in either direction.
1339        assert_eq!(NodeId::new(2).az_children(&h).count(), 0);
1340        assert_eq!(NodeId::new(2).az_reverse_children(&h).count(), 0);
1341        assert!(NodeId::new(3).az_children_collect(&h).is_empty());
1342    }
1343
1344    #[test]
1345    #[should_panic]
1346    fn az_children_of_out_of_bounds_node_panics_loudly() {
1347        let nodes = tree_5();
1348        let it = items(&nodes);
1349        let h = NodeDataContainerRef::from_slice(&it);
1350        let _ = NodeId::new(5).az_children(&h);
1351    }
1352
1353    /// ADVERSARIAL: a `next_sibling` that points back at the node itself makes
1354    /// `az_children` an infinite iterator. It must not panic, but
1355    /// `az_children_collect` on such a hierarchy would allocate until OOM —
1356    /// so only a bounded prefix is taken here.
1357    #[test]
1358    fn az_children_on_sibling_cycle_repeats_without_panicking() {
1359        let nodes = vec![
1360            Node {
1361                last_child: Some(NodeId::new(1)),
1362                ..Node::ROOT
1363            },
1364            Node {
1365                parent: Some(NodeId::new(0)),
1366                previous_sibling: None,
1367                next_sibling: Some(NodeId::new(1)), // points at itself
1368                last_child: None,
1369            },
1370        ];
1371        let it = items(&nodes);
1372        let h = NodeDataContainerRef::from_slice(&it);
1373        let prefix: Vec<NodeId> = NodeId::new(0).az_children(&h).take(5).collect();
1374        assert_eq!(prefix, vec![NodeId::new(1); 5]);
1375    }
1376
1377    // ---------------------------------------------------------------------
1378    // NodeId::get_nearest_matching_parent
1379    // ---------------------------------------------------------------------
1380
1381    #[test]
1382    fn nearest_matching_parent_skips_non_matching_ancestors() {
1383        let nodes = tree_5();
1384        let it = items(&nodes);
1385        let h = NodeDataContainerRef::from_slice(&it);
1386        // From node 2, the first ancestor is 1, then the root 0.
1387        assert_eq!(
1388            NodeId::new(2).get_nearest_matching_parent(&h, |_| true),
1389            Some(NodeId::new(1))
1390        );
1391        assert_eq!(
1392            NodeId::new(2).get_nearest_matching_parent(&h, |n| n == NodeId::new(0)),
1393            Some(NodeId::new(0))
1394        );
1395        // The root has no parent at all.
1396        assert_eq!(
1397            NodeId::new(0).get_nearest_matching_parent(&h, |_| true),
1398            None
1399        );
1400        // Nothing matches -> None, and the walk terminates.
1401        assert_eq!(
1402            NodeId::new(3).get_nearest_matching_parent(&h, |_| false),
1403            None
1404        );
1405    }
1406
1407    #[test]
1408    fn nearest_matching_parent_out_of_bounds_self_returns_none() {
1409        let nodes = tree_5();
1410        let it = items(&nodes);
1411        let h = NodeDataContainerRef::from_slice(&it);
1412        assert_eq!(
1413            NodeId::new(5).get_nearest_matching_parent(&h, |_| true),
1414            None
1415        );
1416        assert_eq!(
1417            NodeId::new(usize::MAX).get_nearest_matching_parent(&h, |_| true),
1418            None
1419        );
1420    }
1421
1422    #[test]
1423    fn nearest_matching_parent_self_parent_cycle_terminates() {
1424        // node 1 is its own parent — the node-count cap must break the loop.
1425        let nodes = vec![
1426            Node::ROOT,
1427            Node {
1428                parent: Some(NodeId::new(1)),
1429                ..Node::ROOT
1430            },
1431        ];
1432        let it = items(&nodes);
1433        let h = NodeDataContainerRef::from_slice(&it);
1434        assert_eq!(
1435            NodeId::new(1).get_nearest_matching_parent(&h, |_| false),
1436            None
1437        );
1438    }
1439
1440    /// ADVERSARIAL: a corrupt `parent` index that points past the end of the
1441    /// arena. The lookup must not panic. Note the predicate is still called
1442    /// with that out-of-bounds id, so a permissive predicate hands the caller
1443    /// back an id that will panic when used to index the arena — callers must
1444    /// bounds-check the returned id, not assume it is valid.
1445    #[test]
1446    fn nearest_matching_parent_out_of_bounds_ancestor_does_not_panic() {
1447        let nodes = vec![
1448            Node::ROOT,
1449            Node {
1450                parent: Some(NodeId::new(99)), // dangling
1451                ..Node::ROOT
1452            },
1453        ];
1454        let it = items(&nodes);
1455        let h = NodeDataContainerRef::from_slice(&it);
1456
1457        // Rejecting predicate: the dangling id fails the `get()` and yields None.
1458        assert_eq!(
1459            NodeId::new(1).get_nearest_matching_parent(&h, |_| false),
1460            None
1461        );
1462        // Accepting predicate: the dangling id is returned as-is.
1463        assert_eq!(
1464            NodeId::new(1).get_nearest_matching_parent(&h, |_| true),
1465            Some(NodeId::new(99))
1466        );
1467        assert!(h.get(NodeId::new(99)).is_none(), "and it is NOT a valid index");
1468    }
1469}