浏览代码

chore: more tests passing

Jonathan Kelley 2 年之前
父节点
当前提交
565df11f7b

+ 32 - 11
packages/core/src/diff.rs

@@ -605,7 +605,7 @@ impl<'b: 'static> VirtualDom {
                 // only valid of the if there are no trailing elements
                 // self.create_and_append_children(new);
 
-                todo!()
+                todo!("we should never be appending - just creating N");
             }
             return;
         }
@@ -615,8 +615,7 @@ impl<'b: 'static> VirtualDom {
         for child in old {
             let key = child.key.unwrap();
             if !shared_keys.contains(&key) {
-                todo!("remove node");
-                // self.remove_nodes( [child]);
+                self.remove_node(child);
             }
         }
 
@@ -719,13 +718,6 @@ impl<'b: 'static> VirtualDom {
         }
     }
 
-    fn insert_after(&mut self, node: &'b VNode<'b>, nodes_created: usize) {
-        todo!()
-    }
-    fn insert_before(&mut self, node: &'b VNode<'b>, nodes_created: usize) {
-        todo!()
-    }
-
     fn replace_node_with_on_stack(&mut self, old: &'b VNode<'b>, m: usize) {
         todo!()
     }
@@ -775,7 +767,36 @@ impl<'b: 'static> VirtualDom {
 
     /// Push all the real nodes on the stack
     fn push_all_real_nodes(&mut self, node: &VNode) -> usize {
-        todo!()
+        let mut onstack = 0;
+
+        for (idx, _) in node.template.roots.iter().enumerate() {
+            match node.dynamic_root(idx) {
+                Some(Text(t)) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.id.get() });
+                    onstack += 1;
+                }
+                Some(Fragment(VFragment::Empty(t))) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.get() });
+                    onstack += 1;
+                }
+                Some(Fragment(VFragment::NonEmpty(t))) => {
+                    for node in *t {
+                        onstack += self.push_all_real_nodes(node);
+                    }
+                }
+                Some(Component(comp)) => {
+                    todo!()
+                }
+                None => {
+                    self.mutations.push(Mutation::PushRoot {
+                        id: node.root_ids[idx].get(),
+                    });
+                    onstack += 1;
+                }
+            };
+        }
+
+        onstack
     }
 
     fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {

+ 4 - 0
packages/core/src/mutations.rs

@@ -189,4 +189,8 @@ pub enum Mutation<'a> {
     Remove {
         id: ElementId,
     },
+
+    PushRoot {
+        id: ElementId,
+    },
 }

+ 2 - 0
packages/core/src/virtual_dom.rs

@@ -483,6 +483,7 @@ impl VirtualDom {
     ///
     /// apply_edits(edits);
     /// ```
+    #[must_use]
     pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
         match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
             // Rebuilding implies we append the created elements to the root
@@ -500,6 +501,7 @@ impl VirtualDom {
 
     /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
     /// suspended subtrees.
+    #[must_use]
     pub fn render_immediate(&mut self) -> Mutations {
         // Build a waker that won't wake up since our deadline is already expired when it's polled
         let waker = futures_util::task::noop_waker();

+ 29 - 0
packages/core/tests/create_element.rs

@@ -0,0 +1,29 @@
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+
+#[test]
+fn multiroot() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div { "Hello a" }
+            div { "Hello b" }
+            div { "Hello c" }
+        })
+    });
+
+    assert_eq!(
+        dom.rebuild().santize().template_mutations,
+        [
+            CreateElement { name: "div" },
+            CreateStaticText { value: "Hello a" },
+            AppendChildren { m: 1 },
+            CreateElement { name: "div" },
+            CreateStaticText { value: "Hello b" },
+            AppendChildren { m: 1 },
+            CreateElement { name: "div" },
+            CreateStaticText { value: "Hello c" },
+            AppendChildren { m: 1 },
+            SaveTemplate { name: "template", m: 3 }
+        ]
+    )
+}

+ 192 - 693
packages/core/tests/diff_keyed_list.rs

@@ -6,661 +6,298 @@
 //!
 //! It does not validated that component lifecycles work properly. This is done in another test file.
 
