feat: keyed diffing
This commit is contained in:
1 parent
778baffb10
commit
0479252a5f
5 files changed
+563
-139
No files matched your search
@@ -48,7 +48,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
match &node {
|
match &node {
|
||||||
// We can only exit our looping when we get "real" nodes
|
// We can only exit our looping when we get "real" nodes
|
||||||
// This includes fragments and components when they're empty (have a single root)
|
// This includes fragments and components when they're empty (have a single root)
|
||||||
VNode::Element(_) | VNode::Text(_) => {
|
VNode::Element(_) | VNode::Text(_) | VNode::Suspended(_) | VNode::Anchor(_) => {
|
||||||
// We've recursed INTO an element/text
|
// We've recursed INTO an element/text
|
||||||
// We need to recurse *out* of it and move forward to the next
|
// We need to recurse *out* of it and move forward to the next
|
||||||
should_pop = true;
|
should_pop = true;
|
||||||
@@ -70,31 +70,6 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
should_push = Some(&frag.children[subcount]);
|
should_push = Some(&frag.children[subcount]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// // If we get a fragment we push the next child
|
|
||||||
// VNodeKind::Fragment(frag) => {
|
|
||||||
// let subcount = *count as usize;
|
|
||||||
|
|
||||||
// if frag.children.len() == 0 {
|
|
||||||
// should_pop = true;
|
|
||||||
// returned_node = Some(&*node);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if subcount >= frag.children.len() {
|
|
||||||
// should_pop = true;
|
|
||||||
// } else {
|
|
||||||
// should_push = Some(&frag.children[subcount]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Immediately abort suspended nodes - can't do anything with them yet
|
|
||||||
VNode::Suspended(node) => {
|
|
||||||
// VNodeKind::Suspended => should_pop = true,
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
VNode::Anchor(a) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
// For components, we load their root and push them onto the stack
|
||||||
VNode::Component(sc) => {
|
VNode::Component(sc) => {
|
||||||
|
|||||||
+139
-57
@@ -190,7 +190,11 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DiffInstruction::PrepareMoveNode { node } => {
|
DiffInstruction::PrepareMoveNode { node } => {
|
||||||
//
|
log::debug!("Preparing to move node: {:?}", node);
|
||||||
|
for el in RealChildIterator::new(node, self.vdom) {
|
||||||
|
self.mutations.push_root(el.direct_id());
|
||||||
|
self.stack.add_child_count(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -211,10 +215,15 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
many: nodes_created as u32,
|
many: nodes_created as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::Replace { old } => {
|
MountType::Replace { old } => {
|
||||||
let root = todo!();
|
let mut iter = RealChildIterator::new(old, self.vdom);
|
||||||
self.mutations.replace_with(root, nodes_created as u32);
|
let first = iter.next().unwrap();
|
||||||
|
self.mutations
|
||||||
|
.replace_with(first.direct_id(), nodes_created as u32);
|
||||||
|
self.remove_nodes(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::ReplaceByElementId { el: old } => {
|
MountType::ReplaceByElementId { el: old } => {
|
||||||
self.mutations.replace_with(old, nodes_created as u32);
|
self.mutations.replace_with(old, nodes_created as u32);
|
||||||
}
|
}
|
||||||
@@ -225,15 +234,9 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MountType::InsertBefore { other_node } => {
|
MountType::InsertBefore { other_node } => {
|
||||||
let root = self.find_first_element(other_node).direct_id();
|
let root = self.find_first_element_id(other_node).unwrap();
|
||||||
self.mutations.insert_before(root, nodes_created as u32);
|
self.mutations.insert_before(root, nodes_created as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::InsertAfterFlush { other_node } => {
|
|
||||||
self.stack.push_nodes_created(0);
|
|
||||||
let root = self.find_last_element(other_node).direct_id();
|
|
||||||
self.mutations.insert_after(root, nodes_created as u32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,8 +418,15 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
//
|
//
|
||||||
// This case is rather rare (typically only in non-keyed lists)
|
// This case is rather rare (typically only in non-keyed lists)
|
||||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||||
todo!("element changed");
|
// maybe make this an instruction?
|
||||||
// self.replace_node_with_node(old, new);
|
// issue is that we need the "vnode" but this method only has the velement
|
||||||
|
self.stack.push_nodes_created(0);
|
||||||
|
self.stack.push(DiffInstruction::Mount {
|
||||||
|
and: MountType::ReplaceByElementId {
|
||||||
|
el: old.dom_id.get().unwrap(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
self.create_element_node(new);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,6 +650,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
// the change list stack is in the same state when this function returns.
|
// the change list stack is in the same state when this function returns.
|
||||||
fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
|
fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
|
||||||
// Handled these cases in `diff_children` before calling this function.
|
// Handled these cases in `diff_children` before calling this function.
|
||||||
|
log::debug!("diffing non-keyed case");
|
||||||
debug_assert!(!new.is_empty());
|
debug_assert!(!new.is_empty());
|
||||||
debug_assert!(!old.is_empty());
|
debug_assert!(!old.is_empty());
|
||||||
|
|
||||||
@@ -649,7 +660,8 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
|
|
||||||
if old.len() > new.len() {
|
if old.len() > new.len() {
|
||||||
self.remove_nodes(&old[new.len()..]);
|
self.remove_nodes(&old[new.len()..]);
|
||||||
} else {
|
} else if new.len() > old.len() {
|
||||||
|
log::debug!("Calling create children on array differences");
|
||||||
self.stack.create_children(
|
self.stack.create_children(
|
||||||
&new[old.len()..],
|
&new[old.len()..],
|
||||||
MountType::InsertAfter {
|
MountType::InsertAfter {
|
||||||
@@ -707,6 +719,11 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
Some(count) => count,
|
Some(count) => count,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
log::debug!(
|
||||||
|
"Left offset, right offset, {}, {}",
|
||||||
|
left_offset,
|
||||||
|
right_offset,
|
||||||
|
);
|
||||||
|
|
||||||
// Ok, we now hopefully have a smaller range of children in the middle
|
// Ok, we now hopefully have a smaller range of children in the middle
|
||||||
// within which to re-order nodes with the same keys, remove old nodes with
|
// within which to re-order nodes with the same keys, remove old nodes with
|
||||||
@@ -846,64 +863,107 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
|
|
||||||
// 4. Compute the LIS of this list
|
// 4. Compute the LIS of this list
|
||||||
// TODO: investigate if we can go without the hashset here, I can't quite tell from the LIS crate
|
// TODO: investigate if we can go without the hashset here, I can't quite tell from the LIS crate
|
||||||
let mut new_index_is_in_lis = FxHashSet::default();
|
// let mut new_index_is_in_lis = Vec::default();
|
||||||
new_index_is_in_lis.reserve(new_index_to_old_index.len());
|
let mut lis_sequence = Vec::default();
|
||||||
|
lis_sequence.reserve(new_index_to_old_index.len());
|
||||||
|
|
||||||
let mut predecessors = vec![0; new_index_to_old_index.len()];
|
let mut predecessors = vec![0; new_index_to_old_index.len()];
|
||||||
let mut starts = vec![0; new_index_to_old_index.len()];
|
let mut starts = vec![0; new_index_to_old_index.len()];
|
||||||
|
|
||||||
longest_increasing_subsequence::lis_with(
|
longest_increasing_subsequence::lis_with(
|
||||||
&new_index_to_old_index,
|
&new_index_to_old_index,
|
||||||
&mut new_index_is_in_lis,
|
&mut lis_sequence,
|
||||||
|a, b| a < b,
|
|a, b| a < b,
|
||||||
&mut predecessors,
|
&mut predecessors,
|
||||||
&mut starts,
|
&mut starts,
|
||||||
);
|
);
|
||||||
|
|
||||||
// REMEMBER: we're generating instructions. we cannot rely on anything stack related to be consistent.
|
// the lis comes out backwards, I think. can't quite tell
|
||||||
|
lis_sequence.sort_unstable();
|
||||||
|
|
||||||
// walk the new list, creating nodes or diffing them. if we hit an LIS, then we call "insert before"
|
// if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
|
||||||
let mut last_lis = 0;
|
if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
|
||||||
|
lis_sequence.pop();
|
||||||
|
}
|
||||||
|
|
||||||
self.stack.push_nodes_created(0);
|
log::debug!("LIS Indicies: {:?}", lis_sequence);
|
||||||
for (idx, new_node) in new.into_iter().enumerate().rev() {
|
|
||||||
let old_index = new_index_to_old_index[idx];
|
|
||||||
|
|
||||||
|
let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| {
|
||||||
|
log::debug!("applying {:?}", new_node);
|
||||||
|
|
||||||
|
let old_index = new_index_to_old_index[new_idx];
|
||||||
|
dbg!(new_idx, old_index);
|
||||||
if old_index == u32::MAX as usize {
|
if old_index == u32::MAX as usize {
|
||||||
self.stack.create_node(new_node, MountType::Absorb);
|
stack.create_node(new_node, MountType::Absorb);
|
||||||
} else {
|
} else {
|
||||||
// diff / move
|
// this funciton should never take LIS indicies
|
||||||
if let Some(new_index) = new_index_is_in_lis.get(&idx) {
|
stack.push(DiffInstruction::PrepareMoveNode { node: new_node });
|
||||||
// take all the nodes on the stack and insert them before this one
|
stack.push(DiffInstruction::DiffNode {
|
||||||
self.stack.push(DiffInstruction::Mount {
|
new: new_node,
|
||||||
and: MountType::InsertAfterFlush {
|
old: &old[old_index],
|
||||||
other_node: new_node,
|
});
|
||||||
},
|
}
|
||||||
});
|
};
|
||||||
self.stack.push(DiffInstruction::DiffNode {
|
|
||||||
new: new_node,
|
// add mount instruction for the last items not covered by the lis
|
||||||
old: &old[old_index],
|
let first_lis = *lis_sequence.first().unwrap();
|
||||||
});
|
dbg!(first_lis);
|
||||||
last_lis = *new_index;
|
if first_lis > 0 {
|
||||||
} else {
|
self.stack.push_nodes_created(0);
|
||||||
// this is not an LIS node, we need to diff it and move it
|
self.stack.push(DiffInstruction::Mount {
|
||||||
self.stack
|
and: MountType::InsertBefore {
|
||||||
.push(DiffInstruction::PrepareMoveNode { node: new_node });
|
other_node: &new[first_lis],
|
||||||
self.stack.push(DiffInstruction::DiffNode {
|
},
|
||||||
new: new_node,
|
});
|
||||||
old: &old[old_index],
|
|
||||||
});
|
for (idx, new_node) in new[..first_lis].iter().enumerate().rev() {
|
||||||
}
|
apply(idx, new_node, &mut self.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// There will be nodes left on top of the stack. They need to be inserted before the final
|
// for each spacing, generate a mount instruction
|
||||||
let last_node = &old[last_lis];
|
let mut lis_iter = lis_sequence.iter().rev();
|
||||||
self.stack.push(DiffInstruction::Mount {
|
let mut last = *lis_iter.next().unwrap();
|
||||||
and: MountType::InsertBefore {
|
while let Some(&next) = lis_iter.next() {
|
||||||
other_node: last_node,
|
if last - next > 1 {
|
||||||
},
|
self.stack.push_nodes_created(0);
|
||||||
});
|
self.stack.push(DiffInstruction::Mount {
|
||||||
|
and: MountType::InsertBefore {
|
||||||
|
other_node: &new[last],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() {
|
||||||
|
apply(idx + next + 1, new_node, &mut self.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add mount instruction for the first items not covered by the lis
|
||||||
|
let last_lis = *lis_sequence.last().unwrap();
|
||||||
|
log::debug!("last lis is {}, new len is {}", last_lis, new.len());
|
||||||
|
if last_lis < (new.len() - 1) {
|
||||||
|
let after = &new[last_lis];
|
||||||
|
dbg!(after);
|
||||||
|
self.stack.push_nodes_created(0);
|
||||||
|
self.stack.push(DiffInstruction::Mount {
|
||||||
|
and: MountType::InsertAfter { other_node: after },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (idx, new_node) in new[(last_lis + 1)..].iter().enumerate().rev() {
|
||||||
|
apply(idx + last_lis + 1, new_node, &mut self.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx in lis_sequence.iter().rev() {
|
||||||
|
let old_index = new_index_to_old_index[*idx];
|
||||||
|
let new_node = &new[*idx];
|
||||||
|
log::debug!("diffing {:?}", new_node);
|
||||||
|
self.stack.push(DiffInstruction::DiffNode {
|
||||||
|
new: new_node,
|
||||||
|
old: &old[old_index],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
@@ -933,17 +993,13 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> {
|
fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
|
||||||
let mut search_node = Some(vnode);
|
let mut search_node = Some(vnode);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let node = search_node.take().unwrap();
|
let node = search_node.take().unwrap();
|
||||||
match &node {
|
match &node {
|
||||||
// the ones that have a direct id
|
// the ones that have a direct id
|
||||||
VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
|
|
||||||
break node
|
|
||||||
}
|
|
||||||
|
|
||||||
VNode::Fragment(frag) => {
|
VNode::Fragment(frag) => {
|
||||||
search_node = Some(&frag.children[0]);
|
search_node = Some(&frag.children[0]);
|
||||||
}
|
}
|
||||||
@@ -952,9 +1008,35 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||||
search_node = Some(scope.root());
|
search_node = Some(scope.root());
|
||||||
}
|
}
|
||||||
|
VNode::Text(t) => break t.dom_id.get(),
|
||||||
|
VNode::Element(t) => break t.dom_id.get(),
|
||||||
|
VNode::Suspended(t) => break t.node.get(),
|
||||||
|
VNode::Anchor(t) => break t.dom_id.get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> {
|
||||||
|
// let mut search_node = Some(vnode);
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// let node = search_node.take().unwrap();
|
||||||
|
// match &node {
|
||||||
|
// // the ones that have a direct id
|
||||||
|
// VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
|
||||||
|
// break node
|
||||||
|
// }
|
||||||
|
|
||||||
|
// VNode::Fragment(frag) => {
|
||||||
|
// search_node = Some(&frag.children[0]);
|
||||||
|
// }
|
||||||
|
// VNode::Component(el) => {
|
||||||
|
// let scope_id = el.ass_scope.get().unwrap();
|
||||||
|
// let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||||
|
// search_node = Some(scope.root());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
fn replace_and_create_many_with_one(
|
fn replace_and_create_many_with_one(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ pub enum MountType<'a> {
|
|||||||
Replace { old: &'a VNode<'a> },
|
Replace { old: &'a VNode<'a> },
|
||||||
ReplaceByElementId { el: ElementId },
|
ReplaceByElementId { el: ElementId },
|
||||||
InsertAfter { other_node: &'a VNode<'a> },
|
InsertAfter { other_node: &'a VNode<'a> },
|
||||||
InsertAfterFlush { other_node: &'a VNode<'a> },
|
|
||||||
InsertBefore { other_node: &'a VNode<'a> },
|
InsertBefore { other_node: &'a VNode<'a> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -651,7 +651,13 @@ impl Debug for NodeFactory<'_> {
|
|||||||
impl Debug for VNode<'_> {
|
impl Debug for VNode<'_> {
|
||||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
match &self {
|
match &self {
|
||||||
VNode::Element(el) => write!(s, "VElement {{ name: {} }}", el.tag_name),
|
VNode::Element(el) => {
|
||||||
|
//
|
||||||
|
s.debug_struct("VElement")
|
||||||
|
.field("name", &el.tag_name)
|
||||||
|
.field("key", &el.key)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
|
VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
|
||||||
VNode::Anchor(a) => write!(s, "VAnchor"),
|
VNode::Anchor(a) => write!(s, "VAnchor"),
|
||||||
|
|
||||||
|
|||||||
+416
-54
@@ -1,7 +1,7 @@
|
|||||||
//! Diffing Tests
|
//! Diffing Tests
|
||||||
//! -------------
|
//! -------------
|
||||||
//!
|
//!
|
||||||
//! These should always compile and run, but the result is not validated for each test.
|
//! These should always compile and run, but the result is not validat root: (), m: () ed for each test.
|
||||||
//! TODO: Validate the results beyond visual inspection.
|
//! TODO: Validate the results beyond visual inspection.
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
@@ -13,6 +13,8 @@ use dioxus::{
|
|||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
|
mod test_logging;
|
||||||
|
use DomEdit::*;
|
||||||
|
|
||||||
struct TestDom {
|
struct TestDom {
|
||||||
bump: Bump,
|
bump: Bump,
|
||||||
@@ -20,6 +22,7 @@ struct TestDom {
|
|||||||
}
|
}
|
||||||
impl TestDom {
|
impl TestDom {
|
||||||
fn new() -> TestDom {
|
fn new() -> TestDom {
|
||||||
|
test_logging::set_up_logging();
|
||||||
let bump = Bump::new();
|
let bump = Bump::new();
|
||||||
let resources = SharedResources::new();
|
let resources = SharedResources::new();
|
||||||
TestDom { bump, resources }
|
TestDom { bump, resources }
|
||||||
@@ -106,28 +109,70 @@ fn diffing_works() {}
|
|||||||
#[test]
|
#[test]
|
||||||
fn html_and_rsx_generate_the_same_output() {
|
fn html_and_rsx_generate_the_same_output() {
|
||||||
let dom = TestDom::new();
|
let dom = TestDom::new();
|
||||||
let edits = dom.lazy_diff(
|
let (create, change) = dom.lazy_diff(
|
||||||
rsx! ( div { "Hello world" } ),
|
rsx! ( div { "Hello world" } ),
|
||||||
rsx! ( div { "Goodbye world" } ),
|
rsx! ( div { "Goodbye world" } ),
|
||||||
);
|
);
|
||||||
dbg!(edits);
|
assert_eq!(
|
||||||
|
create.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 0, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
id: 1,
|
||||||
|
text: "Hello world"
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
PushRoot { id: 1 },
|
||||||
|
SetText {
|
||||||
|
text: "Goodbye world"
|
||||||
|
},
|
||||||
|
PopRoot
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in 3 elements on the stack
|
/// Should result in 3 elements on the stack
|
||||||
#[test]
|
#[test]
|
||||||
fn fragments_create_properly() {
|
fn fragments_create_properly() {
|
||||||
let dom = TestDom::new();
|
let dom = TestDom::new();
|
||||||
let Mutations { edits, noderefs } = dom.create(rsx! {
|
|
||||||
|
let create = dom.create(rsx! {
|
||||||
div { "Hello a" }
|
div { "Hello a" }
|
||||||
div { "Hello b" }
|
div { "Hello b" }
|
||||||
div { "Hello c" }
|
div { "Hello c" }
|
||||||
});
|
});
|
||||||
assert!(&edits[0].is("CreateElement"));
|
|
||||||
assert!(&edits[3].is("CreateElement"));
|
|
||||||
assert!(&edits[6].is("CreateElement"));
|
|
||||||
|
|
||||||
assert_eq!(*edits.last().unwrap(), DomEdit::AppendChildren { many: 3 });
|
assert_eq!(
|
||||||
dbg!(edits);
|
create.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 0, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
id: 1,
|
||||||
|
text: "Hello a"
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement { id: 2, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
id: 3,
|
||||||
|
text: "Hello b"
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement { id: 4, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
id: 5,
|
||||||
|
text: "Hello c"
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
AppendChildren { many: 3 },
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
||||||
@@ -138,8 +183,19 @@ fn empty_fragments_create_anchors() {
|
|||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
||||||
let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
|
let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(edits);
|
|
||||||
|
assert_eq!(
|
||||||
|
create.edits,
|
||||||
|
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 1, tag: "div" },
|
||||||
|
ReplaceWith { m: 1, root: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
|
/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
|
||||||
@@ -150,8 +206,22 @@ fn empty_fragments_create_many_anchors() {
|
|||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
||||||
let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
|
let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(edits);
|
assert_eq!(
|
||||||
|
create.edits,
|
||||||
|
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 1, tag: "div" },
|
||||||
|
CreateElement { id: 2, tag: "div" },
|
||||||
|
CreateElement { id: 3, tag: "div" },
|
||||||
|
CreateElement { id: 4, tag: "div" },
|
||||||
|
CreateElement { id: 5, tag: "div" },
|
||||||
|
ReplaceWith { m: 5, root: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
/// Should result in the creation of an anchor (placeholder) and then a replacewith
|
||||||
@@ -162,15 +232,40 @@ fn empty_fragments_create_anchors_with_many_children() {
|
|||||||
|
|
||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
(0..5).map(|f| {
|
(0..3).map(|f| {
|
||||||
rsx! { div { "hello" }}
|
rsx! { div { "hello: {f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert_eq!(
|
||||||
let last_edit = edits.1.edits.last().unwrap();
|
create.edits,
|
||||||
assert!(last_edit.is("ReplaceWith"));
|
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 1, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
text: "hello: 0",
|
||||||
|
id: 2
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement { id: 3, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
text: "hello: 1",
|
||||||
|
id: 4
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement { id: 5, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
text: "hello: 2",
|
||||||
|
id: 6
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
ReplaceWith { m: 3, root: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in every node being pushed and then replaced with an anchor
|
/// Should result in every node being pushed and then replaced with an anchor
|
||||||
@@ -185,8 +280,35 @@ fn many_items_become_fragment() {
|
|||||||
});
|
});
|
||||||
let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert_eq!(
|
||||||
|
create.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 0, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
text: "hello",
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement { id: 2, tag: "div" },
|
||||||
|
CreateTextNode {
|
||||||
|
text: "hello",
|
||||||
|
id: 3
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
AppendChildren { many: 2 },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// hmmmmmmmmm worried about reusing IDs that we shouldnt be
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
Remove { root: 2 },
|
||||||
|
CreatePlaceholder { id: 4 },
|
||||||
|
ReplaceWith { root: 0, m: 1 },
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in no edits
|
/// Should result in no edits
|
||||||
@@ -205,9 +327,8 @@ fn two_equal_fragments_are_equal() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert!(change.edits.is_empty());
|
||||||
assert!(edits.1.edits.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result the creation of more nodes appended after the old last node
|
/// Should result the creation of more nodes appended after the old last node
|
||||||
@@ -216,11 +337,11 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
|||||||
let dom = TestDom::new();
|
let dom = TestDom::new();
|
||||||
|
|
||||||
let left = rsx!(
|
let left = rsx!(
|
||||||
{(0..2).map(|f| {rsx! { div { }}})}
|
{ (0..2).map(|f| rsx! { div { }} ) }
|
||||||
p {}
|
p {}
|
||||||
);
|
);
|
||||||
let right = rsx!(
|
let right = rsx!(
|
||||||
{(0..5).map(|f| {rsx! { h1 { }}})}
|
{ (0..5).map(|f| rsx! (h1 { }) ) }
|
||||||
p {}
|
p {}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -242,8 +363,31 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
|||||||
p {}
|
p {}
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert_eq!(
|
||||||
|
create.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 0, tag: "div" },
|
||||||
|
CreateElement { id: 1, tag: "div" },
|
||||||
|
CreateElement { id: 2, tag: "div" },
|
||||||
|
CreateElement { id: 3, tag: "div" },
|
||||||
|
CreateElement { id: 4, tag: "div" },
|
||||||
|
CreateElement { id: 5, tag: "p" },
|
||||||
|
AppendChildren { many: 6 },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
Remove { root: 2 },
|
||||||
|
Remove { root: 3 },
|
||||||
|
Remove { root: 4 },
|
||||||
|
CreateElement { id: 6, tag: "h1" },
|
||||||
|
ReplaceWith { root: 0, m: 1 },
|
||||||
|
CreateElement { id: 7, tag: "h1" },
|
||||||
|
ReplaceWith { root: 1, m: 1 },
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in multiple nodes destroyed - with no changes
|
/// Should result in multiple nodes destroyed - with no changes
|
||||||
@@ -260,26 +404,25 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||||||
p {}
|
p {}
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert_eq!(
|
||||||
}
|
create.edits,
|
||||||
|
[
|
||||||
// Similar test from above, but with extra child nodes
|
CreateElement { id: 0, tag: "div" },
|
||||||
#[test]
|
CreateElement { id: 1, tag: "div" },
|
||||||
fn two_fragments_with_same_elements_are_differet_shorter() {
|
CreateElement { id: 2, tag: "p" },
|
||||||
let dom = TestDom::new();
|
AppendChildren { many: 3 },
|
||||||
|
]
|
||||||
let left = rsx!(
|
|
||||||
{(0..5).map(|f| {rsx! { div { }}})}
|
|
||||||
p {"e"}
|
|
||||||
);
|
);
|
||||||
let right = rsx!(
|
assert_eq!(
|
||||||
{(0..2).map(|f| {rsx! { div { }}})}
|
change.edits,
|
||||||
p {"e"}
|
[
|
||||||
|
CreateElement { id: 3, tag: "div" },
|
||||||
|
CreateElement { id: 4, tag: "div" },
|
||||||
|
CreateElement { id: 5, tag: "div" },
|
||||||
|
InsertAfter { root: 1, n: 3 },
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
|
||||||
dbg!(&edits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// should result in the removal of elements
|
/// should result in the removal of elements
|
||||||
@@ -296,8 +439,11 @@ fn keyed_diffing_order() {
|
|||||||
p {"e"}
|
p {"e"}
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -313,23 +459,239 @@ fn fragment_keys() {
|
|||||||
fn keyed_diffing_out_of_order() {
|
fn keyed_diffing_out_of_order() {
|
||||||
let dom = TestDom::new();
|
let dom = TestDom::new();
|
||||||
|
|
||||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8,
|
|
||||||
let left = rsx!({
|
let left = rsx!({
|
||||||
(0..3).chain(3..6).chain(6..9).map(|f| {
|
[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
|
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
(0..3).chain((3..7).rev()).chain(7..10).map(|f| {
|
[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// LIS: 3, 7, 8,
|
|
||||||
let edits = dom.lazy_diff(left, right);
|
let edits = dom.lazy_diff(left, right);
|
||||||
dbg!(&edits);
|
dbg!(&edits.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should result in moves only
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_out_of_order_adds() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
PushRoot { id: 4 },
|
||||||
|
PushRoot { id: 3 },
|
||||||
|
InsertBefore { n: 2, root: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// Should result in moves onl
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_out_of_order_adds_2() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
PushRoot { id: 3 },
|
||||||
|
PushRoot { id: 4 },
|
||||||
|
InsertBefore { n: 2, root: 0 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should result in moves onl
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_out_of_order_adds_3() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
PushRoot { id: 4 },
|
||||||
|
PushRoot { id: 3 },
|
||||||
|
InsertBefore { n: 2, root: 1 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should result in moves onl
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_out_of_order_adds_4() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
PushRoot { id: 4 },
|
||||||
|
PushRoot { id: 3 },
|
||||||
|
InsertBefore { n: 2, root: 2 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should result in moves onl
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_out_of_order_adds_5() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[PushRoot { id: 4 }, InsertBefore { n: 1, root: 3 }]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_additions() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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.lazy_diff(left, right);
|
||||||
|
assert_eq!(
|
||||||
|
change.edits,
|
||||||
|
[
|
||||||
|
CreateElement { id: 5, tag: "div" },
|
||||||
|
CreateElement { id: 6, tag: "div" },
|
||||||
|
InsertAfter { n: 2, root: 4 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_additions_and_moves_on_ends() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
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| {
|
||||||
|
// [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| {
|
||||||
|
rsx! { div { key: "{f}" }}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let (_, change) = dom.lazy_diff(left, right);
|
||||||
|
dbg!(change);
|
||||||
|
// assert_eq!(
|
||||||
|
// change.edits,
|
||||||
|
// [
|
||||||
|
// CreateElement { id: 5, tag: "div" },
|
||||||
|
// CreateElement { id: 6, tag: "div" },
|
||||||
|
// InsertAfter { n: 2, root: 4 }
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyed_diffing_additions_and_moves_in_middle() {
|
||||||
|
let dom = TestDom::new();
|
||||||
|
|
||||||
|
let left = rsx!({
|
||||||
|
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
|
||||||
|
rsx! { div { key: "{f}" }}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let right = rsx!({
|
||||||
|
[/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
|
||||||
|
rsx! { div { key: "{f}" }}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let (_, change) = dom.lazy_diff(left, right);
|
||||||
|
dbg!(change);
|
||||||
|
// assert_eq!(
|
||||||
|
// change.edits,
|
||||||
|
// [
|
||||||
|
// CreateElement { id: 5, tag: "div" },
|
||||||
|
// CreateElement { id: 6, tag: "div" },
|
||||||
|
// InsertAfter { n: 2, root: 4 }
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user