wip: more tests!

This commit is contained in:
Jonathan Kelley 2022-11-22 21:32:26 -08:00
parent 662f58c8bc
commit 9c4abcbea0
10 changed files with 203 additions and 26 deletions

View file

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

View file

@ -157,6 +157,28 @@ impl<'b: 'static> VirtualDom {
/// Insert a new template into the VirtualDom's template registry /// Insert a new template into the VirtualDom's template registry
fn register_template(&mut self, template: &'b VNode<'b>) { 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 { for node in template.template.roots {
self.create_static_node(template, node); self.create_static_node(template, node);
} }
@ -165,9 +187,6 @@ impl<'b: 'static> VirtualDom {
name: template.template.id, name: template.template.id,
m: template.template.roots.len(), m: template.template.roots.len(),
}); });
self.templates
.insert(template.template.id, template.template);
} }
pub(crate) fn create_static_node( pub(crate) fn create_static_node(

View file

@ -155,8 +155,7 @@ impl<'a, 'b> IntoDynNode<'a> for () {
} }
impl<'a, 'b> IntoDynNode<'a> for VNode<'a> { impl<'a, 'b> IntoDynNode<'a> for VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
// DynamicNode::Fragment { nodes: cx., inner: () } DynamicNode::Fragment(VFragment::NonEmpty(_cx.bump().alloc([self])))
todo!()
} }
} }
@ -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> { impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(VFragment::NonEmpty(cx.bump().alloc([self.call(cx)]))) DynamicNode::Fragment(VFragment::NonEmpty(cx.bump().alloc([self.call(cx)])))

View file

@ -15,6 +15,25 @@ impl<'a> Mutations<'a> {
template_mutations: Vec::new(), 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> { impl<'a> std::ops::Deref for Mutations<'a> {

View file

@ -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::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::{Cell, RefCell}, 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 /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
/// static parts of the template. /// static parts of the template.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct VNode<'a> { pub struct VNode<'a> {
// The ID assigned for the root of this template // The ID assigned for the root of this template
pub node_id: Cell<ElementId>, pub node_id: Cell<ElementId>,
@ -47,6 +47,23 @@ impl<'a> VNode<'a> {
.unwrap() .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( pub fn template_from_dynamic_node(
cx: &'a ScopeState, cx: &'a ScopeState,
node: DynamicNode<'a>, node: DynamicNode<'a>,

View file

@ -20,7 +20,7 @@ impl VirtualDom {
pub(super) fn new_scope(&mut self, props: *const dyn AnyProps<'static>) -> &mut ScopeState { pub(super) fn new_scope(&mut self, props: *const dyn AnyProps<'static>) -> &mut ScopeState {
let parent = self.acquire_current_scope_raw(); let parent = self.acquire_current_scope_raw();
let entry = self.scopes.vacant_entry(); 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()); let id = ScopeId(entry.key());
entry.insert(ScopeState { entry.insert(ScopeState {

View file

@ -120,8 +120,27 @@ impl ScopeState {
/// Get a handle to the currently active head node arena for this Scope /// 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. /// 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> { 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) } unsafe { std::mem::transmute(r) }
} }

View file

@ -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 }
]
)
}

View file

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

View file

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