-use dioxus::{core_macro::rsx_without_templates, prelude::*};
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
 
-fn new_dom() -> VirtualDom {
-    VirtualDom::new(|cx| cx.render(rsx_without_templates!("hi")))
-}
-
-use dioxus_core::DomEdit::*;
-
-/// Should push the text node onto the stack and modify it
-#[test]
-fn html_and_rsx_generate_the_same_output() {
-    let dom = new_dom();
-    let (create, change) = dom.diff_lazynodes(
-        rsx_without_templates! ( div { "Hello world" } ),
-        rsx_without_templates! ( div { "Goodbye world" } ),
-    );
-    assert_eq!(
-        create.edits,
-        [
-            CreateElement { root: Some(1,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(2,), text: "Hello world" },
-            AppendChildren { root: Some(1,), children: vec![2] },
-            AppendChildren { root: Some(0,), children: vec![1] },
-        ]
-    );
-
-    assert_eq!(
-        change.edits,
-        [SetText { root: Some(2,), text: "Goodbye world" },]
-    );
-}
-
-/// Should result in 3 elements on the stack
-#[test]
-fn fragments_create_properly() {
-    let dom = new_dom();
-
-    let create = dom.create_vnodes(rsx_without_templates! {
-        div { "Hello a" }
-        div { "Hello b" }
-        div { "Hello c" }
-    });
-
-    assert_eq!(
-        create.edits,
-        [
-            CreateElement { root: Some(1,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(2,), text: "Hello a" },
-            AppendChildren { root: Some(1,), children: vec![2,] },
-            CreateElement { root: Some(3,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(4,), text: "Hello b" },
-            AppendChildren { root: Some(3,), children: vec![4,] },
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(6,), text: "Hello c" },
-            AppendChildren { root: Some(5,), children: vec![6,] },
-            AppendChildren { root: Some(0,), children: vec![1, 3, 5,] },
-        ]
-    );
-}
-
-/// Should result in the creation of an anchor (placeholder) and then a replacewith
-#[test]
-fn empty_fragments_create_anchors() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
-    let right = rsx_without_templates!({ (0..1).map(|_f| rsx_without_templates! { div {}}) });
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        create.edits,
-        [
-            CreatePlaceholder { root: Some(1,) },
-            AppendChildren { root: Some(0,), children: vec![1,] },
-        ]
-    );
-    assert_eq!(
-        change.edits,
-        [
-            CreateElement { root: Some(2,), tag: "div", children: 0 },
-            ReplaceWith { root: Some(1,), nodes: vec![2,] },
-        ]
-    );
-}
-
-/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
-#[test]
-fn empty_fragments_create_many_anchors() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
-    let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) });
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        create.edits,
-        [
-            CreatePlaceholder { root: Some(1,) },
-            AppendChildren { root: Some(0,), children: vec![1,] },
-        ]
-    );
-
-    assert_eq!(
-        change.edits,
-        [
-            CreateElement { root: Some(2,), tag: "div", children: 0 },
-            CreateElement { root: Some(3,), tag: "div", children: 0 },
-            CreateElement { root: Some(4,), tag: "div", children: 0 },
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            CreateElement { root: Some(6,), tag: "div", children: 0 },
-            ReplaceWith { root: Some(1,), nodes: vec![2, 3, 4, 5, 6,] },
-        ]
-    );
-}
-
-/// Should result in the creation of an anchor (placeholder) and then a replacewith
-/// Includes child nodes inside the fragment
-#[test]
-fn empty_fragments_create_anchors_with_many_children() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
-    let right = rsx_without_templates!({
-        (0..3).map(|f| {
-            rsx_without_templates! { div { "hello: {f}" }}
-        })
-    });
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        create.edits,
-        [
-            CreatePlaceholder { root: Some(1,) },
-            AppendChildren { root: Some(0,), children: vec![1,] },
-        ]
-    );
-
-    assert_eq!(
-        change.edits,
-        [
-            CreateElement { root: Some(2,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(3,), text: "hello: 0" },
-            AppendChildren { root: Some(2,), children: vec![3,] },
-            CreateElement { root: Some(4,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(5,), text: "hello: 1" },
-            AppendChildren { root: Some(4,), children: vec![5,] },
-            CreateElement { root: Some(6,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(7,), text: "hello: 2" },
-            AppendChildren { root: Some(6,), children: vec![7,] },
-            ReplaceWith { root: Some(1,), nodes: vec![2, 4, 6,] },
-        ]
-    );
-}
-
-/// Should result in every node being pushed and then replaced with an anchor
+/// Should result in moves, but not removals or additions
 #[test]
-fn many_items_become_fragment() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        (0..2).map(|_| {
-            rsx_without_templates! { div { "hello" }}
-        })
+fn keyed_diffing_out_of_order() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9],
+            1 => &[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
-    let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
-
-    let (create, change) = dom.diff_lazynodes(left, right);
 
     assert_eq!(
-        create.edits,
+        dom.rebuild().santize().edits,
         [
-            CreateElement { root: Some(1,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(2,), text: "hello" },
-            AppendChildren { root: Some(1,), children: vec![2,] },
-            CreateElement { root: Some(3,), tag: "div", children: 0 },
-            CreateTextNode { root: Some(4,), text: "hello" },
-            AppendChildren { root: Some(3,), children: vec![4,] },
-            AppendChildren { root: Some(0,), children: vec![1, 3,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
+            AppendChildren { m: 10 },
         ]
     );
 
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().edits,
         [
-            CreatePlaceholder { root: Some(5,) },
-            ReplaceWith { root: Some(1,), nodes: vec![5,] },
-            Remove { root: Some(3,) },
+            PushRoot { id: ElementId(7,) },
+            InsertBefore { id: ElementId(5,), m: 1 },
         ]
-    );
+    )
 }
 
-/// Should result in no edits
+/// Should result in moves only
 #[test]
-fn two_equal_fragments_are_equal() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        (0..2).map(|_| {
-            rsx_without_templates! { div { "hello" }}
-        })
-    });
-    let right = rsx_without_templates!({
-        (0..2).map(|_| {
-            rsx_without_templates! { div { "hello" }}
-        })
+fn keyed_diffing_out_of_order_adds() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 8, 7, 4, 5, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let (_create, change) = dom.diff_lazynodes(left, right);
-    assert!(change.edits.is_empty());
-}
-
-/// Should result the creation of more nodes appended after the old last node
-#[test]
-fn two_fragments_with_differrent_elements_are_differet() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!(
-        { (0..2).map(|_| rsx_without_templates! { div {  }} ) }
-        p {}
-    );
-    let right = rsx_without_templates!(
-        { (0..5).map(|_| rsx_without_templates! (h1 {  }) ) }
-        p {}
-    );
-
-    let (_create, changes) = dom.diff_lazynodes(left, right);
-    assert_eq!(
-        changes.edits,
-        [
-            CreateElement { root: Some(4,), tag: "h1", children: 0 },
-            CreateElement { root: Some(5,), tag: "h1", children: 0 },
-            CreateElement { root: Some(6,), tag: "h1", children: 0 },
-            InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
-            CreateElement { root: Some(7,), tag: "h1", children: 0 },
-            ReplaceWith { root: Some(1,), nodes: vec![7,] }, // notice how 1 gets re-used
-            CreateElement { root: Some(1,), tag: "h1", children: 0 },
-            ReplaceWith { root: Some(2,), nodes: vec![1,] },
-        ]
-    );
-}
-
-/// Should result in multiple nodes destroyed - with changes to the first nodes
-#[test]
-fn two_fragments_with_differrent_elements_are_differet_shorter() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!(
-        {(0..5).map(|f| {rsx_without_templates! { div {  }}})}
-        p {}
-    );
-    let right = rsx_without_templates!(
-        {(0..2).map(|f| {rsx_without_templates! { h1 {  }}})}
-        p {}
-    );
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        create.edits,
-        [
-            CreateElement { root: Some(1,), tag: "div", children: 0 },
-            CreateElement { root: Some(2,), tag: "div", children: 0 },
-            CreateElement { root: Some(3,), tag: "div", children: 0 },
-            CreateElement { root: Some(4,), tag: "div", children: 0 },
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            CreateElement { root: Some(6,), tag: "p", children: 0 },
-            AppendChildren { root: Some(0,), children: vec![1, 2, 3, 4, 5, 6,] },
-        ]
-    );
+    _ = dom.rebuild();
 
