mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
wip: more tests!
This commit is contained in:
parent
662f58c8bc
commit
9c4abcbea0
10 changed files with 203 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)])))
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
101
packages/core/tests/passthru.rs
Normal file
101
packages/core/tests/passthru.rs
Normal 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 }
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -171,9 +171,7 @@ impl ToTokens for Component {
|
||||||
|
|
||||||
toks.append_all(quote! {
|
toks.append_all(quote! {
|
||||||
.children(
|
.children(
|
||||||
Some({
|
Ok({ #renderer })
|
||||||
#renderer
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue