1
0
Jonathan Kelley 3 жил өмнө
parent
commit
0479252a5f

+ 1 - 26
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) => {

+ 138 - 56
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();
+
+        // 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();
+        }
 
-        // walk the new list, creating nodes or diffing them. if we hit an LIS, then we call "insert before"
-        let mut last_lis = 0;
+        log::debug!("LIS Indicies: {:?}", lis_sequence);
 
-        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];
+        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);
+            }
+        }
+
+        // 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;
         }
 
-        // 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,
-            },
-        });
+        // 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<ElementId> {
         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,

+ 0 - 1
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> },
 }
 

+ 7 - 1
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"),
 

+ 416 - 54
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]