-    // note: key reuse is always the last node that got used
-    // slab maintains a linked list, essentially
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().edits,
         [
-            Remove { root: Some(3,) },
-            Remove { root: Some(4,) },
-            Remove { root: Some(5,) },
-            CreateElement { root: Some(5,), tag: "h1", children: 0 }, // 5 gets reused
-            ReplaceWith { root: Some(1,), nodes: vec![5,] },          // 1 gets deleted
-            CreateElement { root: Some(1,), tag: "h1", children: 0 }, // 1 gets reused
-            ReplaceWith { root: Some(2,), nodes: vec![1,] },
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(1,), m: 2 },
         ]
-    );
-}
-
-/// Should result in multiple nodes destroyed - with no changes
-#[test]
-fn two_fragments_with_same_elements_are_differet() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!(
-        {(0..2).map(|f| rsx_without_templates! { div {  }})}
-        p {}
-    );
-    let right = rsx_without_templates!(
-        {(0..5).map(|f| rsx_without_templates! { div {  }})}
-        p {}
-    );
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        create.edits,
-        [
-            CreateElement { root: Some(1,), tag: "div", children: 0 },
-            CreateElement { root: Some(2,), tag: "div", children: 0 },
-            CreateElement { root: Some(3,), tag: "p", children: 0 },
-            AppendChildren { root: Some(0,), children: vec![1, 2, 3,] },
-        ]
-    );
-    assert_eq!(
-        change.edits,
-        [
-            CreateElement { root: Some(4,), tag: "div", children: 0 },
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            CreateElement { root: Some(6,), tag: "div", children: 0 },
-            InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
-        ]
-    );
-}
-
-/// should result in the removal of elements
-#[test]
-fn keyed_diffing_order() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!(
-        {(0..5).map(|f| {rsx_without_templates! { div { key: "{f}"  }}})}
-        p {"e"}
-    );
-    let right = rsx_without_templates!(
-        {(0..2).map(|f| rsx_without_templates! { div { key: "{f}" }})}
-        p {"e"}
-    );
-
-    let (create, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        change.edits,
-        [
-            Remove { root: Some(3,) },
-            Remove { root: Some(4,) },
-            Remove { root: Some(5,) },
-        ]
-    );
-}
-
-/// Should result in moves, but not removals or additions
-#[test]
-fn keyed_diffing_out_of_order() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        [0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let (_, changes) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        changes.edits,
-        [InsertBefore { root: Some(5,), nodes: vec![7,] },]
-    );
+    )
 }
 
 /// Should result in moves only
 #[test]
