This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/dom_bundle/
blist.rs

1//! This module contains fragments bundles, a [BList]
2use std::borrow::Borrow;
3use std::cmp::Ordering;
4use std::collections::HashSet;
5use std::hash::Hash;
6use std::ops::Deref;
7
8use web_sys::Element;
9
10use super::{test_log, BNode, BSubtree, DomSlot};
11use crate::dom_bundle::{Reconcilable, ReconcileTarget};
12use crate::html::AnyScope;
13use crate::utils::RcExt;
14use crate::virtual_dom::{Key, VList, VNode};
15
16/// This struct represents a mounted [VList]
17#[derive(Debug)]
18pub(super) struct BList {
19    /// The reverse (render order) list of child [BNode]s
20    rev_children: Vec<BNode>,
21    /// All [BNode]s in the BList have keys
22    fully_keyed: bool,
23    key: Option<Key>,
24}
25
26impl VList {
27    // Splits a VList for creating / reconciling to a BList.
28    fn split_for_blist(self) -> (Option<Key>, bool, Vec<VNode>) {
29        let fully_keyed = self.fully_keyed();
30
31        let children = self
32            .children
33            .map(RcExt::unwrap_or_clone)
34            .unwrap_or_default();
35
36        (self.key, fully_keyed, children)
37    }
38}
39
40impl Deref for BList {
41    type Target = Vec<BNode>;
42
43    fn deref(&self) -> &Self::Target {
44        &self.rev_children
45    }
46}
47
48/// Helper struct, that keeps the position where the next element is to be placed at
49#[derive(Clone)]
50struct NodeWriter<'s> {
51    root: &'s BSubtree,
52    parent_scope: &'s AnyScope,
53    parent: &'s Element,
54    slot: DomSlot,
55}
56
57impl NodeWriter<'_> {
58    /// Write a new node that has no ancestor
59    fn add(self, node: VNode) -> (Self, BNode) {
60        test_log!("adding: {:?}", node);
61        test_log!(
62            "  parent={:?}, slot={:?}",
63            self.parent.outer_html(),
64            self.slot
65        );
66        let (next, bundle) = node.attach(self.root, self.parent_scope, self.parent, self.slot);
67        test_log!("  next_slot: {:?}", next);
68        (Self { slot: next, ..self }, bundle)
69    }
70
71    /// Shift a bundle into place without patching it
72    fn shift(&self, bundle: &BNode) {
73        bundle.shift(self.parent, self.slot.clone());
74    }
75
76    /// Patch a bundle with a new node
77    fn patch(self, node: VNode, bundle: &mut BNode) -> Self {
78        test_log!("patching: {:?} -> {:?}", bundle, node);
79        test_log!(
80            "  parent={:?}, slot={:?}",
81            self.parent.outer_html(),
82            self.slot
83        );
84        // Advance the next sibling reference (from right to left)
85        let next =
86            node.reconcile_node(self.root, self.parent_scope, self.parent, self.slot, bundle);
87        test_log!("  next_position: {:?}", next);
88        Self { slot: next, ..self }
89    }
90}
91/// Helper struct implementing [Eq] and [Hash] by only looking at a node's key
92struct KeyedEntry(usize, BNode);
93impl Borrow<Key> for KeyedEntry {
94    fn borrow(&self) -> &Key {
95        self.1.key().expect("unkeyed child in fully keyed list")
96    }
97}
98impl Hash for KeyedEntry {
99    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
100        <Self as Borrow<Key>>::borrow(self).hash(state)
101    }
102}
103impl PartialEq for KeyedEntry {
104    fn eq(&self, other: &Self) -> bool {
105        <Self as Borrow<Key>>::borrow(self) == <Self as Borrow<Key>>::borrow(other)
106    }
107}
108impl Eq for KeyedEntry {}
109
110impl BNode {
111    /// Assert that a bundle node is a list, or convert it to a list with a single child
112    fn make_list(&mut self) -> &mut BList {
113        match self {
114            Self::List(blist) => blist,
115            self_ => {
116                let b = std::mem::replace(self_, BNode::List(BList::new()));
117                let self_list = match self_ {
118                    BNode::List(blist) => blist,
119                    _ => unreachable!("just been set to the variant"),
120                };
121                let key = b.key().cloned();
122                self_list.rev_children.push(b);
123                self_list.fully_keyed = key.is_some();
124                self_list.key = key;
125                self_list
126            }
127        }
128    }
129}
130
131impl BList {
132    /// Create a new empty [BList]
133    pub const fn new() -> BList {
134        BList {
135            rev_children: vec![],
136            fully_keyed: true,
137            key: None,
138        }
139    }
140
141    /// Get the key of the underlying fragment
142    pub fn key(&self) -> Option<&Key> {
143        self.key.as_ref()
144    }
145
146    /// Diff and patch unkeyed child lists
147    fn apply_unkeyed(
148        root: &BSubtree,
149        parent_scope: &AnyScope,
150        parent: &Element,
151        slot: DomSlot,
152        lefts: Vec<VNode>,
153        rights: &mut Vec<BNode>,
154    ) -> DomSlot {
155        let mut writer = NodeWriter {
156            root,
157            parent_scope,
158            parent,
159            slot,
160        };
161
162        // Remove extra nodes
163        if lefts.len() < rights.len() {
164            for r in rights.drain(lefts.len()..) {
165                test_log!("removing: {:?}", r);
166                r.detach(root, parent, false);
167            }
168        }
169
170        let mut lefts_it = lefts.into_iter().rev();
171        for (r, l) in rights.iter_mut().zip(&mut lefts_it) {
172            writer = writer.patch(l, r);
173        }
174
175        // Add missing nodes
176        for l in lefts_it {
177            let (next_writer, el) = writer.add(l);
178            rights.push(el);
179            writer = next_writer;
180        }
181        writer.slot
182    }
183
184    /// Diff and patch fully keyed child lists.
185    ///
186    /// Optimized for node addition or removal from either end of the list and small changes in the
187    /// middle.
188    fn apply_keyed(
189        root: &BSubtree,
190        parent_scope: &AnyScope,
191        parent: &Element,
192        slot: DomSlot,
193        left_vdoms: Vec<VNode>,
194        rev_bundles: &mut Vec<BNode>,
195    ) -> DomSlot {
196        macro_rules! key {
197            ($v:expr) => {
198                $v.key().expect("unkeyed child in fully keyed list")
199            };
200        }
201        /// Find the first differing key in 2 iterators
202        fn matching_len<'a, 'b>(
203            a: impl Iterator<Item = &'a Key>,
204            b: impl Iterator<Item = &'b Key>,
205        ) -> usize {
206            a.zip(b).take_while(|(a, b)| a == b).count()
207        }
208
209        // Find first key mismatch from the back
210        let matching_len_end = matching_len(
211            left_vdoms.iter().map(|v| key!(v)).rev(),
212            rev_bundles.iter().map(|v| key!(v)),
213        );
214
215        if cfg!(debug_assertions) {
216            let mut keys = HashSet::with_capacity(left_vdoms.len());
217            for (idx, n) in left_vdoms.iter().enumerate() {
218                let key = key!(n);
219                debug_assert!(
220                    keys.insert(key!(n)),
221                    "duplicate key detected: {key} at index {idx}. Keys in keyed lists must be \
222                     unique!",
223                );
224            }
225        }
226
227        // If there is no key mismatch, apply the unkeyed approach
228        // Corresponds to adding or removing items from the back of the list
229        if matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
230            // No key changes
231            return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, rev_bundles);
232        }
233
234        // We partially drain the new vnodes in several steps.
235        let mut lefts = left_vdoms;
236        let mut writer = NodeWriter {
237            root,
238            parent_scope,
239            parent,
240            slot,
241        };
242        // Step 1. Diff matching children at the end
243        let lefts_to = lefts.len() - matching_len_end;
244        for (l, r) in lefts
245            .drain(lefts_to..)
246            .rev()
247            .zip(rev_bundles[..matching_len_end].iter_mut())
248        {
249            writer = writer.patch(l, r);
250        }
251
252        // Step 2. Diff matching children in the middle, that is between the first and last key
253        // mismatch Find first key mismatch from the front
254        let mut matching_len_start = matching_len(
255            lefts.iter().map(|v| key!(v)),
256            rev_bundles.iter().map(|v| key!(v)).rev(),
257        );
258
259        // Step 2.1. Splice out the existing middle part and build a lookup by key
260        let rights_to = rev_bundles.len() - matching_len_start;
261        let mut bundle_middle = matching_len_end..rights_to;
262        if bundle_middle.start > bundle_middle.end {
263            // If this range is "inverted", this implies that the incoming nodes in lefts contain a
264            // duplicate key!
265            // Pictogram:
266            //                                         v lefts_to
267            // lefts:              | SSSSSSSS | ------ | EEEEEEEE |
268            //                                ↕ matching_len_start
269            // rev_bundles.rev():  | SSS | ?? | EEE |
270            //                           ^ rights_to
271            // Both a key from the (S)tarting portion and (E)nding portion of lefts has matched a
272            // key in the ? portion of bundles. Since the former can't overlap, a key
273            // must be duplicate. Duplicates might lead to us forgetting about some
274            // bundles entirely. It is NOT straight forward to adjust the below code to
275            // consistently check and handle this. The duplicate keys might
276            // be in the start or end portion.
277            // With debug_assertions we can never reach this. For production code, hope for the best
278            // by pretending. We still need to adjust some things so splicing doesn't
279            // panic:
280            matching_len_start = 0;
281            bundle_middle = matching_len_end..rev_bundles.len();
282        }
283        let (matching_len_start, bundle_middle) = (matching_len_start, bundle_middle);
284
285        // BNode contains js objects that look suspicious to clippy but are harmless
286        #[allow(clippy::mutable_key_type)]
287        let mut spare_bundles: HashSet<KeyedEntry> = HashSet::with_capacity(bundle_middle.len());
288        let mut spliced_middle = rev_bundles.splice(bundle_middle, std::iter::empty());
289        for (idx, r) in (&mut spliced_middle).enumerate() {
290            #[cold]
291            fn duplicate_in_bundle(root: &BSubtree, parent: &Element, r: BNode) {
292                test_log!("removing: {:?}", r);
293                r.detach(root, parent, false);
294            }
295            if let Some(KeyedEntry(_, dup)) = spare_bundles.replace(KeyedEntry(idx, r)) {
296                duplicate_in_bundle(root, parent, dup);
297            }
298        }
299
300        // Step 2.2. Put the middle part back together in the new key order
301        let mut replacements: Vec<BNode> = Vec::with_capacity((matching_len_start..lefts_to).len());
302        // The goal is to shift as few nodes as possible.
303
304        // We handle runs of in-order nodes. When we encounter one out-of-order, we decide whether:
305        // - to shift all nodes in the current run to the position after the node before of the run,
306        //   or to
307        // - "commit" to the current run, shift all nodes before the end of the run that we might
308        //   encounter in the future, and then start a new run.
309        // Example of a run:
310        //               barrier_idx --v                   v-- end_idx
311        // spliced_middle  [ ... , M , N , C , D , E , F , G , ... ] (original element order)
312        //                                 ^---^-----------^ the nodes that are part of the current
313        // run                           v start_writer
314        // replacements    [ ... , M , C , D , G ]                   (new element order)
315        //                             ^-- start_idx
316        let mut barrier_idx = 0; // nodes from spliced_middle[..barrier_idx] are shifted unconditionally
317        struct RunInformation<'a> {
318            start_writer: NodeWriter<'a>,
319            start_idx: usize,
320            end_idx: usize,
321        }
322        let mut current_run: Option<RunInformation<'_>> = None;
323
324        for l in lefts
325            .drain(matching_len_start..) // lefts_to.. has been drained
326            .rev()
327        {
328            let ancestor = spare_bundles.take(key!(l));
329            // Check if we need to shift or commit a run
330            if let Some(run) = current_run.as_mut() {
331                if let Some(KeyedEntry(idx, _)) = ancestor {
332                    // If there are only few runs, this is a cold path
333                    if idx < run.end_idx {
334                        // Have to decide whether to shift or commit the current run. A few
335                        // calculations: A perfect estimate of the amount of
336                        // nodes we have to shift if we move this run:
337                        let run_length = replacements.len() - run.start_idx;
338                        // A very crude estimate of the amount of nodes we will have to shift if we
339                        // commit the run: Note nodes of the current run
340                        // should not be counted here!
341                        let estimated_skipped_nodes = run.end_idx - idx.max(barrier_idx);
342                        // double run_length to counteract that the run is part of the
343                        // estimated_skipped_nodes
344                        if 2 * run_length > estimated_skipped_nodes {
345                            // less work to commit to this run
346                            barrier_idx = 1 + run.end_idx;
347                        } else {
348                            // Less work to shift this run
349                            for r in replacements[run.start_idx..].iter_mut().rev() {
350                                run.start_writer.shift(r);
351                            }
352                        }
353                        current_run = None;
354                    }
355                }
356            }
357            let bundle = if let Some(KeyedEntry(idx, mut r_bundle)) = ancestor {
358                match current_run.as_mut() {
359                    // hot path
360                    // We know that idx >= run.end_idx, so this node doesn't need to shift
361                    Some(run) => run.end_idx = idx,
362                    None => match idx.cmp(&barrier_idx) {
363                        // peep hole optimization, don't start a run as the element is already where
364                        // it should be
365                        Ordering::Equal => barrier_idx += 1,
366                        // shift the node unconditionally, don't start a run
367                        Ordering::Less => writer.shift(&r_bundle),
368                        // start a run
369                        Ordering::Greater => {
370                            current_run = Some(RunInformation {
371                                start_writer: writer.clone(),
372                                start_idx: replacements.len(),
373                                end_idx: idx,
374                            })
375                        }
376                    },
377                }
378                writer = writer.patch(l, &mut r_bundle);
379                r_bundle
380            } else {
381                // Even if there is an active run, we don't have to modify it
382                let (next_writer, bundle) = writer.add(l);
383                writer = next_writer;
384                bundle
385            };
386            replacements.push(bundle);
387        }
388        // drop the splice iterator and immediately replace the range with the reordered elements
389        drop(spliced_middle);
390        rev_bundles.splice(matching_len_end..matching_len_end, replacements);
391
392        // Step 2.3. Remove any extra rights
393        for KeyedEntry(_, r) in spare_bundles.drain() {
394            test_log!("removing: {:?}", r);
395            r.detach(root, parent, false);
396        }
397
398        // Step 3. Diff matching children at the start
399        let rights_to = rev_bundles.len() - matching_len_start;
400        for (l, r) in lefts
401            .drain(..) // matching_len_start.. has been drained already
402            .rev()
403            .zip(rev_bundles[rights_to..].iter_mut())
404        {
405            writer = writer.patch(l, r);
406        }
407
408        writer.slot
409    }
410}
411
412impl ReconcileTarget for BList {
413    fn detach(self, root: &BSubtree, parent: &Element, parent_to_detach: bool) {
414        for child in self.rev_children.into_iter() {
415            child.detach(root, parent, parent_to_detach);
416        }
417    }
418
419    fn shift(&self, next_parent: &Element, mut slot: DomSlot) -> DomSlot {
420        for node in self.rev_children.iter() {
421            slot = node.shift(next_parent, slot);
422        }
423
424        slot
425    }
426}
427
428impl Reconcilable for VList {
429    type Bundle = BList;
430
431    fn attach(
432        self,
433        root: &BSubtree,
434        parent_scope: &AnyScope,
435        parent: &Element,
436        slot: DomSlot,
437    ) -> (DomSlot, Self::Bundle) {
438        let mut self_ = BList::new();
439        let node_ref = self.reconcile(root, parent_scope, parent, slot, &mut self_);
440        (node_ref, self_)
441    }
442
443    fn reconcile_node(
444        self,
445        root: &BSubtree,
446        parent_scope: &AnyScope,
447        parent: &Element,
448        slot: DomSlot,
449        bundle: &mut BNode,
450    ) -> DomSlot {
451        // 'Forcefully' pretend the existing node is a list. Creates a
452        // singleton list if it isn't already.
453        let blist = bundle.make_list();
454        self.reconcile(root, parent_scope, parent, slot, blist)
455    }
456
457    fn reconcile(
458        self,
459        root: &BSubtree,
460        parent_scope: &AnyScope,
461        parent: &Element,
462        slot: DomSlot,
463        blist: &mut BList,
464    ) -> DomSlot {
465        // Here, we will try to diff the previous list elements with the new
466        // ones we want to insert. For that, we will use two lists:
467        //  - lefts: new elements to render in the DOM
468        //  - rights: previously rendered elements.
469        //
470        // The left items are known since we want to insert them
471        // (self.children). For the right ones, we will look at the bundle,
472        // i.e. the current DOM list element that we want to replace with self.
473        let (key, fully_keyed, lefts) = self.split_for_blist();
474
475        let rights = &mut blist.rev_children;
476        test_log!("lefts: {:?}", lefts);
477        test_log!("rights: {:?}", rights);
478
479        if let Some(additional) = lefts.len().checked_sub(rights.len()) {
480            rights.reserve_exact(additional);
481        }
482        let first = if fully_keyed && blist.fully_keyed {
483            BList::apply_keyed(root, parent_scope, parent, slot, lefts, rights)
484        } else {
485            BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
486        };
487        blist.fully_keyed = fully_keyed;
488        blist.key = key;
489        test_log!("result: {:?}", rights);
490        first
491    }
492}
493
494#[cfg(feature = "hydration")]
495mod feat_hydration {
496    use super::*;
497    use crate::dom_bundle::{Fragment, Hydratable};
498
499    impl Hydratable for VList {
500        fn hydrate(
501            self,
502            root: &BSubtree,
503            parent_scope: &AnyScope,
504            parent: &Element,
505            fragment: &mut Fragment,
506        ) -> Self::Bundle {
507            let (key, fully_keyed, vchildren) = self.split_for_blist();
508
509            let mut children = Vec::with_capacity(vchildren.len());
510
511            for child in vchildren.into_iter() {
512                let child = child.hydrate(root, parent_scope, parent, fragment);
513
514                children.push(child);
515            }
516
517            children.reverse();
518
519            BList {
520                rev_children: children,
521                fully_keyed,
522                key,
523            }
524        }
525    }
526}
527
528#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
529#[cfg(test)]
530mod layout_tests {
531    extern crate self as yew;
532
533    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
534
535    use crate::html;
536    use crate::tests::layout_tests::{diff_layouts, TestLayout};
537
538    wasm_bindgen_test_configure!(run_in_browser);
539
540    #[test]
541    fn diff() {
542        let layout1 = TestLayout {
543            name: "1",
544            node: html! {
545                <>
546                    {"a"}
547                    {"b"}
548                    <>
549                        {"c"}
550                        {"d"}
551                    </>
552                    {"e"}
553                </>
554            },
555            expected: "abcde",
556        };
557
558        let layout2 = TestLayout {
559            name: "2",
560            node: html! {
561                <>
562                    {"a"}
563                    {"b"}
564                    <></>
565                    {"e"}
566                    {"f"}
567                </>
568            },
569            expected: "abef",
570        };
571
572        let layout3 = TestLayout {
573            name: "3",
574            node: html! {
575                <>
576                    {"a"}
577                    <></>
578                    {"b"}
579                    {"e"}
580                </>
581            },
582            expected: "abe",
583        };
584
585        let layout4 = TestLayout {
586            name: "4",
587            node: html! {
588                <>
589                    {"a"}
590                    <>
591                        {"c"}
592                        {"d"}
593                    </>
594                    {"b"}
595                    {"e"}
596                </>
597            },
598            expected: "acdbe",
599        };
600
601        diff_layouts(vec![layout1, layout2, layout3, layout4]);
602    }
603}
604
605#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
606#[cfg(test)]
607mod layout_tests_keys {
608    extern crate self as yew;
609
610    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
611    use web_sys::Node;
612
613    use crate::tests::layout_tests::{diff_layouts, TestLayout};
614    use crate::virtual_dom::VNode;
615    use crate::{html, Children, Component, Context, Html, Properties};
616
617    wasm_bindgen_test_configure!(run_in_browser);
618
619    struct Comp {}
620
621    #[derive(Properties, Clone, PartialEq)]
622    struct CountingCompProps {
623        id: usize,
624        #[prop_or(false)]
625        can_change: bool,
626    }
627
628    impl Component for Comp {
629        type Message = ();
630        type Properties = CountingCompProps;
631
632        fn create(_: &Context<Self>) -> Self {
633            Comp {}
634        }
635
636        fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
637            unimplemented!();
638        }
639
640        fn view(&self, ctx: &Context<Self>) -> Html {
641            html! { <p>{ ctx.props().id }</p> }
642        }
643    }
644
645    #[derive(Clone, Properties, PartialEq)]
646    pub struct ListProps {
647        pub children: Children,
648    }
649
650    pub struct List();
651
652    impl Component for List {
653        type Message = ();
654        type Properties = ListProps;
655
656        fn create(_: &Context<Self>) -> Self {
657            Self()
658        }
659
660        fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
661            unimplemented!();
662        }
663
664        fn view(&self, ctx: &Context<Self>) -> Html {
665            html! { <>{ for ctx.props().children.iter() }</> }
666        }
667    }
668
669    #[test]
670    fn diff() {
671        let mut layouts = vec![];
672
673        let vref_node: Node = gloo::utils::document().create_element("i").unwrap().into();
674        layouts.push(TestLayout {
675            name: "All VNode types as children",
676            node: html! {
677                <>
678                    {"a"}
679                    <span key="vtag"></span>
680                    {"c"}
681                    {"d"}
682                    <Comp id=0 key="vchild" />
683                    <key="vlist">
684                        {"foo"}
685                        {"bar"}
686                    </>
687                    {VNode::VRef(vref_node)}
688                </>
689            },
690            expected: "a<span></span>cd<p>0</p>foobar<i></i>",
691        });
692
693        layouts.extend(vec![
694            TestLayout {
695                name: "Inserting into VList first child - before",
696                node: html! {
697                    <>
698                        <key="VList">
699                            <i key="i"></i>
700                        </>
701                        <p key="p"></p>
702                    </>
703                },
704                expected: "<i></i><p></p>",
705            },
706            TestLayout {
707                name: "Inserting into VList first child - after",
708                node: html! {
709                    <>
710                        <key="VList">
711                            <i key="i"></i>
712                            <e key="e"></e>
713                        </>
714                        <p key="p"></p>
715                    </>
716                },
717                expected: "<i></i><e></e><p></p>",
718            },
719        ]);
720
721        layouts.extend(vec![
722            TestLayout {
723                name: "No matches - before",
724                node: html! {
725                    <>
726                        <i key="i"></i>
727                        <e key="e"></e>
728                    </>
729                },
730                expected: "<i></i><e></e>",
731            },
732            TestLayout {
733                name: "No matches - after",
734                node: html! {
735                    <>
736                        <a key="a"></a>
737                        <p key="p"></p>
738                    </>
739                },
740                expected: "<a></a><p></p>",
741            },
742        ]);
743
744        layouts.extend(vec![
745            TestLayout {
746                name: "Append - before",
747                node: html! {
748                    <>
749                        <i key="i"></i>
750                        <e key="e"></e>
751                    </>
752                },
753                expected: "<i></i><e></e>",
754            },
755            TestLayout {
756                name: "Append - after",
757                node: html! {
758                    <>
759                        <i key="i"></i>
760                        <e key="e"></e>
761                        <p key="p"></p>
762                    </>
763                },
764                expected: "<i></i><e></e><p></p>",
765            },
766        ]);
767
768        layouts.extend(vec![
769            TestLayout {
770                name: "Prepend - before",
771                node: html! {
772                    <>
773                        <i key="i"></i>
774                        <e key="e"></e>
775                    </>
776                },
777                expected: "<i></i><e></e>",
778            },
779            TestLayout {
780                name: "Prepend - after",
781                node: html! {
782                    <>
783                        <p key="p"></p>
784                        <i key="i"></i>
785                        <e key="e"></e>
786                    </>
787                },
788                expected: "<p></p><i></i><e></e>",
789            },
790        ]);
791
792        layouts.extend(vec![
793            TestLayout {
794                name: "Delete first - before",
795                node: html! {
796                    <>
797                        <i key="i"></i>
798                        <e key="e"></e>
799                        <p key="p"></p>
800                    </>
801                },
802                expected: "<i></i><e></e><p></p>",
803            },
804            TestLayout {
805                name: "Delete first - after",
806                node: html! {
807                    <>
808                        <e key="e"></e>
809                        <p key="p"></p>
810                    </>
811                },
812                expected: "<e></e><p></p>",
813            },
814        ]);
815
816        layouts.extend(vec![
817            TestLayout {
818                name: "Delete last - before",
819                node: html! {
820                    <>
821                        <i key="i"></i>
822                        <e key="e"></e>
823                        <p key="p"></p>
824                    </>
825                },
826                expected: "<i></i><e></e><p></p>",
827            },
828            TestLayout {
829                name: "Delete last - after",
830                node: html! {
831                    <>
832                        <i key="i"></i>
833                        <e key="e"></e>
834                    </>
835                },
836                expected: "<i></i><e></e>",
837            },
838        ]);
839
840        layouts.extend(vec![
841            TestLayout {
842                name: "Delete last and change node type - before",
843                node: html! {
844                    <>
845                        <i key="i"></i>
846                        <e key="e"></e>
847                        <p key="p"></p>
848                    </>
849                },
850                expected: "<i></i><e></e><p></p>",
851            },
852            TestLayout {
853                name: "Delete last - after",
854                node: html! {
855                    <>
856                        <List key="i"><i/></List>
857                        <List key="e"><e/></List>
858                        <List key="a"><a/></List>
859                    </>
860                },
861                expected: "<i></i><e></e><a></a>",
862            },
863        ]);
864
865        layouts.extend(vec![
866            TestLayout {
867                name: "Delete middle - before",
868                node: html! {
869                    <>
870                        <i key="i"></i>
871                        <e key="e"></e>
872                        <p key="p"></p>
873                        <a key="a"></a>
874                    </>
875                },
876                expected: "<i></i><e></e><p></p><a></a>",
877            },
878            TestLayout {
879                name: "Delete middle - after",
880                node: html! {
881                    <>
882                        <i key="i"></i>
883                        <e key="e2"></e>
884                        <p key="p2"></p>
885                        <a key="a"></a>
886                    </>
887                },
888                expected: "<i></i><e></e><p></p><a></a>",
889            },
890        ]);
891
892        layouts.extend(vec![
893            TestLayout {
894                name: "Delete middle and change node type - before",
895                node: html! {
896                    <>
897                        <i key="i"></i>
898                        <e key="e"></e>
899                        <p key="p"></p>
900                        <a key="a"></a>
901                    </>
902                },
903                expected: "<i></i><e></e><p></p><a></a>",
904            },
905            TestLayout {
906                name: "Delete middle and change node type- after",
907                node: html! {
908                    <>
909                        <List key="i2"><i/></List>
910                        <e key="e"></e>
911                        <List key="p"><p/></List>
912                        <List key="a2"><a/></List>
913                    </>
914                },
915                expected: "<i></i><e></e><p></p><a></a>",
916            },
917        ]);
918
919        layouts.extend(vec![
920            TestLayout {
921                name: "Reverse - before",
922                node: html! {
923                    <>
924                        <i key="i"></i>
925                        <e key="e"></e>
926                        <p key="p"></p>
927                        <u key="u"></u>
928                    </>
929                },
930                expected: "<i></i><e></e><p></p><u></u>",
931            },
932            TestLayout {
933                name: "Reverse - after",
934                node: html! {
935                    <>
936                        <u key="u"></u>
937                        <p key="p"></p>
938                        <e key="e"></e>
939                        <i key="i"></i>
940                    </>
941                },
942                expected: "<u></u><p></p><e></e><i></i>",
943            },
944        ]);
945
946        layouts.extend(vec![
947            TestLayout {
948                name: "Reverse and change node type - before",
949                node: html! {
950                    <>
951                        <i key="i"></i>
952                        <key="i1"></>
953                        <key="i2"></>
954                        <key="i3"></>
955                        <e key="e"></e>
956                        <key="yo">
957                            <p key="p"></p>
958                        </>
959                        <u key="u"></u>
960                    </>
961                },
962                expected: "<i></i><e></e><p></p><u></u>",
963            },
964            TestLayout {
965                name: "Reverse and change node type - after",
966                node: html! {
967                    <>
968                        <List key="u"><u/></List>
969                        <List key="p"><p/></List>
970                        <List key="e"><e/></List>
971                        <List key="i"><i/></List>
972                    </>
973                },
974                expected: "<u></u><p></p><e></e><i></i>",
975            },
976        ]);
977
978        layouts.extend(vec![
979            TestLayout {
980                name: "Swap 1&2 - before",
981                node: html! {
982                    <>
983                        <i key="1"></i>
984                        <e key="2"></e>
985                        <p key="3"></p>
986                        <a key="4"></a>
987                        <u key="5"></u>
988                    </>
989                },
990                expected: "<i></i><e></e><p></p><a></a><u></u>",
991            },
992            TestLayout {
993                name: "Swap 1&2 - after",
994                node: html! {
995                    <>
996                        <e key="2"></e>
997                        <i key="1"></i>
998                        <p key="3"></p>
999                        <a key="4"></a>
1000                        <u key="5"></u>
1001                    </>
1002                },
1003                expected: "<e></e><i></i><p></p><a></a><u></u>",
1004            },
1005        ]);
1006
1007        layouts.extend(vec![
1008            TestLayout {
1009                name: "Swap 1&2 and change node type - before",
1010                node: html! {
1011                    <>
1012                        <i key="1"></i>
1013                        <e key="2"></e>
1014                        <p key="3"></p>
1015                        <a key="4"></a>
1016                        <u key="5"></u>
1017                    </>
1018                },
1019                expected: "<i></i><e></e><p></p><a></a><u></u>",
1020            },
1021            TestLayout {
1022                name: "Swap 1&2 and change node type - after",
1023                node: html! {
1024                    <>
1025                        <List key="2"><e/></List>
1026                        <List key="1"><i/></List>
1027                        <List key="3"><p/></List>
1028                        <List key="4"><a/></List>
1029                        <List key="5"><u/></List>
1030                    </>
1031                },
1032                expected: "<e></e><i></i><p></p><a></a><u></u>",
1033            },
1034        ]);
1035
1036        layouts.extend(vec![
1037            TestLayout {
1038                name: "test - before",
1039                node: html! {
1040                    <>
1041                        <key="1">
1042                            <e key="e"></e>
1043                            <p key="p"></p>
1044                            <a key="a"></a>
1045                            <u key="u"></u>
1046                        </>
1047                        <key="2">
1048                            <e key="e"></e>
1049                            <p key="p"></p>
1050                            <a key="a"></a>
1051                            <u key="u"></u>
1052                        </>
1053                    </>
1054                },
1055                expected: "<e></e><p></p><a></a><u></u><e></e><p></p><a></a><u></u>",
1056            },
1057            TestLayout {
1058                name: "Swap 4&5 - after",
1059                node: html! {
1060                    <>
1061                        <e key="1"></e>
1062                        <key="2">
1063                            <p key="p"></p>
1064                            <i key="i"></i>
1065                        </>
1066                    </>
1067                },
1068                expected: "<e></e><p></p><i></i>",
1069            },
1070        ]);
1071
1072        layouts.extend(vec![
1073            TestLayout {
1074                name: "Swap 4&5 - before",
1075                node: html! {
1076                    <>
1077                        <i key="1"></i>
1078                        <e key="2"></e>
1079                        <p key="3"></p>
1080                        <a key="4"></a>
1081                        <u key="5"></u>
1082                    </>
1083                },
1084                expected: "<i></i><e></e><p></p><a></a><u></u>",
1085            },
1086            TestLayout {
1087                name: "Swap 4&5 - after",
1088                node: html! {
1089                    <>
1090                        <i key="1"></i>
1091                        <e key="2"></e>
1092                        <p key="3"></p>
1093                        <u key="5"></u>
1094                        <a key="4"></a>
1095                    </>
1096                },
1097                expected: "<i></i><e></e><p></p><u></u><a></a>",
1098            },
1099        ]);
1100
1101        layouts.extend(vec![
1102            TestLayout {
1103                name: "Swap 1&5 - before",
1104                node: html! {
1105                    <>
1106                        <i key="1"></i>
1107                        <e key="2"></e>
1108                        <p key="3"></p>
1109                        <a key="4"></a>
1110                        <u key="5"></u>
1111                    </>
1112                },
1113                expected: "<i></i><e></e><p></p><a></a><u></u>",
1114            },
1115            TestLayout {
1116                name: "Swap 1&5 - after",
1117                node: html! {
1118                    <>
1119                        <u key="5"></u>
1120                        <e key="2"></e>
1121                        <p key="3"></p>
1122                        <a key="4"></a>
1123                        <i key="1"></i>
1124                    </>
1125                },
1126                expected: "<u></u><e></e><p></p><a></a><i></i>",
1127            },
1128        ]);
1129
1130        layouts.extend(vec![
1131            TestLayout {
1132                name: "Move 2 after 4 - before",
1133                node: html! {
1134                    <>
1135                        <i key="1"></i>
1136                        <e key="2"></e>
1137                        <p key="3"></p>
1138                        <a key="4"></a>
1139                        <u key="5"></u>
1140                    </>
1141                },
1142                expected: "<i></i><e></e><p></p><a></a><u></u>",
1143            },
1144            TestLayout {
1145                name: "Move 2 after 4 - after",
1146                node: html! {
1147                    <>
1148                        <i key="1"></i>
1149                        <p key="3"></p>
1150                        <a key="4"></a>
1151                        <e key="2"></e>
1152                        <u key="5"></u>
1153                    </>
1154                },
1155                expected: "<i></i><p></p><a></a><e></e><u></u>",
1156            },
1157        ]);
1158
1159        layouts.extend(vec![
1160            TestLayout {
1161                name: "Swap 1,2 <-> 3,4 - before",
1162                node: html! {
1163                    <>
1164                        <i key="1"></i>
1165                        <e key="2"></e>
1166                        <p key="3"></p>
1167                        <a key="4"></a>
1168                        <u key="5"></u>
1169                    </>
1170                },
1171                expected: "<i></i><e></e><p></p><a></a><u></u>",
1172            },
1173            TestLayout {
1174                name: "Swap 1,2 <-> 3,4 - after",
1175                node: html! {
1176                    <>
1177                        <p key="3"></p>
1178                        <a key="4"></a>
1179                        <i key="1"></i>
1180                        <e key="2"></e>
1181                        <u key="5"></u>
1182                    </>
1183                },
1184                expected: "<p></p><a></a><i></i><e></e><u></u>",
1185            },
1186        ]);
1187
1188        layouts.extend(vec![
1189            TestLayout {
1190                name: "Swap lists - before",
1191                node: html! {
1192                    <>
1193                        <key="1">
1194                            <i></i>
1195                            <e></e>
1196                        </>
1197                        <key="2">
1198                            <a></a>
1199                            <u></u>
1200                        </>
1201                    </>
1202                },
1203                expected: "<i></i><e></e><a></a><u></u>",
1204            },
1205            TestLayout {
1206                name: "Swap lists - after",
1207                node: html! {
1208                    <>
1209                        <key="2">
1210                            <a></a>
1211                            <u></u>
1212                        </>
1213                        <key="1">
1214                            <i></i>
1215                            <e></e>
1216                        </>
1217                    </>
1218                },
1219                expected: "<a></a><u></u><i></i><e></e>",
1220            },
1221        ]);
1222
1223        layouts.extend(vec![
1224            TestLayout {
1225                name: "Swap lists with in-between - before",
1226                node: html! {
1227                    <>
1228                        <key="1">
1229                            <i></i>
1230                            <e></e>
1231                        </>
1232                        <p key="between"></p>
1233                        <key="2">
1234                            <a></a>
1235                            <u></u>
1236                        </>
1237                    </>
1238                },
1239                expected: "<i></i><e></e><p></p><a></a><u></u>",
1240            },
1241            TestLayout {
1242                name: "Swap lists with in-between - after",
1243                node: html! {
1244                    <>
1245                        <key="2">
1246                            <a></a>
1247                            <u></u>
1248                        </>
1249                        <p key="between"></p>
1250                        <key="1">
1251                            <i></i>
1252                            <e></e>
1253                        </>
1254                    </>
1255                },
1256                expected: "<a></a><u></u><p></p><i></i><e></e>",
1257            },
1258        ]);
1259
1260        layouts.extend(vec![
1261            TestLayout {
1262                name: "Insert VComp front - before",
1263                node: html! {
1264                    <>
1265                        <u key=1></u>
1266                        <a key=2></a>
1267                    </>
1268                },
1269                expected: "<u></u><a></a>",
1270            },
1271            TestLayout {
1272                name: "Insert VComp front - after",
1273                node: html! {
1274                    <>
1275                        <Comp id=0 key="comp"/>
1276                        <u key=1></u>
1277                        <a key=2></a>
1278                    </>
1279                },
1280                expected: "<p>0</p><u></u><a></a>",
1281            },
1282        ]);
1283
1284        layouts.extend(vec![
1285            TestLayout {
1286                name: "Insert VComp middle - before",
1287                node: html! {
1288                    <>
1289                        <u key=1></u>
1290                        <a key=2></a>
1291                    </>
1292                },
1293                expected: "<u></u><a></a>",
1294            },
1295            TestLayout {
1296                name: "Insert VComp middle - after",
1297                node: html! {
1298                    <>
1299                        <u key=1></u>
1300                        <Comp id=0 key="comp"/>
1301                        <a key=2></a>
1302                    </>
1303                },
1304                expected: "<u></u><p>0</p><a></a>",
1305            },
1306        ]);
1307
1308        layouts.extend(vec![
1309            TestLayout {
1310                name: "Insert VComp back - before",
1311                node: html! {
1312                    <>
1313                        <u key=1></u>
1314                        <a key=2></a>
1315                    </>
1316                },
1317                expected: "<u></u><a></a>",
1318            },
1319            TestLayout {
1320                name: "Insert VComp back - after",
1321                node: html! {
1322                    <>
1323                        <u key=1></u>
1324                        <a key=2></a>
1325                        <Comp id=0 key="comp"/>
1326                    </>
1327                },
1328                expected: "<u></u><a></a><p>0</p>",
1329            },
1330        ]);
1331
1332        layouts.extend(vec![
1333            TestLayout {
1334                name: "Reverse VComp children - before",
1335                node: html! {
1336                    <>
1337                        <Comp id=1 key="comp-1"/>
1338                        <Comp id=2 key="comp-2"/>
1339                        <Comp id=3 key="comp-3"/>
1340                    </>
1341                },
1342                expected: "<p>1</p><p>2</p><p>3</p>",
1343            },
1344            TestLayout {
1345                name: "Reverse VComp children - after",
1346                node: html! {
1347                    <>
1348                        <Comp id=3 key="comp-3"/>
1349                        <Comp id=2 key="comp-2"/>
1350                        <Comp id=1 key="comp-1"/>
1351                    </>
1352                },
1353                expected: "<p>3</p><p>2</p><p>1</p>",
1354            },
1355        ]);
1356
1357        layouts.extend(vec![
1358            TestLayout {
1359                name: "Reverse VComp children with children - before",
1360                node: html! {
1361                    <>
1362                        <List key="comp-1"><p>{"11"}</p><p>{"12"}</p></List>
1363                        <List key="comp-2"><p>{"21"}</p><p>{"22"}</p></List>
1364                        <List key="comp-3"><p>{"31"}</p><p>{"32"}</p></List>
1365                    </>
1366                },
1367                expected: "<p>11</p><p>12</p><p>21</p><p>22</p><p>31</p><p>32</p>",
1368            },
1369            TestLayout {
1370                name: "Reverse VComp children with children - after",
1371                node: html! {
1372                    <>
1373                        <List key="comp-3"><p>{"31"}</p><p>{"32"}</p></List>
1374                        <List key="comp-2"><p>{"21"}</p><p>{"22"}</p></List>
1375                        <List key="comp-1"><p>{"11"}</p><p>{"12"}</p></List>
1376                    </>
1377                },
1378                expected: "<p>31</p><p>32</p><p>21</p><p>22</p><p>11</p><p>12</p>",
1379            },
1380        ]);
1381
1382        layouts.extend(vec![
1383            TestLayout {
1384                name: "Complex component update - before",
1385                node: html! {
1386                    <List>
1387                        <Comp id=1 key="comp-1"/>
1388                        <Comp id=2 key="comp-2"/>
1389                    </List>
1390                },
1391                expected: "<p>1</p><p>2</p>",
1392            },
1393            TestLayout {
1394                name: "Complex component update - after",
1395                node: html! {
1396                    <List>
1397                        <List key="comp-1">
1398                            <Comp id=1 />
1399                        </List>
1400                        <List key="comp-2">
1401                            <p>{"2"}</p>
1402                        </List>
1403                    </List>
1404                },
1405                expected: "<p>1</p><p>2</p>",
1406            },
1407        ]);
1408
1409        layouts.extend(vec![
1410            TestLayout {
1411                name: "Reorder VComp children with children - before",
1412                node: html! {
1413                    <>
1414                        <List key="comp-1"><p>{"1"}</p></List>
1415                        <List key="comp-3"><p>{"3"}</p></List>
1416                        <List key="comp-5"><p>{"5"}</p></List>
1417                        <List key="comp-2"><p>{"2"}</p></List>
1418                        <List key="comp-4"><p>{"4"}</p></List>
1419                        <List key="comp-6"><p>{"6"}</p></List>
1420                    </>
1421                },
1422                expected: "<p>1</p><p>3</p><p>5</p><p>2</p><p>4</p><p>6</p>",
1423            },
1424            TestLayout {
1425                name: "Reorder VComp children with children - after",
1426                node: html! {
1427                    <>
1428                        <Comp id=6 key="comp-6"/>
1429                        <Comp id=5 key="comp-5"/>
1430                        <Comp id=4 key="comp-4"/>
1431                        <Comp id=3 key="comp-3"/>
1432                        <Comp id=2 key="comp-2"/>
1433                        <Comp id=1 key="comp-1"/>
1434                    </>
1435                },
1436                expected: "<p>6</p><p>5</p><p>4</p><p>3</p><p>2</p><p>1</p>",
1437            },
1438        ]);
1439
1440        layouts.extend(vec![
1441            TestLayout {
1442                name: "Replace and reorder components - before",
1443                node: html! {
1444                    <List>
1445                        <List key="comp-1"><p>{"1"}</p></List>
1446                        <List key="comp-2"><p>{"2"}</p></List>
1447                        <List key="comp-3"><p>{"3"}</p></List>
1448                    </List>
1449                },
1450                expected: "<p>1</p><p>2</p><p>3</p>",
1451            },
1452            TestLayout {
1453                name: "Replace and reorder components - after",
1454                node: html! {
1455                    <List>
1456                        <Comp id=3 key="comp-3" />
1457                        <Comp id=2 key="comp-2" />
1458                        <Comp id=1 key="comp-1" />
1459                    </List>
1460                },
1461                expected: "<p>3</p><p>2</p><p>1</p>",
1462            },
1463        ]);
1464
1465        diff_layouts(layouts);
1466    }
1467
1468    #[test]
1469    //#[should_panic(expected = "duplicate key detected: vtag at index 1")]
1470    // can't inspect panic message in wasm :/
1471    #[should_panic]
1472    fn duplicate_keys() {
1473        let mut layouts = vec![];
1474
1475        layouts.push(TestLayout {
1476            name: "A list with duplicate keys",
1477            node: html! {
1478                <>
1479                    <i key="vtag" />
1480                    <i key="vtag" />
1481                </>
1482            },
1483            expected: "<i></i><i></i>",
1484        });
1485
1486        diff_layouts(layouts);
1487    }
1488}