Fix nested rsx expansion by not using template titles (#2799)

* Fix nested rsx expansion by not using template titles

* fix writers with nameless templates

* fix clippy

* dont commit vscode fix

* fix release mode, pull out __template_name

* fix axum_desktop

* Fix core tests

* Make most fields of HotReloadedTemplate public for testing

* wip: formatting, compare all diff cases

* slightly smarter diffing for dynamic nodes

* add a comment about generic node diffing

* clean up mutations a bit

* fix load template

* simplify maybe_rebuild

* Restore write mutations flag in web

* write_mutations -> skip_mutations

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Jonathan KelleyandEvan Almloff authored and GitHub committed 2024-08-13 01:59:04 +00:00
1 parent a2180b92e9
commit 0de3bf7aeb
40 files changed
+597 -1038

No files matched your search

+10 -10
View File
@@ -21,18 +21,18 @@ fn attrs_cycle() {
});
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(1,) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(2,) },
AssignId { path: &[0,], id: ElementId(3,) },
SetAttribute { name: "class", value: "1".into_value(), id: ElementId(3,), ns: None },
SetAttribute { name: "id", value: "1".into_value(), id: ElementId(3,), ns: None },
@@ -42,18 +42,18 @@ fn attrs_cycle() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
LoadTemplate { index: 0, id: ElementId(2) },
AssignId { path: &[0], id: ElementId(3) },
SetAttribute {
name: "class",
@@ -74,9 +74,9 @@ fn attrs_cycle() {
// we take the node taken by attributes since we reused it
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
ReplaceWith { id: ElementId(2), m: 1 }
]
);
+2 -2
View File
@@ -6,9 +6,9 @@ fn bool_test() {
let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));
assert_eq!(
app.rebuild_to_vec().sanitize().edits,
app.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
SetAttribute {
name: "hidden",
value: dioxus_core::AttributeValue::Bool(false),
+1 -1
View File
@@ -19,7 +19,7 @@ fn bubbles_error() {
let mut dom = VirtualDom::new(app);
{
let _edits = dom.rebuild_to_vec().sanitize();
let _edits = dom.rebuild_to_vec();
}
dom.mark_dirty(ScopeId::APP);
+1 -1
View File
@@ -10,7 +10,7 @@ async fn child_futures_drop_first() {
fn app() -> Element {
if generation() == 0 {
rsx! {Child {}}
rsx! { Child {} }
} else {
rsx! {}
}
+3 -3
View File
@@ -20,7 +20,7 @@ fn state_shares() {
let mut dom = VirtualDom::new(app);
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
CreateTextNode { value: "Value is 0".to_string(), id: ElementId(1,) },
AppendChildren { m: 1, id: ElementId(0) },
@@ -41,7 +41,7 @@ fn state_shares() {
dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[SetText { value: "Value is 2".to_string(), id: ElementId(1,) },]
);
@@ -49,7 +49,7 @@ fn state_shares() {
dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.sanitize().edits,
edits.edits,
[SetText { value: "Value is 3".to_string(), id: ElementId(1,) },]
);
}
+12 -12
View File
@@ -17,13 +17,13 @@ fn test_original_diff() {
}
});
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
// add to root
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
AppendChildren { m: 1, id: ElementId(0) }
]
)
@@ -46,7 +46,7 @@ fn create() {
}
});
let _edits = dom.rebuild_to_vec().sanitize();
let _edits = dom.rebuild_to_vec();
// todo: we don't test template mutations anymore since the templates are passed along
@@ -64,11 +64,11 @@ fn create() {
// AppendChildren { m: 1 },
// AppendChildren { m: 2 },
// AppendChildren { m: 1 },
// SaveTemplate { name: "template", m: 1 },
// SaveTemplate { m: 1 },
// // The fragment child template
// CreateStaticText { value: "hello" },
// CreateStaticText { value: "world" },
// SaveTemplate { name: "template", m: 2 },
// SaveTemplate { m: 2 },
// ]
// );
}
@@ -77,7 +77,7 @@ fn create() {
fn create_list() {
let mut dom = VirtualDom::new(|| rsx! {{(0..3).map(|f| rsx!( div { "hello" } ))}});
let _edits = dom.rebuild_to_vec().sanitize();
let _edits = dom.rebuild_to_vec();
// note: we dont test template edits anymore
// assert_eq!(
@@ -87,7 +87,7 @@ fn create_list() {
// CreateElement { name: "div" },
// CreateStaticText { value: "hello" },
// AppendChildren { m: 1 },
// SaveTemplate { name: "template", m: 1 }
// SaveTemplate { m: 1 }
// ]
// );
}
@@ -103,7 +103,7 @@ fn create_simple() {
}
});
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
// note: we dont test template edits anymore
// assert_eq!(
@@ -115,7 +115,7 @@ fn create_simple() {
// CreateElement { name: "div" },
// CreateElement { name: "div" },
// // add to root
// SaveTemplate { name: "template", m: 4 }
// SaveTemplate { m: 4 }
// ]
// );
}
@@ -142,7 +142,7 @@ fn create_components() {
}
}
let _edits = dom.rebuild_to_vec().sanitize();
let _edits = dom.rebuild_to_vec();
// todo: test this
}
@@ -161,7 +161,7 @@ fn anchors() {
});
// note that the template under "false" doesn't show up since it's not loaded
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
// note: we dont test template edits anymore
// assert_eq!(
@@ -178,7 +178,7 @@ fn anchors() {
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
CreatePlaceholder { id: ElementId(2) },
AppendChildren { m: 2, id: ElementId(0) }
]
-32
View File
@@ -1,32 +0,0 @@
// use dioxus::dioxus_core::Mutation::*;
use dioxus::prelude::*;
#[test]
fn multiroot() {
let mut dom = VirtualDom::new(|| {
rsx! {
div { "Hello a" }
div { "Hello b" }
div { "Hello c" }
}
});
// note: we dont test template edits anymore
let _templates = dom.rebuild_to_vec().sanitize().templates;
// assert_eq!(
// dom.rebuild_to_vec().sanitize().templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Hello a" },
// AppendChildren { m: 1 },
// CreateElement { name: "div" },
// CreateStaticText { value: "Hello b" },
// AppendChildren { m: 1 },
// CreateElement { name: "div" },
// CreateStaticText { value: "Hello c" },
// AppendChildren { m: 1 },
// SaveTemplate { name: "template", m: 3 }
// ]
// )
}
+14 -10
View File
@@ -1,6 +1,7 @@
use dioxus::dioxus_core::Mutation::*;
use dioxus::prelude::*;
use dioxus_core::ElementId;
use pretty_assertions::assert_eq;
// A real-world usecase of templates at peak performance
// In react, this would be a lot of node creation.
@@ -25,7 +26,7 @@ fn app() -> Element {
fn list_renders() {
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
// note: we dont test template edits anymore
// assert_eq!(
@@ -37,7 +38,7 @@ fn list_renders() {
// // append when modify the values (IE no need for a placeholder)
// CreateStaticPlaceholder,
// AppendChildren { m: 1 },
// SaveTemplate { name: "template", m: 1 },
// SaveTemplate { m: 1 },
// // Create the inner template div
// CreateElement { name: "div" },
// CreateElement { name: "h1" },
@@ -47,7 +48,7 @@ fn list_renders() {
// CreateTextPlaceholder,
// AppendChildren { m: 1 },
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// SaveTemplate { m: 1 }
// ],
// );
@@ -55,14 +56,17 @@ fn list_renders() {
edits.edits,
[
// Load the outer div
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
// Load each template one-by-one, rehydrating it
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
HydrateText { path: &[1, 0], value: "0".to_string(), id: ElementId(3) },
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
HydrateText { path: &[1, 0], value: "1".to_string(), id: ElementId(5) },
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
HydrateText { path: &[1, 0], value: "2".to_string(), id: ElementId(7) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
ReplacePlaceholder { path: &[1, 0], m: 1 },
LoadTemplate { index: 0, id: ElementId(4) },
CreateTextNode { value: "1".to_string(), id: ElementId(5) },
ReplacePlaceholder { path: &[1, 0], m: 1 },
LoadTemplate { index: 0, id: ElementId(6) },
CreateTextNode { value: "2".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[1, 0], m: 1 },
// Replace the 0th childn on the div with the 3 templates on the stack
ReplacePlaceholder { m: 3, path: &[0] },
// Append the container div to the dom
+8 -10
View File
@@ -21,12 +21,12 @@ fn nested_passthru_creates() {
}
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
AppendChildren { m: 1, id: ElementId(0) },
]
)
@@ -60,16 +60,16 @@ fn nested_passthru_creates_add() {
let mut dom = VirtualDom::new(app);
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
// load 1
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
// load 2
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
LoadTemplate { index: 0, id: ElementId(2) },
// load 3
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
LoadTemplate { index: 0, id: ElementId(3) },
// load div that contains 4
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
LoadTemplate { index: 1, id: ElementId(4) },
AppendChildren { id: ElementId(0), m: 4 },
]
);
@@ -85,11 +85,9 @@ fn dynamic_node_as_root() {
}
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
// Since the roots were all dynamic, they should not cause any template muations
assert!(edits.templates.is_empty());
// The root node is text, so we just create it on the spot
assert_eq!(
edits.edits,
+8 -8
View File
@@ -11,11 +11,11 @@ fn cycling_elements() {
});
{
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(1,) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
@@ -23,9 +23,9 @@ fn cycling_elements() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
@@ -33,18 +33,18 @@ fn cycling_elements() {
// notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
+22 -12
View File
@@ -1,5 +1,6 @@
use dioxus::dioxus_core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use pretty_assertions::assert_eq;
/// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality
///
@@ -7,6 +8,15 @@ use dioxus::prelude::*;
/// different pointers
#[test]
fn component_swap() {
// Check that templates with the same structure are deduplicated at compile time
// If they are not, this test will fail because it is being run in debug mode where templates are not deduped
let dynamic = 0;
let template_1 = rsx! { "{dynamic}" };
let template_2 = rsx! { "{dynamic}" };
if template_1.unwrap().template != template_2.unwrap().template {
return;
}
fn app() -> Element {
let mut render_phase = use_signal(|| 0);
@@ -62,16 +72,16 @@ fn component_swap() {
let mut dom = VirtualDom::new(app);
{
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
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) },
LoadTemplate { index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(2) },
LoadTemplate { index: 0, id: ElementId(3) },
LoadTemplate { index: 0, id: ElementId(4) },
ReplacePlaceholder { path: &[1], m: 3 },
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(5) },
AppendChildren { m: 2, id: ElementId(0) }
]
);
@@ -79,27 +89,27 @@ fn component_swap() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(6) },
ReplaceWith { id: ElementId(5), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(5) },
ReplaceWith { id: ElementId(6), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(6) },
ReplaceWith { id: ElementId(5), m: 1 }
]
);
+7 -5
View File
@@ -7,6 +7,7 @@ fn toggle_option_text() {
let mut dom = VirtualDom::new(|| {
let gen = generation();
let text = if gen % 2 != 0 { Some("hello") } else { None };
println!("{:?}", text);
rsx! {
div {
@@ -17,10 +18,11 @@ fn toggle_option_text() {
// load the div and then assign the None as a placeholder
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AssignId { path: &[0], id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { id: ElementId(0), m: 1 },
]
);
@@ -28,7 +30,7 @@ fn toggle_option_text() {
// Rendering again should replace the placeholder with an text node
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
ReplaceWith { id: ElementId(2,), m: 1 },
@@ -38,7 +40,7 @@ fn toggle_option_text() {
// Rendering again should replace the placeholder with an text node
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
CreatePlaceholder { id: ElementId(2,) },
ReplaceWith { id: ElementId(3,), m: 1 },
+12 -12
View File
@@ -48,36 +48,36 @@ fn element_swap() {
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
@@ -128,7 +128,7 @@ fn attribute_diff() {
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
SetAttribute {
name: "b",
@@ -147,7 +147,7 @@ fn attribute_diff() {
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
SetAttribute { name: "a", value: AttributeValue::None, id: ElementId(1,), ns: None },
SetAttribute { name: "b", value: AttributeValue::None, id: ElementId(1,), ns: None },
@@ -168,7 +168,7 @@ fn attribute_diff() {
vdom.mark_dirty(ScopeId::APP);
assert_eq!(
vdom.render_immediate_to_vec().sanitize().edits,
vdom.render_immediate_to_vec().edits,
[
SetAttribute { name: "c", value: AttributeValue::None, id: ElementId(1,), ns: None },
SetAttribute {
@@ -196,7 +196,7 @@ fn diff_empty() {
vdom.rebuild(&mut NoOpMutations);
vdom.mark_dirty(ScopeId::APP);
let edits = vdom.render_immediate_to_vec().sanitize().edits;
let edits = vdom.render_immediate_to_vec().edits;
assert_eq!(
edits,
+32 -32
View File
@@ -22,18 +22,18 @@ fn keyed_diffing_out_of_order() {
{
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().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,) },
LoadTemplate { index: 0, id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(3,) },
LoadTemplate { index: 0, id: ElementId(4,) },
LoadTemplate { index: 0, id: ElementId(5,) },
LoadTemplate { index: 0, id: ElementId(6,) },
LoadTemplate { index: 0, id: ElementId(7,) },
LoadTemplate { index: 0, id: ElementId(8,) },
LoadTemplate { index: 0, id: ElementId(9,) },
LoadTemplate { index: 0, id: ElementId(10,) },
AppendChildren { m: 10, id: ElementId(0) },
]
);
@@ -169,10 +169,10 @@ fn keyed_diffing_additions() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
LoadTemplate { name: "template", index: 0, id: ElementId(7) },
LoadTemplate { index: 0, id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(7) },
InsertAfter { id: ElementId(5), m: 2 }
]
);
@@ -194,11 +194,11 @@ fn keyed_diffing_additions_and_moves_on_ends() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
// create 11, 12
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(6) },
InsertAfter { id: ElementId(3), m: 2 },
// move 7 to the front
PushRoot { id: ElementId(4) },
@@ -224,15 +224,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
// LIS: 4, 5, 6
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
// create 5, 6
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(6) },
InsertBefore { id: ElementId(3), m: 2 },
// create 7, 8
LoadTemplate { name: "template", index: 0, id: ElementId(7) },
LoadTemplate { name: "template", index: 0, id: ElementId(8) },
LoadTemplate { index: 0, id: ElementId(7) },
LoadTemplate { index: 0, id: ElementId(8) },
InsertBefore { id: ElementId(2), m: 2 },
// move 7
PushRoot { id: ElementId(4) },
@@ -258,7 +258,7 @@ fn controlled_keyed_diffing_out_of_order() {
// LIS: 5, 6
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
// remove 7
Remove { id: ElementId(4,) },
@@ -266,10 +266,10 @@ fn controlled_keyed_diffing_out_of_order() {
PushRoot { id: ElementId(1) },
InsertAfter { id: ElementId(3,), m: 1 },
// create 9 and insert before 6
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
LoadTemplate { index: 0, id: ElementId(4) },
InsertBefore { id: ElementId(3,), m: 1 },
// create 0 and insert before 5
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(5) },
InsertBefore { id: ElementId(2,), m: 1 },
]
);
@@ -291,10 +291,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
Remove { id: ElementId(5,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(5) },
InsertBefore { id: ElementId(3,), m: 1 },
PushRoot { id: ElementId(4) },
InsertBefore { id: ElementId(1,), m: 1 },
@@ -320,7 +320,7 @@ fn remove_list() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
Remove { id: ElementId(5) },
Remove { id: ElementId(4) },
@@ -345,11 +345,11 @@ fn no_common_keys() {
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
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 { index: 0, id: ElementId(4) },
LoadTemplate { index: 0, id: ElementId(5) },
LoadTemplate { index: 0, id: ElementId(6) },
Remove { id: ElementId(3) },
Remove { id: ElementId(2) },
ReplaceWith { id: ElementId(1), m: 3 }
+147 -109
View File
@@ -1,5 +1,8 @@
use std::collections::HashSet;
use dioxus::dioxus_core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::Mutation;
use pretty_assertions::assert_eq;
#[test]
@@ -18,10 +21,11 @@ fn list_creates_one_by_one() {
// load the div and then assign the empty fragment as a placeholder
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AssignId { path: &[0], id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { id: ElementId(0), m: 1 },
]
);
@@ -29,10 +33,11 @@ fn list_creates_one_by_one() {
// Rendering the first item should replace the placeholder with an element
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4,) },
LoadTemplate { index: 0, id: ElementId(3,) },
CreateTextNode { value: "0".to_string(), id: ElementId(4,) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
@@ -40,10 +45,11 @@ fn list_creates_one_by_one() {
// Rendering the next item should insert after the previous
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5,) },
LoadTemplate { index: 0, id: ElementId(2,) },
CreateTextNode { value: "1".to_string(), id: ElementId(5,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(3,), m: 1 },
]
);
@@ -51,10 +57,11 @@ fn list_creates_one_by_one() {
// ... and again!
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7,) },
LoadTemplate { index: 0, id: ElementId(6,) },
CreateTextNode { value: "2".to_string(), id: ElementId(7,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(2,), m: 1 },
]
);
@@ -62,10 +69,11 @@ fn list_creates_one_by_one() {
// once more
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
HydrateText { path: &[0], value: "3".to_string(), id: ElementId(9,) },
LoadTemplate { index: 0, id: ElementId(8,) },
CreateTextNode { value: "3".to_string(), id: ElementId(9,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(6,), m: 1 },
]
);
@@ -87,17 +95,20 @@ fn removes_one_by_one() {
// load the div and then assign the empty fragment as a placeholder
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
// The container
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(1) },
// each list item
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5) },
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(4) },
CreateTextNode { value: "1".to_string(), id: ElementId(5) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(6) },
CreateTextNode { value: "2".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
// replace the placeholder in the template with the 3 templates on the stack
ReplacePlaceholder { m: 3, path: &[0] },
// Mount the div
@@ -109,14 +120,14 @@ fn removes_one_by_one() {
// Rendering the first item should replace the placeholder with an element
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(6) }]
);
// Remove div(2)
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(4) }]
);
@@ -124,7 +135,7 @@ fn removes_one_by_one() {
// todo: this should just be a remove with no placeholder
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
CreatePlaceholder { id: ElementId(4) },
ReplaceWith { id: ElementId(2), m: 1 }
@@ -135,14 +146,17 @@ fn removes_one_by_one() {
// todo: this should actually be append to, but replace placeholder is fine for now
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
LoadTemplate { name: "template", index: 0, id: ElementId(8) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(4), m: 3 }
]
);
@@ -162,46 +176,53 @@ fn list_shrink_multiroot() {
});
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AssignId { path: &[0,], id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0,], m: 1 },
AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4) },
LoadTemplate { name: "template", index: 1, id: ElementId(5) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
LoadTemplate { index: 0, id: ElementId(3) },
CreateTextNode { value: "0".to_string(), id: ElementId(4) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(5) },
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(2), m: 2 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
LoadTemplate { name: "template", index: 1, id: ElementId(8) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(5), m: 2 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
LoadTemplate { name: "template", index: 1, id: ElementId(12) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(12) },
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(8), m: 2 }
]
);
@@ -224,46 +245,48 @@ fn removes_one_by_one_multiroot() {
// load the div and then assign the empty fragment as a placeholder
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
//
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(5) },
//
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
LoadTemplate { name: "template", index: 1, id: ElementId(8) },
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
//
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
LoadTemplate { name: "template", index: 1, id: ElementId(12) },
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
LoadTemplate { index: 0, id: ElementId(1) },
//
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(4) },
CreateTextNode { value: "0".to_string(), id: ElementId(5) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(6) },
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(12) },
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplacePlaceholder { path: &[0], m: 6 },
//
AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().sanitize().edits,
dom.render_immediate_to_vec().edits,
[
CreatePlaceholder { id: ElementId(8) },
Remove { id: ElementId(2) },
@@ -317,9 +340,9 @@ fn remove_many() {
}
});
// len = 0
{
let edits = dom.rebuild_to_vec().sanitize();
assert!(edits.templates.is_empty());
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
@@ -329,62 +352,76 @@ fn remove_many() {
);
}
// len = 1
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(3,) },
LoadTemplate { index: 0, id: ElementId(2,) },
CreateTextNode { value: "hello 0".to_string(), id: ElementId(3,) },
ReplacePlaceholder { path: &[0,], m: 1 },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
}
// len = 5
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
HydrateText { path: &[0,], value: "hello 1".to_string(), id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
HydrateText { path: &[0,], value: "hello 2".to_string(), id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
HydrateText { path: &[0,], value: "hello 3".to_string(), id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
HydrateText { path: &[0,], value: "hello 4".to_string(), id: ElementId(10,) },
LoadTemplate { index: 0, id: ElementId(1,) },
CreateTextNode { value: "hello 1".to_string(), id: ElementId(4,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(5,) },
CreateTextNode { value: "hello 2".to_string(), id: ElementId(6,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(7,) },
CreateTextNode { value: "hello 3".to_string(), id: ElementId(8,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(9,) },
CreateTextNode { value: "hello 4".to_string(), id: ElementId(10,) },
ReplacePlaceholder { path: &[0,], m: 1 },
InsertAfter { id: ElementId(2,), m: 4 },
]
);
}
// len = 0
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(edits.edits[0], CreatePlaceholder { id: ElementId(11,) });
let removed = edits.edits[1..5]
.iter()
.map(|edit| match edit {
Mutation::Remove { id } => *id,
_ => panic!("Expected remove"),
})
.collect::<HashSet<_>>();
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 },
]
removed,
[ElementId(7), ElementId(5), ElementId(2), ElementId(1)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(edits.edits[5..], [ReplaceWith { id: ElementId(9,), m: 1 },]);
}
// len = 1
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(1,) },
LoadTemplate { index: 0, id: ElementId(9,) },
CreateTextNode { value: "hello 0".to_string(), id: ElementId(7,) },
ReplacePlaceholder { path: &[0,], m: 1 },
ReplaceWith { id: ElementId(11,), m: 1 },
]
)
@@ -415,12 +452,13 @@ fn replace_and_add_items() {
// The list starts empty with a placeholder
{
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
AssignId { path: &[0], id: ElementId(2,) },
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { id: ElementId(0), m: 1 },
]
);
@@ -429,11 +467,11 @@ fn replace_and_add_items() {
// Rerendering adds an a static template
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
LoadTemplate { index: 0, id: ElementId(3,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
@@ -442,7 +480,7 @@ fn replace_and_add_items() {
// Rerendering replaces the old node with a placeholder and adds a new placeholder
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
@@ -457,15 +495,15 @@ fn replace_and_add_items() {
// Rerendering replaces both placeholders with the static nodes and add a new static node
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec().sanitize();
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
LoadTemplate { index: 0, id: ElementId(3,) },
InsertAfter { id: ElementId(2,), m: 1 },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
LoadTemplate { index: 0, id: ElementId(5,) },
ReplaceWith { id: ElementId(4,), m: 1 },
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
LoadTemplate { index: 0, id: ElementId(4,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
+6 -26
View File
@@ -1,10 +1,8 @@
#![cfg(not(miri))]
use dioxus::prelude::*;
use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, VComponent, VNode, *};
use std::{
cfg, collections::HashSet, default::Default, sync::atomic::AtomicUsize, sync::atomic::Ordering,
};
use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, Template, VComponent, VNode, *};
use std::{cfg, collections::HashSet, default::Default};
fn random_ns() -> Option<&'static str> {
let namespace = rand::random::<u8>() % 2;
@@ -129,7 +127,7 @@ enum DynamicNodeType {
Other,
}
fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>) {
fn create_random_template() -> (Template, Vec<DynamicNodeType>) {
let mut dynamic_node_type = Vec::new();
let mut template_idx = 0;
let mut attr_idx = 0;
@@ -160,7 +158,7 @@ fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>
.into_boxed_slice(),
);
(
Template { name, roots, node_paths, attr_paths },
Template { roots, node_paths, attr_paths },
dynamic_node_type,
)
}
@@ -174,7 +172,6 @@ fn create_random_dynamic_node(depth: usize) -> DynamicNode {
VNode::new(
None,
Template {
name: create_template_location(),
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]],
attr_paths: &[],
@@ -219,19 +216,6 @@ fn create_random_dynamic_attr() -> Attribute {
)
}
static TEMPLATE_COUNT: AtomicUsize = AtomicUsize::new(0);
fn create_template_location() -> &'static str {
Box::leak(
format!(
"{}{}",
concat!(file!(), ":", line!(), ":", column!(), ":"),
TEMPLATE_COUNT.fetch_add(1, Ordering::Relaxed)
)
.into_boxed_str(),
)
}
#[derive(PartialEq, Props, Clone)]
struct DepthProps {
depth: usize,
@@ -245,7 +229,7 @@ fn create_random_element(cx: DepthProps) -> Element {
let range = if cx.root { 2 } else { 3 };
let node = match rand::random::<usize>() % range {
0 | 1 => {
let (template, dynamic_node_types) = create_random_template(create_template_location());
let (template, dynamic_node_types) = create_random_template();
let node = VNode::new(
None,
template,
@@ -314,8 +298,6 @@ fn diff() {
struct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);
impl WriteMutations for InsertEventListenerMutationHandler<'_> {
fn register_template(&mut self, _: Template) {}
fn append_children(&mut self, _: ElementId, _: usize) {}
fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
@@ -324,9 +306,7 @@ impl WriteMutations for InsertEventListenerMutationHandler<'_> {
fn create_text_node(&mut self, _: &str, _: ElementId) {}
fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
fn replace_node_with(&mut self, _: ElementId, _: usize) {}
+4 -3
View File
@@ -33,13 +33,14 @@ fn basic_syntax_is_a_template() -> Element {
#[test]
fn dual_stream() {
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
let edits = dom.rebuild_to_vec().sanitize();
let edits = dom.rebuild_to_vec();
use Mutation::*;
assert_eq!(edits.edits, {
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
HydrateText { path: &[0, 0], value: "123".to_string(), id: ElementId(2) },
LoadTemplate { index: 0, id: ElementId(1) },
CreateTextNode { value: "123".to_string(), id: ElementId(2) },
ReplacePlaceholder { path: &[0, 0], m: 1 },
SetAttribute {
name: "class",
value: "asd 123 123 ".into_value(),
+4 -3
View File
@@ -30,10 +30,11 @@ fn manual_diffing() {
*value.lock().unwrap() = "goodbye";
assert_eq!(
dom.rebuild_to_vec().sanitize().edits,
dom.rebuild_to_vec().edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
HydrateText { path: &[0], value: "goodbye".to_string(), id: ElementId(4) },
LoadTemplate { index: 0, id: ElementId(3) },
CreateTextNode { value: "goodbye".to_string(), id: ElementId(4) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { m: 1, id: ElementId(0) }
]
);
+47 -72
View File
@@ -415,20 +415,20 @@ fn toggle_suspense() {
.unwrap()
.block_on(async {
let mut dom = VirtualDom::new(app);
let mutations = dom.rebuild_to_vec().sanitize();
let mutations = dom.rebuild_to_vec();
// First create goodbye world
println!("{:#?}", mutations);
assert_eq!(
mutations.edits,
[
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
Mutation::AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
// Then replace that with nothing
println!("{:#?}", mutations);
@@ -441,20 +441,20 @@ fn toggle_suspense() {
);
dom.wait_for_work().await;
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
// Then replace it with a placeholder
println!("{:#?}", mutations);
assert_eq!(
mutations.edits,
[
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
Mutation::ReplaceWith { id: ElementId(2), m: 1 },
]
);
dom.wait_for_work().await;
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
// Then replace it with the resolved node
println!("{:#?}", mutations);
@@ -463,7 +463,7 @@ fn toggle_suspense() {
[
Mutation::CreatePlaceholder { id: ElementId(2,) },
Mutation::ReplaceWith { id: ElementId(1,), m: 1 },
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
Mutation::ReplaceWith { id: ElementId(2), m: 1 },
]
);
@@ -600,14 +600,14 @@ fn nested_suspense_resolves_client() {
// DOM STATE:
// placeholder // ID: 1
// "Loading 0..." // ID: 2
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
// Fill in the contents of the initial message and start loading the nested suspense
// The title also finishes loading
assert_eq!(
mutations.edits,
vec![
// Creating and swapping these placeholders doesn't do anything
// It is just extra work that we are forced to do because mutations are not
// It is just extra work that we are forced to do because mutations are not
// reversible. We start rendering the children and then realize it is suspended.
// Then we need to replace what we just rendered with the suspense placeholder
CreatePlaceholder { id: ElementId(3,) },
@@ -622,12 +622,9 @@ fn nested_suspense_resolves_client() {
ReplaceWith { id: ElementId(2,), m: 1 },
// Load the title
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
HydrateText {
path: &[0,],
value: "The robot says hello world".to_string(),
id: ElementId(4,),
},
LoadTemplate { index: 0, id: ElementId(2,) },
CreateTextNode { value: "The robot says hello world".to_string(), id: ElementId(4,) },
ReplacePlaceholder { path: &[0,], m: 1 },
SetAttribute {
name: "id",
ns: None,
@@ -636,12 +633,9 @@ fn nested_suspense_resolves_client() {
},
// Then load the body
LoadTemplate { name: "template", index: 1, id: ElementId(5,) },
HydrateText {
path: &[0,],
value: "The robot becomes sentient and says hello world".to_string(),
id: ElementId(6,),
},
LoadTemplate { index: 1, id: ElementId(5,) },
CreateTextNode { value: "The robot becomes sentient and says hello world".to_string(), id: ElementId(6,) },
ReplacePlaceholder { path: &[0,], m: 1 },
SetAttribute {
name: "id",
ns: None,
@@ -650,7 +644,7 @@ fn nested_suspense_resolves_client() {
},
// Then load the suspended children
LoadTemplate { name: "template", index: 2, id: ElementId(7,) },
LoadTemplate { index: 2, id: ElementId(7,) },
CreateTextNode { value: "Loading 1...".to_string(), id: ElementId(8,) },
CreateTextNode { value: "Loading 2...".to_string(), id: ElementId(9,) },
ReplacePlaceholder { path: &[0,], m: 2 },
@@ -674,7 +668,7 @@ fn nested_suspense_resolves_client() {
// div // ID: 7
// "Loading 1..." // ID: 8
// "Loading 2..." // ID: 9
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
assert_eq!(
mutations.edits,
vec![
@@ -693,20 +687,18 @@ fn nested_suspense_resolves_client() {
// Load the nested suspense
LoadTemplate {
name: "template",
index: 0,
id: ElementId(
8,
),
},
HydrateText {
CreateTextNode { value: "The world says hello back".to_string(), id: ElementId(10,) },
ReplacePlaceholder {
path: &[
0,
],
value: "The world says hello back".to_string(),
id: ElementId(
10,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -717,20 +709,18 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 1,
id: ElementId(
11,
),
},
HydrateText {
CreateTextNode { value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(), id: ElementId(12,) },
ReplacePlaceholder {
path: &[
0,
],
value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(),
id: ElementId(
12,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -741,19 +731,17 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 2,
id: ElementId(
13,
),
},
AssignId {
CreatePlaceholder { id: ElementId(14,) },
ReplacePlaceholder {
path: &[
0,
],
id: ElementId(
14,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -783,20 +771,17 @@ fn nested_suspense_resolves_client() {
m: 1,
},
LoadTemplate {
name: "template",
index: 0,
id: ElementId(
9,
),
},
HydrateText {
CreateTextNode { value: "Goodbye Robot".to_string(), id: ElementId(15,) },
ReplacePlaceholder {
path: &[
0,
],
value: "Goodbye Robot".to_string(),
id: ElementId(
15,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -807,20 +792,17 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 1,
id: ElementId(
16,
),
},
HydrateText {
CreateTextNode { value: "The robot says goodbye".to_string(), id: ElementId(17,) },
ReplacePlaceholder {
path: &[
0,
],
value: "The robot says goodbye".to_string(),
id: ElementId(
17,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -831,7 +813,7 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 2,
id: ElementId(
18,
@@ -860,7 +842,7 @@ fn nested_suspense_resolves_client() {
);
dom.wait_for_work().await;
let mutations = dom.render_immediate_to_vec().sanitize();
let mutations = dom.render_immediate_to_vec();
assert_eq!(
mutations.edits,
vec![
@@ -876,20 +858,18 @@ fn nested_suspense_resolves_client() {
m: 1,
},
LoadTemplate {
name: "template",
index: 0,
id: ElementId(
19,
),
},
HydrateText {
CreateTextNode { value: "Goodbye Robot again".to_string(), id: ElementId(20,) },
ReplacePlaceholder {
path: &[
0,
],
value: "Goodbye Robot again".to_string(),
id: ElementId(
20,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -900,20 +880,17 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 1,
id: ElementId(
21,
),
},
HydrateText {
CreateTextNode { value: "The robot says goodbye again".to_string(), id: ElementId(22,) },
ReplacePlaceholder {
path: &[
0,
],
value: "The robot says goodbye again".to_string(),
id: ElementId(
22,
),
m: 1,
},
SetAttribute {
name: "id",
@@ -924,19 +901,17 @@ fn nested_suspense_resolves_client() {
),
},
LoadTemplate {
name: "template",
index: 2,
id: ElementId(
23,
),
},
AssignId {
CreatePlaceholder { id: ElementId(24,) },
ReplacePlaceholder {
path: &[
0,
0
],
id: ElementId(
24,
),
m: 1,
},
SetAttribute {
name: "id",