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        // get parent and position
326        let current = cursor.current();
327        let parent = if position.get() == Position::FirstChild {
328            current
329        } else {
330            Rndr::get_parent(&current)
331                .expect("first child of keyed list has no parent")
332        };
333        let parent = crate::renderer::types::Element::cast_from(parent)
334            .expect("parent of keyed list should be an element");
335
336        // build list
337        let items = self.items.into_iter();
338        let (capacity, _) = items.size_hint();
339        let mut hashed_items =
340            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
341        let mut rendered_items = Vec::new();
342        for (index, item) in items.enumerate() {
343            hashed_items.insert((self.key_fn)(&item));
344            let (set_index, view) = (self.view_fn)(index, item);
345            let item = view.hydrate::<FROM_SERVER>(cursor, position);
346            rendered_items.push(Some((set_index, item)));
347        }
348        let marker = cursor.next_placeholder(position);
349        position.set(Position::NextChild);
350
351        KeyedState {
352            parent: Some(parent),
353            marker,
354            hashed_items,
355            rendered_items,
356        }
357    }
358
359    async fn hydrate_async(
360        self,
361        cursor: &Cursor,
362        position: &PositionState,
363    ) -> Self::State {
364        // get parent and position
365        let current = cursor.current();
366        let parent = if position.get() == Position::FirstChild {
367            current
368        } else {
369            Rndr::get_parent(&current)
370                .expect("first child of keyed list has no parent")
371        };
372        let parent = crate::renderer::types::Element::cast_from(parent)
373            .expect("parent of keyed list should be an element");
374
375        // build list
376        let items = self.items.into_iter();
377        let (capacity, _) = items.size_hint();
378        let mut hashed_items =
379            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
380        let mut rendered_items = Vec::new();
381        for (index, item) in items.enumerate() {
382            hashed_items.insert((self.key_fn)(&item));
383            let (set_index, view) = (self.view_fn)(index, item);
384            let item = view.hydrate_async(cursor, position).await;
385            rendered_items.push(Some((set_index, item)));
386        }
387        let marker = cursor.next_placeholder(position);
388        position.set(Position::NextChild);
389
390        KeyedState {
391            parent: Some(parent),
392            marker,
393            hashed_items,
394            rendered_items,
395        }
396    }
397
398    fn into_owned(self) -> Self::Owned {
399        self
400    }
401}
402
403impl<K, VFS, V> Mountable for KeyedState<K, VFS, V>
404where
405    K: Eq + Hash + 'static,
406    VFS: Fn(usize),
407    V: Render,
408{
409    fn mount(
410        &mut self,
411        parent: &crate::renderer::types::Element,
412        marker: Option<&crate::renderer::types::Node>,
413    ) {
414        self.parent = Some(parent.clone());
415        for (_, item) in self.rendered_items.iter_mut().flatten() {
416            item.mount(parent, marker);
417        }
418        self.marker.mount(parent, marker);
419    }
420
421    fn unmount(&mut self) {
422        for (_, item) in self.rendered_items.iter_mut().flatten() {
423            item.unmount();
424        }
425        self.marker.unmount();
426    }
427
428    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
429        self.rendered_items
430            .first()
431            .map(|item| {
432                if let Some((_, item)) = item {
433                    item.insert_before_this(child)
434                } else {
435                    false
436                }
437            })
438            .unwrap_or_else(|| self.marker.insert_before_this(child))
439    }
440
441    fn elements(&self) -> Vec<crate::renderer::types::Element> {
442        self.rendered_items
443            .iter()
444            .flatten()
445            .flat_map(|item| item.1.elements())
446            .collect()
447    }
448}
449
450trait VecExt<T> {
451    fn get_next_closest_mounted_sibling(
452        &self,
453        start_at: usize,
454    ) -> Option<&Option<T>>;
455}
456
457impl<T> VecExt<T> for Vec<Option<T>> {
458    fn get_next_closest_mounted_sibling(
459        &self,
460        start_at: usize,
461    ) -> Option<&Option<T>> {
462        self[start_at..].iter().find(|s| s.is_some())
463    }
464}
465
466/// Calculates the operations needed to get from `from` to `to`.
467fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
468    if from.is_empty() && to.is_empty() {
469        return Diff::default();
470    } else if to.is_empty() {
471        return Diff {
472            clear: true,
473            ..Default::default()
474        };
475    } else if from.is_empty() {
476        return Diff {
477            added: to
478                .iter()
479                .enumerate()
480                .map(|(at, _)| DiffOpAdd {
481                    at,
482                    mode: DiffOpAddMode::Append,
483                })
484                .collect(),
485            ..Default::default()
486        };
487    }
488
489    let mut removed = vec![];
490    let mut moved = vec![];
491    let mut added = vec![];
492    let max_len = std::cmp::max(from.len(), to.len());
493
494    for index in 0..max_len {
495        let from_item = from.get_index(index);
496        let to_item = to.get_index(index);
497
498        // if they're the same, do nothing
499        if from_item != to_item {
500            // if it's only in old, not new, remove it
501            if from_item.is_some() && !to.contains(from_item.unwrap()) {
502                let op = DiffOpRemove { at: index };
503                removed.push(op);
504            }
505            // if it's only in new, not old, add it
506            if to_item.is_some() && !from.contains(to_item.unwrap()) {
507                let op = DiffOpAdd {
508                    at: index,
509                    mode: DiffOpAddMode::Normal,
510                };
511                added.push(op);
512            }
513            // if it's in both old and new, it can either
514            // 1) be moved (and need to move in the DOM)
515            // 2) be moved (but not need to move in the DOM)
516            //    * this would happen if, for example, 2 items
517            //      have been added before it, and it has moved by 2
518            if let Some(from_item) = from_item {
519                if let Some(to_item) = to.get_full(from_item) {
520                    let moves_forward_by = (to_item.0 as i32) - (index as i32);
521                    let move_in_dom = moves_forward_by
522                        != (added.len() as i32) - (removed.len() as i32);
523
524                    let op = DiffOpMove {
525                        from: index,
526                        len: 1,
527                        to: to_item.0,
528                        move_in_dom,
529                    };
530                    moved.push(op);
531                }
532            }
533        }
534    }
535
536    moved = group_adjacent_moves(moved);
537
538    Diff {
539        removed,
540        items_to_move: moved.iter().map(|m| m.len).sum(),
541        moved,
542        added,
543        clear: false,
544    }
545}
546
547/// Group adjacent items that are being moved as a group.
548/// For example from `[2, 3, 5, 6]` to `[1, 2, 3, 4, 5, 6]` should result
549/// in a move for `2,3` and `5,6` rather than 4 individual moves.
550fn group_adjacent_moves(moved: Vec<DiffOpMove>) -> Vec<DiffOpMove> {
551    let mut prev: Option<DiffOpMove> = None;
552    let mut new_moved = Vec::with_capacity(moved.len());
553    for m in moved {
554        match prev {
555            Some(mut p) => {
556                if (m.from == p.from + p.len) && (m.to == p.to + p.len) {
557                    p.len += 1;
558                    prev = Some(p);
559                } else {
560                    new_moved.push(prev.take().unwrap());
561                    prev = Some(m);
562                }
563            }
564            None => prev = Some(m),
565        }
566    }
567    if let Some(prev) = prev {
568        new_moved.push(prev)
569    }
570    new_moved
571}
572
573#[derive(Debug, Default, PartialEq, Eq)]
574struct Diff {
575    removed: Vec<DiffOpRemove>,
576    moved: Vec<DiffOpMove>,
577    items_to_move: usize,
578    added: Vec<DiffOpAdd>,
579    clear: bool,
580}
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583struct DiffOpMove {
584    /// The index this range is starting relative to `from`.
585    from: usize,
586    /// The number of elements included in this range.
587    len: usize,
588    /// The starting index this range will be moved to relative to `to`.
589    to: usize,
590    /// Marks this move to be applied to the DOM, or just to the underlying
591    /// storage
592    move_in_dom: bool,
593}
594
595impl Default for DiffOpMove {
596    fn default() -> Self {
597        Self {
598            from: 0,
599            to: 0,
600            len: 1,
601            move_in_dom: true,
602        }
603    }
604}
605
606#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
607struct DiffOpAdd {
608    at: usize,
609    mode: DiffOpAddMode,
610}
611
612#[derive(Debug, PartialEq, Eq)]
613struct DiffOpRemove {
614    at: usize,
615}
616
617#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
618enum DiffOpAddMode {
619    #[default]
620    Normal,
621    Append,
622}
623
624fn apply_diff<T, VFS, V>(
625    parent: Option<&crate::renderer::types::Element>,
626    marker: &crate::renderer::types::Placeholder,
627    diff: Diff,
628    children: &mut Vec<Option<(VFS, V::State)>>,
629    view_fn: impl Fn(usize, T) -> (VFS, V),
630    mut items: Vec<Option<T>>,
631) where
632    VFS: Fn(usize),
633    V: Render,
634{
635    // The order of cmds needs to be:
636    // 1. Clear
637    // 2. Removals
638    // 3. Move out
639    // 4. Resize
640    // 5. Move in
641    // 6. Additions
642    // 7. Removes holes
643    if diff.clear {
644        for (_, mut child) in children.drain(0..).flatten() {
645            child.unmount();
646        }
647
648        if diff.added.is_empty() {
649            return;
650        }
651    }
652
653    for DiffOpRemove { at } in &diff.removed {
654        let (_, mut item_to_remove) = children[*at].take().unwrap();
655
656        item_to_remove.unmount();
657    }
658
659    let (move_cmds, add_cmds) = unpack_moves(&diff);
660
661    let mut moved_children = move_cmds
662        .iter()
663        .map(|move_| children[move_.from].take())
664        .collect::<Vec<_>>();
665
666    children.resize_with(children.len() + diff.added.len(), || None);
667
668    for (i, DiffOpMove { to, .. }) in move_cmds
669        .iter()
670        .enumerate()
671        .filter(|(_, move_)| !move_.move_in_dom)
672    {
673        children[*to] = moved_children[i]
674            .take()
675            .inspect(|(set_index, _)| set_index(*to));
676    }
677
678    for (i, DiffOpMove { to, .. }) in move_cmds
679        .into_iter()
680        .enumerate()
681        .filter(|(_, move_)| move_.move_in_dom)
682    {
683        let (set_index, mut each_item) = moved_children[i].take().unwrap();
684
685        if let Some(parent) = parent {
686            if let Some(Some((_, state))) =
687                children.get_next_closest_mounted_sibling(to)
688            {
689                state.insert_before_this_or_marker(
690                    parent,
691                    &mut each_item,
692                    Some(marker.as_ref()),
693                )
694            } else {
695                each_item.try_mount(parent, Some(marker.as_ref()));
696            }
697        }
698
699        set_index(to);
700        children[to] = Some((set_index, each_item));
701    }
702
703    for DiffOpAdd { at, mode } in add_cmds {
704        let item = items[at].take().unwrap();
705        let (set_index, item) = view_fn(at, item);
706        let mut item = item.build();
707
708        if let Some(parent) = parent {
709            match mode {
710                DiffOpAddMode::Normal => {
711                    if let Some(Some((_, state))) =
712                        children.get_next_closest_mounted_sibling(at)
713                    {
714                        state.insert_before_this_or_marker(
715                            parent,
716                            &mut item,
717                            Some(marker.as_ref()),
718                        )
719                    } else {
720                        item.try_mount(parent, Some(marker.as_ref()));
721                    }
722                }
723                DiffOpAddMode::Append => {
724                    item.try_mount(parent, Some(marker.as_ref()));
725                }
726            }
727        }
728
729        children[at] = Some((set_index, item));
730    }
731
732    #[allow(unstable_name_collisions)]
733    children.drain_filter(|c| c.is_none());
734}
735
736fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
737    let mut moves = Vec::with_capacity(diff.items_to_move);
738    let mut adds = Vec::with_capacity(diff.added.len());
739
740    let mut removes_iter = diff.removed.iter();
741    let mut adds_iter = diff.added.iter();
742    let mut moves_iter = diff.moved.iter();
743
744    let mut removes_next = removes_iter.next();
745    let mut adds_next = adds_iter.next();
746    let mut moves_next = moves_iter.next().copied();
747
748    for i in 0..diff.items_to_move + diff.added.len() + diff.removed.len() {
749        if let Some(DiffOpRemove { at, .. }) = removes_next {
750            if i == *at {
751                removes_next = removes_iter.next();
752
753                continue;
754            }
755        }
756
757        match (adds_next, &mut moves_next) {
758            (Some(add), Some(move_)) => {
759                if add.at == i {
760                    adds.push(*add);
761
762                    adds_next = adds_iter.next();
763                } else {
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            }
778            (Some(add), None) => {
779                adds.push(*add);
780
781                adds_next = adds_iter.next();
782            }
783            (None, Some(move_)) => {
784                let mut single_move = *move_;
785                single_move.len = 1;
786
787                moves.push(single_move);
788
789                move_.len -= 1;
790                move_.from += 1;
791                move_.to += 1;
792
793                if move_.len == 0 {
794                    moves_next = moves_iter.next().copied();
795                }
796            }
797            (None, None) => break,
798        }
799    }
800
801    (moves, adds)
802}
803/*
804#[cfg(test)]
805mod tests {
806    use crate::{
807        html::element::{li, ul, HtmlElement, Li},
808        renderer::mock_dom::MockDom,
809        view::{keyed::keyed, Render},
810    };
811
812    fn item(key: usize) -> HtmlElement<Li, (), String, MockDom> {
813        li((), key.to_string())
814    }
815
816    #[test]
817    fn keyed_creates_list() {
818        let el = ul((), keyed(1..=3, |k| *k, item));
819        let el_state = el.build();
820        assert_eq!(
821            el_state.el.to_debug_html(),
822            "<ul><li>1</li><li>2</li><li>3</li></ul>"
823        );
824    }
825
826    #[test]
827    fn adding_items_updates_list() {
828        let el = ul((), keyed(1..=3, |k| *k, item));
829        let mut el_state = el.build();
830        let el = ul((), keyed(1..=5, |k| *k, item));
831        el.rebuild(&mut el_state);
832        assert_eq!(
833            el_state.el.to_debug_html(),
834            "<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>"
835        );
836    }
837
838    #[test]
839    fn removing_items_updates_list() {
840        let el = ul((), keyed(1..=3, |k| *k, item));
841        let mut el_state = el.build();
842        let el = ul((), keyed(1..=2, |k| *k, item));
843        el.rebuild(&mut el_state);
844        assert_eq!(
845            el_state.el.to_debug_html(),
846            "<ul><li>1</li><li>2</li></ul>"
847        );
848    }
849
850    #[test]
851    fn swapping_items_updates_list() {
852        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
853        let mut el_state = el.build();
854        let el = ul((), keyed([1, 4, 3, 2, 5], |k| *k, item));
855        el.rebuild(&mut el_state);
856        assert_eq!(
857            el_state.el.to_debug_html(),
858            "<ul><li>1</li><li>4</li><li>3</li><li>2</li><li>5</li></ul>"
859        );
860    }
861
862    #[test]
863    fn swapping_and_removing_orders_correctly() {
864        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
865        let mut el_state = el.build();
866        let el = ul((), keyed([1, 4, 3, 5], |k| *k, item));
867        el.rebuild(&mut el_state);
868        assert_eq!(
869            el_state.el.to_debug_html(),
870            "<ul><li>1</li><li>4</li><li>3</li><li>5</li></ul>"
871        );
872    }
873
874    #[test]
875    fn arbitrarily_hard_adjustment() {
876        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
877        let mut el_state = el.build();
878        let el = ul((), keyed([2, 4, 3], |k| *k, item));
879        el.rebuild(&mut el_state);
880        assert_eq!(
881            el_state.el.to_debug_html(),
882            "<ul><li>2</li><li>4</li><li>3</li></ul>"
883        );
884    }
885
886    #[test]
887    fn a_series_of_moves() {
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([2, 4, 3], |k| *k, item));
891        el.rebuild(&mut el_state);
892        let el = ul((), keyed([1, 7, 5, 11, 13, 17], |k| *k, item));
893        el.rebuild(&mut el_state);
894        let el = ul((), keyed([2, 6, 8, 7, 13], |k| *k, item));
895        el.rebuild(&mut el_state);
896        let el = ul((), keyed([13, 4, 5, 3], |k| *k, item));
897        el.rebuild(&mut el_state);
898        let el = ul((), keyed([1, 2, 3, 4], |k| *k, item));
899        el.rebuild(&mut el_state);
900        assert_eq!(
901            el_state.el.to_debug_html(),
902            "<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>"
903        );
904    }
905
906    #[test]
907    fn clearing_works() {
908        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
909        let mut el_state = el.build();
910        let el = ul((), keyed([], |k| *k, item));
911        el.rebuild(&mut el_state);
912        assert_eq!(el_state.el.to_debug_html(), "<ul></ul>");
913    }
914}
915*/