-fn keyed_diffing_out_of_order_adds() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        [/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let (_, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        change.edits,
-        [InsertBefore { root: Some(1,), nodes: vec![5, 4,] },]
-    );
-}
-/// Should result in moves only
-#[test]
-fn keyed_diffing_out_of_order_adds_2() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        [/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let (_, change) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        change.edits,
-        [InsertBefore { root: Some(1,), nodes: vec![4, 5,] },]
-    );
-}
-
-/// Should result in moves onl
-#[test]
 fn keyed_diffing_out_of_order_adds_3() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 8, 7, 5, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let (_, change) = dom.diff_lazynodes(left, right);
+    _ = dom.rebuild();
 
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
-        [InsertBefore { root: Some(2,), nodes: vec![5, 4,] },]
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(2,), m: 2 },
+        ]
     );
 }
 
 /// Should result in moves onl
 #[test]
 fn keyed_diffing_out_of_order_adds_4() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        [/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 8, 7, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let (_, change) = dom.diff_lazynodes(left, right);
+    _ = dom.rebuild();
 
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
-        [InsertBefore { root: Some(3), nodes: vec![5, 4,] },]
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(3,), m: 2 },
+        ]
     );
 }
 
 /// Should result in moves onl
 #[test]
 fn keyed_diffing_out_of_order_adds_5() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 6, 8, 7 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
