chore: move tests out of core and into the top level crate

This commit moves the tests out of core so rust analyzer
is happier with the workspace.
This commit is contained in:
Jonathan Kelley committed 2022-03-02 22:48:22 -05:00
1 parent e932ed202a
commit e04a6d63a5
20 files changed
+27 -59

No files matched your search

+1
View File
@@ -0,0 +1 @@
struct_lit_width = 80
+53
View File
@@ -0,0 +1,53 @@
# 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
+46
View File
@@ -0,0 +1,46 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
#[test]
fn test_borrowed_state() {
let _ = VirtualDom::new(Parent);
}
fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
Child { name: value }
Child { name: value }
Child { name: value }
Child { name: value }
}
})
}
#[derive(Props)]
struct ChildProps<'a> {
name: &'a str,
}
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
h1 { "it's nested" }
Child2 { name: cx.props.name }
}
})
}
#[derive(Props)]
struct Grandchild<'a> {
name: &'a str,
}
fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
cx.render(rsx! {
div { "Hello {cx.props.name}!" }
})
}
+214
View File
@@ -0,0 +1,214 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
//! Prove that the dom works normally through virtualdom methods.
//!
//! This methods all use "rebuild" which completely bypasses the scheduler.
//! Hard rebuilds don't consume any events from the event queue.
use dioxus::prelude::*;
mod test_logging;
use dioxus_core::DomEdit::*;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new_with_props(app, props)
}
#[test]
fn test_original_diff() {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
"Hello, world!"
}
}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "Hello, world!" },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
}
#[test]
fn create() {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
"Hello, world!"
div {
div {
Fragment {
"hello"
"world"
}
}
}
}
}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "Hello, world!" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "hello" },
CreateTextNode { root: 7, text: "world" },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
}
#[test]
fn create_list() {
static APP: Component = |cx| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
}})}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { root: 4, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "hello" },
AppendChildren { many: 1 },
AppendChildren { many: 3 },
]
);
}
#[test]
fn create_simple() {
static APP: Component = |cx| {
cx.render(rsx! {
div {}
div {}
div {}
div {}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
AppendChildren { many: 4 },
]
);
}
#[test]
fn create_components() {
static App: Component = |cx| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
Child { "abc3" }
})
};
#[derive(Props)]
struct ChildProps<'a> {
children: Element<'a>,
}
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
h1 {}
div { {&cx.props.children} }
p {}
})
}
let mut dom = new_dom(App, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "h1" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "abc1" },
AppendChildren { many: 1 },
CreateElement { root: 4, tag: "p" },
CreateElement { root: 5, tag: "h1" },
CreateElement { root: 6, tag: "div" },
CreateTextNode { root: 7, text: "abc2" },
AppendChildren { many: 1 },
CreateElement { root: 8, tag: "p" },
CreateElement { root: 9, tag: "h1" },
CreateElement { root: 10, tag: "div" },
CreateTextNode { root: 11, text: "abc3" },
AppendChildren { many: 1 },
CreateElement { root: 12, tag: "p" },
AppendChildren { many: 9 },
]
);
}
#[test]
fn anchors() {
static App: Component = |cx| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}
})
};
let mut dom = new_dom(App, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreatePlaceholder { root: 3 },
AppendChildren { many: 2 },
]
);
}
+732
View File
@@ -0,0 +1,732 @@
#![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::prelude::*;
mod test_logging;
fn new_dom() -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new(|cx| rsx!(cx, "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! ( div { "Hello world" } ),
rsx! ( div { "Goodbye world" } ),
);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "Hello world" },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]);
}
/// Should result in 3 elements on the stack
#[test]
fn fragments_create_properly() {
let dom = new_dom();
let create = dom.create_vnodes(rsx! {
div { "Hello a" }
div { "Hello b" }
div { "Hello c" }
});
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "Hello a" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { root: 4, text: "Hello b" },
AppendChildren { many: 1 },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "Hello c" },
AppendChildren { many: 1 },
AppendChildren { many: 3 },
]
);
}
/// 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!({ (0..0).map(|_f| rsx! { div {}}) });
let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { root: 2, tag: "div" },
ReplaceWith { m: 1, root: 1 }
]
);
}
/// 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!({ (0..0).map(|_f| rsx! { div {}}) });
let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "div" },
ReplaceWith { m: 5, root: 1 }
]
);
}
/// 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!({ (0..0).map(|_| rsx! { div {} }) });
let right = rsx!({
(0..3).map(|f| {
rsx! { div { "hello: {f}" }}
})
});
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { tag: "div", root: 2 },
CreateTextNode { text: "hello: 0", root: 3 },
AppendChildren { many: 1 },
CreateElement { tag: "div", root: 4 },
CreateTextNode { text: "hello: 1", root: 5 },
AppendChildren { many: 1 },
CreateElement { tag: "div", root: 6 },
CreateTextNode { text: "hello: 2", root: 7 },
AppendChildren { many: 1 },
ReplaceWith { root: 1, m: 3 }
]
);
}
/// 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!({
(0..2).map(|_| {
rsx! { div { "hello" }}
})
});
let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { text: "hello", root: 2 },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { text: "hello", root: 4 },
AppendChildren { many: 1 },
AppendChildren { many: 2 },
]
);
assert_eq!(
change.edits,
[
CreatePlaceholder { root: 5 },
ReplaceWith { root: 1, m: 1 },
Remove { root: 3 },
]
);
}
/// Should result in no edits
#[test]
fn two_equal_fragments_are_equal() {
let dom = new_dom();
let left = rsx!({
(0..2).map(|_| {
rsx! { div { "hello" }}
})
});
let right = rsx!({
(0..2).map(|_| {
rsx! { 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!(
{ (0..2).map(|_| rsx! { div { }} ) }
p {}
);
let right = rsx!(
{ (0..5).map(|_| rsx! (h1 { }) ) }
p {}
);
let (_create, changes) = dom.diff_lazynodes(left, right);
log::debug!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
// create the new h1s
CreateElement { tag: "h1", root: 4 },
CreateElement { tag: "h1", root: 5 },
CreateElement { tag: "h1", root: 6 },
InsertAfter { root: 2, n: 3 },
// replace the divs with new h1s
CreateElement { tag: "h1", root: 7 },
ReplaceWith { root: 1, m: 1 },
CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
ReplaceWith { root: 2, m: 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!(
{(0..5).map(|f| {rsx! { div { }}})}
p {}
);
let right = rsx!(
{(0..2).map(|f| {rsx! { h1 { }}})}
p {}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "p" },
AppendChildren { many: 6 },
]
);
// note: key reuse is always the last node that got used
// slab maintains a linked list, essentially
assert_eq!(
change.edits,
[
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
CreateElement { root: 5, tag: "h1" }, // 3 gets reused
ReplaceWith { root: 1, m: 1 }, // 1 gets deleted
CreateElement { root: 1, tag: "h1" }, // 1 gets reused
ReplaceWith { root: 2, m: 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!(
{(0..2).map(|f| {rsx! { div { }}})}
p {}
);
let right = rsx!(
{(0..5).map(|f| {rsx! { div { }}})}
p {}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "p" },
AppendChildren { many: 3 },
]
);
assert_eq!(
change.edits,
[
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "div" },
InsertAfter { root: 2, n: 3 },
]
);
}
/// should result in the removal of elements
#[test]
fn keyed_diffing_order() {
let dom = new_dom();
let left = rsx!(
{(0..5).map(|f| {rsx! { div { key: "{f}" }}})}
p {"e"}
);
let right = rsx!(
{(0..2).map(|f| rsx! { div { key: "{f}" }})}
p {"e"}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 },]
);
}
/// Should result in moves, but not removals or additions
#[test]
fn keyed_diffing_out_of_order() {
let dom = new_dom();
let left = rsx!({
[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, changes) = dom.diff_lazynodes(left, right);
log::debug!("{:?}", &changes);
assert_eq!(
changes.edits,
[PushRoot { root: 7 }, InsertBefore { root: 5, n: 1 }]
);
}
/// Should result in moves only
#[test]
fn keyed_diffing_out_of_order_adds() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 1 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_2() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 4 },
PushRoot { root: 5 },
InsertBefore { n: 2, root: 1 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_3() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 2 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_4() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 3 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_5() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[PushRoot { root: 5 }, InsertBefore { n: 1, root: 4 }]
);
}
#[test]
fn keyed_diffing_additions() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
CreateElement { root: 6, tag: "div" },
CreateElement { root: 7, tag: "div" },
InsertAfter { n: 2, root: 5 }
]
);
}
#[test]
fn keyed_diffing_additions_and_moves_on_ends() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
log::debug!("{:?}", change);
assert_eq!(
change.edits,
[
// create 11, 12
CreateElement { tag: "div", root: 5 },
CreateElement { tag: "div", root: 6 },
InsertAfter { root: 3, n: 2 },
// move 7 to the front
PushRoot { root: 4 },
InsertBefore { root: 1, n: 1 }
]
);
}
#[test]
fn keyed_diffing_additions_and_moves_in_middle() {
let dom = new_dom();
let left = rsx!({
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
// LIS: 4, 5, 6
let (_, change) = dom.diff_lazynodes(left, right);
log::debug!("{:#?}", change);
assert_eq!(
change.edits,
[
// create 5, 6
CreateElement { tag: "div", root: 5 },
CreateElement { tag: "div", root: 6 },
InsertBefore { root: 3, n: 2 },
// create 7, 8
CreateElement { tag: "div", root: 7 },
CreateElement { tag: "div", root: 8 },
InsertBefore { root: 2, n: 2 },
// move 7
PushRoot { root: 4 },
InsertBefore { root: 1, n: 1 }
]
);
}
#[test]
fn controlled_keyed_diffing_out_of_order() {
let dom = new_dom();
let left = rsx!({
[4, 5, 6, 7].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[0, 5, 9, 6, 4].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
// LIS: 5, 6
let (_, changes) = dom.diff_lazynodes(left, right);
log::debug!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
Remove { root: 4 },
// move 4 to after 6
PushRoot { root: 1 },
InsertAfter { n: 1, root: 3 },
// remove 7
// create 9 and insert before 6
CreateElement { root: 4, tag: "div" },
InsertBefore { n: 1, root: 3 },
// create 0 and insert before 5
CreateElement { root: 5, tag: "div" },
InsertBefore { n: 1, root: 2 },
]
);
}
#[test]
fn controlled_keyed_diffing_out_of_order_max_test() {
let dom = new_dom();
let left = rsx!({
[0, 1, 2, 3, 4].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[3, 0, 1, 10, 2].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, changes) = dom.diff_lazynodes(left, right);
log::debug!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
Remove { root: 5 },
CreateElement { root: 5, tag: "div" },
InsertBefore { n: 1, root: 3 },
PushRoot { root: 4 },
InsertBefore { n: 1, root: 1 },
]
);
}
// 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!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { 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: 3 },
Remove { root: 4 },
Remove { root: 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!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}
+65
View File
@@ -0,0 +1,65 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
//! Prove that the dom works normally through virtualdom methods.
//!
//! This methods all use "rebuild" which completely bypasses the scheduler.
//! Hard rebuilds don't consume any events from the event queue.
use dioxus::prelude::*;
mod test_logging;
use dioxus_core::{DomEdit::*, ScopeId};
const IS_LOGGING_ENABLED: bool = false;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
test_logging::set_up_logging(IS_LOGGING_ENABLED);
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]
fn test_early_abort() {
const app: Component = |cx| {
let val = cx.use_hook(|_| 0);
*val += 1;
if *val == 2 {
return None;
}
rsx!(cx, div { "Hello, world!" })
};
let mut dom = new_dom(app, ());
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
CreateTextNode { text: "Hello, world!", root: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
);
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 }, // keys get reused
CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
AppendChildren { many: 1 },
ReplaceWith { root: 3, m: 1 },
]
);
}
+264
View File
@@ -0,0 +1,264 @@
#![allow(unused, non_upper_case_globals)]
#![allow(non_snake_case)]
//! Tests for the lifecycle of components.
use dioxus::prelude::*;
use dioxus_core::DomEdit::*;
use std::sync::{Arc, Mutex};
mod test_logging;
const IS_LOGGING_ENABLED: bool = true;
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! { 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();
log::debug!("edits: {:?}", edits);
}
#[test]
fn events_generate() {
fn app(cx: Scope) -> Element {
let count = cx.use_hook(|_| 0);
let inner = match *count {
0 => {
rsx! {
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 { tag: "div", root: 1 },
NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 },
CreateElement { tag: "div", root: 2 },
CreateTextNode { text: "nested", root: 3 },
AppendChildren { many: 1 },
CreateTextNode { text: "Click me!", root: 4 },
AppendChildren { many: 2 },
AppendChildren { many: 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!("Text0"),
2 => rsx!(div {}),
3 => rsx!("Text2"),
4 => rsx!(Child {}),
5 => rsx!({ None as Option<()> }),
6 => rsx!("text 3"),
7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
8 => rsx!(Child {}),
_ => todo!(),
})
};
fn Child(cx: Scope) -> Element {
log::debug!("Running child");
cx.render(rsx! {
h1 {}
})
}
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateTextNode { text: "Text0", root: 1 },
AppendChildren { many: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "div", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "Text2", root: 1 },
ReplaceWith { root: 2, m: 1 },
]
);
// child {}
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
// placeholder
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 3", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 0", root: 1 },
CreateTextNode { text: "text 1", root: 3 },
ReplaceWith { root: 2, m: 2 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
Remove { root: 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!(
div {
NavBar {}
Dashboard {}
}
),
1 => rsx!(
div {
NavBar {}
Results {}
}
),
2 => rsx!(
div {
NavBar {}
Dashboard {}
}
),
3 => rsx!(
div {
NavBar {}
Results {}
}
),
4 => rsx!(
div {
NavBar {}
Dashboard {}
}
),
_ => rsx!("blah"),
})
};
static NavBar: Component = |cx| {
println!("running navbar");
cx.render(rsx! {
h1 {
"NavBar"
{(0..3).map(|f| rsx!(NavLink {}))}
}
})
};
static NavLink: Component = |cx| {
println!("running navlink");
cx.render(rsx! {
h1 {
"NavLink"
}
})
};
static Dashboard: Component = |cx| {
println!("running dashboard");
cx.render(rsx! {
div {
"dashboard"
}
})
};
static Results: Component = |cx| {
println!("running results");
cx.render(rsx! {
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);
}
+418
View File
@@ -0,0 +1,418 @@
#![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;
mod test_logging;
const IS_LOGGING_ENABLED: bool = false;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
test_logging::set_up_logging(IS_LOGGING_ENABLED);
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]
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()
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
))
}
#[derive(Props)]
struct BorrowedProps<'a> {
na: &'a str,
}
fn borrowed_child<'a>(cx: Scope<'a, BorrowedProps<'a>>) -> Element {
rsx!(cx, div {
"goodbye {cx.props.na}"
child()
child()
})
}
fn child(cx: Scope) -> Element {
rsx!(cx, 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 {
rsx!(cx, 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 {
rsx!(cx, "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");
rsx!(cx, "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")));
rsx!(cx, 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 {
rsx!(cx, div { child(a: "abcdef".to_string()) })
} else {
rsx!(cx, 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);
rsx!(cx, 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 {
rsx!(cx, 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);
rsx!(cx, 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 {
rsx!(cx, 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 {
rsx!(cx,
NavBrand {}
div {
NavStart {}
NavEnd {}
}
)
}
fn NavBrand(cx: Scope) -> Element {
rsx!(cx, div {})
}
fn NavStart(cx: Scope) -> Element {
rsx!(cx, div {})
}
fn NavEnd(cx: Scope) -> Element {
rsx!(cx, 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);
}
}
+104
View File
@@ -0,0 +1,104 @@
#![allow(unused, non_upper_case_globals)]
#![allow(non_snake_case)]
//! 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::prelude::*;
mod test_logging;
fn new_dom() -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new(|cx| rsx!(cx, "hi"))
}
use dioxus_core::DomEdit::*;
/// Should push the text node onto the stack and modify it
#[test]
fn nested_passthru_creates() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
Child {
Child {
div {
"hi"
}
}
}
}
})
};
#[inline_props]
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! { children })
};
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 { tag: "div", root: 1 },
CreateTextNode { text: "hi", root: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
)
}
/// Should push the text node onto the stack and modify it
#[test]
fn nested_passthru_creates_add() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
"1"
Child {
"2"
Child {
"3"
div {
"hi"
}
}
}
}
})
};
#[inline_props]
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! {
children
})
};
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,
[
CreateTextNode { text: "1", root: 1 },
CreateTextNode { text: "2", root: 2 },
CreateTextNode { text: "3", root: 3 },
CreateElement { tag: "div", root: 4 },
CreateTextNode { text: "hi", root: 5 },
AppendChildren { many: 1 },
AppendChildren { many: 4 },
]
)
}
+6
View File
@@ -0,0 +1,6 @@
//! Tests for the scheduler.
//!
//! TODO
//! - priority lanes
//! - periodic checking
//!
+127
View File
@@ -0,0 +1,127 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
use dioxus::prelude::*;
use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
use DomEdit::*;
mod test_logging;
#[test]
fn shared_state_test() {
struct MySharedState(&'static str);
static App: Component = |cx| {
cx.provide_context(MySharedState("world!"));
cx.render(rsx!(Child {}))
};
static Child: Component = |cx| {
let shared = cx.consume_context::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}"))
};
let mut dom = VirtualDom::new(App);
let Mutations { edits, .. } = dom.rebuild();
assert_eq!(
edits,
[
CreateTextNode { root: 1, text: "Hello, world!" },
AppendChildren { many: 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(MySharedState("world!"));
let child = match *val % 2 {
0 => rsx!(
Child1 {
Child1 { }
Child2 { }
}
),
_ => rsx!(
Child2 {
Child2 { }
Child2 { }
}
),
};
cx.render(rsx!(
Router {
div { child }
}
))
}
#[inline_props]
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
cx.render(rsx!(div { children }))
}
#[inline_props]
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<MySharedState>().unwrap();
println!("Child1: {}", shared.0);
cx.render(rsx! {
div {
"{shared.0}",
children
}
})
}
#[inline_props]
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<MySharedState>().unwrap();
println!("Child2: {}", shared.0);
cx.render(rsx! {
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
View File
@@ -0,0 +1 @@
fn main() {}
+51
View File
@@ -0,0 +1,51 @@
pub fn set_up_logging(enabled: bool) {
use fern::colors::{Color, ColoredLevelConfig};
if !enabled {
return;
}
// configure colors for the whole line
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
// we actually don't need to specify the color for debug and info, they are white by default
.info(Color::White)
.debug(Color::White)
// depending on the terminals color scheme, this is the same as the background color
.trace(Color::BrightBlack);
// configure colors for the name of the level.
// since almost all of them are the same as the color for the whole line, we
// just clone `colors_line` and overwrite our changes
let colors_level = colors_line.info(Color::Green);
// here we set up our fern Dispatch
// when running tests in batch, the logger is re-used, so ignore the logger error
let _ = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{color_line}[{level}{color_line}] {message}\x1B[0m",
color_line = format_args!(
"\x1B[{}m",
colors_line.get_color(&record.level()).to_fg_str()
),
level = colors_level.color(record.level()),
message = message,
));
})
// set the default log level. to filter out verbose log messages from dependencies, set
// this to Warn and overwrite the log level for your crate.
.level(log::LevelFilter::Debug)
// .level(log::LevelFilter::Warn)
// change log levels for individual modules. Note: This looks for the record's target
// field which defaults to the module path but can be overwritten with the `target`
// parameter:
// `info!(target="special_target", "This log message is about special_target");`
// .level_for("dioxus", log::LevelFilter::Debug)
// .level_for("dioxus", log::LevelFilter::Info)
// .level_for("pretty_colored", log::LevelFilter::Trace)
// output to stdout
.chain(std::io::stdout())
.apply();
}
+95
View File
@@ -0,0 +1,95 @@
#![allow(unused, non_upper_case_globals)]
//! Rebuilding tests
//! ----------------
//!
//! This tests module ensures that the initial build of the virtualdom is correct.
//! This does not include dynamic tests or the diffing algorithm itself.
//!
//! It does prove that mounting works properly and the correct edit streams are generated.
//!
//! Don't have a good way to validate, everything is done manually ATM
use dioxus::prelude::*;
use dioxus_core::DomEdit::*;
#[test]
fn app_runs() {
static App: Component = |cx| rsx!(cx, div{"hello"} );
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
}
#[test]
fn fragments_work() {
static App: Component = |cx| {
cx.render(rsx!(
div{"hello"}
div{"goodbye"}
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
// should result in a final "appendchildren n=2"
dbg!(edits);
}
#[test]
fn lists_work() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
(0..6).map(|f| rsx!(span{ "{f}" }))
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
dbg!(edits);
}
#[test]
fn conditional_rendering() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))}
{false.then(|| rsx!(span{ "b" }))}
))
};
let mut vdom = VirtualDom::new(App);
let mutations = vdom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "h1" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "span" },
CreateTextNode { root: 4, text: "a" },
AppendChildren { many: 1 },
CreatePlaceholder { root: 5 },
AppendChildren { many: 3 },
]
)
}
#[test]
fn child_components() {
static App: Component = |cx| {
cx.render(rsx!(
{true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))}
))
};
static Child: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
h1 {"goodbye"}
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
dbg!(edits);
}