1use 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#[derive(Debug)]
18pub(super) struct BList {
19 rev_children: Vec<BNode>,
21 fully_keyed: bool,
23 key: Option<Key>,
24}
25
26impl VList {
27 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#[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 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 fn shift(&self, bundle: &BNode) {
73 bundle.shift(self.parent, self.slot.clone());
74 }
75
76 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 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}
91struct 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 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 pub const fn new() -> BList {
134 BList {
135 rev_children: vec![],
136 fully_keyed: true,
137 key: None,
138 }
139 }
140
141 pub fn key(&self) -> Option<&Key> {
143 self.key.as_ref()
144 }
145
146 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 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 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 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 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 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 matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
230 return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, rev_bundles);
232 }
233
234 let mut lefts = left_vdoms;
236 let mut writer = NodeWriter {
237 root,
238 parent_scope,
239 parent,
240 slot,
241 };
242 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 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 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 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 #[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 let mut replacements: Vec<BNode> = Vec::with_capacity((matching_len_start..lefts_to).len());
302 let mut barrier_idx = 0; 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..) .rev()
327 {
328 let ancestor = spare_bundles.take(key!(l));
329 if let Some(run) = current_run.as_mut() {
331 if let Some(KeyedEntry(idx, _)) = ancestor {
332 if idx < run.end_idx {
334 let run_length = replacements.len() - run.start_idx;
338 let estimated_skipped_nodes = run.end_idx - idx.max(barrier_idx);
342 if 2 * run_length > estimated_skipped_nodes {
345 barrier_idx = 1 + run.end_idx;
347 } else {
348 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 Some(run) => run.end_idx = idx,
362 None => match idx.cmp(&barrier_idx) {
363 Ordering::Equal => barrier_idx += 1,
366 Ordering::Less => writer.shift(&r_bundle),
368 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 let (next_writer, bundle) = writer.add(l);
383 writer = next_writer;
384 bundle
385 };
386 replacements.push(bundle);
387 }
388 drop(spliced_middle);
390 rev_bundles.splice(matching_len_end..matching_len_end, replacements);
391
392 for KeyedEntry(_, r) in spare_bundles.drain() {
394 test_log!("removing: {:?}", r);
395 r.detach(root, parent, false);
396 }
397
398 let rights_to = rev_bundles.len() - matching_len_start;
400 for (l, r) in lefts
401 .drain(..) .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 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 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]
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}