tachys/view/
keyed.rs

1use super::{
2    add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState,
3    Render, RenderHtml,
4};
5use crate::{
6    html::attribute::{any_attribute::AnyAttribute, Attribute},
7    hydration::Cursor,
8    renderer::{CastFrom, Rndr},
9    ssr::StreamBuilder,
10};
11use drain_filter_polyfill::VecExt as VecDrainFilterExt;
12use indexmap::IndexSet;
13use rustc_hash::FxHasher;
14use std::hash::{BuildHasherDefault, Hash};
15
16type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
17
18/// Creates a keyed list of views.
19pub fn keyed<T, I, K, KF, VF, VFS, V>(
20    items: I,
21    key_fn: KF,
22    view_fn: VF,
23) -> Keyed<T, I, K, KF, VF, VFS, V>
24where
25    I: IntoIterator<Item = T>,
26    K: Eq + Hash + 'static,
27    KF: Fn(&T) -> K,
28    V: Render,
29    VF: Fn(usize, T) -> (VFS, V),
30    VFS: Fn(usize),
31{
32    Keyed {
33        items,
34        key_fn,
35        view_fn,
36    }
37}
38
39/// A keyed list of views.
40pub struct Keyed<T, I, K, KF, VF, VFS, V>
41where
42    I: IntoIterator<Item = T>,
43    K: Eq + Hash + 'static,
44    KF: Fn(&T) -> K,
45    VF: Fn(usize, T) -> (VFS, V),
46    VFS: Fn(usize),
47{
48    items: I,
49    key_fn: KF,
50    view_fn: VF,
51}
52
53/// By default, keys used in for keyed iteration do not need to be serializable.
54///
55/// However, for some scenarios (like the “islands routing” mode that mixes server-side
56/// rendering with client-side navigation) it is useful to have serializable keys.
57///
58/// When the `islands` feature is not enabled, this trait is implemented by all types.
59///
60/// When the `islands` features is enabled, this is automatically implemented for all types
61/// that implement [`Serialize`](serde::Serialize), and can be manually implemented otherwise.
62pub trait SerializableKey {
63    /// Serializes the key to a unique string.
64    ///
65    /// The string can have any value, as long as it is idempotent (i.e., serializing the same key
66    /// multiple times will give the same value).
67    fn ser_key(&self) -> String;
68}
69
70#[cfg(not(feature = "islands"))]
71impl<T> SerializableKey for T {
72    fn ser_key(&self) -> String {
73        panic!(
74            "SerializableKey called without the `islands` feature enabled. \
75             Something has gone wrong."
76        );
77    }
78}
79#[cfg(feature = "islands")]
80impl<T: serde::Serialize> SerializableKey for T {
81    fn ser_key(&self) -> String {
82        serde_json::to_string(self).expect("failed to serialize key")
83    }
84}
85
86/// Retained view state for a keyed list.
87pub struct KeyedState<K, VFS, V>
88where
89    K: Eq + Hash + 'static,
90    VFS: Fn(usize),
91    V: Render,
92{
93    parent: Option<crate::renderer::types::Element>,
94    marker: crate::renderer::types::Placeholder,
95    hashed_items: IndexSet<K, BuildHasherDefault<FxHasher>>,
96    rendered_items: Vec<Option<(VFS, V::State)>>,
97}
98
99impl<T, I, K, KF, VF, VFS, V> Render for Keyed<T, I, K, KF, VF, VFS, V>
100where
101    I: IntoIterator<Item = T>,
102    K: Eq + Hash + SerializableKey + 'static,
103    KF: Fn(&T) -> K,
104    V: Render,
105    VF: Fn(usize, T) -> (VFS, V),
106    VFS: Fn(usize),
107{
108    type State = KeyedState<K, VFS, V>;
109    // TODO fallible state and try_build()/try_rebuild() here
110
111    fn build(self) -> Self::State {
112        let items = self.items.into_iter();
113        let (capacity, _) = items.size_hint();
114        let mut hashed_items =
115            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
116        let mut rendered_items = Vec::new();
117        for (index, item) in items.enumerate() {
118            hashed_items.insert((self.key_fn)(&item));
119            let (set_index, view) = (self.view_fn)(index, item);
120            rendered_items.push(Some((set_index, view.build())));
121        }
122        KeyedState {
123            parent: None,
124            marker: Rndr::create_placeholder(),
125            hashed_items,
126            rendered_items,
127        }
128    }
129
130    fn rebuild(self, state: &mut Self::State) {
131        let KeyedState {
132            parent,
133            marker,
134            hashed_items,
135            ref mut rendered_items,
136        } = state;
137        let new_items = self.items.into_iter();
138        let (capacity, _) = new_items.size_hint();
139        let mut new_hashed_items =
140            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
141
142        let mut items = Vec::new();
143        for item in new_items {
144            new_hashed_items.insert((self.key_fn)(&item));
145            items.push(Some(item));
146        }
147
148        let cmds = diff(hashed_items, &new_hashed_items);
149
150        apply_diff(
151            parent.as_ref(),
152            marker,
153            cmds,
154            rendered_items,
155            &self.view_fn,
156            items,
157        );
158
159        *hashed_items = new_hashed_items;
160    }
161}
162
163impl<T, I, K, KF, VF, VFS, V> AddAnyAttr for Keyed<T, I, K, KF, VF, VFS, V>
164where
165    I: IntoIterator<Item = T> + Send + 'static,
166    K: Eq + Hash + SerializableKey + 'static,
167    KF: Fn(&T) -> K + Send + 'static,
168    V: RenderHtml,
169    V: 'static,
170    VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
171    VFS: Fn(usize) + 'static,
172    T: 'static,
173{
174    type Output<SomeNewAttr: Attribute> = Keyed<
175        T,
176        I,
177        K,
178        KF,
179        Box<
180            dyn Fn(
181                    usize,
182                    T,
183                ) -> (
184                    VFS,
185                    <V as AddAnyAttr>::Output<SomeNewAttr::CloneableOwned>,
186                ) + Send,
187        >,
188        VFS,
189        V::Output<SomeNewAttr::CloneableOwned>,
190    >;
191
192    fn add_any_attr<NewAttr: Attribute>(
193        self,
194        attr: NewAttr,
195    ) -> Self::Output<NewAttr>
196    where
197        Self::Output<NewAttr>: RenderHtml,
198    {
199        let Keyed {
200            items,
201            key_fn,
202            view_fn,
203        } = self;
204        let attr = attr.into_cloneable_owned();
205        Keyed {
206            items,
207            key_fn,
208            view_fn: Box::new(move |index, item| {
209                let (index, view) = view_fn(index, item);
210                (index, view.add_any_attr(attr.clone()))
211            }),
212        }
213    }
214}
215
216impl<T, I, K, KF, VF, VFS, V> RenderHtml for Keyed<T, I, K, KF, VF, VFS, V>
217where
218    I: IntoIterator<Item = T> + Send + 'static,
219    K: Eq + Hash + SerializableKey + 'static,
220    KF: Fn(&T) -> K + Send + 'static,
221    V: RenderHtml + 'static,
222    VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
223    VFS: Fn(usize) + 'static,
224    T: 'static,
225{
226    type AsyncOutput = Vec<V::AsyncOutput>; // TODO
227    type Owned = Self;
228
229    const MIN_LENGTH: usize = 0;
230
231    fn dry_resolve(&mut self) {
232        // TODO...
233    }
234
235    async fn resolve(self) -> Self::AsyncOutput {
236        futures::future::join_all(self.items.into_iter().enumerate().map(
237            |(index, item)| {
238                let (_, view) = (self.view_fn)(index, item);
239                view.resolve()
240            },
241        ))
242        .await
243        .into_iter()
244        .collect::<Vec<_>>()
245    }
246
247    fn to_html_with_buf(
248        self,
249        buf: &mut String,
250        position: &mut Position,
251        escape: bool,
252        mark_branches: bool,
253        extra_attrs: Vec<AnyAttribute>,
254    ) {
255        if mark_branches && escape {
256            buf.open_branch("for");
257        }
258        for (index, item) in self.items.into_iter().enumerate() {
259            let (_, item) = (self.view_fn)(index, item);
260            if mark_branches && escape {
261                buf.open_branch("item");
262            }
263            item.to_html_with_buf(
264                buf,
265                position,
266                escape,
267                mark_branches,
268                extra_attrs.clone(),
269            );
270            if mark_branches && escape {
271                buf.close_branch("item");
272            }
273            *position = Position::NextChild;
274        }
275        if mark_branches && escape {
276            buf.close_branch("for");
277        }
278        buf.push_str("<!>");
279    }
280
281    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
282        self,
283        buf: &mut StreamBuilder,
284        position: &mut Position,
285        escape: bool,
286        mark_branches: bool,
287        extra_attrs: Vec<AnyAttribute>,
288    ) {
289        if mark_branches && escape {
290            buf.open_branch("for");
291        }
292        for (index, item) in self.items.into_iter().enumerate() {
293            let branch_name = mark_branches.then(|| {
294                let key = (self.key_fn)(&item);
295                let key = key.ser_key();
296                format!("item-{key}")
297            });
298            let (_, item) = (self.view_fn)(index, item);
299            if mark_branches && escape {
300                buf.open_branch(branch_name.as_ref().unwrap());
301            }
302            item.to_html_async_with_buf::<OUT_OF_ORDER>(
303                buf,
304                position,
305                escape,
306                mark_branches,
307                extra_attrs.clone(),
308            );
309            if mark_branches && escape {
310                buf.close_branch(branch_name.as_ref().unwrap());
311            }
312            *position = Position::NextChild;
313        }
314        if mark_branches && escape {
315            buf.close_branch("for");
316        }
317        buf.push_sync("<!>");
318    }
319
320    fn hydrate<const FROM_SERVER: bool>(
321        self,
322        cursor: &Cursor,
323        position: &PositionState,
324    ) -> Self::State {
325        if cfg!(feature = "mark_branches") {
326            cursor.advance_to_placeholder(position);
327        }
328
329        // get parent and position
330        let current = cursor.current();
331        let parent = if position.get() == Position::FirstChild {
332            current
333        } else {
334            Rndr::get_parent(&current)
335                .expect("first child of keyed list has no parent")
336        };
337        let parent = crate::renderer::types::Element::cast_from(parent)
338            .expect("parent of keyed list should be an element");
339
340        // build list
341        let items = self.items.into_iter();
342        let (capacity, _) = items.size_hint();
343        let mut hashed_items =
344            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
345        let mut rendered_items = Vec::new();
346        for (index, item) in items.enumerate() {
347            hashed_items.insert((self.key_fn)(&item));
348            let (set_index, view) = (self.view_fn)(index, item);
349            if cfg!(feature = "mark_branches") {
350                cursor.advance_to_placeholder(position);
351            }
352            let item = view.hydrate::<FROM_SERVER>(cursor, position);
353            if cfg!(feature = "mark_branches") {
354                cursor.advance_to_placeholder(position);
355            }
356            rendered_items.push(Some((set_index, item)));
357        }
358        let marker = cursor.next_placeholder(position);
359        position.set(Position::NextChild);
360
361        if cfg!(feature = "mark_branches") {
362            cursor.advance_to_placeholder(position);
363        }
364
365        KeyedState {
366            parent: Some(parent),
367            marker,
368            hashed_items,
369            rendered_items,
370        }
371    }
372
373    fn into_owned(self) -> Self::Owned {
374        self
375    }
376}
377
378impl<K, VFS, V> Mountable for KeyedState<K, VFS, V>
379where
380    K: Eq + Hash + 'static,
381    VFS: Fn(usize),
382    V: Render,
383{
384    fn mount(
385        &mut self,
386        parent: &crate::renderer::types::Element,
387        marker: Option<&crate::renderer::types::Node>,
388    ) {
389        self.parent = Some(parent.clone());
390        for (_, item) in self.rendered_items.iter_mut().flatten() {
391            item.mount(parent, marker);
392        }
393        self.marker.mount(parent, marker);
394    }
395
396    fn unmount(&mut self) {
397        for (_, item) in self.rendered_items.iter_mut().flatten() {
398            item.unmount();
399        }
400        self.marker.unmount();
401    }
402
403    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
404        self.rendered_items
405            .first()
406            .map(|item| {
407                if let Some((_, item)) = item {
408                    item.insert_before_this(child)
409                } else {
410                    false
411                }
412            })
413            .unwrap_or_else(|| self.marker.insert_before_this(child))
414    }
415
416    fn elements(&self) -> Vec<crate::renderer::types::Element> {
417        self.rendered_items
418            .iter()
419            .flatten()
420            .flat_map(|item| item.1.elements())
421            .collect()
422    }
423}
424
425trait VecExt<T> {
426    fn get_next_closest_mounted_sibling(
427        &self,
428        start_at: usize,
429    ) -> Option<&Option<T>>;
430}
431
432impl<T> VecExt<T> for Vec<Option<T>> {
433    fn get_next_closest_mounted_sibling(
434        &self,
435        start_at: usize,
436    ) -> Option<&Option<T>> {
437        self[start_at..].iter().find(|s| s.is_some())
438    }
439}
440
441/// Calculates the operations needed to get from `from` to `to`.
442fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
443    if from.is_empty() && to.is_empty() {
444        return Diff::default();
445    } else if to.is_empty() {
446        return Diff {
447            clear: true,
448            ..Default::default()
449        };
450    } else if from.is_empty() {
451        return Diff {
452            added: to
453                .iter()
454                .enumerate()
455                .map(|(at, _)| DiffOpAdd {
456                    at,
457                    mode: DiffOpAddMode::Append,
458                })
459                .collect(),
460            ..Default::default()
461        };
462    }
463
464    let mut removed = vec![];
465    let mut moved = vec![];
466    let mut added = vec![];
467    let max_len = std::cmp::max(from.len(), to.len());
468
469    for index in 0..max_len {
470        let from_item = from.get_index(index);
471        let to_item = to.get_index(index);
472
473        // if they're the same, do nothing
474        if from_item != to_item {
475            // if it's only in old, not new, remove it
476            if from_item.is_some() && !to.contains(from_item.unwrap()) {
477                let op = DiffOpRemove { at: index };
478                removed.push(op);
479            }
480            // if it's only in new, not old, add it
481            if to_item.is_some() && !from.contains(to_item.unwrap()) {
482                let op = DiffOpAdd {
483                    at: index,
484                    mode: DiffOpAddMode::Normal,
485                };
486                added.push(op);
487            }
488            // if it's in both old and new, it can either
489            // 1) be moved (and need to move in the DOM)
490            // 2) be moved (but not need to move in the DOM)
491            //    * this would happen if, for example, 2 items
492            //      have been added before it, and it has moved by 2
493            if let Some(from_item) = from_item {
494                if let Some(to_item) = to.get_full(from_item) {
495                    let moves_forward_by = (to_item.0 as i32) - (index as i32);
496                    let move_in_dom = moves_forward_by
497                        != (added.len() as i32) - (removed.len() as i32);
498
499                    let op = DiffOpMove {
500                        from: index,
501                        len: 1,
502                        to: to_item.0,
503                        move_in_dom,
504                    };
505                    moved.push(op);
506                }
507            }
508        }
509    }
510
511    moved = group_adjacent_moves(moved);
512
513    Diff {
514        removed,
515        items_to_move: moved.iter().map(|m| m.len).sum(),
516        moved,
517        added,
518        clear: false,
519    }
520}
521
522/// Group adjacent items that are being moved as a group.
523/// For example from `[2, 3, 5, 6]` to `[1, 2, 3, 4, 5, 6]` should result
524/// in a move for `2,3` and `5,6` rather than 4 individual moves.
525fn group_adjacent_moves(moved: Vec<DiffOpMove>) -> Vec<DiffOpMove> {
526    let mut prev: Option<DiffOpMove> = None;
527    let mut new_moved = Vec::with_capacity(moved.len());
528    for m in moved {
529        match prev {
530            Some(mut p) => {
531                if (m.from == p.from + p.len) && (m.to == p.to + p.len) {
532                    p.len += 1;
533                    prev = Some(p);
534                } else {
535                    new_moved.push(prev.take().unwrap());
536                    prev = Some(m);
537                }
538            }
539            None => prev = Some(m),
540        }
541    }
542    if let Some(prev) = prev {
543        new_moved.push(prev)
544    }
545    new_moved
546}
547
548#[derive(Debug, Default, PartialEq, Eq)]
549struct Diff {
550    removed: Vec<DiffOpRemove>,
551    moved: Vec<DiffOpMove>,
552    items_to_move: usize,
553    added: Vec<DiffOpAdd>,
554    clear: bool,
555}
556
557#[derive(Clone, Copy, Debug, PartialEq, Eq)]
558struct DiffOpMove {
559    /// The index this range is starting relative to `from`.
560    from: usize,
561    /// The number of elements included in this range.
562    len: usize,
563    /// The starting index this range will be moved to relative to `to`.
564    to: usize,
565    /// Marks this move to be applied to the DOM, or just to the underlying
566    /// storage
567    move_in_dom: bool,
568}
569
570impl Default for DiffOpMove {
571    fn default() -> Self {
572        Self {
573            from: 0,
574            to: 0,
575            len: 1,
576            move_in_dom: true,
577        }
578    }
579}
580
581#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
582struct DiffOpAdd {
583    at: usize,
584    mode: DiffOpAddMode,
585}
586
587#[derive(Debug, PartialEq, Eq)]
588struct DiffOpRemove {
589    at: usize,
590}
591
592#[derive(Clone, Copy, Debug, PartialEq, Eq)]
593enum DiffOpAddMode {
594    Normal,
595    Append,
596}
597
598impl Default for DiffOpAddMode {
599    fn default() -> Self {
600        Self::Normal
601    }
602}
603
604fn apply_diff<T, VFS, V>(
605    parent: Option<&crate::renderer::types::Element>,
606    marker: &crate::renderer::types::Placeholder,
607    diff: Diff,
608    children: &mut Vec<Option<(VFS, V::State)>>,
609    view_fn: impl Fn(usize, T) -> (VFS, V),
610    mut items: Vec<Option<T>>,
611) where
612    VFS: Fn(usize),
613    V: Render,
614{
615    // The order of cmds needs to be:
616    // 1. Clear
617    // 2. Removals
618    // 3. Move out
619    // 4. Resize
620    // 5. Move in
621    // 6. Additions
622    // 7. Removes holes
623    if diff.clear {
624        for (_, mut child) in children.drain(0..).flatten() {
625            child.unmount();
626        }
627
628        if diff.added.is_empty() {
629            return;
630        }
631    }
632
633    for DiffOpRemove { at } in &diff.removed {
634        let (_, mut item_to_remove) = children[*at].take().unwrap();
635
636        item_to_remove.unmount();
637    }
638
639    let (move_cmds, add_cmds) = unpack_moves(&diff);
640
641    let mut moved_children = move_cmds
642        .iter()
643        .map(|move_| children[move_.from].take())
644        .collect::<Vec<_>>();
645
646    children.resize_with(children.len() + diff.added.len(), || None);
647
648    for (i, DiffOpMove { to, .. }) in move_cmds
649        .iter()
650        .enumerate()
651        .filter(|(_, move_)| !move_.move_in_dom)
652    {
653        children[*to] = moved_children[i]
654            .take()
655            .inspect(|(set_index, _)| set_index(*to));
656    }
657
658    for (i, DiffOpMove { to, .. }) in move_cmds
659        .into_iter()
660        .enumerate()
661        .filter(|(_, move_)| move_.move_in_dom)
662    {
663        let (set_index, mut each_item) = moved_children[i].take().unwrap();
664
665        if let Some(parent) = parent {
666            if let Some(Some((_, state))) =
667                children.get_next_closest_mounted_sibling(to)
668            {
669                state.insert_before_this_or_marker(
670                    parent,
671                    &mut each_item,
672                    Some(marker.as_ref()),
673                )
674            } else {
675                each_item.mount(parent, Some(marker.as_ref()));
676            }
677        }
678
679        set_index(to);
680        children[to] = Some((set_index, each_item));
681    }
682
683    for DiffOpAdd { at, mode } in add_cmds {
684        let item = items[at].take().unwrap();
685        let (set_index, item) = view_fn(at, item);
686        let mut item = item.build();
687
688        if let Some(parent) = parent {
689            match mode {
690                DiffOpAddMode::Normal => {
691                    if let Some(Some((_, state))) =
692                        children.get_next_closest_mounted_sibling(at)
693                    {
694                        state.insert_before_this_or_marker(
695                            parent,
696                            &mut item,
697                            Some(marker.as_ref()),
698                        )
699                    } else {
700                        item.mount(parent, Some(marker.as_ref()));
701                    }
702                }
703                DiffOpAddMode::Append => {
704                    item.mount(parent, Some(marker.as_ref()));
705                }
706            }
707        }
708
709        children[at] = Some((set_index, item));
710    }
711
712    #[allow(unstable_name_collisions)]
713    children.drain_filter(|c| c.is_none());
714}
715
716fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
717    let mut moves = Vec::with_capacity(diff.items_to_move);
718    let mut adds = Vec::with_capacity(diff.added.len());
719
720    let mut removes_iter = diff.removed.iter();
721    let mut adds_iter = diff.added.iter();
722    let mut moves_iter = diff.moved.iter();
723
724    let mut removes_next = removes_iter.next();
725    let mut adds_next = adds_iter.next();
726    let mut moves_next = moves_iter.next().copied();
727
728    for i in 0..diff.items_to_move + diff.added.len() + diff.removed.len() {
729        if let Some(DiffOpRemove { at, .. }) = removes_next {
730            if i == *at {
731                removes_next = removes_iter.next();
732
733                continue;
734            }
735        }
736
737        match (adds_next, &mut moves_next) {
738            (Some(add), Some(move_)) => {
739                if add.at == i {
740                    adds.push(*add);
741
742                    adds_next = adds_iter.next();
743                } else {
744                    let mut single_move = *move_;
745                    single_move.len = 1;
746
747                    moves.push(single_move);
748
749                    move_.len -= 1;
750                    move_.from += 1;
751                    move_.to += 1;
752
753                    if move_.len == 0 {
754                        moves_next = moves_iter.next().copied();
755                    }
756                }
757            }
758            (Some(add), None) => {
759                adds.push(*add);
760
761                adds_next = adds_iter.next();
762            }
763            (None, Some(move_)) => {
764                let mut single_move = *move_;
765                single_move.len = 1;
766
767                moves.push(single_move);
768
769                move_.len -= 1;
770                move_.from += 1;
771                move_.to += 1;
772
773                if move_.len == 0 {
774                    moves_next = moves_iter.next().copied();
775                }
776            }
777            (None, None) => break,
778        }
779    }
780
781    (moves, adds)
782}
783/*
784#[cfg(test)]
785mod tests {
786    use crate::{
787        html::element::{li, ul, HtmlElement, Li},
788        renderer::mock_dom::MockDom,
789        view::{keyed::keyed, Render},
790    };
791
792    fn item(key: usize) -> HtmlElement<Li, (), String, MockDom> {
793        li((), key.to_string())
794    }
795
796    #[test]
797    fn keyed_creates_list() {
798        let el = ul((), keyed(1..=3, |k| *k, item));
799        let el_state = el.build();
800        assert_eq!(
801            el_state.el.to_debug_html(),
802            "<ul><li>1</li><li>2</li><li>3</li></ul>"
803        );
804    }
805
806    #[test]
807    fn adding_items_updates_list() {
808        let el = ul((), keyed(1..=3, |k| *k, item));
809        let mut el_state = el.build();
810        let el = ul((), keyed(1..=5, |k| *k, item));
811        el.rebuild(&mut el_state);
812        assert_eq!(
813            el_state.el.to_debug_html(),
814            "<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>"
815        );
816    }
817
818    #[test]
819    fn removing_items_updates_list() {
820        let el = ul((), keyed(1..=3, |k| *k, item));
821        let mut el_state = el.build();
822        let el = ul((), keyed(1..=2, |k| *k, item));
823        el.rebuild(&mut el_state);
824        assert_eq!(
825            el_state.el.to_debug_html(),
826            "<ul><li>1</li><li>2</li></ul>"
827        );
828    }
829
830    #[test]
831    fn swapping_items_updates_list() {
832        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
833        let mut el_state = el.build();
834        let el = ul((), keyed([1, 4, 3, 2, 5], |k| *k, item));
835        el.rebuild(&mut el_state);
836        assert_eq!(
837            el_state.el.to_debug_html(),
838            "<ul><li>1</li><li>4</li><li>3</li><li>2</li><li>5</li></ul>"
839        );
840    }
841
842    #[test]
843    fn swapping_and_removing_orders_correctly() {
844        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
845        let mut el_state = el.build();
846        let el = ul((), keyed([1, 4, 3, 5], |k| *k, item));
847        el.rebuild(&mut el_state);
848        assert_eq!(
849            el_state.el.to_debug_html(),
850            "<ul><li>1</li><li>4</li><li>3</li><li>5</li></ul>"
851        );
852    }
853
854    #[test]
855    fn arbitrarily_hard_adjustment() {
856        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
857        let mut el_state = el.build();
858        let el = ul((), keyed([2, 4, 3], |k| *k, item));
859        el.rebuild(&mut el_state);
860        assert_eq!(
861            el_state.el.to_debug_html(),
862            "<ul><li>2</li><li>4</li><li>3</li></ul>"
863        );
864    }
865
866    #[test]
867    fn a_series_of_moves() {
868        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
869        let mut el_state = el.build();
870        let el = ul((), keyed([2, 4, 3], |k| *k, item));
871        el.rebuild(&mut el_state);
872        let el = ul((), keyed([1, 7, 5, 11, 13, 17], |k| *k, item));
873        el.rebuild(&mut el_state);
874        let el = ul((), keyed([2, 6, 8, 7, 13], |k| *k, item));
875        el.rebuild(&mut el_state);
876        let el = ul((), keyed([13, 4, 5, 3], |k| *k, item));
877        el.rebuild(&mut el_state);
878        let el = ul((), keyed([1, 2, 3, 4], |k| *k, item));
879        el.rebuild(&mut el_state);
880        assert_eq!(
881            el_state.el.to_debug_html(),
882            "<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>"
883        );
884    }
885
886    #[test]
887    fn clearing_works() {
888        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
889        let mut el_state = el.build();
890        let el = ul((), keyed([], |k| *k, item));
891        el.rebuild(&mut el_state);
892        assert_eq!(el_state.el.to_debug_html(), "<ul></ul>");
893    }
894}
895*/