+    _ = dom.rebuild();
 
-    let (_, change) = dom.diff_lazynodes(left, right);
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
-        [InsertBefore { root: Some(4), nodes: vec![5] }]
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            InsertBefore { id: ElementId(4,), m: 1 },
+        ]
     );
 }
 
+/// Should result in moves onl
 #[test]
 fn keyed_diffing_additions() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 6, 7, 8, 9, 10 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
-
-    let (_, change) = dom.diff_lazynodes(left, right);
+    _ = dom.rebuild();
 
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().santize().edits,
         [
-            CreateElement { root: Some(6,), tag: "div", children: 0 },
-            CreateElement { root: Some(7,), tag: "div", children: 0 },
-            InsertAfter { root: Some(5,), nodes: vec![6, 7,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            InsertAfter { id: ElementId(5), m: 2 }
         ]
     );
 }
 
 #[test]
 fn keyed_diffing_additions_and_moves_on_ends() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7 /**/],
+            1 => &[/**/ 7, 4, 5, 6, 11, 12 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
+    _ = dom.rebuild();
 
-    let (_, change) = dom.diff_lazynodes(left, right);
-    println!("{:?}", change);
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().santize().edits,
         [
             // create 11, 12
-            CreateElement { root: Some(5), tag: "div", children: 0 },
-            CreateElement { root: Some(6), tag: "div", children: 0 },
-            InsertAfter { root: Some(3), nodes: vec![5, 6] },
-            // // move 7 to the front
-            InsertBefore { root: Some(1), nodes: vec![4] }
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            InsertAfter { id: ElementId(3), m: 2 },
+            // move 7 to the front
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1), m: 1 }
         ]
     );
 }
 
 #[test]
 fn keyed_diffing_additions_and_moves_in_middle() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 1, 2, 3, 4 /**/],
+            1 => &[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
+    _ = dom.rebuild();
 
     // LIS: 4, 5, 6
-    let (_, change) = dom.diff_lazynodes(left, right);
-    println!("{:#?}", change);
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().santize().edits,
         [
             // create 5, 6
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            CreateElement { root: Some(6,), tag: "div", children: 0 },
-            InsertBefore { root: Some(3,), nodes: vec![5, 6,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            InsertBefore { id: ElementId(3), m: 2 },
             // create 7, 8
-            CreateElement { root: Some(7,), tag: "div", children: 0 },
-            CreateElement { root: Some(8,), tag: "div", children: 0 },
-            InsertBefore { root: Some(2,), nodes: vec![7, 8,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(8) },
+            InsertBefore { id: ElementId(2), m: 2 },
             // move 7
-            InsertBefore { root: Some(1,), nodes: vec![4,] },
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1), m: 1 }
         ]
     );
 }
 
 #[test]
 fn controlled_keyed_diffing_out_of_order() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [4, 5, 6, 7].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}" }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[4, 5, 6, 7],
+            1 => &[0, 5, 9, 6, 4],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [0, 5, 9, 6, 4].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}" }}
-        })
-    });
+    _ = dom.rebuild();
 
     // LIS: 5, 6
-    let (_, changes) = dom.diff_lazynodes(left, right);
-    println!("{:#?}", &changes);
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        changes.edits,
+        dom.render_immediate().santize().edits,
         [
             // remove 7
-            Remove { root: Some(4,) },
+            Remove { id: ElementId(4,) },
             // move 4 to after 6
-            InsertAfter { root: Some(3,), nodes: vec![1,] },
+            PushRoot { id: ElementId(1) },
+            InsertAfter { id: ElementId(3,), m: 1 },
             // create 9 and insert before 6
-            CreateElement { root: Some(4,), tag: "div", children: 0 },
-            InsertBefore { root: Some(3,), nodes: vec![4,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            InsertBefore { id: ElementId(3,), m: 1 },
             // create 0 and insert before 5
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            InsertBefore { root: Some(2,), nodes: vec![5,] },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            InsertBefore { id: ElementId(2,), m: 1 },
         ]
     );
 }
 
 #[test]
 fn controlled_keyed_diffing_out_of_order_max_test() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        [0, 1, 2, 3, 4].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[0, 1, 2, 3, 4],
