From 0479252a5fba96e554dcf2422a8566e248fc6593 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 24 Aug 2021 10:01:01 -0400 Subject: [PATCH] feat: keyed diffing --- packages/core/src/childiter.rs | 27 +- packages/core/src/diff.rs | 196 +++++++++---- packages/core/src/diff_stack.rs | 1 - packages/core/src/nodes.rs | 8 +- packages/core/tests/diffing.rs | 470 ++++++++++++++++++++++++++++---- 5 files changed, 563 insertions(+), 139 deletions(-) diff --git a/packages/core/src/childiter.rs b/packages/core/src/childiter.rs index e3c520d1c..936a632be 100644 --- a/packages/core/src/childiter.rs +++ b/packages/core/src/childiter.rs @@ -48,7 +48,7 @@ impl<'a> Iterator for RealChildIterator<'a> { match &node { // We can only exit our looping when we get "real" nodes // This includes fragments and components when they're empty (have a single root) - VNode::Element(_) | VNode::Text(_) => { + VNode::Element(_) | VNode::Text(_) | VNode::Suspended(_) | VNode::Anchor(_) => { // We've recursed INTO an element/text // We need to recurse *out* of it and move forward to the next should_pop = true; @@ -70,31 +70,6 @@ impl<'a> Iterator for RealChildIterator<'a> { should_push = Some(&frag.children[subcount]); } } - // // If we get a fragment we push the next child - // VNodeKind::Fragment(frag) => { - // let subcount = *count as usize; - - // if frag.children.len() == 0 { - // should_pop = true; - // returned_node = Some(&*node); - // } - - // if subcount >= frag.children.len() { - // should_pop = true; - // } else { - // should_push = Some(&frag.children[subcount]); - // } - // } - - // Immediately abort suspended nodes - can't do anything with them yet - VNode::Suspended(node) => { - // VNodeKind::Suspended => should_pop = true, - todo!() - } - - VNode::Anchor(a) => { - todo!() - } // For components, we load their root and push them onto the stack VNode::Component(sc) => { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 53522c0b2..db5d9930b 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -190,7 +190,11 @@ impl<'bump> DiffMachine<'bump> { } DiffInstruction::PrepareMoveNode { node } => { - // + log::debug!("Preparing to move node: {:?}", node); + for el in RealChildIterator::new(node, self.vdom) { + self.mutations.push_root(el.direct_id()); + self.stack.add_child_count(1); + } } }; } @@ -211,10 +215,15 @@ impl<'bump> DiffMachine<'bump> { many: nodes_created as u32, }); } + MountType::Replace { old } => { - let root = todo!(); - self.mutations.replace_with(root, nodes_created as u32); + let mut iter = RealChildIterator::new(old, self.vdom); + let first = iter.next().unwrap(); + self.mutations + .replace_with(first.direct_id(), nodes_created as u32); + self.remove_nodes(iter); } + MountType::ReplaceByElementId { el: old } => { self.mutations.replace_with(old, nodes_created as u32); } @@ -225,15 +234,9 @@ impl<'bump> DiffMachine<'bump> { } MountType::InsertBefore { other_node } => { - let root = self.find_first_element(other_node).direct_id(); + let root = self.find_first_element_id(other_node).unwrap(); self.mutations.insert_before(root, nodes_created as u32); } - - MountType::InsertAfterFlush { other_node } => { - self.stack.push_nodes_created(0); - let root = self.find_last_element(other_node).direct_id(); - self.mutations.insert_after(root, nodes_created as u32); - } } } @@ -415,8 +418,15 @@ impl<'bump> DiffMachine<'bump> { // // This case is rather rare (typically only in non-keyed lists) if new.tag_name != old.tag_name || new.namespace != old.namespace { - todo!("element changed"); - // self.replace_node_with_node(old, new); + // maybe make this an instruction? + // issue is that we need the "vnode" but this method only has the velement + self.stack.push_nodes_created(0); + self.stack.push(DiffInstruction::Mount { + and: MountType::ReplaceByElementId { + el: old.dom_id.get().unwrap(), + }, + }); + self.create_element_node(new); return; } @@ -640,6 +650,7 @@ impl<'bump> DiffMachine<'bump> { // the change list stack is in the same state when this function returns. fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { // Handled these cases in `diff_children` before calling this function. + log::debug!("diffing non-keyed case"); debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); @@ -649,7 +660,8 @@ impl<'bump> DiffMachine<'bump> { if old.len() > new.len() { self.remove_nodes(&old[new.len()..]); - } else { + } else if new.len() > old.len() { + log::debug!("Calling create children on array differences"); self.stack.create_children( &new[old.len()..], MountType::InsertAfter { @@ -707,6 +719,11 @@ impl<'bump> DiffMachine<'bump> { Some(count) => count, None => return, }; + log::debug!( + "Left offset, right offset, {}, {}", + left_offset, + right_offset, + ); // Ok, we now hopefully have a smaller range of children in the middle // within which to re-order nodes with the same keys, remove old nodes with @@ -846,64 +863,107 @@ impl<'bump> DiffMachine<'bump> { // 4. Compute the LIS of this list // TODO: investigate if we can go without the hashset here, I can't quite tell from the LIS crate - let mut new_index_is_in_lis = FxHashSet::default(); - new_index_is_in_lis.reserve(new_index_to_old_index.len()); + // let mut new_index_is_in_lis = Vec::default(); + let mut lis_sequence = Vec::default(); + lis_sequence.reserve(new_index_to_old_index.len()); let mut predecessors = vec![0; new_index_to_old_index.len()]; let mut starts = vec![0; new_index_to_old_index.len()]; longest_increasing_subsequence::lis_with( &new_index_to_old_index, - &mut new_index_is_in_lis, + &mut lis_sequence, |a, b| a < b, &mut predecessors, &mut starts, ); - // REMEMBER: we're generating instructions. we cannot rely on anything stack related to be consistent. + // the lis comes out backwards, I think. can't quite tell + lis_sequence.sort_unstable(); - // walk the new list, creating nodes or diffing them. if we hit an LIS, then we call "insert before" - let mut last_lis = 0; + // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) + if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { + lis_sequence.pop(); + } - self.stack.push_nodes_created(0); - for (idx, new_node) in new.into_iter().enumerate().rev() { - let old_index = new_index_to_old_index[idx]; + log::debug!("LIS Indicies: {:?}", lis_sequence); + let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| { + log::debug!("applying {:?}", new_node); + + let old_index = new_index_to_old_index[new_idx]; + dbg!(new_idx, old_index); if old_index == u32::MAX as usize { - self.stack.create_node(new_node, MountType::Absorb); + stack.create_node(new_node, MountType::Absorb); } else { - // diff / move - if let Some(new_index) = new_index_is_in_lis.get(&idx) { - // take all the nodes on the stack and insert them before this one - self.stack.push(DiffInstruction::Mount { - and: MountType::InsertAfterFlush { - other_node: new_node, - }, - }); - self.stack.push(DiffInstruction::DiffNode { - new: new_node, - old: &old[old_index], - }); - last_lis = *new_index; - } else { - // this is not an LIS node, we need to diff it and move it - self.stack - .push(DiffInstruction::PrepareMoveNode { node: new_node }); - self.stack.push(DiffInstruction::DiffNode { - new: new_node, - old: &old[old_index], - }); - } + // this funciton should never take LIS indicies + stack.push(DiffInstruction::PrepareMoveNode { node: new_node }); + stack.push(DiffInstruction::DiffNode { + new: new_node, + old: &old[old_index], + }); + } + }; + + // add mount instruction for the last items not covered by the lis + let first_lis = *lis_sequence.first().unwrap(); + dbg!(first_lis); + if first_lis > 0 { + self.stack.push_nodes_created(0); + self.stack.push(DiffInstruction::Mount { + and: MountType::InsertBefore { + other_node: &new[first_lis], + }, + }); + + for (idx, new_node) in new[..first_lis].iter().enumerate().rev() { + apply(idx, new_node, &mut self.stack); } } - // There will be nodes left on top of the stack. They need to be inserted before the final - let last_node = &old[last_lis]; - self.stack.push(DiffInstruction::Mount { - and: MountType::InsertBefore { - other_node: last_node, - }, - }); + // for each spacing, generate a mount instruction + let mut lis_iter = lis_sequence.iter().rev(); + let mut last = *lis_iter.next().unwrap(); + while let Some(&next) = lis_iter.next() { + if last - next > 1 { + self.stack.push_nodes_created(0); + self.stack.push(DiffInstruction::Mount { + and: MountType::InsertBefore { + other_node: &new[last], + }, + }); + for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() { + apply(idx + next + 1, new_node, &mut self.stack); + } + } + last = next; + } + + // add mount instruction for the first items not covered by the lis + let last_lis = *lis_sequence.last().unwrap(); + log::debug!("last lis is {}, new len is {}", last_lis, new.len()); + if last_lis < (new.len() - 1) { + let after = &new[last_lis]; + dbg!(after); + self.stack.push_nodes_created(0); + self.stack.push(DiffInstruction::Mount { + and: MountType::InsertAfter { other_node: after }, + }); + + for (idx, new_node) in new[(last_lis + 1)..].iter().enumerate().rev() { + apply(idx + last_lis + 1, new_node, &mut self.stack); + } + } + + for idx in lis_sequence.iter().rev() { + let old_index = new_index_to_old_index[*idx]; + let new_node = &new[*idx]; + log::debug!("diffing {:?}", new_node); + self.stack.push(DiffInstruction::DiffNode { + new: new_node, + old: &old[old_index], + }); + } } // ===================== @@ -933,17 +993,13 @@ impl<'bump> DiffMachine<'bump> { } } - fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> { + fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option { let mut search_node = Some(vnode); loop { let node = search_node.take().unwrap(); match &node { // the ones that have a direct id - VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => { - break node - } - VNode::Fragment(frag) => { search_node = Some(&frag.children[0]); } @@ -952,9 +1008,35 @@ impl<'bump> DiffMachine<'bump> { let scope = self.vdom.get_scope(scope_id).unwrap(); search_node = Some(scope.root()); } + VNode::Text(t) => break t.dom_id.get(), + VNode::Element(t) => break t.dom_id.get(), + VNode::Suspended(t) => break t.node.get(), + VNode::Anchor(t) => break t.dom_id.get(), } } } + // fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> { + // let mut search_node = Some(vnode); + + // loop { + // let node = search_node.take().unwrap(); + // match &node { + // // the ones that have a direct id + // VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => { + // break node + // } + + // VNode::Fragment(frag) => { + // search_node = Some(&frag.children[0]); + // } + // VNode::Component(el) => { + // let scope_id = el.ass_scope.get().unwrap(); + // let scope = self.vdom.get_scope(scope_id).unwrap(); + // search_node = Some(scope.root()); + // } + // } + // } + // } fn replace_and_create_many_with_one( &mut self, diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index 6876faa96..9819412d8 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -39,7 +39,6 @@ pub enum MountType<'a> { Replace { old: &'a VNode<'a> }, ReplaceByElementId { el: ElementId }, InsertAfter { other_node: &'a VNode<'a> }, - InsertAfterFlush { other_node: &'a VNode<'a> }, InsertBefore { other_node: &'a VNode<'a> }, } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 14d21a5a7..8671cf45d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -651,7 +651,13 @@ impl Debug for NodeFactory<'_> { impl Debug for VNode<'_> { fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match &self { - VNode::Element(el) => write!(s, "VElement {{ name: {} }}", el.tag_name), + VNode::Element(el) => { + // + s.debug_struct("VElement") + .field("name", &el.tag_name) + .field("key", &el.key) + .finish() + } VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text), VNode::Anchor(a) => write!(s, "VAnchor"), diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index a4ce42240..35203f608 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -1,7 +1,7 @@ //! Diffing Tests //! ------------- //! -//! These should always compile and run, but the result is not validated for each test. +//! These should always compile and run, but the result is not validat root: (), m: () ed for each test. //! TODO: Validate the results beyond visual inspection. use bumpalo::Bump; @@ -13,6 +13,8 @@ use dioxus::{ use dioxus_core as dioxus; use dioxus_html as dioxus_elements; use futures_util::FutureExt; +mod test_logging; +use DomEdit::*; struct TestDom { bump: Bump, @@ -20,6 +22,7 @@ struct TestDom { } impl TestDom { fn new() -> TestDom { + test_logging::set_up_logging(); let bump = Bump::new(); let resources = SharedResources::new(); TestDom { bump, resources } @@ -106,28 +109,70 @@ fn diffing_works() {} #[test] fn html_and_rsx_generate_the_same_output() { let dom = TestDom::new(); - let edits = dom.lazy_diff( + let (create, change) = dom.lazy_diff( rsx! ( div { "Hello world" } ), rsx! ( div { "Goodbye world" } ), ); - dbg!(edits); + assert_eq!( + create.edits, + [ + CreateElement { id: 0, tag: "div" }, + CreateTextNode { + id: 1, + text: "Hello world" + }, + AppendChildren { many: 1 }, + AppendChildren { many: 1 }, + ] + ); + + assert_eq!( + change.edits, + [ + PushRoot { id: 1 }, + SetText { + text: "Goodbye world" + }, + PopRoot + ] + ); } /// Should result in 3 elements on the stack #[test] fn fragments_create_properly() { let dom = TestDom::new(); - let Mutations { edits, noderefs } = dom.create(rsx! { + + let create = dom.create(rsx! { div { "Hello a" } div { "Hello b" } div { "Hello c" } }); - assert!(&edits[0].is("CreateElement")); - assert!(&edits[3].is("CreateElement")); - assert!(&edits[6].is("CreateElement")); - assert_eq!(*edits.last().unwrap(), DomEdit::AppendChildren { many: 3 }); - dbg!(edits); + assert_eq!( + create.edits, + [ + CreateElement { id: 0, tag: "div" }, + CreateTextNode { + id: 1, + text: "Hello a" + }, + AppendChildren { many: 1 }, + CreateElement { id: 2, tag: "div" }, + CreateTextNode { + id: 3, + text: "Hello b" + }, + AppendChildren { many: 1 }, + CreateElement { id: 4, tag: "div" }, + CreateTextNode { + id: 5, + text: "Hello c" + }, + AppendChildren { many: 1 }, + AppendChildren { many: 3 }, + ] + ); } /// Should result in the creation of an anchor (placeholder) and then a replacewith @@ -138,8 +183,19 @@ fn empty_fragments_create_anchors() { let left = rsx!({ (0..0).map(|f| rsx! { div {}}) }); let right = rsx!({ (0..1).map(|f| rsx! { div {}}) }); - let edits = dom.lazy_diff(left, right); - dbg!(edits); + let (create, change) = dom.lazy_diff(left, right); + + assert_eq!( + create.edits, + [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + ); + assert_eq!( + change.edits, + [ + CreateElement { id: 1, tag: "div" }, + ReplaceWith { m: 1, root: 0 } + ] + ); } /// Should result in the creation of an anchor (placeholder) and then a replacewith m=5 @@ -150,8 +206,22 @@ fn empty_fragments_create_many_anchors() { let left = rsx!({ (0..0).map(|f| rsx! { div {}}) }); let right = rsx!({ (0..5).map(|f| rsx! { div {}}) }); - let edits = dom.lazy_diff(left, right); - dbg!(edits); + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + create.edits, + [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + ); + assert_eq!( + change.edits, + [ + CreateElement { id: 1, tag: "div" }, + CreateElement { id: 2, tag: "div" }, + CreateElement { id: 3, tag: "div" }, + CreateElement { id: 4, tag: "div" }, + CreateElement { id: 5, tag: "div" }, + ReplaceWith { m: 5, root: 0 } + ] + ); } /// Should result in the creation of an anchor (placeholder) and then a replacewith @@ -162,15 +232,40 @@ fn empty_fragments_create_anchors_with_many_children() { let left = rsx!({ (0..0).map(|f| rsx! { div {} }) }); let right = rsx!({ - (0..5).map(|f| { - rsx! { div { "hello" }} + (0..3).map(|f| { + rsx! { div { "hello: {f}" }} }) }); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); - let last_edit = edits.1.edits.last().unwrap(); - assert!(last_edit.is("ReplaceWith")); + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + create.edits, + [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + ); + assert_eq!( + change.edits, + [ + CreateElement { id: 1, tag: "div" }, + CreateTextNode { + text: "hello: 0", + id: 2 + }, + AppendChildren { many: 1 }, + CreateElement { id: 3, tag: "div" }, + CreateTextNode { + text: "hello: 1", + id: 4 + }, + AppendChildren { many: 1 }, + CreateElement { id: 5, tag: "div" }, + CreateTextNode { + text: "hello: 2", + id: 6 + }, + AppendChildren { many: 1 }, + ReplaceWith { m: 3, root: 0 } + ] + ); } /// Should result in every node being pushed and then replaced with an anchor @@ -185,8 +280,35 @@ fn many_items_become_fragment() { }); let right = rsx!({ (0..0).map(|f| rsx! { div {} }) }); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + create.edits, + [ + CreateElement { id: 0, tag: "div" }, + CreateTextNode { + text: "hello", + id: 1 + }, + AppendChildren { many: 1 }, + CreateElement { id: 2, tag: "div" }, + CreateTextNode { + text: "hello", + id: 3 + }, + AppendChildren { many: 1 }, + AppendChildren { many: 2 }, + ] + ); + + // hmmmmmmmmm worried about reusing IDs that we shouldnt be + assert_eq!( + change.edits, + [ + Remove { root: 2 }, + CreatePlaceholder { id: 4 }, + ReplaceWith { root: 0, m: 1 }, + ] + ); } /// Should result in no edits @@ -205,9 +327,8 @@ fn two_equal_fragments_are_equal() { }) }); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); - assert!(edits.1.edits.is_empty()); + let (create, change) = dom.lazy_diff(left, right); + assert!(change.edits.is_empty()); } /// Should result the creation of more nodes appended after the old last node @@ -216,11 +337,11 @@ fn two_fragments_with_differrent_elements_are_differet() { let dom = TestDom::new(); let left = rsx!( - {(0..2).map(|f| {rsx! { div { }}})} + { (0..2).map(|f| rsx! { div { }} ) } p {} ); let right = rsx!( - {(0..5).map(|f| {rsx! { h1 { }}})} + { (0..5).map(|f| rsx! (h1 { }) ) } p {} ); @@ -242,8 +363,31 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { p {} ); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + create.edits, + [ + CreateElement { id: 0, tag: "div" }, + CreateElement { id: 1, tag: "div" }, + CreateElement { id: 2, tag: "div" }, + CreateElement { id: 3, tag: "div" }, + CreateElement { id: 4, tag: "div" }, + CreateElement { id: 5, tag: "p" }, + AppendChildren { many: 6 }, + ] + ); + assert_eq!( + change.edits, + [ + Remove { root: 2 }, + Remove { root: 3 }, + Remove { root: 4 }, + CreateElement { id: 6, tag: "h1" }, + ReplaceWith { root: 0, m: 1 }, + CreateElement { id: 7, tag: "h1" }, + ReplaceWith { root: 1, m: 1 }, + ] + ); } /// Should result in multiple nodes destroyed - with no changes @@ -260,26 +404,25 @@ fn two_fragments_with_same_elements_are_differet() { p {} ); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); -} - -// Similar test from above, but with extra child nodes -#[test] -fn two_fragments_with_same_elements_are_differet_shorter() { - let dom = TestDom::new(); - - let left = rsx!( - {(0..5).map(|f| {rsx! { div { }}})} - p {"e"} + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + create.edits, + [ + CreateElement { id: 0, tag: "div" }, + CreateElement { id: 1, tag: "div" }, + CreateElement { id: 2, tag: "p" }, + AppendChildren { many: 3 }, + ] ); - let right = rsx!( - {(0..2).map(|f| {rsx! { div { }}})} - p {"e"} + assert_eq!( + change.edits, + [ + CreateElement { id: 3, tag: "div" }, + CreateElement { id: 4, tag: "div" }, + CreateElement { id: 5, tag: "div" }, + InsertAfter { root: 1, n: 3 }, + ] ); - - let edits = dom.lazy_diff(left, right); - dbg!(&edits); } /// should result in the removal of elements @@ -296,8 +439,11 @@ fn keyed_diffing_order() { p {"e"} ); - let edits = dom.lazy_diff(left, right); - dbg!(&edits); + let (create, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },] + ); } #[test] @@ -313,23 +459,239 @@ fn fragment_keys() { fn keyed_diffing_out_of_order() { let dom = TestDom::new(); - // 0, 1, 2, 3, 4, 5, 6, 7, 8, let left = rsx!({ - (0..3).chain(3..6).chain(6..9).map(|f| { + [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| { rsx! { div { key: "{f}" }} }) }); - // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9 let right = rsx!({ - (0..3).chain((3..7).rev()).chain(7..10).map(|f| { + [0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| { rsx! { div { key: "{f}" }} }) }); - // LIS: 3, 7, 8, let edits = dom.lazy_diff(left, right); - dbg!(&edits); + dbg!(&edits.1); +} + +/// Should result in moves only +#[test] +fn keyed_diffing_out_of_order_adds() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [ + PushRoot { id: 4 }, + PushRoot { id: 3 }, + InsertBefore { n: 2, root: 0 } + ] + ); +} +/// Should result in moves onl +#[test] +fn keyed_diffing_out_of_order_adds_2() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [ + PushRoot { id: 3 }, + PushRoot { id: 4 }, + InsertBefore { n: 2, root: 0 } + ] + ); +} + +/// Should result in moves onl +#[test] +fn keyed_diffing_out_of_order_adds_3() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [ + PushRoot { id: 4 }, + PushRoot { id: 3 }, + InsertBefore { n: 2, root: 1 } + ] + ); +} + +/// Should result in moves onl +#[test] +fn keyed_diffing_out_of_order_adds_4() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [ + PushRoot { id: 4 }, + PushRoot { id: 3 }, + InsertBefore { n: 2, root: 2 } + ] + ); +} + +/// Should result in moves onl +#[test] +fn keyed_diffing_out_of_order_adds_5() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [PushRoot { id: 4 }, InsertBefore { n: 1, root: 3 }] + ); +} + +#[test] +fn keyed_diffing_additions() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + assert_eq!( + change.edits, + [ + CreateElement { id: 5, tag: "div" }, + CreateElement { id: 6, tag: "div" }, + InsertAfter { n: 2, root: 4 } + ] + ); +} + +#[test] +fn keyed_diffing_additions_and_moves_on_ends() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| { + // [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + dbg!(change); + // assert_eq!( + // change.edits, + // [ + // CreateElement { id: 5, tag: "div" }, + // CreateElement { id: 6, tag: "div" }, + // InsertAfter { n: 2, root: 4 } + // ] + // ); +} + +#[test] +fn keyed_diffing_additions_and_moves_in_middle() { + let dom = TestDom::new(); + + let left = rsx!({ + [/**/ 4, 5, 6, 7 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let right = rsx!({ + [/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| { + rsx! { div { key: "{f}" }} + }) + }); + + let (_, change) = dom.lazy_diff(left, right); + dbg!(change); + // assert_eq!( + // change.edits, + // [ + // CreateElement { id: 5, tag: "div" }, + // CreateElement { id: 6, tag: "div" }, + // InsertAfter { n: 2, root: 4 } + // ] + // ); } #[test]