wip: migrate all tests to core
This commit is contained in:
1 parent
d0554b9ed6
commit
f614cbb401
12 files changed
+104
-273
No files matched your search
@@ -1 +0,0 @@
|
||||
struct_lit_width = 80
|
||||
@@ -1,53 +0,0 @@
|
||||
# Testing of Dioxus core
|
||||
|
||||
|
||||
NodeFactory
|
||||
- [] rsx, html, NodeFactory generate the same structures
|
||||
|
||||
Diffing
|
||||
- [x] create elements
|
||||
- [x] create text
|
||||
- [x] create fragments
|
||||
- [x] create empty fragments (placeholders)
|
||||
- [x] diff elements
|
||||
- [x] diff from element/text to fragment
|
||||
- [x] diff from element/text to empty fragment
|
||||
- [x] diff to element with children works too
|
||||
- [x] replace with works forward
|
||||
- [x] replace with works backward
|
||||
- [x] un-keyed diffing
|
||||
- [x] keyed diffing
|
||||
- [x] keyed diffing out of order
|
||||
- [x] keyed diffing with prefix/suffix
|
||||
- [x] suspended nodes work
|
||||
|
||||
Lifecycle
|
||||
- [] Components mount properly
|
||||
- [] Components create new child components
|
||||
- [] Replaced components unmount old components and mount new
|
||||
- [] Post-render effects are called
|
||||
- []
|
||||
|
||||
|
||||
Shared Context
|
||||
- [] Shared context propagates downwards
|
||||
- [] unwrapping shared context if it doesn't exist works too
|
||||
|
||||
Suspense
|
||||
- [] use_suspense generates suspended nodes
|
||||
|
||||
|
||||
Hooks
|
||||
- [] Drop order is maintained
|
||||
- [] Shared hook state is okay
|
||||
- [] use_hook works
|
||||
- [] use_ref works
|
||||
- [] use_noderef works
|
||||
- [] use_provide_state
|
||||
- [] use_consume_state
|
||||
|
||||
|
||||
VirtualDOM API
|
||||
- [] work
|
||||
- [] rebuild
|
||||
- [] change props
|
||||
@@ -1,831 +0,0 @@
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
|
||||
//! Diffing Tests
|
||||
//!
|
||||
//! These tests only verify that the diffing algorithm works properly for single components.
|
||||
//!
|
||||
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
||||
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
|
||||
fn new_dom() -> VirtualDom {
|
||||
VirtualDom::new(|cx| cx.render(rsx_without_templates!("hi")))
|
||||
}
|
||||
|
||||
use dioxus_core::DomEdit::*;
|
||||
|
||||
/// Should push the text node onto the stack and modify it
|
||||
#[test]
|
||||
fn html_and_rsx_generate_the_same_output() {
|
||||
let dom = new_dom();
|
||||
let (create, change) = dom.diff_lazynodes(
|
||||
rsx_without_templates! ( div { "Hello world" } ),
|
||||
rsx_without_templates! ( div { "Goodbye world" } ),
|
||||
);
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreateElement { root: Some(1,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(2,), text: "Hello world" },
|
||||
AppendChildren { root: Some(1,), children: vec![2] },
|
||||
AppendChildren { root: Some(0,), children: vec![1] },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[SetText { root: Some(2,), text: "Goodbye world" },]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in 3 elements on the stack
|
||||
#[test]
|
||||
fn fragments_create_properly() {
|
||||
let dom = new_dom();
|
||||
|
||||
let create = dom.create_vnodes(rsx_without_templates! {
|
||||
div { "Hello a" }
|
||||
div { "Hello b" }
|
||||
div { "Hello c" }
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreateElement { root: Some(1,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(2,), text: "Hello a" },
|
||||
AppendChildren { root: Some(1,), children: vec![2,] },
|
||||
CreateElement { root: Some(3,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(4,), text: "Hello b" },
|
||||
AppendChildren { root: Some(3,), children: vec![4,] },
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(6,), text: "Hello c" },
|
||||
AppendChildren { root: Some(5,), children: vec![6,] },
|
||||
AppendChildren { root: Some(0,), children: vec![1, 3, 5,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
||||
#[test]
|
||||
fn empty_fragments_create_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
|
||||
let right = rsx_without_templates!({ (0..1).map(|_f| rsx_without_templates! { div {}}) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreatePlaceholder { root: Some(1,) },
|
||||
AppendChildren { root: Some(0,), children: vec![1,] },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(2,), tag: "div", children: 0 },
|
||||
ReplaceWith { root: Some(1,), nodes: vec![2,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
|
||||
#[test]
|
||||
fn empty_fragments_create_many_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
|
||||
let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreatePlaceholder { root: Some(1,) },
|
||||
AppendChildren { root: Some(0,), children: vec![1,] },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(2,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(3,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(4,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(6,), tag: "div", children: 0 },
|
||||
ReplaceWith { root: Some(1,), nodes: vec![2, 3, 4, 5, 6,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
||||
/// Includes child nodes inside the fragment
|
||||
#[test]
|
||||
fn empty_fragments_create_anchors_with_many_children() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
|
||||
let right = rsx_without_templates!({
|
||||
(0..3).map(|f| {
|
||||
rsx_without_templates! { div { "hello: {f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreatePlaceholder { root: Some(1,) },
|
||||
AppendChildren { root: Some(0,), children: vec![1,] },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(2,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(3,), text: "hello: 0" },
|
||||
AppendChildren { root: Some(2,), children: vec![3,] },
|
||||
CreateElement { root: Some(4,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(5,), text: "hello: 1" },
|
||||
AppendChildren { root: Some(4,), children: vec![5,] },
|
||||
CreateElement { root: Some(6,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(7,), text: "hello: 2" },
|
||||
AppendChildren { root: Some(6,), children: vec![7,] },
|
||||
ReplaceWith { root: Some(1,), nodes: vec![2, 4, 6,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in every node being pushed and then replaced with an anchor
|
||||
#[test]
|
||||
fn many_items_become_fragment() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreateElement { root: Some(1,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(2,), text: "hello" },
|
||||
AppendChildren { root: Some(1,), children: vec![2,] },
|
||||
CreateElement { root: Some(3,), tag: "div", children: 0 },
|
||||
CreateTextNode { root: Some(4,), text: "hello" },
|
||||
AppendChildren { root: Some(3,), children: vec![4,] },
|
||||
AppendChildren { root: Some(0,), children: vec![1, 3,] },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreatePlaceholder { root: Some(5,) },
|
||||
ReplaceWith { root: Some(1,), nodes: vec![5,] },
|
||||
Remove { root: Some(3,) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in no edits
|
||||
#[test]
|
||||
fn two_equal_fragments_are_equal() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_create, change) = dom.diff_lazynodes(left, right);
|
||||
assert!(change.edits.is_empty());
|
||||
}
|
||||
|
||||
/// Should result the creation of more nodes appended after the old last node
|
||||
#[test]
|
||||
fn two_fragments_with_differrent_elements_are_differet() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!(
|
||||
{ (0..2).map(|_| rsx_without_templates! { div { }} ) }
|
||||
p {}
|
||||
);
|
||||
let right = rsx_without_templates!(
|
||||
{ (0..5).map(|_| rsx_without_templates! (h1 { }) ) }
|
||||
p {}
|
||||
);
|
||||
|
||||
let (_create, changes) = dom.diff_lazynodes(left, right);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[
|
||||
CreateElement { root: Some(4,), tag: "h1", children: 0 },
|
||||
CreateElement { root: Some(5,), tag: "h1", children: 0 },
|
||||
CreateElement { root: Some(6,), tag: "h1", children: 0 },
|
||||
InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
|
||||
CreateElement { root: Some(7,), tag: "h1", children: 0 },
|
||||
ReplaceWith { root: Some(1,), nodes: vec![7,] }, // notice how 1 gets re-used
|
||||
CreateElement { root: Some(1,), tag: "h1", children: 0 },
|
||||
ReplaceWith { root: Some(2,), nodes: vec![1,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in multiple nodes destroyed - with changes to the first nodes
|
||||
#[test]
|
||||
fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!(
|
||||
{(0..5).map(|f| {rsx_without_templates! { div { }}})}
|
||||
p {}
|
||||
);
|
||||
let right = rsx_without_templates!(
|
||||
{(0..2).map(|f| {rsx_without_templates! { h1 { }}})}
|
||||
p {}
|
||||
);
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreateElement { root: Some(1,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(2,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(3,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(4,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(6,), tag: "p", children: 0 },
|
||||
AppendChildren { root: Some(0,), children: vec![1, 2, 3, 4, 5, 6,] },
|
||||
]
|
||||
);
|
||||
|
||||
// note: key reuse is always the last node that got used
|
||||
// slab maintains a linked list, essentially
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
Remove { root: Some(3,) },
|
||||
Remove { root: Some(4,) },
|
||||
Remove { root: Some(5,) },
|
||||
CreateElement { root: Some(5,), tag: "h1", children: 0 }, // 5 gets reused
|
||||
ReplaceWith { root: Some(1,), nodes: vec![5,] }, // 1 gets deleted
|
||||
CreateElement { root: Some(1,), tag: "h1", children: 0 }, // 1 gets reused
|
||||
ReplaceWith { root: Some(2,), nodes: vec![1,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in multiple nodes destroyed - with no changes
|
||||
#[test]
|
||||
fn two_fragments_with_same_elements_are_differet() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!(
|
||||
{(0..2).map(|f| rsx_without_templates! { div { }})}
|
||||
p {}
|
||||
);
|
||||
let right = rsx_without_templates!(
|
||||
{(0..5).map(|f| rsx_without_templates! { div { }})}
|
||||
p {}
|
||||
);
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
[
|
||||
CreateElement { root: Some(1,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(2,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(3,), tag: "p", children: 0 },
|
||||
AppendChildren { root: Some(0,), children: vec![1, 2, 3,] },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(4,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(6,), tag: "div", children: 0 },
|
||||
InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// should result in the removal of elements
|
||||
#[test]
|
||||
fn keyed_diffing_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!(
|
||||
{(0..5).map(|f| {rsx_without_templates! { div { key: "{f}" }}})}
|
||||
p {"e"}
|
||||
);
|
||||
let right = rsx_without_templates!(
|
||||
{(0..2).map(|f| rsx_without_templates! { div { key: "{f}" }})}
|
||||
p {"e"}
|
||||
);
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
Remove { root: Some(3,) },
|
||||
Remove { root: Some(4,) },
|
||||
Remove { root: Some(5,) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves, but not removals or additions
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, changes) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[InsertBefore { root: Some(5,), nodes: vec![7,] },]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves only
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order_adds() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[InsertBefore { root: Some(1,), nodes: vec![5, 4,] },]
|
||||
);
|
||||
}
|
||||
/// Should result in moves only
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order_adds_2() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[InsertBefore { root: Some(1,), nodes: vec![4, 5,] },]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves onl
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order_adds_3() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[InsertBefore { root: Some(2,), nodes: vec![5, 4,] },]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves onl
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order_adds_4() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[InsertBefore { root: Some(3), nodes: vec![5, 4,] },]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves onl
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order_adds_5() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[InsertBefore { root: Some(4), nodes: vec![5] }]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed_diffing_additions() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(6,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(7,), tag: "div", children: 0 },
|
||||
InsertAfter { root: Some(5,), nodes: vec![6, 7,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed_diffing_additions_and_moves_on_ends() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
println!("{:?}", change);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
// create 11, 12
|
||||
CreateElement { root: Some(5), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(6), tag: "div", children: 0 },
|
||||
InsertAfter { root: Some(3), nodes: vec![5, 6] },
|
||||
// // move 7 to the front
|
||||
InsertBefore { root: Some(1), nodes: vec![4] }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed_diffing_additions_and_moves_in_middle() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
// LIS: 4, 5, 6
|
||||
let (_, change) = dom.diff_lazynodes(left, right);
|
||||
println!("{:#?}", change);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
// create 5, 6
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(6,), tag: "div", children: 0 },
|
||||
InsertBefore { root: Some(3,), nodes: vec![5, 6,] },
|
||||
// create 7, 8
|
||||
CreateElement { root: Some(7,), tag: "div", children: 0 },
|
||||
CreateElement { root: Some(8,), tag: "div", children: 0 },
|
||||
InsertBefore { root: Some(2,), nodes: vec![7, 8,] },
|
||||
// move 7
|
||||
InsertBefore { root: Some(1,), nodes: vec![4,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_keyed_diffing_out_of_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[4, 5, 6, 7].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[0, 5, 9, 6, 4].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
// LIS: 5, 6
|
||||
let (_, changes) = dom.diff_lazynodes(left, right);
|
||||
println!("{:#?}", &changes);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[
|
||||
// remove 7
|
||||
Remove { root: Some(4,) },
|
||||
// move 4 to after 6
|
||||
InsertAfter { root: Some(3,), nodes: vec![1,] },
|
||||
// create 9 and insert before 6
|
||||
CreateElement { root: Some(4,), tag: "div", children: 0 },
|
||||
InsertBefore { root: Some(3,), nodes: vec![4,] },
|
||||
// create 0 and insert before 5
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
InsertBefore { root: Some(2,), nodes: vec![5,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_keyed_diffing_out_of_order_max_test() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
[0, 1, 2, 3, 4].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
[3, 0, 1, 10, 2].iter().map(|f| {
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, changes) = dom.diff_lazynodes(left, right);
|
||||
println!("{:#?}", &changes);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[
|
||||
Remove { root: Some(5,) },
|
||||
CreateElement { root: Some(5,), tag: "div", children: 0 },
|
||||
InsertBefore { root: Some(3,), nodes: vec![5,] },
|
||||
InsertBefore { root: Some(1,), nodes: vec![4,] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// noticed some weird behavior in the desktop interpreter
|
||||
// just making sure it doesnt happen in the core implementation
|
||||
#[test]
|
||||
fn remove_list() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
(0..10).rev().take(5).map(|i| {
|
||||
rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
(0..10).rev().take(2).map(|i| {
|
||||
rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (create, changes) = dom.diff_lazynodes(left, right);
|
||||
|
||||
// dbg!(create);
|
||||
// dbg!(changes);
|
||||
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
// remove 5, 4, 3
|
||||
[
|
||||
Remove { root: Some(3) },
|
||||
Remove { root: Some(4) },
|
||||
Remove { root: Some(5) }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// noticed some weird behavior in the desktop interpreter
|
||||
// just making sure it doesnt happen in the core implementation
|
||||
#[test]
|
||||
fn remove_list_nokeyed() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx_without_templates!({
|
||||
(0..10).rev().take(5).map(|i| {
|
||||
rsx_without_templates! { Fragment { "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx_without_templates!({
|
||||
(0..10).rev().take(2).map(|i| {
|
||||
rsx_without_templates! { Fragment { "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (create, changes) = dom.diff_lazynodes(left, right);
|
||||
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[
|
||||
// remove 5, 4, 3
|
||||
Remove { root: Some(3) },
|
||||
Remove { root: Some(4) },
|
||||
Remove { root: Some(5) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_nested_elements() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
CreateElement { root: Some(2), tag: "div", children: 0 },
|
||||
AppendChildren { root: Some(1), children: vec![2] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) },
|
||||
NewEventListener { event_name: "keydown", scope: ScopeId(0), root: Some(1) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
RemoveEventListener { event: "keyup", root: Some(1) },
|
||||
RemoveEventListener { event: "keydown", root: Some(1) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
RemoveEventListener { root: Some(1), event: "keydown" },
|
||||
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) }
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
//! Tests for the lifecycle of components.
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
use dioxus_core::DomEdit::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
type Shared<T> = Arc<Mutex<T>>;
|
||||
|
||||
#[test]
|
||||
fn manual_diffing() {
|
||||
struct AppProps {
|
||||
value: Shared<&'static str>,
|
||||
}
|
||||
|
||||
static App: Component<AppProps> = |cx| {
|
||||
let val = cx.props.value.lock().unwrap();
|
||||
cx.render(rsx_without_templates! { div { "{val}" } })
|
||||
};
|
||||
|
||||
let value = Arc::new(Mutex::new("Hello"));
|
||||
let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() });
|
||||
|
||||
let _ = dom.rebuild();
|
||||
|
||||
*value.lock().unwrap() = "goodbye";
|
||||
|
||||
let edits = dom.rebuild();
|
||||
|
||||
println!("edits: {:?}", edits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn events_generate() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = cx.use_hook(|| 0);
|
||||
|
||||
let inner = match *count {
|
||||
0 => {
|
||||
rsx_without_templates! {
|
||||
div {
|
||||
onclick: move |_| *count += 1,
|
||||
div {
|
||||
"nested"
|
||||
}
|
||||
"Click me!"
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
cx.render(inner)
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mut channel = dom.get_scheduler_channel();
|
||||
assert!(dom.has_work());
|
||||
|
||||
let edits = dom.rebuild();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreateElement { root: Some(1), tag: "div", children: 0 },
|
||||
NewEventListener { event_name: "click", scope: ScopeId(0), root: Some(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]
|
||||
fn components_generate() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_phase = cx.use_hook(|| 0);
|
||||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
1 => rsx_without_templates!("Text0"),
|
||||
2 => rsx_without_templates!(div {}),
|
||||
3 => rsx_without_templates!("Text2"),
|
||||
4 => rsx_without_templates!(Child {}),
|
||||
5 => rsx_without_templates!({ None as Option<()> }),
|
||||
6 => rsx_without_templates!("text 3"),
|
||||
7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }),
|
||||
8 => rsx_without_templates!(Child {}),
|
||||
_ => todo!(),
|
||||
})
|
||||
};
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
println!("Running child");
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {}
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let edits = dom.rebuild();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreateTextNode { root: Some(1), text: "Text0" },
|
||||
AppendChildren { root: Some(0), children: vec![1] }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateElement { root: Some(2), tag: "div", children: 0 },
|
||||
ReplaceWith { root: Some(1), nodes: vec![2] }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateTextNode { root: Some(1), text: "Text2" },
|
||||
ReplaceWith { root: Some(2), nodes: vec![1] }
|
||||
]
|
||||
);
|
||||
|
||||
// child {}
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateElement { root: Some(2), tag: "h1", children: 0 },
|
||||
ReplaceWith { root: Some(1), nodes: vec![2] }
|
||||
]
|
||||
);
|
||||
|
||||
// placeholder
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreatePlaceholder { root: Some(1) },
|
||||
ReplaceWith { root: Some(2), nodes: vec![1] }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateTextNode { root: Some(2), text: "text 3" },
|
||||
ReplaceWith { root: Some(1), nodes: vec![2] }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateTextNode { text: "text 0", root: Some(1) },
|
||||
CreateTextNode { text: "text 1", root: Some(3) },
|
||||
ReplaceWith { root: Some(2), nodes: vec![1, 3] },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dom.hard_diff(ScopeId(0)).edits,
|
||||
[
|
||||
CreateElement { tag: "h1", root: Some(2), children: 0 },
|
||||
ReplaceWith { root: Some(1), nodes: vec![2] },
|
||||
Remove { root: Some(3) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_swap() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_phase = cx.use_hook(|| 0);
|
||||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
0 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
1 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Results {}
|
||||
}
|
||||
),
|
||||
2 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
3 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Results {}
|
||||
}
|
||||
),
|
||||
4 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
_ => rsx_without_templates!("blah"),
|
||||
})
|
||||
};
|
||||
|
||||
static NavBar: Component = |cx| {
|
||||
println!("running navbar");
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"NavBar"
|
||||
{(0..3).map(|f| rsx_without_templates!(NavLink {}))}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static NavLink: Component = |cx| {
|
||||
println!("running navlink");
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"NavLink"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static Dashboard: Component = |cx| {
|
||||
println!("running dashboard");
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"dashboard"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static Results: Component = |cx| {
|
||||
println!("running results");
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"results"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let edits = dom.rebuild();
|
||||
dbg!(&edits);
|
||||
|
||||
let edits = dom.work_with_deadline(|| false);
|
||||
dbg!(&edits);
|
||||
let edits = dom.work_with_deadline(|| false);
|
||||
dbg!(&edits);
|
||||
let edits = dom.work_with_deadline(|| false);
|
||||
dbg!(&edits);
|
||||
let edits = dom.work_with_deadline(|| false);
|
||||
dbg!(&edits);
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
/*
|
||||
Stress Miri as much as possible.
|
||||
|
||||
Prove that we don't leak memory and that our methods are safe.
|
||||
|
||||
Specifically:
|
||||
- [ ] VirtualDom drops memory safely
|
||||
- [ ] Borrowed components don't expose invalid pointers
|
||||
- [ ] Async isn't busted
|
||||
*/
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::SchedulerMsg;
|
||||
|
||||
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
|
||||
VirtualDom::new_with_props(app, props)
|
||||
}
|
||||
|
||||
/// This test ensures that if a component aborts early, it is replaced with a placeholder.
|
||||
/// In debug, this should also toss a warning.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_memory_leak() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = cx.use_hook(|| 0);
|
||||
|
||||
*val += 1;
|
||||
|
||||
if *val == 2 || *val == 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|| String::from("asd"));
|
||||
|
||||
cx.render(rsx!(
|
||||
div { "Hello, world!" }
|
||||
Child {}
|
||||
Child {}
|
||||
Child {}
|
||||
Child {}
|
||||
Child {}
|
||||
Child {}
|
||||
BorrowedChild { na: name }
|
||||
BorrowedChild { na: name }
|
||||
BorrowedChild { na: name }
|
||||
BorrowedChild { na: name }
|
||||
BorrowedChild { na: name }
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct BorrowedProps<'a> {
|
||||
na: &'a str,
|
||||
}
|
||||
|
||||
fn BorrowedChild<'a>(cx: Scope<'a, BorrowedProps<'a>>) -> Element {
|
||||
render!(div {
|
||||
"goodbye {cx.props.na}"
|
||||
Child {}
|
||||
Child {}
|
||||
})
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
render!(div { "goodbye world" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
|
||||
dom.rebuild();
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_works_properly() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = cx.use_hook(|| 0);
|
||||
|
||||
*val += 1;
|
||||
|
||||
if *val == 2 || *val == 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|| String::from("asd"));
|
||||
|
||||
cx.render(rsx!(
|
||||
div { "Hello, world! {name}" }
|
||||
Child { na: "asdfg".to_string() }
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ChildProps {
|
||||
na: String,
|
||||
}
|
||||
|
||||
fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
render!(div { "goodbye world" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
|
||||
dom.rebuild();
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
dom.hard_diff(ScopeId(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_works_on_root_props() {
|
||||
fn app(cx: Scope<Custom>) -> Element {
|
||||
cx.render(rsx! {
|
||||
Child { a: "alpha"}
|
||||
Child { a: "beta"}
|
||||
Child { a: "gamma"}
|
||||
Child { a: "delta"}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct ChildProps {
|
||||
a: &'static str,
|
||||
}
|
||||
|
||||
fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
render!("child {cx.props.a}")
|
||||
}
|
||||
|
||||
struct Custom {
|
||||
val: String,
|
||||
}
|
||||
|
||||
impl Drop for Custom {
|
||||
fn drop(&mut self) {
|
||||
dbg!("dropped! {}", &self.val);
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, Custom { val: String::from("asd") });
|
||||
dom.rebuild();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_works_on_borrowed() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Child { a: "alpha", b: "asd".to_string() }
|
||||
})
|
||||
}
|
||||
#[derive(Props)]
|
||||
struct ChildProps<'a> {
|
||||
a: &'a str,
|
||||
b: String,
|
||||
}
|
||||
|
||||
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
|
||||
dbg!("rendering child");
|
||||
render!("child {cx.props.a}, {cx.props.b}")
|
||||
}
|
||||
|
||||
impl Drop for ChildProps<'_> {
|
||||
fn drop(&mut self) {
|
||||
dbg!("dropped child!");
|
||||
}
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_works_on_root_hooks() {
|
||||
/*
|
||||
On Drop, scopearena drops all the hook contents.
|
||||
*/
|
||||
|
||||
struct Droppable<T>(T);
|
||||
impl<T> Drop for Droppable<T> {
|
||||
fn drop(&mut self) {
|
||||
dbg!("dropping droppable");
|
||||
}
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = cx.use_hook(|| Droppable(String::from("asd")));
|
||||
render!(div { "{name.0}" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_props_arent_stale() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
dbg!("rendering parent");
|
||||
let cnt = cx.use_hook(|| 0);
|
||||
*cnt += 1;
|
||||
|
||||
if *cnt == 1 {
|
||||
render!(div { Child { a: "abcdef".to_string() } })
|
||||
} else {
|
||||
render!(div { Child { a: "abcdef".to_string() } })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct ChildProps {
|
||||
a: String,
|
||||
}
|
||||
fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
dbg!("rendering child", &cx.props.a);
|
||||
render!(div { "child {cx.props.a}" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dbg!("forcing update to child");
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
render!(div {
|
||||
Child { a: "abcdef".to_string() }
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct ChildProps {
|
||||
a: String,
|
||||
}
|
||||
|
||||
fn Child(cx: Scope<ChildProps>) -> Element {
|
||||
dbg!("rendering child", &cx.props.a);
|
||||
render!(div { "child {cx.props.a}" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leak_thru_children() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Child {
|
||||
name: "asd".to_string(),
|
||||
}
|
||||
});
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Child(cx: Scope, name: String) -> Element {
|
||||
render!(div { "child {name}" })
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pass_thru() {
|
||||
#[inline_props]
|
||||
fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
cx.render(rsx! {
|
||||
header {
|
||||
nav { children }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn NavMenu(cx: Scope) -> Element {
|
||||
render!( NavBrand {}
|
||||
div {
|
||||
NavStart {}
|
||||
NavEnd {}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn NavBrand(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
fn NavStart(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
fn NavEnd(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn MainContainer<'a>(
|
||||
cx: Scope,
|
||||
nav: Element<'a>,
|
||||
body: Element<'a>,
|
||||
footer: Element<'a>,
|
||||
) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "columns is-mobile",
|
||||
div {
|
||||
class: "column is-full",
|
||||
nav,
|
||||
body,
|
||||
footer,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let nav = cx.render(rsx! {
|
||||
NavContainer {
|
||||
NavMenu {}
|
||||
}
|
||||
});
|
||||
let body = cx.render(rsx! {
|
||||
div {}
|
||||
});
|
||||
let footer = cx.render(rsx! {
|
||||
div {}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
MainContainer {
|
||||
nav: nav,
|
||||
body: body,
|
||||
footer: footer,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = new_dom(app, ());
|
||||
let _ = dom.rebuild();
|
||||
|
||||
for _ in 0..40 {
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
dom.work_with_deadline(|| false);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||
let asd = 123;
|
||||
let var = 123;
|
||||
|
||||
cx.render(rsx! {
|
||||
div { key: "12345",
|
||||
class: "asd",
|
||||
class: "{asd}",
|
||||
onclick: move |_| {},
|
||||
div { "{var}" }
|
||||
div {
|
||||
h1 { "var" }
|
||||
p { "you're great!" }
|
||||
div { background_color: "red",
|
||||
h1 { "var" }
|
||||
div { b { "asd" } "not great" }
|
||||
}
|
||||
p { "you're great!" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// imports come after the test since the rsx! macro is sensitive to its location in the file
|
||||
// (the byte index is used to differentiate sub templates)
|
||||
use dioxus::core::{ElementId, Mutation};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn dual_stream() {
|
||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||
let edits = dom.rebuild();
|
||||
|
||||
use Mutation::*;
|
||||
assert_eq!(
|
||||
edits.template_mutations,
|
||||
vec![
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(1) },
|
||||
SetAttribute { name: "class", value: "asd", id: ElementId(1) },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(2) },
|
||||
CreatePlaceholder { id: ElementId(3) },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(4) },
|
||||
CreateElement { name: "h1", namespace: None, id: ElementId(5) },
|
||||
CreateTextNode { value: "var" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "p", namespace: None, id: ElementId(6) },
|
||||
CreateTextNode { value: "you're great!" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(7) },
|
||||
SetAttribute { name: "background-color", value: "red", id: ElementId(7) },
|
||||
CreateElement { name: "h1", namespace: None, id: ElementId(8) },
|
||||
CreateTextNode { value: "var" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(9) },
|
||||
CreateElement { name: "b", namespace: None, id: ElementId(10) },
|
||||
CreateTextNode { value: "asd" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateTextNode { value: "not great" },
|
||||
AppendChildren { m: 2 },
|
||||
AppendChildren { m: 2 },
|
||||
CreateElement { name: "p", namespace: None, id: ElementId(11) },
|
||||
CreateTextNode { value: "you're great!" },
|
||||
AppendChildren { m: 1 },
|
||||
AppendChildren { m: 4 },
|
||||
AppendChildren { m: 2 },
|
||||
SaveTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
vec![
|
||||
LoadTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", index: 0 },
|
||||
AssignId { path: &[], id: ElementId(12) },
|
||||
SetAttribute { name: "class", value: "123", id: ElementId(12) },
|
||||
SetAttribute { name: "onclick", value: "asd", id: ElementId(12) }, // ---- todo: listeners
|
||||
HydrateText { path: &[0, 0], value: "123", id: ElementId(13) },
|
||||
ReplacePlaceholder { m: 1, path: &[0, 0] },
|
||||
AppendChildren { m: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
|
||||
use std::rc::Rc;
|
||||
use DomEdit::*;
|
||||
|
||||
#[test]
|
||||
fn shared_state_test() {
|
||||
struct MySharedState(&'static str);
|
||||
|
||||
static App: Component = |cx| {
|
||||
cx.provide_context(Rc::new(MySharedState("world!")));
|
||||
cx.render(rsx_without_templates!(Child {}))
|
||||
};
|
||||
|
||||
static Child: Component = |cx| {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>()?;
|
||||
cx.render(rsx_without_templates!("Hello, {shared.0}"))
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let Mutations { edits, .. } = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
edits,
|
||||
[
|
||||
CreateTextNode { root: Some(1), text: "Hello, world!" },
|
||||
AppendChildren { root: Some(0), children: vec![1] },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_test() {
|
||||
struct MySharedState(&'static str);
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = cx.use_hook(|| 0);
|
||||
*val += 1;
|
||||
|
||||
cx.provide_context(Rc::new(MySharedState("world!")));
|
||||
|
||||
let child = match *val % 2 {
|
||||
0 => rsx_without_templates!(
|
||||
Child1 {
|
||||
Child1 { }
|
||||
Child2 { }
|
||||
}
|
||||
),
|
||||
_ => rsx_without_templates!(
|
||||
Child2 {
|
||||
Child2 { }
|
||||
Child2 { }
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
cx.render(rsx_without_templates!(
|
||||
Router {
|
||||
div { child }
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
|
||||
cx.render(rsx_without_templates!(div { children }))
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
|
||||
println!("Child1: {}", shared.0);
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"{shared.0}",
|
||||
children
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
|
||||
println!("Child2: {}", shared.0);
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"{shared.0}",
|
||||
children
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let Mutations { edits, .. } = dom.rebuild();
|
||||
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
// dom.work_with_deadline(|| false);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use std::{future::IntoFuture, rc::Rc};
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::SuspenseBoundary;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_prints() {
|
||||
let mut dom = VirtualDom::new(|cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "var" }
|
||||
suspense_boundary {
|
||||
basic_child { }
|
||||
async_child { }
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
dbg!(dom.rebuild());
|
||||
|
||||
dom.wait_for_work().await;
|
||||
|
||||
dbg!(dom.rebuild());
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
cx.use_hook(|| cx.provide_context(Rc::new(SuspenseBoundary::new(cx.scope_id()))));
|
||||
cx.render(rsx! { children })
|
||||
}
|
||||
|
||||
fn basic_child(cx: Scope) -> Element {
|
||||
cx.render(rsx!( div { "basic child 1" } ))
|
||||
}
|
||||
|
||||
async fn async_child(cx: Scope<'_>) -> Element {
|
||||
let username = use_future!(cx, || async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
"async child 1"
|
||||
});
|
||||
|
||||
let age = use_future!(cx, || async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
println!("long future completed");
|
||||
1234
|
||||
});
|
||||
|
||||
let (_user, _age) = use_future!(cx, || async {
|
||||
tokio::join!(
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)),
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2))
|
||||
);
|
||||
("async child 1", 1234)
|
||||
})
|
||||
.await;
|
||||
|
||||
let (username, age) = tokio::join!(username.into_future(), age.into_future());
|
||||
|
||||
cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } ))
|
||||
}
|
||||
Reference in new issue
Block a user