+            1 => &[3, 0, 1, 10, 2],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let right = rsx_without_templates!({
-        [3, 0, 1, 10, 2].iter().map(|f| {
-            rsx_without_templates! { div { key: "{f}"  }}
-        })
-    });
+    _ = dom.rebuild();
 
-    let (_, changes) = dom.diff_lazynodes(left, right);
-    println!("{:#?}", &changes);
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        changes.edits,
+        dom.render_immediate().santize().edits,
         [
-            Remove { root: Some(5,) },
-            CreateElement { root: Some(5,), tag: "div", children: 0 },
-            InsertBefore { root: Some(3,), nodes: vec![5,] },
-            InsertBefore { root: Some(1,), nodes: vec![4,] },
+            Remove { id: ElementId(5,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            InsertBefore { id: ElementId(3,), m: 1 },
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1,), m: 1 },
         ]
     );
 }
@@ -669,163 +306,25 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 // just making sure it doesnt happen in the core implementation
 #[test]
 fn remove_list() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        (0..10).rev().take(5).map(|i| {
-            rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        (0..10).rev().take(2).map(|i| {
-            rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
-        })
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[9, 8, 7, 6, 5],
+            1 => &[9, 8],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    let (create, changes) = dom.diff_lazynodes(left, right);
-
-    // dbg!(create);
-    // dbg!(changes);
-
-    assert_eq!(
-        changes.edits,
-        // remove 5, 4, 3
-        [
-            Remove { root: Some(3) },
-            Remove { root: Some(4) },
-            Remove { root: Some(5) }
-        ]
-    );
-}
-
-// noticed some weird behavior in the desktop interpreter
-// just making sure it doesnt happen in the core implementation
-#[test]
-fn remove_list_nokeyed() {
-    let dom = new_dom();
-
-    let left = rsx_without_templates!({
-        (0..10).rev().take(5).map(|i| {
-            rsx_without_templates! { Fragment { "{i}" }}
-        })
-    });
-
-    let right = rsx_without_templates!({
-        (0..10).rev().take(2).map(|i| {
-            rsx_without_templates! { Fragment { "{i}" }}
-        })
-    });
-
-    let (create, changes) = dom.diff_lazynodes(left, right);
-
-    assert_eq!(
-        changes.edits,
-        [
-            // remove 5, 4, 3
-            Remove { root: Some(3) },
-            Remove { root: Some(4) },
-            Remove { root: Some(5) },
-        ]
-    );
-}
-
-#[test]
-fn add_nested_elements() {
-    let vdom = new_dom();
-
-    let (_create, change) = vdom.diff_lazynodes(
-        rsx_without_templates! {
-            div{}
-        },
-        rsx_without_templates! {
-            div{
-                div{}
-            }
-        },
-    );
-
-    assert_eq!(
-        change.edits,
-        [
-            CreateElement { root: Some(2), tag: "div", children: 0 },
-            AppendChildren { root: Some(1), children: vec![2] },
-        ]
-    );
-}
-
-#[test]
-fn add_listeners() {
-    let vdom = new_dom();
-
-    let (_create, change) = vdom.diff_lazynodes(
-        rsx_without_templates! {
-            div{}
-        },
-        rsx_without_templates! {
-            div{
-                onkeyup: |_| {},
-                onkeydown: |_| {},
-            }
-        },
-    );
-
-    assert_eq!(
-        change.edits,
-        [
-            NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) },
-            NewEventListener { event_name: "keydown", scope: ScopeId(0), root: Some(1) },
-        ]
-    );
-}
-
-#[test]
-fn remove_listeners() {
-    let vdom = new_dom();
-
-    let (_create, change) = vdom.diff_lazynodes(
-        rsx_without_templates! {
-            div{
-                onkeyup: |_| {},
-                onkeydown: |_| {},
-            }
-        },
-        rsx_without_templates! {
-            div{}
-        },
-    );
-
-    assert_eq!(
-        change.edits,
-        [
-            RemoveEventListener { event: "keyup", root: Some(1) },
-            RemoveEventListener { event: "keydown", root: Some(1) },
-        ]
-    );
-}
-
-#[test]
-fn diff_listeners() {
-    let vdom = new_dom();
-
-    let (_create, change) = vdom.diff_lazynodes(
-        rsx_without_templates! {
-            div{
-                onkeydown: |_| {},
-            }
-        },
-        rsx_without_templates! {
-            div{
-                onkeyup: |_| {},
-            }
-        },
-    );
+    _ = dom.rebuild();
 
+    dom.mark_dirty_scope(ScopeId(0));
     assert_eq!(
-        change.edits,
+        dom.render_immediate().santize().edits,
         [
-            RemoveEventListener { root: Some(1), event: "keydown" },
-            NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) }
+            Remove { id: ElementId(3) },
+            Remove { id: ElementId(4) },
+            Remove { id: ElementId(5) }
         ]
     );
 }

