use anymap::AnyMap; use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::*; use dioxus_native_core::real_dom_new_api::{ AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, }; use dioxus_native_core_macro::State; #[derive(Debug, Clone, Default, State)] struct CallCounterState { #[child_dep_state(ChildDepCallCounter)] child_counter: ChildDepCallCounter, #[parent_dep_state(ParentDepCallCounter)] parent_counter: ParentDepCallCounter, #[node_dep_state()] node_counter: NodeDepCallCounter, } #[derive(Debug, Clone, Default)] struct ChildDepCallCounter(u32); impl ChildDepState for ChildDepCallCounter { type Ctx = (); type DepState = Self; const NODE_MASK: NodeMask = NodeMask::ALL; fn reduce( &mut self, _node: NodeView, _children: Vec<&Self::DepState>, _ctx: &Self::Ctx, ) -> bool { self.0 += 1; true } } #[derive(Debug, Clone, Default)] struct ParentDepCallCounter(u32); impl ParentDepState for ParentDepCallCounter { type Ctx = (); type DepState = Self; const NODE_MASK: NodeMask = NodeMask::ALL; fn reduce( &mut self, _node: NodeView, _parent: Option<&Self::DepState>, _ctx: &Self::Ctx, ) -> bool { self.0 += 1; true } } #[derive(Debug, Clone, Default)] struct NodeDepCallCounter(u32); impl NodeDepState for NodeDepCallCounter { type Ctx = (); const NODE_MASK: NodeMask = NodeMask::ALL; fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool { self.0 += 1; true } } #[derive(Debug, Clone, PartialEq, Default)] struct BubbledUpStateTester(Option, Vec>); impl ChildDepState for BubbledUpStateTester { type Ctx = u32; type DepState = Self; const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = BubbledUpStateTester( node.tag().map(|s| s.to_string()), children.into_iter().map(|c| Box::new(c.clone())).collect(), ); true } } #[derive(Debug, Clone, PartialEq, Default)] struct PushedDownStateTester(Option, Option>); impl ParentDepState for PushedDownStateTester { type Ctx = u32; type DepState = Self; const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = PushedDownStateTester( node.tag().map(|s| s.to_string()), parent.map(|c| Box::new(c.clone())), ); true } } #[derive(State, Clone, Default, Debug)] struct StateTester { #[child_dep_state(BubbledUpStateTester, u32)] bubbled: BubbledUpStateTester, #[parent_dep_state(PushedDownStateTester, u32)] pushed: PushedDownStateTester, #[node_dep_state(u32)] node: NodeStateTester, } #[derive(Debug, Clone, PartialEq, Default)] struct NodeStateTester(Option, Vec<(String, String)>); impl NodeDepState for NodeStateTester { type Ctx = u32; const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false); fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( node.tag().map(|s| s.to_string()), node.attributes() .map(|a| (a.name.to_string(), a.value.to_string())) .collect(), ); true } } #[test] fn state_initial() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { p{} h1{} }) } let vdom = VirtualDom::new(Base); let mutations = vdom.create_vnodes(rsx! { div { p{ color: "red" } h1{} } }); let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); let mut ctx = AnyMap::new(); ctx.insert(42u32); let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx); let root_div = &dom[1]; assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!( root_div.state.bubbled.1, vec![ Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) ] ); assert_eq!(root_div.state.pushed.0, Some("div".to_string())); assert_eq!(root_div.state.pushed.1, None); assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.1, vec![]); let child_p = &dom[2]; assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); assert_eq!(child_p.state.bubbled.1, Vec::new()); assert_eq!(child_p.state.pushed.0, Some("p".to_string())); assert_eq!( child_p.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), None ))) ); assert_eq!(child_p.state.node.0, Some("p".to_string())); assert_eq!( child_p.state.node.1, vec![("color".to_string(), "red".to_string())] ); let child_h1 = &dom[3]; assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); assert_eq!(child_h1.state.bubbled.1, Vec::new()); assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); assert_eq!( child_h1.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), None ))) ); assert_eq!(child_h1.state.node.0, Some("h1".to_string())); assert_eq!(child_h1.state.node.1, vec![]); } #[test] fn state_reduce_initally_called_minimally() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } }) } let vdom = VirtualDom::new(Base); let mutations = vdom.create_vnodes(rsx! { div { div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } } }); let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { assert_eq!(n.state.child_counter.0, 1); assert_eq!(n.state.parent_counter.0, 1); assert_eq!(n.state.node_counter.0, 1); }); } #[test] fn state_reduce_down_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { width: "100%", div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } }) } let vdom = VirtualDom::new(Base); let mutations = vdom.create_vnodes(rsx! { div { width: "100%", div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } } }); let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { root: 1, field: "width", value: "99%", ns: Some("style"), }], dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { println!("{:?}", n.state); // assert_eq!(n.state.parent_counter.0, 2); }); panic!() } #[test] fn state_reduce_up_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { width: "100%", div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } }) } let vdom = VirtualDom::new(Base); let mutations = vdom.create_vnodes(rsx! { div { width: "100%", div{ div{ p{} } p{ "hello" } div{ h1{} } p{ "world" } } } }); let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { root: 4, field: "width", value: "99%", ns: Some("style"), }], dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { println!("{:?}", n.state); // assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); panic!() }