//! This module contains fragments implementation.
use super::{Key, VDiff, VNode, VText};
use crate::html::{AnyScope, NodeRef};
use cfg_if::cfg_if;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::Element;
} else if #[cfg(feature = "web_sys")] {
use web_sys::Element;
}
}
/// This struct represents a fragment of the Virtual DOM tree.
#[derive(Clone, Debug, PartialEq, Default)]
pub struct VList {
/// The list of children nodes.
pub children: Vec<VNode>,
pub key: Option<Key>,
}
impl Deref for VList {
type Target = Vec<VNode>;
fn deref(&self) -> &Self::Target {
&self.children
}
}
impl DerefMut for VList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.children
}
}
impl VList {
/// Creates a new empty `VList` instance.
pub fn new() -> Self {
Self::default()
}
/// Creates a new `VList` instance with children.
pub fn new_with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
VList { children, key }
}
/// Add `VNode` child.
pub fn add_child(&mut self, child: VNode) {
self.children.push(child);
}
/// Add multiple `VNode` children.
pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
self.children.extend(children);
}
fn children_keys(&self, warn: bool) -> HashSet<Key> {
let mut hash_set = HashSet::with_capacity(self.children.len());
for l in self.children.iter() {
if let Some(k) = l.key() {
if !hash_set.insert(k.clone()) && warn {
log::warn!("Key '{}' is not unique in list but must be.", k);
}
}
}
hash_set
}
}
impl VDiff for VList {
fn detach(&mut self, parent: &Element) {
for mut child in self.children.drain(..) {
child.detach(parent);
}
}
fn apply(
&mut self,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
ancestor: Option<VNode>,
) -> NodeRef {
// Here, we will try to diff the previous list elements with the new
// ones we want to insert. For that, we will use two lists:
// - lefts: new elements to render in the DOM
// - rights: previously rendered elements.
//
// The left items are known since we want to insert them
// (self.children). For the right ones, we will look at the ancestor,
// i.e. the current DOM list element that we want to replace with self.
if self.children.is_empty() {
// Without a placeholder the next element becomes first
// and corrupts the order of rendering
// We use empty text element to stake out a place
let placeholder = VText::new("");
self.children.push(placeholder.into());
}
let left_keys = self.children_keys(true);
let lefts_keyed = left_keys.len() == self.children.len();
let right_keys = if let Some(VNode::VList(vlist)) = &ancestor {
vlist.children_keys(false)
} else {
HashSet::new()
};
let mut right_children = match ancestor {
// If the ancestor is also a VList, then the "right" list is the
// previously rendered items.
Some(VNode::VList(vlist)) => vlist.children,
// If the ancestor was not a VList, then the "right" list is a single node
Some(vnode) => vec![vnode],
None => vec![],
};
let rights_keyed = right_keys.len() == right_children.len();
// If the existing list and the new list are both properly keyed,
// then move existing list nodes into the new list's order before diffing
if lefts_keyed && rights_keyed {
// Find the intersection of keys to determine which right nodes can be reused
let matched_keys: HashSet<_> = left_keys.intersection(&right_keys).collect();
// Detach any right nodes that were not matched with a left node
right_children = right_children
.into_iter()
.filter_map(|mut right| {
if matched_keys.contains(right.key().as_ref().unwrap()) {
Some(right)
} else {
right.detach(parent);
None
}
})
.collect();
// Determine which rights are already in correct order and which
// rights need to be moved in the DOM before being reused
let mut rights_to_move = HashMap::with_capacity(right_children.len());
let mut matched_lefts = self
.children
.iter()
.filter(|left| matched_keys.contains(left.key().as_ref().unwrap()))
.peekable();
let mut left = matched_lefts.next();
// Note: `filter_map` is used to move rights into `rights_to_move`
#[allow(clippy::unnecessary_filter_map)]
let rights_in_place: Vec<_> = right_children
.into_iter()
.filter_map(|right| {
if right.key() == left.and_then(|l| l.key()) {
left = matched_lefts.next();
return Some(right);
} else if right.key() == matched_lefts.peek().and_then(|l| l.key()) {
matched_lefts.next();
left = matched_lefts.next();
return Some(right);
}
rights_to_move.insert(right.key().unwrap(), right);
None
})
.collect();
// Move rights into correct order and build `right_children`
right_children = Vec::with_capacity(matched_keys.len());
let mut matched_lefts = self
.children
.iter()
.filter(|left| matched_keys.contains(left.key().as_ref().unwrap()));
for right in rights_in_place.into_iter() {
let mut left = matched_lefts.next().unwrap();
while right.key() != left.key() {
let right_to_move = rights_to_move.remove(&left.key().unwrap()).unwrap();
right_to_move.move_before(parent, Some(right.first_node()));
right_children.push(right_to_move);
left = matched_lefts.next().unwrap();
}
right_children.push(right);
}
for left in matched_lefts {
let right_to_move = rights_to_move.remove(&left.key().unwrap()).unwrap();
right_to_move.move_before(parent, next_sibling.get());
right_children.push(right_to_move);
}
assert!(rights_to_move.is_empty())
}
let mut rights = right_children.into_iter().peekable();
let mut last_next_sibling = NodeRef::default();
let mut nodes: Vec<NodeRef> = self
.children
.iter_mut()
.map(|left| {
let ancestor = rights.next();
// Create a new `next_sibling` reference which points to the next `right` or
// the outer list's `next_sibling` if there are no more `rights`.
let new_next_sibling = NodeRef::default();
if let Some(next_right) = rights.peek() {
new_next_sibling.set(Some(next_right.first_node()));
} else {
new_next_sibling.link(next_sibling.clone());
}
// Update the next list item and then link the previous left's `next_sibling` to the
// returned `node` reference so that the previous left has an up-to-date `next_sibling`.
// This is important for rendering a `VComp` because each `VComp` keeps track of its
// `next_sibling` to properly render its children.
let node = left.apply(parent_scope, parent, new_next_sibling.clone(), ancestor);
last_next_sibling.link(node.clone());
last_next_sibling = new_next_sibling;
node
})
.collect();
// If there are more `rights` than `lefts`, we need to make sure to link the last left's `next_sibling`
// to the outer list's `next_sibling` so that it doesn't point at a `right` that is detached.
last_next_sibling.link(next_sibling);
// Detach all extra rights
for mut right in rights {
right.detach(parent);
}
assert!(!nodes.is_empty(), "VList should have at least one child");
nodes.swap_remove(0)
}
}
#[cfg(all(test, feature = "web_sys"))]
mod layout_tests {
extern crate self as yew;
use crate::html;
use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);
#[test]
fn diff() {
let layout1 = TestLayout {
name: "1",
node: html! {
<>
{"a"}
{"b"}
<>
{"c"}
{"d"}
</>
{"e"}
</>
},
expected: "abcde",
};
let layout2 = TestLayout {
name: "2",
node: html! {
<>
{"a"}
{"b"}
<></>
{"e"}
{"f"}
</>
},
expected: "abef",
};
let layout3 = TestLayout {
name: "3",
node: html! {
<>
{"a"}
<></>
{"b"}
{"e"}
</>
},
expected: "abe",
};
let layout4 = TestLayout {
name: "4",
node: html! {
<>
{"a"}
<>
{"c"}
{"d"}
</>
{"b"}
{"e"}
</>
},
expected: "acdbe",
};
diff_layouts(vec![layout1, layout2, layout3, layout4]);
}
}
#[cfg(all(test, feature = "web_sys"))]
mod layout_tests_keys {
extern crate self as yew;
use crate::html;
use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
use crate::virtual_dom::VNode;
use crate::{Children, Component, ComponentLink, Html, Properties, ShouldRender};
use web_sys::Node;
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);
struct Comp {
id: usize,
panic_if_changes: bool,
}
#[derive(Properties, Clone)]
struct CountingCompProps {
id: usize,
#[prop_or(false)]
can_change: bool,
}
impl Component for Comp {
type Message = ();
type Properties = CountingCompProps;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Comp {
id: props.id,
panic_if_changes: props.can_change,
}
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
#[cfg(feature = "wasm_test")]
wasm_bindgen_test::console_log!("Comp changed: {} -> {}", self.id, props.id);
let changed = self.id != props.id;
if self.panic_if_changes && changed {
panic!(
"VComp changed but should not have: {} -> {}.",
self.id, props.id
);
}
self.id = props.id;
changed
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!();
}
fn view(&self) -> Html {
html! { <p>{ self.id }</p> }
}
}
#[derive(Clone, Properties)]
pub struct ListProps {
pub children: Children,
}
pub struct List(ListProps);
impl Component for List {
type Message = ();
type Properties = ListProps;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Self(props)
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!();
}
fn change(&mut self, mut props: Self::Properties) -> ShouldRender {
std::mem::swap(&mut self.0, &mut props);
self.0.children != props.children
}
fn view(&self) -> Html {
html! { <>{ for self.0.children.iter() }</> }
}
}
#[test]
fn diff() {
let mut layouts = vec![];
let vref_node: Node = crate::utils::document().create_element("i").unwrap().into();
layouts.push(TestLayout {
name: "All VNode types as children",
node: html! {
<>
{"a"}
<span key="vtag"></span>
{"c"}
{"d"}
<Comp id=0 key="vchild" />
<key="vlist">
{"foo"}
{"bar"}
</>
{VNode::VRef(vref_node)}
</>
},
expected: "a<span></span>cd<p>0</p>foobar<i></i>",
});
layouts.extend(vec![
TestLayout {
name: "Inserting into VList first child - before",
node: html! {
<>
<key="VList">
<i key="i"></i>
</>
<p key="p"></p>
</>
},
expected: "<i></i><p></p>",
},
TestLayout {
name: "Inserting into VList first child - after",
node: html! {
<>
<key="VList">
<i key="i"></i>
<e key="e"></e>
</>
<p key="p"></p>
</>
},
expected: "<i></i><e></e><p></p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "No matches - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
</>
},
expected: "<i></i><e></e>",
},
TestLayout {
name: "No matches - after",
node: html! {
<>
<a key="a"></a>
<p key="p"></p>
</>
},
expected: "<a></a><p></p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Append - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
</>
},
expected: "<i></i><e></e>",
},
TestLayout {
name: "Append - after",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
</>
},
expected: "<i></i><e></e><p></p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Prepend - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
</>
},
expected: "<i></i><e></e>",
},
TestLayout {
name: "Prepend - after",
node: html! {
<>
<p key="p"></p>
<i key="i"></i>
<e key="e"></e>
</>
},
expected: "<p></p><i></i><e></e>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Delete first - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
</>
},
expected: "<i></i><e></e><p></p>",
},
TestLayout {
name: "Delete first - after",
node: html! {
<>
<e key="e"></e>
<p key="p"></p>
</>
},
expected: "<e></e><p></p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Delete last - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
</>
},
expected: "<i></i><e></e><p></p>",
},
TestLayout {
name: "Delete last - after",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
</>
},
expected: "<i></i><e></e>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Delete last and change node type - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
</>
},
expected: "<i></i><e></e><p></p>",
},
TestLayout {
name: "Delete last - after",
node: html! {
<>
<List key="i"><i/></List>
<List key="e"><e/></List>
<List key="a"><a/></List>
</>
},
expected: "<i></i><e></e><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Delete middle - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
<a key="a"></a>
</>
},
expected: "<i></i><e></e><p></p><a></a>",
},
TestLayout {
name: "Delete middle - after",
node: html! {
<>
<i key="i"></i>
<e key="e2"></e>
<p key="p2"></p>
<a key="a"></a>
</>
},
expected: "<i></i><e></e><p></p><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Delete middle and change node type - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
<a key="a"></a>
</>
},
expected: "<i></i><e></e><p></p><a></a>",
},
TestLayout {
name: "Delete middle and change node type- after",
node: html! {
<>
<List key="i2"><i/></List>
<e key="e"></e>
<List key="p"><p/></List>
<List key="a2"><a/></List>
</>
},
expected: "<i></i><e></e><p></p><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Reverse - before",
node: html! {
<>
<i key="i"></i>
<e key="e"></e>
<p key="p"></p>
<u key="u"></u>
</>
},
expected: "<i></i><e></e><p></p><u></u>",
},
TestLayout {
name: "Reverse - after",
node: html! {
<>
<u key="u"></u>
<p key="p"></p>
<e key="e"></e>
<i key="i"></i>
</>
},
expected: "<u></u><p></p><e></e><i></i>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Reverse and change node type - before",
node: html! {
<>
<i key="i"></i>
<key="i1"></>
<key="i2"></>
<key="i3"></>
<e key="e"></e>
<key="yo">
<p key="p"></p>
</>
<u key="u"></u>
</>
},
expected: "<i></i><e></e><p></p><u></u>",
},
TestLayout {
name: "Reverse and change node type - after",
node: html! {
<>
<List key="u"><u/></List>
<List key="p"><p/></List>
<List key="e"><e/></List>
<List key="i"><i/></List>
</>
},
expected: "<u></u><p></p><e></e><i></i>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap 1&2 - before",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap 1&2 - after",
node: html! {
<>
<e key="2"></e>
<i key="1"></i>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<e></e><i></i><p></p><a></a><u></u>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap 1&2 and change node type - before",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap 1&2 and change node type - after",
node: html! {
<>
<List key="2"><e/></List>
<List key="1"><i/></List>
<List key="3"><p/></List>
<List key="4"><a/></List>
<List key="5"><u/></List>
</>
},
expected: "<e></e><i></i><p></p><a></a><u></u>",
},
]);
layouts.extend(vec![
TestLayout {
name: "test - before",
node: html! {
<>
<key="1">
<e key="e"></e>
<p key="p"></p>
<a key="a"></a>
<u key="u"></u>
</>
<key="2">
<e key="e"></e>
<p key="p"></p>
<a key="a"></a>
<u key="u"></u>
</>
</>
},
expected: "<e></e><p></p><a></a><u></u><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap 4&5 - after",
node: html! {
<>
<e key="1"></e>
<key="2">
<p key="p"></p>
<i key="i"></i>
</>
</>
},
expected: "<e></e><p></p><i></i>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap 4&5 - before",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap 4&5 - after",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<u key="5"></u>
<a key="4"></a>
</>
},
expected: "<i></i><e></e><p></p><u></u><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap 1&5 - before",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap 1&5 - after",
node: html! {
<>
<u key="5"></u>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<i key="1"></i>
</>
},
expected: "<u></u><e></e><p></p><a></a><i></i>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Move 2 after 4 - before",
node: html! {
<>
<i key="1"></i>
<e key="2"></e>
<p key="3"></p>
<a key="4"></a>
<u key="5"></u>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Move 2 after 4 - after",
node: html! {
<>
<i key="1"></i>
<p key="3"></p>
<a key="4"></a>
<e key="2"></e>
<u key="5"></u>
</>
},
expected: "<i></i><p></p><a></a><e></e><u></u>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap lists - before",
node: html! {
<>
<key="1">
<i></i>
<e></e>
</>
<key="2">
<a></a>
<u></u>
</>
</>
},
expected: "<i></i><e></e><a></a><u></u>",
},
TestLayout {
name: "Swap lists - after",
node: html! {
<>
<key="2">
<a></a>
<u></u>
</>
<key="1">
<i></i>
<e></e>
</>
</>
},
expected: "<a></a><u></u><i></i><e></e>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Swap lists with in-between - before",
node: html! {
<>
<key="1">
<i></i>
<e></e>
</>
<p key="between"></p>
<key="2">
<a></a>
<u></u>
</>
</>
},
expected: "<i></i><e></e><p></p><a></a><u></u>",
},
TestLayout {
name: "Swap lists with in-between - after",
node: html! {
<>
<key="2">
<a></a>
<u></u>
</>
<p key="between"></p>
<key="1">
<i></i>
<e></e>
</>
</>
},
expected: "<a></a><u></u><p></p><i></i><e></e>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Insert VComp front - before",
node: html! {
<>
<u key=1></u>
<a key=2></a>
</>
},
expected: "<u></u><a></a>",
},
TestLayout {
name: "Insert VComp front - after",
node: html! {
<>
<Comp id=0 key="comp"/>
<u key=1></u>
<a key=2></a>
</>
},
expected: "<p>0</p><u></u><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Insert VComp middle - before",
node: html! {
<>
<u key=1></u>
<a key=2></a>
</>
},
expected: "<u></u><a></a>",
},
TestLayout {
name: "Insert VComp middle - after",
node: html! {
<>
<u key=1></u>
<Comp id=0 key="comp"/>
<a key=2></a>
</>
},
expected: "<u></u><p>0</p><a></a>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Insert VComp back - before",
node: html! {
<>
<u key=1></u>
<a key=2></a>
</>
},
expected: "<u></u><a></a>",
},
TestLayout {
name: "Insert VComp back - after",
node: html! {
<>
<u key=1></u>
<a key=2></a>
<Comp id=0 key="comp"/>
</>
},
expected: "<u></u><a></a><p>0</p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Reverse VComp children - before",
node: html! {
<>
<Comp id=1 key="comp-1"/>
<Comp id=2 key="comp-2"/>
<Comp id=3 key="comp-3"/>
</>
},
expected: "<p>1</p><p>2</p><p>3</p>",
},
TestLayout {
name: "Reverse VComp children - after",
node: html! {
<>
<Comp id=3 key="comp-3"/>
<Comp id=2 key="comp-2"/>
<Comp id=1 key="comp-1"/>
</>
},
expected: "<p>3</p><p>2</p><p>1</p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Reverse VComp children with children - before",
node: html! {
<>
<List key="comp-1"><p>{"11"}</p><p>{"12"}</p></List>
<List key="comp-2"><p>{"21"}</p><p>{"22"}</p></List>
<List key="comp-3"><p>{"31"}</p><p>{"32"}</p></List>
</>
},
expected: "<p>11</p><p>12</p><p>21</p><p>22</p><p>31</p><p>32</p>",
},
TestLayout {
name: "Reverse VComp children with children - after",
node: html! {
<>
<List key="comp-3"><p>{"31"}</p><p>{"32"}</p></List>
<List key="comp-2"><p>{"21"}</p><p>{"22"}</p></List>
<List key="comp-1"><p>{"11"}</p><p>{"12"}</p></List>
</>
},
expected: "<p>31</p><p>32</p><p>21</p><p>22</p><p>11</p><p>12</p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Complex component update - before",
node: html! {
<List>
<Comp id=1 key="comp-1"/>
<Comp id=2 key="comp-2"/>
</List>
},
expected: "<p>1</p><p>2</p>",
},
TestLayout {
name: "Complex component update - after",
node: html! {
<List>
<List key="comp-1">
<Comp id=1 />
</List>
<List key="comp-2">
<p>{"2"}</p>
</List>
</List>
},
expected: "<p>1</p><p>2</p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Reorder VComp children with children - before",
node: html! {
<>
<List key="comp-1"><p>{"1"}</p></List>
<List key="comp-3"><p>{"3"}</p></List>
<List key="comp-5"><p>{"5"}</p></List>
<List key="comp-2"><p>{"2"}</p></List>
<List key="comp-4"><p>{"4"}</p></List>
<List key="comp-6"><p>{"6"}</p></List>
</>
},
expected: "<p>1</p><p>3</p><p>5</p><p>2</p><p>4</p><p>6</p>",
},
TestLayout {
name: "Reorder VComp children with children - after",
node: html! {
<>
<Comp id=6 key="comp-6"/>
<Comp id=5 key="comp-5"/>
<Comp id=4 key="comp-4"/>
<Comp id=3 key="comp-3"/>
<Comp id=2 key="comp-2"/>
<Comp id=1 key="comp-1"/>
</>
},
expected: "<p>6</p><p>5</p><p>4</p><p>3</p><p>2</p><p>1</p>",
},
]);
layouts.extend(vec![
TestLayout {
name: "Replace and reorder components - before",
node: html! {
<List>
<List key="comp-1"><p>{"1"}</p></List>
<List key="comp-2"><p>{"2"}</p></List>
<List key="comp-3"><p>{"3"}</p></List>
</List>
},
expected: "<p>1</p><p>2</p><p>3</p>",
},
TestLayout {
name: "Replace and reorder components - after",
node: html! {
<List>
<Comp id=3 key="comp-3" />
<Comp id=2 key="comp-2" />
<Comp id=1 key="comp-1" />
</List>
},
expected: "<p>3</p><p>2</p><p>1</p>",
},
]);
diff_layouts(layouts);
}
}