فهرست منبع

wip: more tests!

Jonathan Kelley 2 سال پیش
والد
کامیت
9c4abcbea0

+ 1 - 1
packages/core-macro/src/props/mod.rs

@@ -194,7 +194,7 @@ mod field_info {
                 // children field is automatically defaulted to None
                 if name == "children" {
                     builder_attr.default =
-                        Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                        Some(syn::parse(quote!(::dioxus::core::VNode::empty()).into()).unwrap());
                 }
 
                 // auto detect optional

+ 22 - 3
packages/core/src/create.rs

@@ -157,6 +157,28 @@ impl<'b: 'static> VirtualDom {
 
     /// Insert a new template into the VirtualDom's template registry
     fn register_template(&mut self, template: &'b VNode<'b>) {
+        // First, make sure we mark the template as seen, regardless if we process it
+        self.templates
+            .insert(template.template.id, template.template);
+
+        // If it's all dynamic nodes, then we don't need to register it
+        // Quickly run through and see if it's all just dynamic nodes
+        let dynamic_roots = template
+            .template
+            .roots
+            .iter()
+            .filter(|root| {
+                matches!(
+                    root,
+                    TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_)
+                )
+            })
+            .count();
+
+        if dynamic_roots == template.template.roots.len() {
+            return;
+        }
+
         for node in template.template.roots {
             self.create_static_node(template, node);
         }
@@ -165,9 +187,6 @@ impl<'b: 'static> VirtualDom {
             name: template.template.id,
             m: template.template.roots.len(),
         });
-
-        self.templates
-            .insert(template.template.id, template.template);
     }
 
     pub(crate) fn create_static_node(

+ 10 - 2
packages/core/src/factory.rs

@@ -155,8 +155,7 @@ impl<'a, 'b> IntoDynNode<'a> for () {
 }
 impl<'a, 'b> IntoDynNode<'a> for VNode<'a> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        // DynamicNode::Fragment { nodes: cx., inner: () }
-        todo!()
+        DynamicNode::Fragment(VFragment::NonEmpty(_cx.bump().alloc([self])))
     }
 }
 
@@ -169,6 +168,15 @@ impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
     }
 }
 
+impl<'a> IntoDynNode<'a> for &Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self.as_ref() {
+            Ok(val) => val.clone().into_vnode(_cx),
+            _ => DynamicNode::Fragment(VFragment::Empty(Cell::new(ElementId(0)))),
+        }
+    }
+}
+
 impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
         DynamicNode::Fragment(VFragment::NonEmpty(cx.bump().alloc([self.call(cx)])))

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

@@ -15,6 +15,25 @@ impl<'a> Mutations<'a> {
             template_mutations: Vec::new(),
         }
     }
+
+    /// A useful tool for testing mutations
+    ///
+    /// Rewrites IDs to just be "template", so you can compare the mutations
+    pub fn santize(mut self) -> Self {
+        for edit in self
+            .template_mutations
+            .iter_mut()
+            .chain(self.edits.iter_mut())
+        {
+            match edit {
+                Mutation::LoadTemplate { name, .. } => *name = "template",
+                Mutation::SaveTemplate { name, .. } => *name = "template",
+                _ => {}
+            }
+        }
+
+        self
+    }
 }
 
 impl<'a> std::ops::Deref for Mutations<'a> {

+ 19 - 2
packages/core/src/nodes.rs

@@ -1,4 +1,4 @@
-use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState, UiEvent};
+use crate::{any_props::AnyProps, arena::ElementId, Element, ScopeId, ScopeState, UiEvent};
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
@@ -10,7 +10,7 @@ pub type TemplateId = &'static str;
 ///
 /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
 /// static parts of the template.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct VNode<'a> {
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
@@ -47,6 +47,23 @@ impl<'a> VNode<'a> {
         .unwrap()
     }
 
