diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index bb6a66efc..75141e002 100644 --- a/packages/core/src/diff.rs +++ b/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 { diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index fa404fafe..7e8e10f88 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -189,4 +189,8 @@ pub enum Mutation<'a> { Remove { id: ElementId, }, + + PushRoot { + id: ElementId, + }, } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index cbb1f7250..d77e1e37c 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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(); diff --git a/packages/core/tests/create_element.rs b/packages/core/tests/create_element.rs new file mode 100644 index 000000000..ff9d485b6 --- /dev/null +++ b/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 } + ] + ) +} diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index 0f807151f..521d9385a 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/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::*}; - -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 -#[test] -fn many_items_become_fragment() { - let dom = new_dom(); - - let left = rsx_without_templates!({ - (0..2).map(|_| { - rsx_without_templates! { div { "hello" }} - }) - }); - let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) }); - - let (create, change) = dom.diff_lazynodes(left, right); - - assert_eq!( - create.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,] }, - ] - ); - - assert_eq!( - change.edits, - [ - CreatePlaceholder { root: Some(5,) }, - ReplaceWith { root: Some(1,), nodes: vec![5,] }, - Remove { root: Some(3,) }, - ] - ); -} - -/// Should result in no edits -#[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" }} - }) - }); - - 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,] }, - ] - ); - - // note: key reuse is always the last node that got used - // slab maintains a linked list, essentially - assert_eq!( - change.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,] }, - ] - ); -} - -/// 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,) }, - ] - ); -} +use dioxus::core::{ElementId, Mutation::*}; +use dioxus::prelude::*; /// Should result in moves, but not removals or additions #[test] fn keyed_diffing_out_of_order() { - let dom = new_dom(); + 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!(), + }; - let left = rsx_without_templates!({ - [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) }); - 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,] },] + dom.rebuild().santize().edits, + [ + 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!( + dom.render_immediate().edits, + [ + PushRoot { id: ElementId(7,) }, + InsertBefore { id: ElementId(5,), m: 1 }, + ] + ) } /// Should result in moves only #[test] fn keyed_diffing_out_of_order_adds() { - let dom = new_dom(); + 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!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) }); - 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); + _ = dom.rebuild(); + dom.mark_dirty_scope(ScopeId(0)); assert_eq!( - change.edits, - [InsertBefore { root: Some(1,), nodes: vec![5, 4,] },] - ); + dom.render_immediate().edits, + [ + PushRoot { id: ElementId(5,) }, + PushRoot { id: ElementId(4,) }, + InsertBefore { id: ElementId(1,), m: 2 }, + ] + ) } + /// 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 mut dom = VirtualDom::new(|cx| { + let order = match cx.generation() % 2 { + 0 => &[/**/ 4, 5, 6, 7, 8 /**/], + 1 => &[/**/ 4, 8, 7, 5, 6 /**/], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 mut dom = VirtualDom::new(|cx| { + let order = match cx.generation() % 2 { + 0 => &[/**/ 4, 5, 6, 7, 8 /**/], + 1 => &[/**/ 4, 5, 8, 7, 6 /**/], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) }); - let right = rsx_without_templates!({ - [/**/ 4, 5, 8, 7, 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(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 mut dom = VirtualDom::new(|cx| { + let order = match cx.generation() % 2 { + 0 => &[/**/ 4, 5, 6, 7, 8 /**/], + 1 => &[/**/ 4, 5, 6, 8, 7 /**/], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 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!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 mut dom = VirtualDom::new(|cx| { + let order: &[_] = match cx.generation() % 2 { + 0 => &[/**/ 4, 5, 6, 7 /**/], + 1 => &[/**/ 7, 4, 5, 6, 11, 12 /**/], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [/**/ 4, 5, 6, 7 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 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!(), + }; - let left = rsx_without_templates!({ - [/**/ 1, 2, 3, 4 /**/].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 mut dom = VirtualDom::new(|cx| { + let order: &[_] = match cx.generation() % 2 { + 0 => &[4, 5, 6, 7], + 1 => &[0, 5, 9, 6, 4], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [4, 5, 6, 7].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 mut dom = VirtualDom::new(|cx| { + let order: &[_] = match cx.generation() % 2 { + 0 => &[0, 1, 2, 3, 4], + 1 => &[3, 0, 1, 10, 2], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - [0, 1, 2, 3, 4].iter().map(|f| { - rsx_without_templates! { div { key: "{f}" }} - }) + 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 mut dom = VirtualDom::new(|cx| { + let order: &[_] = match cx.generation() % 2 { + 0 => &[9, 8, 7, 6, 5], + 1 => &[9, 8], + _ => unreachable!(), + }; - let left = rsx_without_templates!({ - (0..10).rev().take(5).map(|i| { - rsx_without_templates! { Fragment { key: "{i}", "{i}" }} - }) + cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) }); - let right = rsx_without_templates!({ - (0..10).rev().take(2).map(|i| { - rsx_without_templates! { Fragment { key: "{i}", "{i}" }} - }) - }); - - let (create, changes) = dom.diff_lazynodes(left, right); - - // dbg!(create); - // dbg!(changes); + _ = dom.rebuild(); + dom.mark_dirty_scope(ScopeId(0)); assert_eq!( - changes.edits, - // remove 5, 4, 3 + dom.render_immediate().santize().edits, [ - 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: |_| {}, - } - }, - ); - - assert_eq!( - change.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) } ] ); } diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 6d8f0a236..96de44cc3 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/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 }, + ] + ) +} diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 17d198597..0c9850345 100644 --- a/packages/core/tests/lifecycle.rs +++ b/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 = Arc>; diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 11f7d06ad..20a29b30c 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -11,11 +11,6 @@ Specifically: */ use dioxus::prelude::*; -use dioxus_core::SchedulerMsg; - -fn new_dom(app: Component

, 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));