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