use std::{
collections::HashMap,
sync::atomic::{
AtomicI8,
Ordering,
},
};
use freya::helpers::*;
use freya_core::{
integration::*,
path_element::PathElement,
prelude::*,
runner::{
Diff,
MutationRemove,
},
};
use rustc_hash::FxHashMap;
use torin::size::Size;
struct RawIdMap(FxHashMap<u64, Vec<u64>>);
impl From<FxHashMap<u64, Vec<u64>>> for RawIdMap {
fn from(m: FxHashMap<u64, Vec<u64>>) -> Self {
RawIdMap(m)
}
}
impl From<RawIdMap> for FxHashMap<NodeId, Vec<NodeId>> {
fn from(raw: RawIdMap) -> Self {
raw.0
.into_iter()
.map(|(k, v)| (NodeId::from(k), v.into_iter().map(NodeId::from).collect()))
.collect()
}
}
fn convert_ids(map: FxHashMap<u64, Vec<u64>>) -> FxHashMap<NodeId, Vec<NodeId>> {
RawIdMap::from(map).into()
}
#[test]
fn mutations() {
fn app() -> Element {
rect()
.children([
from_fn_captured(|| counter(5, true)),
from_fn_captured(|| counter(10, false)),
])
.into()
}
fn counter(stuff: u8, inc_or_dec: bool) -> Element {
let mut value = use_state(|| stuff);
rect()
.layer(*value.read() as i16)
.on_mouse_up(move |_| {
if inc_or_dec {
*value.write() += 1;
} else {
*value.write() -= 1;
}
})
.children(
[label().text(format!("Value is {}", value.read())).into()]
.into_iter()
.chain({
if *value.read() == stuff + { if inc_or_dec { 1 } else { 0 } } {
vec![label().text("Hello World!").into()]
} else {
vec![]
}
})
.collect::<Vec<Element>>(),
)
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 7]),
(5, vec![6]),
(7, vec![8, 9]),
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 7]),
(5, vec![6]),
(7, vec![8, 9]),
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
5,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 7]),
(5, vec![6, 10]),
(7, vec![8, 9]),
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
7,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 7]),
(5, vec![6, 10]),
(7, vec![8]),
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn components() {
fn app() -> Element {
let mut value = use_state(|| 1);
let curr = *value.read();
rect()
.on_mouse_up(move |_| {
*value.write() += 1;
})
.children([
from_fn_captured(move || counter(&curr)),
from_fn_standalone_borrowed(*value.read(), counter),
])
.into()
}
fn counter(value: &u8) -> Element {
rect()
.children([label().text(format!("Value is {value}")).into()])
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 5);
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
runner.handle_event(
2,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert_eq!(mutations.modified.len(), 2);
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
}
#[test]
fn state_reconcillation() {
fn first() -> Element {
rect().into()
}
fn second() -> Element {
rect().into()
}
let first_render: Element = rect().children([from_fn_standalone(second)]).into();
let second_render: Element = rect()
.children([from_fn_standalone(first), from_fn_standalone(second)])
.into();
let first_render = PathElement::from_element(vec![0], first_render);
let second_render = PathElement::from_element(vec![0], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert_eq!(diff.added, vec![vec![0, 0].into_boxed_slice()]);
assert!(diff.modified.is_empty());
assert!(diff.removed.is_empty());
let first_render: Element = rect()
.children([from_fn_standalone(first), from_fn_standalone(second)])
.into();
let second_render: Element = rect().children([from_fn_standalone(second)]).into();
let first_render = PathElement::from_element(vec![0], first_render);
let second_render = PathElement::from_element(vec![0], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert!(diff.added.is_empty());
assert!(diff.modified.is_empty());
assert_eq!(diff.removed, vec![vec![0, 0].into_boxed_slice()]);
let first_render: Element = rect()
.children([from_fn_standalone(first), from_fn_standalone(second)])
.into();
let first_render = PathElement::from_element(vec![0], first_render);
let mut diff = Diff::default();
first_render.diff(None, &mut diff);
assert!(!diff.added.is_empty());
assert!(diff.modified.is_empty());
assert!(diff.removed.is_empty());
}
#[test]
fn state_reconcillation2() {
fn app() -> Element {
let mut value = use_state(|| 5);
rect()
.children(
vec![
rect()
.on_mouse_up(move |_| {
*value.write() += 1;
})
.into(),
rect()
.on_mouse_up(move |_| {
*value.write() -= 1;
})
.into(),
]
.with(
(0..*value.read())
.map(|_| from_fn_captured(|| counter(5)))
.collect::<Vec<_>>(),
),
)
.into()
}
fn counter(stuff: u8) -> Element {
rect()
.children([label().text(format!("Value is {stuff}")).into()])
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![3, 4, 10, 12, 14, 16, 18]),
(10, vec![11]),
(12, vec![13]),
(14, vec![15]),
(16, vec![17]),
(18, vec![19]),
]))
);
assert_eq!(
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.size(),
9
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
3,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![3, 4, 10, 12, 14, 16, 18, 21]),
(10, vec![11]),
(12, vec![13]),
(14, vec![15]),
(16, vec![17]),
(18, vec![19]),
(21, vec![22]),
]))
);
assert_eq!(
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.size(),
10
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
4,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
runner.handle_event(
4,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![3, 4, 10, 12, 14, 16]),
(10, vec![11]),
(12, vec![13]),
(14, vec![15]),
(16, vec![17]),
]))
);
assert_eq!(
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.size(),
8
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn scopes_smart_rerun() {
static COUNTER: AtomicI8 = AtomicI8::new(0);
fn app() -> Element {
let mut value = use_state(|| 5);
rect()
.children(vec![
rect()
.on_mouse_up(move |_| {
*value.write() += 1;
})
.into(),
label().text(format!("Value is {}", value.read())).into(),
from_fn_standalone_borrowed(*value.read(), counter),
])
.into()
}
fn counter(stuff: &usize) -> Element {
COUNTER.fetch_add(1, Ordering::Relaxed);
let mut value = use_state(|| 5);
rect()
.on_mouse_up(move |_| {
*value.write() += 1;
})
.children([label()
.text(format!("Value is {stuff} {}", value.read()))
.into()])
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
assert_eq!(COUNTER.load(Ordering::Relaxed), 1);
runner.handle_event(
3,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
runner.handle_event(
6,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(COUNTER.load(Ordering::Relaxed), 2);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn element_diffing() {
let first_render: Element = rect()
.children([rect().into(), rect().into(), rect().into()])
.into();
let second_render: Element = rect().children([rect().into(), rect().into()]).into();
let first_render = PathElement::from_element(vec![0], first_render);
let second_render = PathElement::from_element(vec![0], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert!(diff.added.is_empty());
assert!(diff.modified.is_empty());
assert_eq!(diff.removed, vec![vec![0, 2].into_boxed_slice()]);
let first_render: Element = rect()
.children([
rect().key(1).into(),
rect().key(2).into(),
rect().key(3).into(),
])
.into();
let second_render: Element = rect()
.children([rect().key(1).into(), rect().key(3).into()])
.into();
let first_render = PathElement::from_element(vec![0], first_render);
let second_render = PathElement::from_element(vec![0], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert!(diff.added.is_empty());
assert!(diff.modified.is_empty());
assert_eq!(diff.removed, vec![vec![0, 1].into_boxed_slice()]);
}
#[test]
fn element_diffing3() {
let first_render: Element = rect().key(1).into();
let second_render: Element = rect().key(2).into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert_eq!(diff.added, vec![vec![].into_boxed_slice()]);
assert!(diff.modified.is_empty());
assert_eq!(diff.removed, vec![vec![].into_boxed_slice()]);
fn first() -> Element {
rect().into()
}
fn second() -> Element {
rect().into()
}
let first_render: Element = from_fn_standalone(first);
let second_render: Element = from_fn_standalone(second);
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert_eq!(diff.added, vec![vec![].into_boxed_slice()]);
assert!(diff.modified.is_empty());
assert_eq!(diff.removed, vec![vec![].into_boxed_slice()]);
}
#[test]
fn element_diffing4() {
fn container(children: &Element) -> Element {
children.clone()
}
let mut runner = Runner::new(|| {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(from_fn_standalone_borrowed_keyed(
1,
from_fn_standalone_borrowed("1".into(), container),
container,
))
.child(from_fn_standalone_borrowed_keyed(
2,
from_fn_standalone_borrowed("2".into(), container),
container,
))
.into()
} else {
rect()
.child(from_fn_standalone_borrowed_keyed(
2,
from_fn_standalone_borrowed("2".into(), container),
container,
))
.child(from_fn_standalone_borrowed_keyed(
1,
from_fn_standalone_borrowed("1".into(), container),
container,
))
.into()
}
});
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 3);
assert_eq!(mutations.added[0].index, 0);
assert_eq!(mutations.added[1].index, 0);
assert_eq!(mutations.added[2].index, 1);
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
let scope_a_before = runner
.scopes
.get(&ScopeId::from(2))
.unwrap()
.borrow()
.path_in_parent
.clone();
let scope_b_before = runner
.scopes
.get(&ScopeId::from(3))
.unwrap()
.borrow()
.path_in_parent
.clone();
assert_eq!(scope_a_before.as_ref(), [0, 0]);
assert_eq!(scope_b_before.as_ref(), [0, 1]);
state.set(false);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
let scope_a_after = runner
.scopes
.get(&ScopeId::from(2))
.unwrap()
.borrow()
.path_in_parent
.clone();
let scope_b_after = runner
.scopes
.get(&ScopeId::from(3))
.unwrap()
.borrow()
.path_in_parent
.clone();
assert_eq!(scope_a_after.as_ref(), [0, 1]);
assert_eq!(scope_b_after.as_ref(), [0, 0]);
}
#[test]
fn element_diffing5() {
fn container(_: &()) -> Element {
rect().into()
}
let first_render: Element = rect()
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.into();
let second_render: Element = rect()
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert!(diff.added.is_empty());
assert!(diff.modified.is_empty());
assert!(diff.removed.is_empty());
assert_eq!(
diff.moved,
HashMap::from_iter([(Box::from([]), vec![(1, 0), (0, 1)])])
);
let first_render: Element = rect().child(rect().key(1)).child(rect().key(0)).into();
let second_render: Element = rect().child(rect().key(0)).child(rect().key(1)).into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert!(diff.added.is_empty());
assert!(diff.modified.is_empty());
assert!(diff.removed.is_empty());
assert_eq!(
diff.moved,
HashMap::from_iter([(Box::from([]), vec![(1, 0), (0, 1)])])
);
}
#[test]
fn element_diffing6() {
fn container(_: &()) -> Element {
rect().into()
}
let first_render: Element = rect()
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.into();
let second_render: Element = rect()
.child(rect().child(from_fn_standalone_borrowed_keyed(2, (), container)))
.into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert_eq!(
diff.added,
vec![vec![0].into_boxed_slice(), vec![0, 0].into_boxed_slice()]
);
assert!(diff.modified.is_empty());
assert_eq!(
diff.removed,
vec![vec![0].into_boxed_slice(), vec![1].into_boxed_slice()]
);
assert!(diff.moved.is_empty());
}
#[test]
fn element_diffing7() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(4))
.child(rect().key(1))
.child(rect().key(5))
.child(rect().key(0))
.into()
} else {
rect()
.child(rect().key(3))
.child(rect().key(0))
.child(rect().key(1))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 1);
assert!(mutations.modified.is_empty());
assert_eq!(mutations.removed.len(), 2);
assert_eq!(mutations.moved.len(), 1);
assert_eq!(mutations.moved.iter().next().unwrap().1.len(), 1);
tree.apply_mutations(mutations);
}
#[test]
fn element_diffing8() {
fn container(_: &()) -> Element {
rect().into()
}
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(4))
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.child(rect().key(5))
.child(rect().key(0))
.into()
} else {
rect()
.child(rect().key(3))
.child(rect().key(0))
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 1);
assert!(mutations.modified.is_empty());
assert_eq!(mutations.removed.len(), 2);
assert_eq!(mutations.moved.len(), 1);
assert_eq!(mutations.moved.iter().next().unwrap().1.len(), 1);
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(false));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(true);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 2);
assert!(mutations.modified.is_empty());
assert_eq!(mutations.removed.len(), 1);
assert_eq!(mutations.moved.len(), 1);
assert_eq!(mutations.moved.iter().next().unwrap().1.len(), 2);
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn element_diffing9() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
rect()
.maybe_child(state().then(rect))
.child(rect().key(1))
.child(rect().key(2))
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(false));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(true);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 1);
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(mutations.moved.is_empty());
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn element_diffing10() {
fn container() -> Element {
rect().into()
}
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1).child(from_fn_standalone(container)))
.child(rect().key(2).child(from_fn_standalone(container)))
.into()
} else {
rect()
.child(rect().key(2).child(from_fn_standalone(container)))
.child(rect().key(1).child(from_fn_standalone(container)))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(false));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(true);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert_eq!(mutations.moved.len(), 1);
assert_eq!(mutations.moved.iter().next().unwrap().1.len(), 1);
assert_eq!(mutations.moved.iter().next().unwrap().1[0].index, 0);
tree.apply_mutations(mutations);
}
#[test]
fn element_diffing11() {
fn container(_: &()) -> Element {
rect().into()
}
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.into()
} else {
rect()
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 3);
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(mutations.moved.is_empty());
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert_eq!(
mutations.removed,
vec![MutationRemove::Scope { id: 5.into() }]
);
assert!(mutations.moved.is_empty());
tree.apply_mutations(mutations);
}
#[test]
fn element_diffing12() {
fn container(_: &()) -> Element {
rect().into()
}
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.key(3)
.child(from_fn_standalone_borrowed_keyed(2, (), container))
.into()
} else {
rect()
.key(4)
.child(from_fn_standalone_borrowed_keyed(1, (), container))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 2);
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(mutations.moved.is_empty());
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 2);
assert_eq!(mutations.removed.len(), 2);
assert!(mutations.modified.is_empty());
assert!(mutations.moved.is_empty());
tree.apply_mutations(mutations);
}
#[test]
fn element_diffing13() {
let first_render: Element = rect()
.child(
rect()
.child(rect().child(rect()))
.child(rect().child(rect()))
.child(rect()),
)
.child(rect())
.into();
let second_render: Element = rect()
.key(2)
.child(rect().child(rect()))
.child(rect().child(rect()))
.child(rect())
.into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
assert_eq!(diff.added.len(), 6);
assert!(diff.modified.is_empty());
assert_eq!(diff.removed.len(), 7);
assert!(diff.moved.is_empty());
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(
rect()
.child(rect().child(rect()))
.child(rect().child(rect()))
.child(rect()),
)
.child(rect())
.into()
} else {
rect()
.key(2)
.child(rect().child(rect()))
.child(rect().child(rect()))
.child(rect())
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
let first_render: Element = rect()
.child(rect().key(2))
.child(rect().child(rect().child(rect())))
.into();
let second_render: Element = rect()
.child(rect().child(rect().key(3)).child(rect()))
.into();
let first_render = PathElement::from_element(vec![], first_render);
let second_render = PathElement::from_element(vec![], second_render);
let mut diff = Diff::default();
second_render.diff(Some(&first_render), &mut diff);
fn app3() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(2))
.child(
rect()
.child(rect().child(rect()))
.child(rect().child(rect()))
.child(rect().child(rect())),
)
.into()
} else {
rect()
.child(rect().child(rect().key(3)).child(rect()))
.into()
}
}
let mut runner = Runner::new(app3);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
fn app4() -> Element {
let state = use_consume::<State<i32>>();
if state() == 1 {
rect()
.child(rect().key(0))
.child(rect().key(1))
.child(rect().key(2))
.into()
} else if state() == 2 {
rect()
.child(rect().key(3))
.child(rect().key(4))
.child(rect().key(5))
.into()
} else {
rect()
.child(rect().key(4))
.child(rect().key(7))
.child(rect().key(8))
.into()
}
}
let mut runner = Runner::new(app4);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(0));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(1);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(2);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
}
#[test]
fn tree_unordered_mutations() {
fn app() -> Element {
let mut show = use_state(|| false);
rect()
.on_mouse_up(move |_| show.toggle())
.maybe_child(show().then(|| from_fn_captured(|| counter())))
.child(rect().key(1))
.child(rect().key(2))
.maybe_child(show().then(|| rect()))
.into()
}
fn counter() -> Element {
rect().into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([(1, vec![2]), (2, vec![3, 4]),]))
);
assert_eq!(
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.size(),
4
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
2,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(!mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([(1, vec![2]), (2, vec![7, 3, 4, 6]),]))
);
assert_eq!(
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.size(),
6
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn tree_mutations_root_components() {
#[derive(PartialEq)]
struct OtherCoolComp;
impl Component for OtherCoolComp {
fn render(&self) -> impl IntoElement {
"Hello, World!"
}
}
#[derive(PartialEq)]
struct CoolComp(u8);
impl Component for CoolComp {
fn render(&self) -> impl IntoElement {
let mut count = use_state(|| 1);
if count() > 1 {
OtherCoolComp.into()
} else {
rect()
.on_mouse_up(move |_| {
*count.write() += 1;
})
.width(Size::px(100.))
.height(Size::px(100.))
.background((255, 0, 0))
.child(self.0.to_string())
.into_element()
}
}
}
fn app() -> Element {
rect()
.spacing(6.)
.child(CoolComp(1))
.child(CoolComp(2))
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 7]),
(5, vec![6]),
(7, vec![8]),
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
runner.handle_event(
7,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
assert_eq!(
tree.children,
convert_ids(FxHashMap::from_iter([
(1, vec![2]),
(2, vec![5, 10]),
(5, vec![6])
]))
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn conditional_nested_component_removal() {
#[derive(PartialEq)]
struct Container(Element);
impl Component for Container {
fn render(&self) -> impl IntoElement {
rect().child(self.0.clone())
}
}
fn app() -> Element {
let mut count = use_state(|| 0);
if *count.read() > 0 {
return rect().into();
}
rect()
.child(Container("hey".into_element()))
.child(
rect()
.on_mouse_up(move |_| count.set(1))
.child(Container("hi".into_element())),
)
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
assert!(!mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
let initial_node_count = tree.elements.len();
let initial_scope_count = runner.scopes.len();
assert!(initial_node_count > 1);
assert!(initial_scope_count > 0);
runner.handle_event(
4,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), 2);
assert_eq!(tree.children.len(), 2);
assert_ne!(runner.scopes.len(), initial_scope_count);
assert_eq!(runner.scopes.len(), 1);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn effect_cascade_in_new_nodes_with_parent_effects() {
fn app() -> Element {
let mut toggled = use_state(|| false);
let child: Element = if toggled() {
label().text("Hello").into()
} else {
rect().into()
};
rect()
.opacity(0.5)
.on_mouse_up(move |_| toggled.toggle())
.child(child)
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
assert!(tree.effect_state.contains_key(&2u64.into()));
assert_eq!(
tree.effect_state.get(&2u64.into()).unwrap().opacities.len(),
1
);
assert!(tree.effect_state.contains_key(&3u64.into()));
assert_eq!(
tree.effect_state.get(&3u64.into()).unwrap().opacities.len(),
1
);
runner.handle_event(
2,
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert!(!mutations.modified.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
assert!(tree.effect_state.contains_key(&3u64.into()));
assert_eq!(
tree.effect_state.get(&3u64.into()).unwrap().opacities.len(),
1
);
}
#[test]
fn modified_with_removed_sibling() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1).padding(10.))
.child(rect().key(2).padding(20.))
.into()
} else {
rect().child(rect().key(2).padding(30.)).into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
state.set(false);
let mutations = runner.sync_and_update();
assert!(!mutations.modified.is_empty());
assert!(!mutations.removed.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn modified_and_moved_element() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1).padding(10.).child(rect().key(10).padding(5.)))
.child(rect().key(2).padding(20.))
.into()
} else {
rect()
.child(rect().key(2).padding(99.))
.child(
rect()
.key(1)
.padding(88.)
.child(rect().key(10).padding(77.)),
)
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
state.set(false);
let mutations = runner.sync_and_update();
assert!(!mutations.modified.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn modified_and_moved_both_siblings_with_nested_child() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1).padding(10.).child(rect().key(3).padding(30.)))
.child(rect().key(2).padding(20.))
.into()
} else {
rect()
.child(rect().key(2).padding(99.))
.child(rect().key(1).padding(88.).child(rect().key(3).padding(77.)))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
state.set(false);
let mutations = runner.sync_and_update();
assert!(!mutations.modified.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn nested_move_simultaneous_add() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
let show = state();
rect()
.maybe_child(show.then(|| rect().key(0)))
.child(
rect()
.key(1)
.maybe_child(show.then(|| rect().key(3)))
.child(rect().key(2)),
)
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(false));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(true);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 2);
assert!(mutations.removed.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(false);
let mutations = runner.sync_and_update();
assert_eq!(mutations.removed.len(), 2);
assert!(mutations.added.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn nested_move_pure_swap() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1).child(rect().key(2)).child(rect().key(5)))
.child(rect().key(3))
.into()
} else {
rect()
.child(rect().key(3))
.child(rect().key(1).child(rect().key(5)).child(rect().key(2)))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(false));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(true);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(false);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn ordered_scope_mutations_diffing() {
fn second(_: &()) -> Element {
"text".into()
}
fn app() -> Element {
let mut enable = use_state(|| false);
rect()
.expanded()
.background((255, 255, 255))
.on_mouse_up(move |_| enable.toggle())
.maybe_child(enable().then(|| "hi"))
.child(rect().key(0).child(from_fn_standalone_borrowed_keyed(
DiffKey::from(&enable()),
(),
second,
)))
.into_element()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
runner.handle_event(
NodeId::from(2),
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 2);
tree.apply_mutations(mutations);
runner.handle_event(
NodeId::from(2),
EventName::MouseUp,
EventType::Mouse(MouseEventData::default()),
false,
);
let mutations = runner.sync_and_update();
assert_eq!(mutations.added.len(), 1);
assert_eq!(mutations.removed.len(), 2);
tree.apply_mutations(mutations);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn deeply_nested_component_move() {
fn comp3(_: &()) -> Element {
rect().into()
}
fn comp2(_: &()) -> Element {
from_fn_standalone_borrowed((), comp3)
}
fn comp1(_: &()) -> Element {
from_fn_standalone_borrowed((), comp2)
}
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(from_fn_standalone_borrowed_keyed(1, (), comp1))
.child(from_fn_standalone_borrowed_keyed(2, (), comp1))
.into()
} else {
rect()
.child(from_fn_standalone_borrowed_keyed(2, (), comp1))
.child(from_fn_standalone_borrowed_keyed(1, (), comp1))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(false);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(true);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.modified.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn ordered_movements_first_to_last() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(rect().key(1))
.child(rect().key(2))
.child(rect().key(3))
.child(rect().key(4))
.into()
} else {
rect()
.child(rect().key(2))
.child(rect().key(3))
.child(rect().key(4))
.child(rect().key(1))
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
let initial_children = tree.children.get(&NodeId::from(2)).unwrap().clone();
state.set(false);
let mutations = runner.sync_and_update();
assert!(mutations.added.is_empty());
assert!(mutations.removed.is_empty());
assert!(!mutations.moved.is_empty());
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
let reordered_children = tree.children.get(&NodeId::from(2)).unwrap();
assert_eq!(
reordered_children,
&vec![
initial_children[1],
initial_children[2],
initial_children[3],
initial_children[0],
]
);
}
fn page_a(_: &()) -> Element {
label().text("Page A").into()
}
fn page_b(_: &()) -> Element {
label().text("Page B").into()
}
fn outlet(_: &()) -> Element {
label().text("Outlet").into()
}
#[test]
fn moved_element_with_child_component_type_change() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(
rect()
.key("left")
.child(from_fn_standalone_borrowed((), page_a)),
)
.child(
rect()
.key("right")
.child(from_fn_standalone_borrowed((), outlet)),
)
.into()
} else {
rect()
.child(
rect()
.key("right")
.child(from_fn_standalone_borrowed((), outlet)),
)
.child(
rect()
.key("left")
.child(from_fn_standalone_borrowed((), page_b)),
)
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(false);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn moved_element_with_deeply_nested_child_type_change() {
fn app() -> Element {
let state = use_consume::<State<bool>>();
if state() {
rect()
.child(
rect()
.key("left")
.child(rect().child(from_fn_standalone_borrowed((), page_a))),
)
.child(
rect()
.key("right")
.child(rect().child(from_fn_standalone_borrowed((), outlet))),
)
.into()
} else {
rect()
.child(
rect()
.key("right")
.child(rect().child(from_fn_standalone_borrowed((), outlet))),
)
.child(
rect()
.key("left")
.child(rect().child(from_fn_standalone_borrowed((), page_b))),
)
.into()
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(true));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(false);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
#[test]
fn crash_l971_leftover_empty_slot() {
fn app() -> Element {
let state = use_consume::<State<u8>>();
match state() {
0 => rect()
.child(rect().key(1).child(rect().key(2)).child(rect().key(3)))
.child(rect().key(4))
.into(),
1 => rect()
.child(rect().key(4))
.child(
rect()
.key(1)
.child(rect().key(3).child(rect()))
.child(rect())
.child(rect().key(2)),
)
.into(),
_ => rect()
.child(rect().key(4))
.child(
rect()
.key(1)
.child(rect().key(3).child(rect()))
.child(rect())
.child(rect().key(2).child(rect())),
)
.into(),
}
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut state = runner.provide_root_context(|| State::create(0u8));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(1);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
state.set(2);
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
fn replay_keyed_list(history: &[Vec<u8>]) {
fn app() -> Element {
let layout = consume_context::<State<Vec<u8>>>();
rect()
.children(layout.read().iter().map(|key| rect().key(*key).into()))
.into()
}
let mut runner = Runner::new(app);
let mut tree = Tree::default();
let mut layout = runner.provide_root_context(|| State::create(Vec::<u8>::new()));
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
for step in history {
layout.set(step.clone());
let mutations = runner.sync_and_update();
tree.apply_mutations(mutations);
tree.verify_tree_integrity();
for (parent, children) in &tree.children {
for child in children {
assert_ne!(
*child,
NodeId::PLACEHOLDER,
"placeholder in {parent:?} after {step:?}"
);
assert!(
tree.accessibility_state.contains_key(child),
"orphan child {child:?} of {parent:?} after {step:?}"
);
}
}
let mut expected = Vec::new();
runner
.scopes
.get(&ScopeId::ROOT)
.unwrap()
.borrow()
.nodes
.traverse(&[0], |path, node| {
if let [_, index] = *path {
expected.push((index, node.node_id));
}
});
expected.sort_by_key(|(index, _)| *index);
let expected = expected
.into_iter()
.map(|(_, node_id)| node_id)
.collect::<Vec<_>>();
assert_eq!(
tree.children.get(&2u64.into()).cloned().unwrap_or_default(),
expected,
"children drifted from the source tree after {step:?}"
);
assert_eq!(tree.elements.len(), runner.node_to_scope.len());
}
}
#[test]
fn tree_keyed_list_minimal_orphan_repro() {
replay_keyed_list(&[vec![1, 4, 0, 2], vec![2, 0, 3, 4], vec![4]]);
}
#[test]
fn tree_keyed_list_reorders() {
fn permutations(keys: &[u8]) -> Vec<Vec<u8>> {
if keys.is_empty() {
return vec![Vec::new()];
}
let mut output = Vec::new();
for index in 0..keys.len() {
let mut rest = keys.to_vec();
let first = rest.remove(index);
for mut permutation in permutations(&rest) {
permutation.insert(0, first);
output.push(permutation);
}
}
output
}
let layouts = (0..=4)
.flat_map(|size| permutations(&(0..size).collect::<Vec<u8>>()))
.collect::<Vec<_>>();
let mut history = Vec::new();
for from in &layouts {
for to in &layouts {
history.push(from.clone());
history.push(to.clone());
}
}
replay_keyed_list(&history);
}