Renderers are now packages, not features. (#387)
* feat: use synchronous router design * feat: function to get router out of dom * chore: restructure workspace to use renderers as packages, not features
This commit is contained in:
1 parent
c97ca7dff6
commit
d9546d9504
181 files changed
+487
-1463
No files matched your search
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "dioxus"
|
||||
version = "0.2.4"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2021"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.60.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", optional = true }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1", optional = true }
|
||||
dioxus-hooks = { path = "../hooks", version = "^0.2.1", optional = true }
|
||||
dioxus-rsx = { path = "../rsx", optional = true }
|
||||
dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
|
||||
|
||||
|
||||
# dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
|
||||
# dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
|
||||
# dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["macro", "hooks", "html"]
|
||||
macro = ["dioxus-core-macro", "dioxus-rsx"]
|
||||
html = ["dioxus-html"]
|
||||
hooks = ["dioxus-hooks"]
|
||||
hot-reload = [
|
||||
"dioxus-core-macro/hot-reload",
|
||||
"dioxus-rsx-interpreter",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = "0.3.21"
|
||||
log = "0.4.14"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
criterion = "0.3.5"
|
||||
thiserror = "1.0.30"
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[[bench]]
|
||||
name = "create"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "jsframework"
|
||||
harness = false
|
||||
@@ -0,0 +1 @@
|
||||
fn main() {}
|
||||
@@ -0,0 +1,120 @@
|
||||
#![allow(non_snake_case, non_upper_case_globals)]
|
||||
//! This benchmark tests just the overhead of Dioxus itself.
|
||||
//!
|
||||
//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
|
||||
//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
|
||||
//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
|
||||
//!
|
||||
//! On my MBP 2019:
|
||||
//! - Dioxus takes 3ms to create 1_000 rows
|
||||
//! - Dioxus takes 30ms to create 10_000 rows
|
||||
//!
|
||||
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
|
||||
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use dioxus::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
criterion_group!(mbenches, create_rows);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
fn create_rows(c: &mut Criterion) {
|
||||
static App: Component = |cx| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
|
||||
rsx!(cx, table {
|
||||
tbody {
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!(Row {
|
||||
row_id: f,
|
||||
label: label
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
c.bench_function("create rows", |b| {
|
||||
b.iter(|| {
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let g = dom.rebuild();
|
||||
assert!(g.edits.len() > 1);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct RowProps {
|
||||
row_id: usize,
|
||||
label: Label,
|
||||
}
|
||||
fn Row(cx: Scope<RowProps>) -> Element {
|
||||
let [adj, col, noun] = cx.props.label.0;
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{cx.props.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
|
||||
a { class: "lbl", "{adj}" "{col}" "{noun}" }
|
||||
}
|
||||
td { class: "col-md-1",
|
||||
a { class: "remove", onclick: move |_| {/* remove */},
|
||||
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct Label([&'static str; 3]);
|
||||
|
||||
impl Label {
|
||||
fn new(rng: &mut SmallRng) -> Self {
|
||||
Label([
|
||||
ADJECTIVES.choose(rng).unwrap(),
|
||||
COLOURS.choose(rng).unwrap(),
|
||||
NOUNS.choose(rng).unwrap(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
static ADJECTIVES: &[&str] = &[
|
||||
"pretty",
|
||||
"large",
|
||||
"big",
|
||||
"small",
|
||||
"tall",
|
||||
"short",
|
||||
"long",
|
||||
"handsome",
|
||||
"plain",
|
||||
"quaint",
|
||||
"clean",
|
||||
"elegant",
|
||||
"easy",
|
||||
"angry",
|
||||
"crazy",
|
||||
"helpful",
|
||||
"mushy",
|
||||
"odd",
|
||||
"unsightly",
|
||||
"adorable",
|
||||
"important",
|
||||
"inexpensive",
|
||||
"cheap",
|
||||
"expensive",
|
||||
"fancy",
|
||||
];
|
||||
|
||||
static COLOURS: &[&str] = &[
|
||||
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||
"orange",
|
||||
];
|
||||
|
||||
static NOUNS: &[&str] = &[
|
||||
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||
"pizza", "mouse", "keyboard",
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
#![doc = include_str!("../../../notes/README.md")]
|
||||
|
||||
pub use dioxus_core as core;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
pub use dioxus_hooks as hooks;
|
||||
|
||||
pub mod events {
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_html::{on::*, KeyCode};
|
||||
}
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_html as html;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
pub use dioxus_rsx as rsx;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
pub use dioxus_core_macro as core_macro;
|
||||
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "hooks")]
|
||||
pub use crate::hooks::*;
|
||||
|
||||
pub use dioxus_core::prelude::*;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props};
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_html as dioxus_elements;
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
#[cfg(feature = "hot-reload")]
|
||||
pub use dioxus_rsx_interpreter::{
|
||||
captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
|
||||
get_line_num, resolve_scope, CodeLocation, RsxContext,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
struct_lit_width = 80
|
||||
@@ -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
|
||||
@@ -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}!" }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#![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::*;
|
||||
|
||||
use dioxus_core::DomEdit::*;
|
||||
|
||||
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
|
||||
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 },
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
#![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::*;
|
||||
|
||||
fn new_dom() -> VirtualDom {
|
||||
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);
|
||||
println!("{:#?}", &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);
|
||||
println!("{:?}", &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);
|
||||
println!("{:?}", 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);
|
||||
println!("{:#?}", 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);
|
||||
println!("{:#?}", &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);
|
||||
println!("{:#?}", &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 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_nested_elements() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
rsx! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
PushRoot { root: 1 },
|
||||
CreateElement { root: 2, tag: "div" },
|
||||
AppendChildren { many: 1 },
|
||||
PopRoot {},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
rsx! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 },
|
||||
NewEventListener { event_name: "keydown", scope: ScopeId(0), root: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
RemoveEventListener { event: "keyup", root: 1 },
|
||||
RemoveEventListener { event: "keydown", root: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_listeners() {
|
||||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
RemoveEventListener { root: 1, event: "keydown" },
|
||||
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#![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::*;
|
||||
|
||||
use dioxus_core::{DomEdit::*, ScopeId};
|
||||
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
|
||||
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]
|
||||
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 },
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
#![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};
|
||||
|
||||
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();
|
||||
|
||||
println!("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 {
|
||||
println!("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);
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
#![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]
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#![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::*;
|
||||
|
||||
fn new_dom() -> VirtualDom {
|
||||
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 },
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Tests for the scheduler.
|
||||
//!
|
||||
//! TODO
|
||||
//! - priority lanes
|
||||
//! - periodic checking
|
||||
//!
|
||||
@@ -0,0 +1,126 @@
|
||||
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
|
||||
use dioxus::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!(Child {}))
|
||||
};
|
||||
|
||||
static Child: Component = |cx| {
|
||||
let shared = cx.consume_context::<Rc<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(Rc::new(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::<Rc<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::<Rc<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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fn main() {}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user