use std::collections::HashSet;
use dioxus::dioxus_core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::Mutation;
use pretty_assertions::assert_eq;
#[test]
fn list_creates_one_by_one() {
let mut dom = VirtualDom::new(|| {
let gen = generation();
rsx! {
div {
for i in 0..gen {
div { "{i}" }
}
}
}
});
assert_eq!(
dom.rebuild_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { id: ElementId(0), m: 1 },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(3,) },
CreateTextNode { value: "0".to_string(), id: ElementId(4,) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(2,) },
CreateTextNode { value: "1".to_string(), id: ElementId(5,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(3,), m: 1 },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(6,) },
CreateTextNode { value: "2".to_string(), id: ElementId(7,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(2,), m: 1 },
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(8,) },
CreateTextNode { value: "3".to_string(), id: ElementId(9,) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(6,), m: 1 },
]
);
}
#[test]
fn removes_one_by_one() {
let mut dom = VirtualDom::new(|| {
let gen = 3 - generation() % 4;
rsx! {
div {
for i in 0..gen {
div { "{i}" }
}
}
}
});
assert_eq!(
dom.rebuild_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(4) },
CreateTextNode { value: "1".to_string(), id: ElementId(5) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(6) },
CreateTextNode { value: "2".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplacePlaceholder { m: 3, path: &[0] },
AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(6) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(4) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
CreatePlaceholder { id: ElementId(4) },
ReplaceWith { id: ElementId(2), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(4), m: 3 }
]
);
}
#[test]
fn list_shrink_multiroot() {
let mut dom = VirtualDom::new(|| {
rsx! {
div {
for i in 0..generation() {
div { "{i}" }
div { "{i}" }
}
}
}
});
assert_eq!(
dom.rebuild_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0,], m: 1 },
AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(3) },
CreateTextNode { value: "0".to_string(), id: ElementId(4) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(5) },
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplaceWith { id: ElementId(2), m: 2 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(5), m: 2 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(12) },
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
ReplacePlaceholder { path: &[0], m: 1 },
InsertAfter { id: ElementId(8), m: 2 }
]
);
}
#[test]
fn removes_one_by_one_multiroot() {
let mut dom = VirtualDom::new(|| {
let gen = 3 - generation() % 4;
rsx! {
div {
{(0..gen).map(|i| rsx! {
div { "{i}" }
div { "{i}" }
})}
}
}
});
assert_eq!(
dom.rebuild_to_vec().edits,
[
LoadTemplate { index: 0, id: ElementId(1) },
LoadTemplate { index: 0, id: ElementId(2) },
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(4) },
CreateTextNode { value: "0".to_string(), id: ElementId(5) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(6) },
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(8) },
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 0, id: ElementId(10) },
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
ReplacePlaceholder { path: &[0], m: 1 },
LoadTemplate { index: 1, id: ElementId(12) },
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
ReplacePlaceholder { path: &[0], m: 1 },
ReplacePlaceholder { path: &[0], m: 6 },
AppendChildren { id: ElementId(0), m: 1 }
]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
);
dom.mark_dirty(ScopeId::APP);
assert_eq!(
dom.render_immediate_to_vec().edits,
[
CreatePlaceholder { id: ElementId(8) },
Remove { id: ElementId(2) },
ReplaceWith { id: ElementId(4), m: 1 }
]
);
}
#[test]
fn two_equal_fragments_are_equal_static() {
let mut dom = VirtualDom::new(|| {
rsx! {
for _ in 0..5 {
div { "hello" }
}
}
});
dom.rebuild(&mut dioxus_core::NoOpMutations);
assert!(dom.render_immediate_to_vec().edits.is_empty());
}
#[test]
fn two_equal_fragments_are_equal() {
let mut dom = VirtualDom::new(|| {
rsx! {
for i in 0..5 {
div { "hello {i}" }
}
}
});
dom.rebuild(&mut dioxus_core::NoOpMutations);
assert!(dom.render_immediate_to_vec().edits.is_empty());
}
#[test]
fn remove_many() {
let mut dom = VirtualDom::new(|| {
let num = match generation() % 3 {
0 => 0,
1 => 1,
2 => 5,
_ => unreachable!(),
};
rsx! {
for i in 0..num {
div { "hello {i}" }
}
}
});
{
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(1,) },
AppendChildren { id: ElementId(0), m: 1 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(2,) },
CreateTextNode { value: "hello 0".to_string(), id: ElementId(3,) },
ReplacePlaceholder { path: &[0,], m: 1 },
ReplaceWith { id: ElementId(1,), m: 1 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(1,) },
CreateTextNode { value: "hello 1".to_string(), id: ElementId(4,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(5,) },
CreateTextNode { value: "hello 2".to_string(), id: ElementId(6,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(7,) },
CreateTextNode { value: "hello 3".to_string(), id: ElementId(8,) },
ReplacePlaceholder { path: &[0,], m: 1 },
LoadTemplate { index: 0, id: ElementId(9,) },
CreateTextNode { value: "hello 4".to_string(), id: ElementId(10,) },
ReplacePlaceholder { path: &[0,], m: 1 },
InsertAfter { id: ElementId(2,), m: 4 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(edits.edits[0], CreatePlaceholder { id: ElementId(11,) });
let removed = edits.edits[1..5]
.iter()
.map(|edit| match edit {
Mutation::Remove { id } => *id,
_ => panic!("Expected remove"),
})
.collect::<HashSet<_>>();
assert_eq!(
removed,
[ElementId(7), ElementId(5), ElementId(2), ElementId(1)]
.into_iter()
.collect::<HashSet<_>>()
);
assert_eq!(edits.edits[5..], [ReplaceWith { id: ElementId(9,), m: 1 },]);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(9,) },
CreateTextNode { value: "hello 0".to_string(), id: ElementId(7,) },
ReplacePlaceholder { path: &[0,], m: 1 },
ReplaceWith { id: ElementId(11,), m: 1 },
]
)
}
}
#[test]
fn replace_and_add_items() {
let mut dom = VirtualDom::new(|| {
let items = (0..generation()).map(|_| {
if generation() % 2 == 0 {
VNode::empty()
} else {
rsx! {
li {
"Fizz"
}
}
}
});
rsx! {
ul {
{items}
}
}
});
{
let edits = dom.rebuild_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(1,) },
CreatePlaceholder { id: ElementId(2,) },
ReplacePlaceholder { path: &[0], m: 1 },
AppendChildren { id: ElementId(0), m: 1 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(3,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(2,) },
InsertAfter { id: ElementId(3,), m: 1 },
CreatePlaceholder { id: ElementId(4,) },
ReplaceWith { id: ElementId(3,), m: 1 },
]
);
}
{
dom.mark_dirty(ScopeId::APP);
let edits = dom.render_immediate_to_vec();
assert_eq!(
edits.edits,
[
LoadTemplate { index: 0, id: ElementId(3,) },
InsertAfter { id: ElementId(2,), m: 1 },
LoadTemplate { index: 0, id: ElementId(5,) },
ReplaceWith { id: ElementId(4,), m: 1 },
LoadTemplate { index: 0, id: ElementId(4,) },
ReplaceWith { id: ElementId(2,), m: 1 },
]
);
}
}