chore: get event test working

This commit is contained in:
Jonathan Kelley 2022-11-27 09:38:40 -05:00
parent 565df11f7b
commit 0027cdd938
5 changed files with 263 additions and 214 deletions

View file

@ -6,17 +6,23 @@ pub struct ElementId(pub usize);
pub struct ElementRef { pub struct ElementRef {
// the pathway of the real element inside the template // the pathway of the real element inside the template
pub path: &'static [u8], pub path: ElementPath,
// The actual template // The actual template
pub template: *mut VNode<'static>, pub template: *mut VNode<'static>,
} }
#[derive(Clone, Copy)]
pub enum ElementPath {
Deep(&'static [u8]),
Root(usize),
}
impl ElementRef { impl ElementRef {
pub fn null() -> Self { pub fn null() -> Self {
Self { Self {
template: std::ptr::null_mut(), template: std::ptr::null_mut(),
path: &[], path: ElementPath::Root(0),
} }
} }
} }
@ -28,7 +34,21 @@ impl<'b> VirtualDom {
entry.insert(ElementRef { entry.insert(ElementRef {
template: template as *const _ as *mut _, template: template as *const _ as *mut _,
path, path: ElementPath::Deep(path),
});
println!("Claiming {}", id);
ElementId(id)
}
pub fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
entry.insert(ElementRef {
template: template as *const _ as *mut _,
path: ElementPath::Root(path),
}); });
println!("Claiming {}", id); println!("Claiming {}", id);
@ -76,3 +96,30 @@ impl<'b> VirtualDom {
// } // }
} }
} }
impl ElementPath {
pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool {
match *self {
ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()],
ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8,
}
}
}
#[test]
fn path_ascendant() {
// assert!(&ElementPath::Deep(&[]).is_ascendant(&&[0_u8]));
// assert!(&ElementPath::Deep(&[1, 2]), &[1, 2, 3]);
// assert!(!is_path_ascendant(
// &ElementPath::Deep(&[1, 2, 3, 4]),
// &[1, 2, 3]
// ));
}
impl PartialEq<&[u8]> for ElementPath {
fn eq(&self, other: &&[u8]) -> bool {
match *self {
ElementPath::Deep(deep) => deep.eq(*other),
ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
}
}
}

View file