+ 107 - 0
packages/core/tests/diff_unkeyed_list.rs

@@ -270,3 +270,110 @@ fn removes_one_by_one_multiroot() {
         ]
     );
 }
+
+#[test]
+fn two_equal_fragments_are_equal_static() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            (0..5).map(|_| rsx! {
+                div { "hello" }
+            })
+        })
+    });
+
+    _ = dom.rebuild();
+    assert!(dom.render_immediate().edits.is_empty());
+}
+
+#[test]
+fn two_equal_fragments_are_equal() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            (0..5).map(|i| rsx! {
+                div { "hello {i}" }
+            })
+        })
+    });
+
+    _ = dom.rebuild();
+    assert!(dom.render_immediate().edits.is_empty());
+}
+
+#[test]
+fn remove_many() {
+    let mut dom = VirtualDom::new(|cx| {
+        let num = match cx.generation() % 3 {
+            0 => 0,
+            1 => 1,
+            2 => 5,
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx! {
+            (0..num).map(|i| rsx! { div { "hello {i}" } })
+        })
+    });
+
+    let edits = dom.rebuild().santize();
+    assert!(edits.template_mutations.is_empty());
+    assert_eq!(
+        edits.edits,
+        [
+            CreatePlaceholder { id: ElementId(1,) },
+            AppendChildren { m: 1 },
+        ]
+    );
+
+    dom.mark_dirty_scope(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty_scope(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+            HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+            HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+            HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
+            InsertAfter { id: ElementId(2,), m: 4 },
+        ]
+    );
+
+    dom.mark_dirty_scope(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            Remove { id: ElementId(1,) },
+            Remove { id: ElementId(5,) },
+            Remove { id: ElementId(7,) },
+            Remove { id: ElementId(9,) },
+            CreatePlaceholder { id: ElementId(9,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty_scope(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) },
+            ReplaceWith { id: ElementId(9,), m: 1 },
+        ]
+    )
+}

+ 2 - 2
packages/core/tests/lifecycle.rs

@@ -2,8 +2,8 @@
 #![allow(non_snake_case)]
 
 //! Tests for the lifecycle of components.
-use dioxus::{core_macro::rsx_without_templates, prelude::*};
-use dioxus_core::DomEdit::*;
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
 use std::sync::{Arc, Mutex};
 
 type Shared<T> = Arc<Mutex<T>>;

+ 2 - 7
packages/core/tests/miri_stress.rs

@@ -11,11 +11,6 @@ Specifically:
 */
 
 use dioxus::prelude::*;
-use dioxus_core::SchedulerMsg;
-
-fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
-    VirtualDom::new_with_props(app, props)
-}
 
 /// This test ensures that if a component aborts early, it is replaced with a placeholder.
 /// In debug, this should also toss a warning.
@@ -28,7 +23,7 @@ fn test_memory_leak() {
         *val += 1;
 
         if *val == 2 || *val == 4 {
-            return None;
+            return cx.render(rsx!(()));
         }
 
         let name = cx.use_hook(|| String::from("asd"));
@@ -66,7 +61,7 @@ fn test_memory_leak() {
         render!(div { "goodbye world" })
     }
 
-    let mut dom = new_dom(app, ());
+    let mut dom = VirtualDom::new(app);
 
     dom.rebuild();
     dom.hard_diff(ScopeId(0));