1use crate::{Attribute, DynamicNode, VNode};
2use dioxus_core_template::{
3 StaticTemplateElement, StaticTemplateNode, StaticTemplateNodeIter, StaticTemplateText,
4 TemplateAnchor, TemplatePath, TemplateSlotTarget,
5};
6
7#[derive(Clone, Copy)]
9pub enum VNodeChild<'a> {
10 Element(StaticElement<'a>),
12 Text(StaticText<'a>),
14 Dynamic(DynamicAnchor<'a>),
16}
17
18#[derive(Clone, Copy)]
20pub struct StaticElement<'a> {
21 vnode: &'a VNode,
22 op: usize,
23 path: TemplatePath,
24 anchor_idx: Option<usize>,
25}
26
27impl<'a> StaticElement<'a> {
28 fn new(vnode: &'a VNode, op: usize, path: TemplatePath, anchor_idx: Option<usize>) -> Self {
29 Self {
30 vnode,
31 op,
32 path,
33 anchor_idx,
34 }
35 }
36
37 pub fn op(self) -> usize {
39 self.op
40 }
41
42 fn template_element(self) -> StaticTemplateElement<'a> {
43 self.vnode
44 .template()
45 .static_element(self.op)
46 .expect("static element")
47 }
48
49 pub fn tag(self) -> &'static str {
51 self.template_element().tag()
52 }
53
54 pub fn namespace(self) -> Option<&'static str> {
56 self.template_element().namespace()
57 }
58
59 pub fn anchor_index(self) -> Option<usize> {
61 self.anchor_idx
62 }
63
64 pub fn static_attributes(
66 self,
67 ) -> impl Iterator<Item = (&'static str, &'static str, Option<&'static str>)> + 'a {
68 self.template_element()
69 .attributes()
70 .map(|attr| (attr.name, attr.value, attr.namespace))
71 }
72
73 pub fn children(self) -> impl ExactSizeIterator<Item = VNodeChild<'a>> + 'a {
75 VNodeChildren::element(self)
76 }
77
78 pub fn has_children(self) -> bool {
80 self.children().next().is_some()
81 }
82
83 pub fn dynamic_anchors(self) -> impl Iterator<Item = DynamicAnchor<'a>> + 'a {
85 self.vnode
86 .dynamic_anchors()
87 .filter(move |anchor| anchor.parent_element_op_index() == Some(self.op))
88 }
89}
90
91#[derive(Clone, Copy)]
93pub struct StaticText<'a> {
94 vnode: &'a VNode,
95 op: usize,
96 anchor_idx: Option<usize>,
97}
98
99impl<'a> StaticText<'a> {
100 fn new(vnode: &'a VNode, op: usize, anchor_idx: Option<usize>) -> Self {
101 Self {
102 vnode,
103 op,
104 anchor_idx,
105 }
106 }
107
108 pub fn op(self) -> usize {
110 self.op
111 }
112
113 fn template_text(self) -> StaticTemplateText<'a> {
114 self.vnode
115 .template()
116 .static_text(self.op)
117 .expect("static text")
118 }
119
120 pub fn text(self) -> &'static str {
122 self.template_text().text()
123 }
124
125 pub fn anchor_index(self) -> Option<usize> {
127 self.anchor_idx
128 }
129}
130
131pub(crate) struct VNodeChildren<'a> {
133 inner: VNodeChildrenInner<'a>,
134}
135
136#[derive(Clone, Copy)]
137pub(super) struct StaticAnchorTarget {
138 pub(super) anchor_index: usize,
139 pub(super) path: TemplatePath,
140}
141
142impl<'a> VNodeChildren<'a> {
143 fn roots(vnode: &'a VNode) -> Self {
144 Self {
145 inner: VNodeChildrenInner::Roots(RootChildCursor {
146 vnode,
147 anchor_index: 0,
148 pending_static: None,
149 }),
150 }
151 }
152
153 fn element(element: StaticElement<'a>) -> Self {
154 Self {
155 inner: VNodeChildrenInner::Element(ElementChildCursor::new(element)),
156 }
157 }
158
159 fn remaining_len(&self) -> usize {
160 self.inner.remaining_len()
161 }
162}
163
164impl<'a> Iterator for VNodeChildren<'a> {
165 type Item = VNodeChild<'a>;
166
167 fn next(&mut self) -> Option<Self::Item> {
168 self.inner.next()
169 }
170
171 fn size_hint(&self) -> (usize, Option<usize>) {
172 let len = self.remaining_len();
173 (len, Some(len))
174 }
175}
176
177impl ExactSizeIterator for VNodeChildren<'_> {}
178
179#[derive(Clone, Copy)]
180#[allow(clippy::large_enum_variant)]
181enum VNodeChildrenInner<'a> {
182 Roots(RootChildCursor<'a>),
183 Element(ElementChildCursor<'a>),
184}
185
186impl<'a> VNodeChildrenInner<'a> {
187 fn next(&mut self) -> Option<VNodeChild<'a>> {
188 match self {
189 Self::Roots(cursor) => cursor.next(),
190 Self::Element(cursor) => cursor.next(),
191 }
192 }
193
194 fn remaining_len(self) -> usize {
195 let mut remaining = 0;
196 let mut cursor = self;
197 while cursor.next().is_some() {
198 remaining += 1;
199 }
200 remaining
201 }
202}
203
204#[derive(Clone, Copy)]
205struct RootChildCursor<'a> {
206 vnode: &'a VNode,
207 anchor_index: usize,
208 pending_static: Option<(usize, StaticTemplateNode<'a>)>,
209}
210
211impl<'a> RootChildCursor<'a> {
212 fn next(&mut self) -> Option<VNodeChild<'a>> {
213 if let Some((anchor_index, node)) = self.pending_static.take() {
214 let path = self.vnode.template().anchors()[anchor_index].static_path();
215 return Some(static_child(self.vnode, node, path, Some(anchor_index)));
216 }
217
218 while self.anchor_index < self.vnode.template().anchors().len() {
219 let anchor_index = self.anchor_index;
220 self.anchor_index += 1;
221 let anchor = DynamicAnchor::new(self.vnode, anchor_index);
222 if !anchor.is_root_level() {
223 continue;
224 }
225
226 let static_root = (!anchor.is_last_static_node() && anchor.static_path().is_root())
227 .then(|| {
228 self.vnode
229 .template()
230 .static_node_at_path(anchor.static_path())
231 .expect("static root anchor")
232 });
233
234 if anchor.nodes().len() > 0 {
235 self.pending_static = static_root.map(|node| (anchor_index, node));
236 return Some(VNodeChild::Dynamic(anchor));
237 }
238
239 let node = static_root.expect("zero-node root anchor must own a static node");
240 let path = self.vnode.template().anchors()[anchor_index].static_path();
241 return Some(static_child(self.vnode, node, path, Some(anchor_index)));
242 }
243
244 None
245 }
246}
247
248#[derive(Clone, Copy)]
249struct ElementChildCursor<'a> {
250 static_children: StaticChildCursor<'a>,
251 dynamic_children: DynamicChildCursor<'a>,
252 next_static: Option<PositionedChild<'a>>,
253 next_dynamic: Option<PositionedChild<'a>>,
254}
255
256impl<'a> ElementChildCursor<'a> {
257 fn new(element: StaticElement<'a>) -> Self {
258 let mut static_children = StaticChildCursor::new(element);
259 let mut dynamic_children = DynamicChildCursor::new(element);
260 let next_static = static_children.next();
261 let next_dynamic = dynamic_children.next();
262 Self {
263 static_children,
264 dynamic_children,
265 next_static,
266 next_dynamic,
267 }
268 }
269
270 fn next(&mut self) -> Option<VNodeChild<'a>> {
271 let take_static = match (self.next_static, self.next_dynamic) {
272 (Some(static_child), Some(dynamic_child)) => static_child.key() <= dynamic_child.key(),
273 (Some(_), None) => true,
274 (None, Some(_)) => false,
275 (None, None) => return None,
276 };
277
278 if take_static {
279 let child = self.next_static.take().expect("static child checked");
280 self.next_static = self.static_children.next();
281 Some(child.child)
282 } else {
283 let child = self.next_dynamic.take().expect("dynamic child checked");
284 self.next_dynamic = self.dynamic_children.next();
285 Some(child.child)
286 }
287 }
288}
289
290#[derive(Clone, Copy)]
291struct PositionedChild<'a> {
292 position: usize,
293 order: usize,
294 child: VNodeChild<'a>,
295}
296
297impl<'a> PositionedChild<'a> {
298 fn key(self) -> (usize, usize) {
299 (self.position, self.order)
300 }
301}
302
303#[derive(Clone, Copy)]
304struct StaticChildCursor<'a> {
305 vnode: &'a VNode,
306 nodes: StaticTemplateNodeIter<'a>,
307 path: TemplatePath,
308 slot: usize,
309}
310
311impl<'a> StaticChildCursor<'a> {
312 fn new(element: StaticElement<'a>) -> Self {
313 let vnode = element.vnode;
314 Self {
315 vnode,
316 nodes: element.template_element().children(),
317 path: element.path.next_child(),
318 slot: 0,
319 }
320 }
321
322 fn next(&mut self) -> Option<PositionedChild<'a>> {
323 let node = self.nodes.next()?;
324 let path = self.path;
325 let current_slot = self.slot;
326 let anchor_idx = self.vnode.static_anchor_index_for_path(path);
327 self.path = self.path.next_sibling();
328 self.slot += 1;
329 Some(PositionedChild {
330 position: current_slot * 2 + 1,
331 order: current_slot,
332 child: static_child(self.vnode, node, path, anchor_idx),
333 })
334 }
335}
336
337#[derive(Clone, Copy)]
338struct DynamicChildCursor<'a> {
339 element: StaticElement<'a>,
340 anchor_index: usize,
341 order: usize,
342}
343
344impl<'a> DynamicChildCursor<'a> {
345 fn new(element: StaticElement<'a>) -> Self {
346 Self {
347 element,
348 anchor_index: 0,
349 order: 0,
350 }
351 }
352
353 fn next(&mut self) -> Option<PositionedChild<'a>> {
354 let element_op = self.element.op;
355 next_dynamic_child(
356 self.element.vnode,
357 &mut self.anchor_index,
358 &mut self.order,
359 |anchor| {
360 (anchor.parent_element_op_index() == Some(element_op))
361 .then(|| child_position(anchor.slot_target()))
362 },
363 )
364 }
365}
366
367fn next_dynamic_child<'a>(
368 vnode: &'a VNode,
369 anchor_index: &mut usize,
370 order: &mut usize,
371 mut position: impl FnMut(DynamicAnchor<'a>) -> Option<usize>,
372) -> Option<PositionedChild<'a>> {
373 while *anchor_index < vnode.template().anchors().len() {
374 let current_anchor_index = *anchor_index;
375 *anchor_index += 1;
376 let anchor = DynamicAnchor::new(vnode, current_anchor_index);
377 if anchor.nodes().len() == 0 {
378 continue;
379 }
380
381 let Some(position) = position(anchor) else {
382 continue;
383 };
384
385 let current_order = *order;
386 *order += 1;
387 return Some(PositionedChild {
388 position,
389 order: current_order,
390 child: VNodeChild::Dynamic(anchor),
391 });
392 }
393
394 None
395}
396
397impl VNode {
398 pub fn children(&self) -> impl ExactSizeIterator<Item = VNodeChild<'_>> + '_ {
400 VNodeChildren::roots(self)
401 }
402
403 pub fn root_child_count(&self) -> usize {
405 self.children().len()
406 }
407
408 pub fn dynamic_anchors(&self) -> impl DoubleEndedIterator<Item = DynamicAnchor<'_>> + '_ {
410 (0..self.template().anchors().len())
411 .map(|anchor_index| DynamicAnchor::new(self, anchor_index))
412 }
413
414 pub(super) fn dynamic_node_slots(
415 &self,
416 ) -> impl DoubleEndedIterator<Item = DynamicNodeSlot<'_>> + '_ {
417 self.dynamic_anchors().flat_map(|anchor| anchor.nodes())
418 }
419
420 pub(crate) fn dynamic_anchor(&self, anchor_index: usize) -> DynamicAnchor<'_> {
421 DynamicAnchor::new(self, anchor_index)
422 }
423
424 pub(crate) fn dynamic_node_slot(
425 &self,
426 anchor_index: usize,
427 slot_index: usize,
428 ) -> Option<DynamicNodeSlot<'_>> {
429 let anchor = DynamicAnchor::new(self, anchor_index);
430 anchor
431 .template_anchor()
432 .nodes()
433 .contains(&slot_index)
434 .then_some(DynamicNodeSlot {
435 anchor,
436 index: slot_index,
437 })
438 }
439
440 fn static_anchor_index_for_path(&self, path: TemplatePath) -> Option<usize> {
441 self.template()
442 .anchors()
443 .iter()
444 .position(|anchor| anchor.static_path() == path)
445 }
446
447 pub(super) fn dynamic_node_slots_after<'a>(
448 &'a self,
449 slot: DynamicNodeSlot<'a>,
450 ) -> impl Iterator<Item = DynamicNodeSlot<'a>> + 'a {
451 let anchor = slot.anchor();
452 let anchor_index = anchor.anchor_index();
453 let current_anchor_slots = ((slot.index() + 1)..anchor.template_anchor().nodes().end)
454 .map(move |index| DynamicNodeSlot { anchor, index });
455 let later_anchor_slots = ((anchor_index + 1)..self.template().anchors().len())
456 .flat_map(move |anchor_index| DynamicAnchor::new(self, anchor_index).nodes());
457
458 current_anchor_slots.chain(later_anchor_slots)
459 }
460
461 pub(super) fn dynamic_node_slots_after_sharing_insertion_position<'a>(
462 &'a self,
463 slot: DynamicNodeSlot<'a>,
464 ) -> impl Iterator<Item = DynamicNodeSlot<'a>> + 'a {
465 self.dynamic_node_slots_after(slot)
466 .map_while(move |sibling| {
467 if !sibling.has_same_insertion_parent(slot) {
468 Some(None)
469 } else if sibling.shares_insertion_position(slot) {
470 Some(Some(sibling))
471 } else {
472 None
473 }
474 })
475 .flatten()
476 }
477
478 pub(super) fn static_anchor_targets_under(
479 &self,
480 root_anchor_index: usize,
481 ) -> impl Iterator<Item = StaticAnchorTarget> + '_ {
482 let root_path = self.template().anchors()[root_anchor_index].static_path();
483 self.template()
484 .anchors()
485 .iter()
486 .enumerate()
487 .filter_map(move |(anchor_index, anchor)| {
488 let path = anchor.static_path();
489 (!path.is_empty() && path.starts_with(root_path))
490 .then_some(StaticAnchorTarget { anchor_index, path })
491 })
492 }
493}
494
495#[derive(Clone, Copy)]
497pub struct DynamicAnchor<'a> {
498 vnode: &'a VNode,
499 anchor_index: usize,
500}
501
502impl<'a> DynamicAnchor<'a> {
503 pub(super) fn new(vnode: &'a VNode, anchor_index: usize) -> Self {
504 Self {
505 vnode,
506 anchor_index,
507 }
508 }
509
510 fn template_anchor(self) -> &'a TemplateAnchor {
511 &self.vnode.template().anchors()[self.anchor_index]
512 }
513
514 pub fn nodes(
516 self,
517 ) -> impl ExactSizeIterator<Item = DynamicNodeSlot<'a>> + DoubleEndedIterator + 'a {
518 self.template_anchor()
519 .nodes()
520 .map(move |index| DynamicNodeSlot {
521 anchor: self,
522 index,
523 })
524 }
525
526 pub fn attrs(
528 self,
529 ) -> impl ExactSizeIterator<Item = DynamicAttrSlot<'a>> + DoubleEndedIterator + 'a {
530 self.template_anchor()
531 .attributes()
532 .map(move |index| DynamicAttrSlot {
533 anchor: self,
534 index,
535 })
536 }
537
538 pub fn slot_target(self) -> TemplateSlotTarget {
540 self.template_anchor().slot_target()
541 }
542
543 pub(crate) fn anchor_index(self) -> usize {
544 self.anchor_index
545 }
546
547 pub fn is_last_static_node(self) -> bool {
549 self.slot_target().is_last_static_node()
550 }
551
552 pub(crate) fn is_parent_append_target(self) -> bool {
553 self.template_anchor().is_parent_append_target()
554 }
555
556 pub fn is_root_level(self) -> bool {
559 self.parent_element_op_index().is_none()
560 }
561
562 pub fn parent_element_op_index(self) -> Option<usize> {
564 self.template_anchor().parent_element_op_index()
565 }
566
567 pub fn static_path(self) -> TemplatePath {
569 self.template_anchor().static_path()
570 }
571
572 fn has_same_insertion_parent(self, other: Self) -> bool {
573 self.parent_element_op_index() == other.parent_element_op_index()
574 }
575
576 pub(super) fn shares_insertion_position(self, other: Self) -> bool {
577 self.has_same_insertion_parent(other) && self.slot_target() == other.slot_target()
578 }
579
580 pub(super) fn static_attr_value_for_key(
581 self,
582 key: (&'static str, Option<&'static str>),
583 ) -> Option<&'static str> {
584 let element_op = self
585 .template_anchor()
586 .parent_element_op_index()
587 .expect("bad attr anchor");
588 self.vnode
589 .template()
590 .static_element(element_op)
591 .expect("dynamic attr anchor must reference a static element op")
592 .attribute_value(key)
593 }
594}
595
596#[derive(Clone, Copy)]
597pub struct DynamicNodeSlot<'a> {
599 anchor: DynamicAnchor<'a>,
600 index: usize,
601}
602
603impl<'a> DynamicNodeSlot<'a> {
604 pub fn anchor(self) -> DynamicAnchor<'a> {
606 self.anchor
607 }
608
609 pub(crate) fn index(self) -> usize {
610 self.index
611 }
612
613 pub(super) fn has_same_insertion_parent(self, other: Self) -> bool {
614 self.anchor.has_same_insertion_parent(other.anchor)
615 }
616
617 pub(super) fn shares_insertion_position(self, other: Self) -> bool {
618 self.anchor.shares_insertion_position(other.anchor)
619 }
620}
621
622impl std::ops::Deref for DynamicNodeSlot<'_> {
623 type Target = DynamicNode;
624
625 fn deref(&self) -> &Self::Target {
626 &self.anchor.vnode.dynamic_node_values()[self.index]
627 }
628}
629
630#[derive(Clone, Copy)]
631pub struct DynamicAttrSlot<'a> {
633 anchor: DynamicAnchor<'a>,
634 index: usize,
635}
636
637impl<'a> DynamicAttrSlot<'a> {
638 pub fn attrs(self) -> &'a [Attribute] {
640 self.anchor.vnode.dynamic_attr_values()[self.index].as_ref()
641 }
642
643 pub fn anchor(self) -> DynamicAnchor<'a> {
645 self.anchor
646 }
647}
648
649fn static_child<'a>(
650 vnode: &'a VNode,
651 node: StaticTemplateNode<'_>,
652 path: TemplatePath,
653 anchor_idx: Option<usize>,
654) -> VNodeChild<'a> {
655 match node {
656 StaticTemplateNode::Element(element) => {
657 VNodeChild::Element(StaticElement::new(vnode, element.op(), path, anchor_idx))
658 }
659 StaticTemplateNode::Text(text) => {
660 VNodeChild::Text(StaticText::new(vnode, text.op(), anchor_idx))
661 }
662 }
663}
664
665fn child_position(target: TemplateSlotTarget) -> usize {
666 if target.is_last_static_node() {
667 usize::MAX
668 } else {
669 target.static_path().split_insertion().1 * 2
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use crate::{Attribute, DynamicNode, DynamicValues};
677 use dioxus_core_template::RuntimeTemplateBuilder;
678
679 fn vnode_from_builder(
680 builder: RuntimeTemplateBuilder,
681 dynamic_nodes: usize,
682 dynamic_attrs: usize,
683 ) -> VNode {
684 VNode::new(
685 builder.finish(),
686 DynamicValues::from_parts(
687 None,
688 (0..dynamic_nodes)
689 .map(|_| DynamicNode::Fragment(Vec::new()))
690 .collect::<Vec<_>>()
691 .into_boxed_slice(),
692 (0..dynamic_attrs)
693 .map(|_| vec![Attribute::new("class", "value", None, false)].into_boxed_slice())
694 .collect::<Vec<_>>()
695 .into_boxed_slice(),
696 ),
697 )
698 }
699
700 #[test]
701 fn nested_trailing_dynamic_before_root_dynamic_is_parent_append_target() {
702 let mut builder = RuntimeTemplateBuilder::default();
703 builder.open_element("tag0", None);
704 builder.dynamic_node(false);
705 builder.close_element();
706 builder.dynamic_node(false);
707 let template = builder.finish();
708
709 let vnode = VNode::new(
710 template,
711 DynamicValues::from_parts(
712 None,
713 Box::new([
714 DynamicNode::Fragment(Vec::new()),
715 DynamicNode::Fragment(Vec::new()),
716 ]),
717 Box::new([]),
718 ),
719 );
720
721 let anchor = vnode
722 .dynamic_anchors()
723 .find(|anchor| anchor.parent_element_op_index().is_some() && anchor.nodes().len() > 0)
724 .expect("nested dynamic anchor");
725
726 assert!(anchor.is_parent_append_target());
727 }
728
729 #[test]
730 fn nested_static_child_after_dynamic_keeps_anchor_index() {
731 let mut builder = RuntimeTemplateBuilder::default();
732 builder.open_element("root", None);
733 builder.dynamic_node(true);
734 builder.open_element("child", None);
735 builder.close_element();
736 builder.close_element();
737
738 let vnode = vnode_from_builder(builder, 1, 0);
739 let root = vnode
740 .children()
741 .find_map(|child| match child {
742 VNodeChild::Element(element) => Some(element),
743 _ => None,
744 })
745 .expect("root element");
746
747 let mut children = root.children();
748 let dynamic_anchor_index = match children.next().expect("dynamic child") {
749 VNodeChild::Dynamic(anchor) => anchor.anchor_index(),
750 _ => panic!("expected dynamic child before static child"),
751 };
752 let static_anchor_index = match children.next().expect("static child") {
753 VNodeChild::Element(element) => element.anchor_index(),
754 _ => panic!("expected static child"),
755 };
756
757 assert_eq!(static_anchor_index, Some(dynamic_anchor_index));
758 }
759
760 #[test]
761 fn nested_trailing_dynamic_targets_parent_append() {
762 let mut builder = RuntimeTemplateBuilder::default();
763 builder.open_element("root", None);
764 builder.open_element("child", None);
765 builder.close_element();
766 builder.dynamic_node(false);
767 builder.close_element();
768
769 let vnode = vnode_from_builder(builder, 1, 0);
770 let root = vnode
771 .children()
772 .find_map(|child| match child {
773 VNodeChild::Element(element) => Some(element),
774 _ => None,
775 })
776 .expect("root element");
777
778 let mut children = root.children();
779 let static_anchor_index = match children.next().expect("static child") {
780 VNodeChild::Element(element) => element.anchor_index(),
781 _ => panic!("expected static child"),
782 };
783 let dynamic_anchor = match children.next().expect("dynamic child") {
784 VNodeChild::Dynamic(anchor) => anchor,
785 _ => panic!("expected trailing dynamic child"),
786 };
787
788 assert_eq!(static_anchor_index, None);
789 assert!(dynamic_anchor.is_parent_append_target());
790 }
791
792 #[test]
793 fn root_static_before_last_static_emits_directly() {
794 let mut builder = RuntimeTemplateBuilder::default();
800 builder.open_element("first", None);
801 builder.close_element();
802 builder.open_element("second", None);
803 builder.close_element();
804 builder.dynamic_node(false);
805
806 let vnode = vnode_from_builder(builder, 1, 0);
807
808 let children: Vec<_> = vnode.children().collect();
809 assert!(
810 matches!(children.first(), Some(VNodeChild::Element(_))),
811 "leading static element must be emitted as the first root child",
812 );
813 }
814
815 #[test]
816 fn root_child_count_matches_emitted_children() {
817 let mut builder = RuntimeTemplateBuilder::default();
818 builder.open_element("only", None);
819 builder.close_element();
820 builder.dynamic_node(false);
821
822 let vnode = vnode_from_builder(builder, 1, 0);
823
824 assert_eq!(vnode.root_child_count(), vnode.children().count());
825 }
826
827 #[test]
828 fn dynamic_attr_slot_exposes_anchor_and_index() {
829 let mut builder = RuntimeTemplateBuilder::default();
830 builder.open_element("el", None);
831 builder.dynamic_attr();
832 builder.close_element();
833
834 let vnode = vnode_from_builder(builder, 0, 1);
835
836 let anchor = vnode
837 .dynamic_anchors()
838 .find(|anchor| anchor.attrs().len() > 0)
839 .expect("anchor with a dynamic attribute slot");
840 let slot = anchor.attrs().next().expect("dynamic attr slot");
841
842 assert_eq!(slot.anchor().anchor_index(), anchor.anchor_index());
843 }
844}