+    pub fn empty() -> Element<'a> {
+        Ok(VNode {
+            node_id: Cell::new(ElementId(0)),
+            key: None,
+            parent: None,
+            root_ids: &[],
+            dynamic_nodes: &[],
+            dynamic_attrs: &[],
+            template: Template {
+                id: "dioxus-empty",
+                roots: &[],
+                node_paths: &[],
+                attr_paths: &[],
+            },
+        })
+    }
+
     pub fn template_from_dynamic_node(
         cx: &'a ScopeState,
         node: DynamicNode<'a>,

+ 1 - 1
packages/core/src/scope_arena.rs

@@ -20,7 +20,7 @@ impl VirtualDom {
     pub(super) fn new_scope(&mut self, props: *const dyn AnyProps<'static>) -> &mut ScopeState {
         let parent = self.acquire_current_scope_raw();
         let entry = self.scopes.vacant_entry();
-        let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
+        let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
         let id = ScopeId(entry.key());
 
         entry.insert(ScopeState {

+ 20 - 1
packages/core/src/scopes.rs

@@ -120,8 +120,27 @@ impl ScopeState {
     /// Get a handle to the currently active head node arena for this Scope
     ///
     /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
+    ///
+    /// Panics if the tree has not been built yet.
     pub fn root_node<'a>(&'a self) -> &'a RenderReturn<'a> {
-        let r: &RenderReturn = unsafe { &*self.current_frame().node.get() };
+        self.try_root_node()
+            .expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
+    }
+
+    /// Try to get a handle to the currently active head node arena for this Scope
+    ///
+    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
+    ///
+    /// Returns [`None`] if the tree has not been built yet.
+    pub fn try_root_node<'a>(&'a self) -> Option<&'a RenderReturn<'a>> {
+        let ptr = self.current_frame().node.get();
+
+        if ptr.is_null() {
+            return None;
+        }
+
+        let r: &RenderReturn = unsafe { &*ptr };
+
         unsafe { std::mem::transmute(r) }
     }
 

+ 101 - 0
packages/core/tests/passthru.rs

@@ -0,0 +1,101 @@
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+/// Should push the text node onto the stack and modify it
+#[test]
+fn nested_passthru_creates() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            Child {
+                Child {
+                    Child {
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+            }
+        })
+    }
+
+    #[inline_props]
+    fn Child<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
+        cx.render(rsx! { children })
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let edits = dom.rebuild().santize();
+
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0 },
+            AppendChildren { m: 1 },
+        ]
+    )
+}
+
+/// Should load all the templates and append them
+#[test]
+fn nested_passthru_creates_add() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            child_comp {
+                "1"
+                child_comp {
+                    "2"
+                    child_comp {
+                        "3"
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+            }
+        })
+    }
+
+    #[inline_props]
+    fn child_comp<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! { children })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0 },
+            LoadTemplate { name: "template", index: 0 },
+            LoadTemplate { name: "template", index: 0 },
+            LoadTemplate { name: "template", index: 1 },
+            AppendChildren { m: 4 },
+        ]
+    );
+}
+
+#[test]
+fn dynamic_node_as_root() {
+    fn app(cx: Scope) -> Element {
+        let a = 123;
+        let b = 456;
+        cx.render(rsx! { "{a}" "{b}" })
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let edits = dom.rebuild().santize();
+
+    // Since the roots were all dynamic, they should not cause any template muations
+    assert_eq!(edits.template_mutations, []);
+
+    // The root node is text, so we just create it on the spot
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode { value: "123", id: ElementId(1) },
+            CreateTextNode { value: "456", id: ElementId(2) },
+            AppendChildren { m: 2 }
+        ]
+    )
+}

+ 9 - 13
packages/core/tests/element.rs → packages/core/tests/safety.rs

@@ -1,15 +1,21 @@
+//! Tests related to safety of the library.
+
 use dioxus::prelude::*;
 use dioxus_core::SuspenseContext;
 
-/// Ensure no issues with not building the virtualdom before
+/// Ensure no issues with not calling rebuild
 #[test]
 fn root_node_isnt_null() {
     let dom = VirtualDom::new(|cx| render!("Hello world!"));
 
     let scope = dom.base_scope();
 
-    // The root should be a valid pointer
-    assert_ne!(scope.root_node() as *const _, std::ptr::null_mut());
+    // We haven't built the tree, so trying to get out the root node should fail
+    assert!(scope.try_root_node().is_none());
+
+    // There should be no way to gain an invalid pointer
+    assert!(scope.current_frame().node.get().is_null());
+    assert!(scope.previous_frame().node.get().is_null());
 
     // The height should be 0
     assert_eq!(scope.height(), 0);
@@ -18,13 +24,3 @@ fn root_node_isnt_null() {
     // todo: there should also be a default error boundary
     assert!(scope.has_context::<SuspenseContext>().is_some());
 }
-
-#[test]
-fn elements() {
-    let mut dom = VirtualDom::new(|cx| {
-        //
-        cx.render(rsx!( div { "Hello world!" } ))
-    });
-
-    let muts = dom.rebuild();
-}

+ 1 - 3
packages/rsx/src/component.rs

@@ -171,9 +171,7 @@ impl ToTokens for Component {
 
                     toks.append_all(quote! {
                         .children(
-                            Some({
-                                #renderer
-                            })
+                            Ok({ #renderer })
                         )
                     });
                 }