use dioxus::prelude::Props; use dioxus_core::*; use dioxus_native_core::{ node_ref::{AttributeMask, NodeView}, real_dom::RealDom, state::{ParentDepState, State}, NodeMask, SendAnyMap, }; use dioxus_native_core_macro::{sorted_str_slice, State}; use std::cell::Cell; fn random_ns() -> Option<&'static str> { let namespace = rand::random::() % 2; match namespace { 0 => None, 1 => Some(Box::leak( format!("ns{}", rand::random::()).into_boxed_str(), )), _ => unreachable!(), } } fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> { match rand::random::() % 2 { 0 => TemplateAttribute::Static { name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), value: Box::leak(format!("value{}", rand::random::()).into_boxed_str()), namespace: random_ns(), }, 1 => TemplateAttribute::Dynamic { id: { let old_idx = *attr_idx; *attr_idx += 1; old_idx }, }, _ => unreachable!(), } } fn create_random_template_node( dynamic_node_types: &mut Vec, template_idx: &mut usize, attr_idx: &mut usize, depth: usize, ) -> TemplateNode<'static> { match rand::random::() % 4 { 0 => { let attrs = { let attrs: Vec<_> = (0..(rand::random::() % 10)) .map(|_| create_random_attribute(attr_idx)) .collect(); Box::leak(attrs.into_boxed_slice()) }; TemplateNode::Element { tag: Box::leak(format!("tag{}", rand::random::()).into_boxed_str()), namespace: random_ns(), attrs, children: { if depth > 4 { &[] } else { let children: Vec<_> = (0..(rand::random::() % 3)) .map(|_| { create_random_template_node( dynamic_node_types, template_idx, attr_idx, depth + 1, ) }) .collect(); Box::leak(children.into_boxed_slice()) } }, } } 1 => TemplateNode::Text { text: Box::leak(format!("{}", rand::random::()).into_boxed_str()), }, 2 => TemplateNode::DynamicText { id: { let old_idx = *template_idx; *template_idx += 1; dynamic_node_types.push(DynamicNodeType::Text); old_idx }, }, 3 => TemplateNode::Dynamic { id: { let old_idx = *template_idx; *template_idx += 1; dynamic_node_types.push(DynamicNodeType::Other); old_idx }, }, _ => unreachable!(), } } fn generate_paths( node: &TemplateNode<'static>, current_path: &[u8], node_paths: &mut Vec>, attr_paths: &mut Vec>, ) { match node { TemplateNode::Element { children, attrs, .. } => { for attr in *attrs { match attr { TemplateAttribute::Static { .. } => {} TemplateAttribute::Dynamic { .. } => { attr_paths.push(current_path.to_vec()); } } } for (i, child) in children.iter().enumerate() { let mut current_path = current_path.to_vec(); current_path.push(i as u8); generate_paths(child, ¤t_path, node_paths, attr_paths); } } TemplateNode::Text { .. } => {} TemplateNode::DynamicText { .. } => { node_paths.push(current_path.to_vec()); } TemplateNode::Dynamic { .. } => { node_paths.push(current_path.to_vec()); } } } enum DynamicNodeType { Text, Other, } fn create_random_template(name: &'static str) -> (Template<'static>, Vec) { let mut dynamic_node_type = Vec::new(); let mut template_idx = 0; let mut attr_idx = 0; let roots = (0..(1 + rand::random::() % 5)) .map(|_| { create_random_template_node(&mut dynamic_node_type, &mut template_idx, &mut attr_idx, 0) }) .collect::>(); assert!(!roots.is_empty()); let roots = Box::leak(roots.into_boxed_slice()); let mut node_paths = Vec::new(); let mut attr_paths = Vec::new(); for (i, root) in roots.iter().enumerate() { generate_paths(root, &[i as u8], &mut node_paths, &mut attr_paths); } let node_paths = Box::leak( node_paths .into_iter() .map(|v| &*Box::leak(v.into_boxed_slice())) .collect::>() .into_boxed_slice(), ); let attr_paths = Box::leak( attr_paths .into_iter() .map(|v| &*Box::leak(v.into_boxed_slice())) .collect::>() .into_boxed_slice(), ); ( Template { name, roots, node_paths, attr_paths, }, dynamic_node_type, ) } fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { let range = if depth > 3 { 1 } else { 3 }; match rand::random::() % range { 0 => DynamicNode::Placeholder(Default::default()), 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { key: None, parent: Default::default(), template: Cell::new(Template { name: concat!(file!(), ":", line!(), ":", column!(), ":0"), roots: &[TemplateNode::Dynamic { id: 0 }], node_paths: &[&[0]], attr_paths: &[], }), root_ids: Default::default(), dynamic_nodes: cx.bump().alloc([cx.component( create_random_element, DepthProps { depth, root: false }, "create_random_element", )]), dynamic_attrs: &[], })), 2 => cx.component( create_random_element, DepthProps { depth, root: false }, "create_random_element", ), _ => unreachable!(), } } fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { let value = match rand::random::() % 6 { 0 => AttributeValue::Text(Box::leak( format!("{}", rand::random::()).into_boxed_str(), )), 1 => AttributeValue::Float(rand::random()), 2 => AttributeValue::Int(rand::random()), 3 => AttributeValue::Bool(rand::random()), 4 => cx.any_value(rand::random::()), 5 => AttributeValue::None, // Listener(RefCell>>), _ => unreachable!(), }; Attribute { name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), value, namespace: random_ns(), mounted_element: Default::default(), volatile: rand::random(), } } static mut TEMPLATE_COUNT: usize = 0; #[derive(PartialEq, Props)] struct DepthProps { depth: usize, root: bool, } fn create_random_element(cx: Scope) -> Element { cx.needs_update(); let range = if cx.props.root { 2 } else { 3 }; let node = match rand::random::() % range { 0 | 1 => { let (template, dynamic_node_types) = create_random_template(Box::leak( format!( "{}{}", concat!(file!(), ":", line!(), ":", column!(), ":"), { unsafe { let old = TEMPLATE_COUNT; TEMPLATE_COUNT += 1; old } } ) .into_boxed_str(), )); println!("{template:#?}"); let node = VNode { key: None, parent: None, template: Cell::new(template), root_ids: Default::default(), dynamic_nodes: { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() .map(|ty| match ty { DynamicNodeType::Text => DynamicNode::Text(VText { value: Box::leak( format!("{}", rand::random::()).into_boxed_str(), ), id: Default::default(), }), DynamicNodeType::Other => { create_random_dynamic_node(cx, cx.props.depth + 1) } }) .collect(); cx.bump().alloc(dynamic_nodes) }, dynamic_attrs: cx.bump().alloc( (0..template.attr_paths.len()) .map(|_| create_random_dynamic_attr(cx)) .collect::>(), ), }; Some(node) } _ => None, }; println!("{node:#?}"); node } #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct BlablaState {} /// Font style are inherited by default if not specified otherwise by some of the supported attributes. impl ParentDepState for BlablaState { type Ctx = (); type DepState = (Self,); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",]))); fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { false } } #[derive(Clone, State, Default, Debug)] pub struct NodeState { #[parent_dep_state(blabla)] blabla: BlablaState, } // test for panics when creating random nodes and templates #[test] fn create() { for _ in 0..100 { let mut vdom = VirtualDom::new_with_props( create_random_element, DepthProps { depth: 0, root: true, }, ); let mutations = vdom.rebuild(); let mut rdom: RealDom = RealDom::new(); let (to_update, _diff) = rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); rdom.update_state(to_update, ctx); } } // test for panics when diffing random nodes // This test will change the template every render which is not very realistic, but it helps stress the system #[test] fn diff() { for _ in 0..10 { let mut vdom = VirtualDom::new_with_props( create_random_element, DepthProps { depth: 0, root: true, }, ); let mutations = vdom.rebuild(); let mut rdom: RealDom = RealDom::new(); let (to_update, _diff) = rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); rdom.update_state(to_update, ctx); for _ in 0..10 { let mutations = vdom.render_immediate(); let (to_update, _diff) = rdom.apply_mutations(mutations); let ctx = SendAnyMap::new(); rdom.update_state(to_update, ctx); } } }