@ -63,7 +63,8 @@ impl<'b: 'static> VirtualDom {
} }
TemplateNode::Element { .. } | TemplateNode::Text(_) => { TemplateNode::Element { .. } | TemplateNode::Text(_) => {
let this_id = self.next_element(template, &[]); let this_id = self.next_root(template, root_idx);
template.root_ids[root_idx].set(this_id); template.root_ids[root_idx].set(this_id);
self.mutations.push(LoadTemplate { self.mutations.push(LoadTemplate {
name: template.template.id, name: template.template.id,

View file

@ -6,7 +6,7 @@ use crate::{
any_props::VProps, any_props::VProps,
arena::{ElementId, ElementRef}, arena::{ElementId, ElementRef},
factory::RenderReturn, factory::RenderReturn,
innerlude::{DirtyScope, Mutations, Scheduler, SchedulerMsg}, innerlude::{DirtyScope, ElementPath, Mutations, Scheduler, SchedulerMsg},
mutations::Mutation, mutations::Mutation,
nodes::{Template, TemplateId}, nodes::{Template, TemplateId},
scheduler::{SuspenseBoundary, SuspenseId}, scheduler::{SuspenseBoundary, SuspenseId},
@ -368,15 +368,11 @@ impl VirtualDom {
let target_path = el_ref.path; let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() { for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
small.len() >= big.len() && small == &big[..small.len()]
}
let this_path = template.template.attr_paths[idx]; let this_path = template.template.attr_paths[idx];
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
// we should fix this so that we look for "onclick" instead of "click" // we should fix this so that we look for "onclick" instead of "click"
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) { if &attr.name[2..] == name && target_path.is_ascendant(&this_path) {
listeners.push(&attr.value); listeners.push(&attr.value);
// Break if the event doesn't bubble anyways // Break if the event doesn't bubble anyways
@ -387,7 +383,7 @@ impl VirtualDom {
// Break if this is the exact target element. // Break if this is the exact target element.
// This means we won't call two listeners with the same name on the same element. This should be // This means we won't call two listeners with the same name on the same element. This should be
// documented, or be rejected from the rsx! macro outright // documented, or be rejected from the rsx! macro outright
if this_path == target_path { if target_path == this_path {
break; break;
} }
} }

View file

@ -4,6 +4,7 @@
//! Tests for the lifecycle of components. //! Tests for the lifecycle of components.
use dioxus::core::{ElementId, Mutation::*}; use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*; use dioxus::prelude::*;
use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
type Shared<T> = Arc<Mutex<T>>; type Shared<T> = Arc<Mutex<T>>;
@ -14,21 +15,26 @@ fn manual_diffing() {
value: Shared<&'static str>, value: Shared<&'static str>,
} }
static App: Component<AppProps> = |cx| { fn app(cx: Scope<AppProps>) -> Element {
let val = cx.props.value.lock().unwrap(); let val = cx.props.value.lock().unwrap();
cx.render(rsx_without_templates! { div { "{val}" } }) cx.render(rsx! { div { "{val}" } })
}; };
let value = Arc::new(Mutex::new("Hello")); let value = Arc::new(Mutex::new("Hello"));
let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() }); let mut dom = VirtualDom::new_with_props(app, AppProps { value: value.clone() });
let _ = dom.rebuild(); let _ = dom.rebuild();
*value.lock().unwrap() = "goodbye"; *value.lock().unwrap() = "goodbye";
let edits = dom.rebuild(); assert_eq!(
dom.rebuild().santize().edits,
println!("edits: {:?}", edits); [
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
AppendChildren { m: 1 }
]
);
} }
#[test] #[test]
@ -36,229 +42,228 @@ fn events_generate() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let count = cx.use_hook(|| 0); let count = cx.use_hook(|| 0);
let inner = match *count { match *count {
0 => { 0 => cx.render(rsx! {
rsx_without_templates! {
div { div {
onclick: move |_| *count += 1, onclick: move |_| {
div { *count += 1
"nested" },
} div { "nested" }
"Click me!" "Click me!"
} }
}),
_ => cx.render(rsx!(())),
} }
}
_ => todo!(),
};
cx.render(inner)
}; };
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let mut channel = dom.get_scheduler_channel(); _ = dom.rebuild();
assert!(dom.has_work());
dom.handle_event(
"click",
Rc::new(MouseData::default()),
ElementId(1),
true,
EventPriority::Immediate,
);
dom.mark_dirty_scope(ScopeId(0));
let edits = dom.render_immediate();
let edits = dom.rebuild();
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateElement { root: Some(1), tag: "div", children: 0 }, CreatePlaceholder { id: ElementId(2) },
NewEventListener { event_name: "click", scope: ScopeId(0), root: Some(1) }, ReplaceWith { id: ElementId(1), m: 1 }
CreateElement { root: Some(2), tag: "div", children: 0 },
CreateTextNode { root: Some(3), text: "nested" },
AppendChildren { root: Some(2), children: vec![3] },
CreateTextNode { root: Some(4), text: "Click me!" },
AppendChildren { root: Some(1), children: vec![2, 4] },
AppendChildren { root: Some(0), children: vec![1] }
] ]
) )
} }
#[test] // #[test]
fn components_generate() { // fn components_generate() {
fn app(cx: Scope) -> Element { // fn app(cx: Scope) -> Element {
let render_phase = cx.use_hook(|| 0); // let render_phase = cx.use_hook(|| 0);
*render_phase += 1; // *render_phase += 1;
cx.render(match *render_phase { // cx.render(match *render_phase {
1 => rsx_without_templates!("Text0"), // 1 => rsx_without_templates!("Text0"),
2 => rsx_without_templates!(div {}), // 2 => rsx_without_templates!(div {}),
3 => rsx_without_templates!("Text2"), // 3 => rsx_without_templates!("Text2"),
4 => rsx_without_templates!(Child {}), // 4 => rsx_without_templates!(Child {}),
5 => rsx_without_templates!({ None as Option<()> }), // 5 => rsx_without_templates!({ None as Option<()> }),
6 => rsx_without_templates!("text 3"), // 6 => rsx_without_templates!("text 3"),
7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }), // 7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }),
8 => rsx_without_templates!(Child {}), // 8 => rsx_without_templates!(Child {}),
_ => todo!(), // _ => todo!(),
}) // })
}; // };
fn Child(cx: Scope) -> Element { // fn Child(cx: Scope) -> Element {
println!("Running child"); // println!("Running child");
cx.render(rsx_without_templates! { // cx.render(rsx_without_templates! {
h1 {} // h1 {}
}) // })
} // }
let mut dom = VirtualDom::new(app); // let mut dom = VirtualDom::new(app);
let edits = dom.rebuild(); // let edits = dom.rebuild();
assert_eq!( // assert_eq!(
edits.edits, // edits.edits,
[ // [
CreateTextNode { root: Some(1), text: "Text0" }, // CreateTextNode { root: Some(1), text: "Text0" },
AppendChildren { root: Some(0), children: vec![1] } // AppendChildren { root: Some(0), children: vec![1] }
] // ]
); // );
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateElement { root: Some(2), tag: "div", children: 0 }, // CreateElement { root: Some(2), tag: "div", children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] } // ReplaceWith { root: Some(1), nodes: vec![2] }
] // ]
); // );
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateTextNode { root: Some(1), text: "Text2" }, // CreateTextNode { root: Some(1), text: "Text2" },
ReplaceWith { root: Some(2), nodes: vec![1] } // ReplaceWith { root: Some(2), nodes: vec![1] }
] // ]
); // );
// child {} // // child {}
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateElement { root: Some(2), tag: "h1", children: 0 }, // CreateElement { root: Some(2), tag: "h1", children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] } // ReplaceWith { root: Some(1), nodes: vec![2] }
] // ]
); // );
// placeholder // // placeholder
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreatePlaceholder { root: Some(1) }, // CreatePlaceholder { root: Some(1) },
ReplaceWith { root: Some(2), nodes: vec![1] } // ReplaceWith { root: Some(2), nodes: vec![1] }
] // ]
); // );
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateTextNode { root: Some(2), text: "text 3" }, // CreateTextNode { root: Some(2), text: "text 3" },
ReplaceWith { root: Some(1), nodes: vec![2] } // ReplaceWith { root: Some(1), nodes: vec![2] }
] // ]
); // );
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateTextNode { text: "text 0", root: Some(1) }, // CreateTextNode { text: "text 0", root: Some(1) },
CreateTextNode { text: "text 1", root: Some(3) }, // CreateTextNode { text: "text 1", root: Some(3) },
ReplaceWith { root: Some(2), nodes: vec![1, 3] }, // ReplaceWith { root: Some(2), nodes: vec![1, 3] },
] // ]
); // );
assert_eq!( // assert_eq!(
dom.hard_diff(ScopeId(0)).edits, // dom.hard_diff(ScopeId(0)).edits,
[ // [
CreateElement { tag: "h1", root: Some(2), children: 0 }, // CreateElement { tag: "h1", root: Some(2), children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] }, // ReplaceWith { root: Some(1), nodes: vec![2] },
Remove { root: Some(3) }, // Remove { root: Some(3) },
] // ]
); // );
} // }
#[test] // #[test]
fn component_swap() { // fn component_swap() {
fn app(cx: Scope) -> Element { // fn app(cx: Scope) -> Element {
let render_phase = cx.use_hook(|| 0); // let render_phase = cx.use_hook(|| 0);
*render_phase += 1; // *render_phase += 1;
cx.render(match *render_phase { // cx.render(match *render_phase {
0 => rsx_without_templates!( // 0 => rsx_without_templates!(
div { // div {
NavBar {} // NavBar {}
Dashboard {} // Dashboard {}
} // }
), // ),
1 => rsx_without_templates!( // 1 => rsx_without_templates!(
div { // div {
NavBar {} // NavBar {}
Results {} // Results {}
} // }
), // ),
2 => rsx_without_templates!( // 2 => rsx_without_templates!(
div { // div {
NavBar {} // NavBar {}
Dashboard {} // Dashboard {}
} // }
), // ),
3 => rsx_without_templates!( // 3 => rsx_without_templates!(
div { // div {
NavBar {} // NavBar {}
Results {} // Results {}
} // }
), // ),
4 => rsx_without_templates!( // 4 => rsx_without_templates!(
div { // div {
NavBar {} // NavBar {}
Dashboard {} // Dashboard {}
} // }
), // ),
_ => rsx_without_templates!("blah"), // _ => rsx_without_templates!("blah"),
}) // })
}; // };
static NavBar: Component = |cx| { // static NavBar: Component = |cx| {
println!("running navbar"); // println!("running navbar");
cx.render(rsx_without_templates! { // cx.render(rsx_without_templates! {
h1 { // h1 {
"NavBar" // "NavBar"
{(0..3).map(|f| rsx_without_templates!(NavLink {}))} // {(0..3).map(|f| rsx_without_templates!(NavLink {}))}
} // }
}) // })
}; // };
static NavLink: Component = |cx| { // static NavLink: Component = |cx| {
println!("running navlink"); // println!("running navlink");
cx.render(rsx_without_templates! { // cx.render(rsx_without_templates! {
h1 { // h1 {
"NavLink" // "NavLink"
} // }
}) // })
}; // };
static Dashboard: Component = |cx| { // static Dashboard: Component = |cx| {
println!("running dashboard"); // println!("running dashboard");
cx.render(rsx_without_templates! { // cx.render(rsx_without_templates! {
div { // div {
"dashboard" // "dashboard"
} // }
}) // })
}; // };
static Results: Component = |cx| { // static Results: Component = |cx| {
println!("running results"); // println!("running results");
cx.render(rsx_without_templates! { // cx.render(rsx_without_templates! {
div { // div {
"results" // "results"
} // }
}) // })
}; // };
let mut dom = VirtualDom::new(app); // let mut dom = VirtualDom::new(app);
let edits = dom.rebuild(); // let edits = dom.rebuild();
dbg!(&edits); // dbg!(&edits);
let edits = dom.work_with_deadline(|| false); // let edits = dom.work_with_deadline(|| false);
dbg!(&edits); // dbg!(&edits);
let edits = dom.work_with_deadline(|| false); // let edits = dom.work_with_deadline(|| false);
dbg!(&edits); // dbg!(&edits);
let edits = dom.work_with_deadline(|| false); // let edits = dom.work_with_deadline(|| false);
dbg!(&edits); // dbg!(&edits);
let edits = dom.work_with_deadline(|| false); // let edits = dom.work_with_deadline(|| false);
dbg!(&edits); // dbg!(&edits);
} // }

View file

@ -10,7 +10,7 @@ pub type MouseEvent = UiEvent<MouseData>;
/// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) /// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone)] #[derive(Clone, Default)]
/// Data associated with a mouse event /// Data associated with a mouse event
/// ///
/// Do not use the deprecated fields; they may change or become private in the future. /// Do not use the deprecated fields; they may change or become private in the future.