Merge pull request #648 from Demonthos/fix-non-str-attributes

fix custom attribute value support
This commit is contained in:
Jon Kelley authored and GitHub committed 2023-01-02 23:41:45 -05:00
commit 07e71956d4
36 files changed
+806 -711

No files matched your search

+1 -1
View File
@@ -44,4 +44,4 @@ pretty_assertions = "1.3.0"
[features]
default = []
serialize = ["serde"]
serialize = ["serde"]
+9 -3
View File
@@ -153,11 +153,17 @@ impl VirtualDom {
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
let mut listeners = scope.listeners.borrow_mut();
let mut listeners = scope.attributes_to_drop.borrow_mut();
listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener };
if let AttributeValue::Listener(l) = &listener.value {
_ = l.take();
match &listener.value {
AttributeValue::Listener(l) => {
_ = l.take();
}
AttributeValue::Any(a) => {
_ = a.take();
}
_ => (),
}
});
}
+12 -20
View File
@@ -1,5 +1,5 @@
use crate::any_props::AnyProps;
use crate::innerlude::{VComponent, VPlaceholder, VText};
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@@ -285,7 +285,7 @@ impl<'b> VirtualDom {
}
}
fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
// Make sure we set the attribute's associated id
attribute.mounted_element.set(id);
@@ -293,9 +293,17 @@ impl<'b> VirtualDom {
let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
match &attribute.value {
AttributeValue::Text(value) => {
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
name: &unbounded_name[2..],
id,
})
}
_ => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value: &str = unsafe { std::mem::transmute(*value) };
let value: BorrowedAttributeValue<'b> = (&attribute.value).into();
let unbounded_value = unsafe { std::mem::transmute(value) };
self.mutations.push(SetAttribute {
name: unbounded_name,
@@ -304,22 +312,6 @@ impl<'b> VirtualDom {
id,
})
}
AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
name: unbounded_name,
value: *value,
id,
}),
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
name: &unbounded_name[2..],
id,
})
}
AttributeValue::Float(_) => todo!(),
AttributeValue::Int(_) => todo!(),
AttributeValue::Any(_) => todo!(),
AttributeValue::None => todo!(),
}
}
+11 -17
View File
@@ -1,7 +1,7 @@
use crate::{
any_props::AnyProps,
arena::ElementId,
innerlude::{DirtyScope, VComponent, VPlaceholder, VText},
innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
mutations::Mutation,
nodes::RenderReturn,
nodes::{DynamicNode, VNode},
@@ -150,22 +150,16 @@ impl<'b> VirtualDom {
};
}
fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
// todo: add more types of attribute values
match right_attr.value {
AttributeValue::Text(text) => {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value = unsafe { std::mem::transmute(text) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
// todo: more types of attribute values
_ => todo!("other attribute types"),
}
fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
let value = unsafe { std::mem::transmute(value) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
fn diff_vcomponent(
+7 -7
View File
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
Throw, VNode, VirtualDom,
fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
};
}
+4 -15
View File
@@ -1,6 +1,6 @@
use rustc_hash::FxHashSet;
use crate::{arena::ElementId, ScopeId, Template};
use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template};
/// A container for all the relevant steps to modify the Real DOM
///
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
pub enum Mutation<'a> {
/// Add these m children to the target element
AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
SetAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: &'a str,
value: BorrowedAttributeValue<'a>,
/// The ID of the node to set the attribute of.
id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
ns: Option<&'a str>,
},
/// Set the value of a node's attribute.
SetBoolAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: bool,
/// The ID of the node to set the attribute of.
id: ElementId,
},
/// Set the textcontent of a node.
SetText {
/// The textcontent of the node
+133 -15
View File
@@ -5,8 +5,8 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell},
fmt::Arguments,
cell::{self, Cell, RefCell, UnsafeCell},
fmt::{Arguments, Debug},
future::Future,
};
@@ -523,13 +523,108 @@ pub enum AttributeValue<'a> {
Listener(RefCell<Option<ListenerCb<'a>>>),
/// An arbitrary value that implements PartialEq and is static
Any(BumpBox<'a, dyn AnyValue>),
Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
///
/// These varients are used to communicate what the value of an attribute is that needs to be updated
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
pub enum BorrowedAttributeValue<'a> {
/// Text attribute
Text(&'a str),
/// A float
Float(f64),
/// Signed integer
Int(i64),
/// Boolean
Bool(bool),
/// An arbitrary value that implements PartialEq and is static
#[cfg_attr(
feature = "serialize",
serde(
deserialize_with = "deserialize_any_value",
serialize_with = "serialize_any_value"
)
)]
Any(std::cell::Ref<'a, dyn AnyValue>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
impl<'a> From<&'a AttributeValue<'a>> for BorrowedAttributeValue<'a> {
fn from(value: &'a AttributeValue<'a>) -> Self {
match value {
AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
AttributeValue::Listener(_) => {
panic!("A listener cannot be turned into a borrowed value")
}
AttributeValue::Any(value) => {
let value = value.borrow();
BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| {
&**value.as_ref().unwrap()
}))
}
AttributeValue::None => BorrowedAttributeValue::None,
}
}
}
impl Debug for BorrowedAttributeValue<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(),
Self::None => write!(f, "None"),
}
}
}
impl PartialEq for BorrowedAttributeValue<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[cfg(feature = "serialize")]
fn serialize_any_value<S>(_: &cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("Any cannot be serialized")
}
#[cfg(feature = "serialize")]
fn deserialize_any_value<'de, 'a, D>(_: D) -> Result<cell::Ref<'a, dyn AnyValue>, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("Any cannot be deserialized")
}
impl<'a> std::fmt::Debug for AttributeValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -553,29 +648,36 @@ impl<'a> PartialEq for AttributeValue<'a> {
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Listener(_), Self::Listener(_)) => true,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
(Self::Any(l0), Self::Any(r0)) => {
let l0 = l0.borrow();
let r0 = r0.borrow();
l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
}
_ => false,
}
}
}
#[doc(hidden)]
pub trait AnyValue {
pub trait AnyValue: 'static {
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
fn our_typeid(&self) -> TypeId;
fn as_any(&self) -> &dyn Any;
fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
}
impl<T: PartialEq + Any> AnyValue for T {
impl<T: Any + PartialEq + 'static> AnyValue for T {
fn any_cmp(&self, other: &dyn AnyValue) -> bool {
if self.type_id() != other.our_typeid() {
return false;
if let Some(other) = other.as_any().downcast_ref() {
self == other
} else {
false
}
self == unsafe { &*(other as *const _ as *const T) }
}
fn our_typeid(&self) -> TypeId {
self.type_id()
fn as_any(&self) -> &dyn Any {
self
}
}
@@ -742,26 +844,36 @@ pub trait IntoAttributeValue<'a> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
}
impl<'a> IntoAttributeValue<'a> for AttributeValue<'a> {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
self
}
}
impl<'a> IntoAttributeValue<'a> for &'a str {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(self)
}
}
impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
}
}
impl<'a> IntoAttributeValue<'a> for i64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Int(self)
}
}
impl<'a> IntoAttributeValue<'a> for bool {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Bool(self)
}
}
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
use bumpalo::core_alloc::fmt::Write;
@@ -770,3 +882,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
AttributeValue::Text(str_buf.into_bump_str())
}
}
impl<'a> IntoAttributeValue<'a> for BumpBox<'a, dyn AnyValue> {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Any(RefCell::new(Some(self)))
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ impl VirtualDom {
hook_idx: Default::default(),
shared_contexts: Default::default(),
borrowed_props: Default::default(),
listeners: Default::default(),
attributes_to_drop: Default::default(),
}))
}
+21 -6
View File
@@ -7,7 +7,7 @@ use crate::{
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
Attribute, AttributeValue, Element, Event, Properties, TaskId,
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -88,7 +88,7 @@ pub struct ScopeState {
pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) listeners: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
pub(crate) placeholder: Cell<Option<ElementId>>,
@@ -374,11 +374,15 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
let mut listeners = self.listeners.borrow_mut();
let mut listeners = self.attributes_to_drop.borrow_mut();
for attr in element.dynamic_attrs {
if let AttributeValue::Listener(_) = attr.value {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
match attr.value {
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
_ => (),
}
}
@@ -509,6 +513,17 @@ impl<'src> ScopeState {
AttributeValue::Listener(RefCell::new(Some(boxed)))
}
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue<'src> {
// safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
// This is the suggested way to build a bumpbox
//
// In theory, we could just use regular boxes
let boxed: BumpBox<'src, dyn AnyValue> =
unsafe { BumpBox::from_raw(self.bump().alloc(value)) };
AttributeValue::Any(RefCell::new(Some(boxed)))
}
/// Inject an error into the nearest error boundary and quit rendering
///
/// The error doesn't need to implement Error or any specific traits since the boundary
+1 -4
View File
@@ -641,10 +641,7 @@ impl VirtualDom {
/// Swap the current mutations with a new
fn finalize(&mut self) -> Mutations {
// todo: make this a routine
let mut out = Mutations::default();
std::mem::swap(&mut self.mutations, &mut out);
out
std::mem::take(&mut self.mutations)
}
}
+28 -4
View File
@@ -2,8 +2,10 @@
//!
//! This tests to ensure we clean it up
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
#[test]
fn attrs_cycle() {
@@ -22,6 +24,8 @@ fn attrs_cycle() {
}
});
let bump = Bump::new();
assert_eq!(
dom.rebuild().santize().edits,
[
@@ -36,8 +40,18 @@ fn attrs_cycle() {
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
AssignId { path: &[0,], id: ElementId(3,) },
SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None },
SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None },
SetAttribute {
name: "class",
value: (&*bump.alloc("1".into_value(&bump))).into(),
id: ElementId(3,),
ns: None
},
SetAttribute {
name: "id",
value: (&*bump.alloc("1".into_value(&bump))).into(),
id: ElementId(3,),
ns: None
},
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
@@ -57,8 +71,18 @@ fn attrs_cycle() {
[
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
AssignId { path: &[0], id: ElementId(3) },
SetAttribute { name: "class", value: "3", id: ElementId(3), ns: None },
SetAttribute { name: "id", value: "3", id: ElementId(3), ns: None },
SetAttribute {
name: "class",
value: BorrowedAttributeValue::Text("3"),
id: ElementId(3),
ns: None
},
SetAttribute {
name: "id",
value: BorrowedAttributeValue::Text("3"),
id: ElementId(3),
ns: None
},
ReplaceWith { id: ElementId(1), m: 1 }
]
);
+10 -2
View File
@@ -1,15 +1,23 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*;
#[test]
fn bool_test() {
let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
let bump = Bump::new();
assert_eq!(
app.rebuild().santize().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
SetAttribute {
name: "hidden",
value: (&*bump.alloc(false.into_value(&bump))).into(),
id: ElementId(1,),
ns: None
},
AppendChildren { m: 1, id: ElementId(0) },
]
)
);
}
+1 -1
View File
@@ -18,7 +18,7 @@ fn test_borrowed_state() {
ReplacePlaceholder { path: &[0,], m: 1 },
AppendChildren { m: 1, id: ElementId(0) },
]
)
);
}
fn Parent(cx: Scope) -> Element {
+3 -1
View File
@@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element {
fn bubbles_error() {
let mut dom = VirtualDom::new(app);
let _edits = dom.rebuild().santize();
{
let _edits = dom.rebuild().santize();
}
dom.mark_dirty(ScopeId(0));
+10 -8
View File
@@ -12,14 +12,16 @@ fn cycling_elements() {
})
});
let edits = dom.rebuild().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
{
let edits = dom.rebuild().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
dom.mark_dirty(ScopeId(0));
assert_eq!(
+15 -13
View File
@@ -59,19 +59,21 @@ fn component_swap() {
}
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
ReplacePlaceholder { path: &[1], m: 3 },
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
AppendChildren { m: 2, id: ElementId(0) }
]
);
{
let edits = dom.rebuild().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
ReplacePlaceholder { path: &[1], m: 3 },
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
AppendChildren { m: 2, id: ElementId(0) }
]
);
}
dom.mark_dirty(ScopeId(0));
assert_eq!(
+20 -18
View File
@@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() {
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
});
assert_eq!(
dom.rebuild().santize().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
AppendChildren { m: 10, id: ElementId(0) },
]
);
{
assert_eq!(
dom.rebuild().santize().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
AppendChildren { m: 10, id: ElementId(0) },
]
);
}
dom.mark_dirty(ScopeId(0));
assert_eq!(
@@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() {
PushRoot { id: ElementId(7,) },
InsertBefore { id: ElementId(5,), m: 1 },
]
)
);
}
/// Should result in moves only
@@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() {
PushRoot { id: ElementId(4,) },
InsertBefore { id: ElementId(1,), m: 2 },
]
)
);
}
/// Should result in moves only
+68 -58
View File
@@ -315,66 +315,76 @@ fn remove_many() {
})
});
let edits = dom.rebuild().santize();
assert!(edits.templates.is_empty());
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(1,) },
AppendChildren { id: ElementId(0), m: 1 },
]
);
{
let edits = dom.rebuild().santize();
assert!(edits.templates.is_empty());
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(1,) },
AppendChildren { id: ElementId(0), m: 1 },
]
);
}
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
{
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
}
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
InsertAfter { id: ElementId(2,), m: 4 },
]
);
{
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
InsertAfter { id: ElementId(2,), m: 4 },
]
);
}
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(11,) },
Remove { id: ElementId(9,) },
Remove { id: ElementId(7,) },
Remove { id: ElementId(5,) },
Remove { id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
{
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(11,) },
Remove { id: ElementId(9,) },
Remove { id: ElementId(7,) },
Remove { id: ElementId(5,) },
Remove { id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
}
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
ReplaceWith { id: ElementId(11,), m: 1 },
]
)
{
dom.mark_dirty(ScopeId(0));
let edits = dom.render_immediate().santize();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
ReplaceWith { id: ElementId(11,), m: 1 },
]
)
}
}
+12 -6
View File
@@ -1,3 +1,4 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation};
use dioxus::prelude::*;
@@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
#[test]
fn dual_stream() {
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
let bump = Bump::new();
let edits = dom.rebuild().santize();
use Mutation::*;
assert_eq!(
edits.edits,
assert_eq!(edits.edits, {
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
SetAttribute {
name: "class",
value: (&*bump.alloc("123".into_value(&bump))).into(),
id: ElementId(1),
ns: None,
},
NewEventListener { name: "click", id: ElementId(1) },
HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
AppendChildren { id: ElementId(0), m: 1 }
],
);
AppendChildren { id: ElementId(0), m: 1 },
]
});
}
+26 -24
View File
@@ -9,32 +9,34 @@ use std::time::Duration;
async fn it_works() {
let mut dom = VirtualDom::new(app);
let mutations = dom.rebuild().santize();
{
let mutations = dom.rebuild().santize();
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
// wait just a moment, not enough time for the boundary to resolve