Skip to main content

ferroui_base/collections/
ferro_list.rs

1use super::IFerroListItemValidator;
2use crate::data::model::{CollectionChange, Event, INotifyCollectionChanged, INotifyPropertyChanged};
3pub use ferroui_system::collections::specialized::{
4    NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, SharedNotifyCollectionChangedEventArgs,
5};
6use crate::utilities::{HandlerList, WeakEventSender};
7use std::cell::{Cell, RefCell};
8use std::rc::{Rc, Weak};
9
10/// Describes the action notified on a clear of a [`FerroList`].
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum ResetBehavior {
13    /// Clearing the list notifies a with a [`NotifyCollectionChangedAction::Reset`].
14    #[default]
15    Reset,
16    /// Clearing the list notifies a with a [`NotifyCollectionChangedAction::Remove`].
17    Remove,
18}
19
20/// The signature of a collection changed handler.
21pub type CollectionChangedHandler<T> = dyn for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, T>);
22
23/// A notifying list.
24///
25/// Enumeration works on a snapshot, so the list may be modified from within
26/// an enumeration or a change notification. The snapshot shares storage with
27/// the list and is only copied if the list is modified while it is alive.
28///
29/// The list is a reference object: the value is a shared handle, clones
30/// refer to the same list and handles compare by identity.
31pub struct FerroList<T>(Rc<FerroListData<T>>);
32
33struct FerroListData<T> {
34    items: RefCell<Rc<Vec<T>>>,
35    collection_changed: HandlerList<CollectionChangedHandler<T>>,
36    /// The untyped form of the collection changed event
37    /// ([`INotifyCollectionChanged`]). Its handlers share their tokens with
38    /// the typed ones, so that one change runs all of them in subscription
39    /// order, as the single event of the original.
40    untyped_collection_changed: Event<CollectionChange>,
41    property_changed: Event<str>,
42    validator: RefCell<Option<ListValidator<T>>>,
43    reset_behavior: Cell<ResetBehavior>,
44}
45
46/// The validator of a list: one set through
47/// [`set_validate`](FerroList::set_validate), whose routine can be read back
48/// and replaced, or any other.
49enum ListValidator<T> {
50    Item(Rc<ItemValidator<T>>),
51    Other(Rc<dyn IFerroListItemValidator<T>>),
52}
53
54impl<T> Clone for ListValidator<T> {
55    fn clone(&self) -> Self {
56        match self {
57            Self::Item(validator) => Self::Item(validator.clone()),
58            Self::Other(validator) => Self::Other(validator.clone()),
59        }
60    }
61}
62
63struct ItemValidator<T> {
64    validate: RefCell<Rc<dyn Fn(&T)>>,
65}
66
67impl<T> IFerroListItemValidator<T> for ItemValidator<T> {
68    fn validate(&self, item: &T) {
69        let validate = self.validate.borrow().clone();
70        validate(item)
71    }
72}
73
74/// The weak form of a [`FerroList`] handle.
75pub struct WeakFerroList<T>(Weak<FerroListData<T>>);
76
77impl<T> Clone for WeakFerroList<T> {
78    fn clone(&self) -> Self {
79        Self(self.0.clone())
80    }
81}
82
83impl<T: 'static> WeakEventSender for FerroList<T> {
84    type Weak = WeakFerroList<T>;
85
86    fn downgrade_sender(&self) -> WeakFerroList<T> {
87        WeakFerroList(Rc::downgrade(&self.0))
88    }
89
90    fn upgrade_sender(weak: &WeakFerroList<T>) -> Option<Self> {
91        weak.0.upgrade().map(FerroList)
92    }
93
94    fn sender_address(&self) -> usize {
95        Rc::as_ptr(&self.0) as *const () as usize
96    }
97}
98
99impl<T> Clone for FerroList<T> {
100    #[inline]
101    fn clone(&self) -> Self {
102        Self(self.0.clone())
103    }
104}
105
106impl<T> PartialEq for FerroList<T> {
107    #[inline]
108    fn eq(&self, other: &Self) -> bool {
109        self.ptr_eq(other)
110    }
111}
112
113impl<T> FerroList<T> {
114    /// Whether two handles refer to the same list.
115    #[inline]
116    pub fn ptr_eq(&self, other: &Self) -> bool {
117        Rc::ptr_eq(&self.0, &other.0)
118    }
119}
120
121impl<T> std::fmt::Debug for FerroList<T> {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        write!(f, "FerroList({})", self.0.items.borrow().len())
124    }
125}
126
127impl<T: Clone> Default for FerroList<T> {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133impl<T: Clone> FerroList<T> {
134    /// Creates an empty list.
135    pub fn new() -> Self {
136        Self(Rc::new(FerroListData {
137            items: RefCell::new(Rc::new(Vec::new())),
138            collection_changed: HandlerList::with_ordered_ids(),
139            untyped_collection_changed: Event::with_ordered_ids(),
140            property_changed: Event::new(),
141            validator: RefCell::new(None),
142            reset_behavior: Cell::new(ResetBehavior::Reset),
143        }))
144    }
145
146    /// Creates a list with initial items.
147    pub fn from_items(items: impl IntoIterator<Item = T>) -> Self {
148        let list = Self::new();
149        *list.0.items.borrow_mut() = Rc::new(items.into_iter().collect());
150        list
151    }
152
153    /// Subscribes to changes. Returns a token for
154    /// [`remove_collection_changed`](Self::remove_collection_changed).
155    pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<T>>) -> u64 {
156        self.0.collection_changed.add(handler)
157    }
158
159    /// Unsubscribes a handler.
160    pub fn remove_collection_changed(&self, token: u64) -> bool {
161        self.0.collection_changed.remove(token)
162    }
163
164    /// Whether anything is subscribed to changes, typed or untyped.
165    pub fn has_collection_changed_subscribers(&self) -> bool {
166        !self.0.collection_changed.is_empty() || self.0.untyped_collection_changed.has_handlers()
167    }
168
169    /// The number of items in the list.
170    #[inline]
171    pub fn count(&self) -> usize {
172        self.0.items.borrow().len()
173    }
174
175    #[inline]
176    pub fn is_empty(&self) -> bool {
177        self.0.items.borrow().is_empty()
178    }
179
180    /// The number of items the list can hold before its storage grows.
181    pub fn capacity(&self) -> usize {
182        self.0.items.borrow().capacity()
183    }
184
185    /// Sets the number of items the list can hold before its storage grows.
186    ///
187    /// # Panics
188    /// Panics if `capacity` is less than the number of items, as the
189    /// managed original throws.
190    pub fn set_capacity(&self, capacity: usize) {
191        let mut items = self.0.items.borrow_mut();
192        assert!(capacity >= items.len(), "capacity was less than the current size.");
193        if capacity == items.capacity() {
194            return;
195        }
196        // The storage of a snapshot that is still alive is left to the snapshot.
197        let mut resized = Vec::with_capacity(capacity);
198        resized.extend(items.iter().cloned());
199        *items = Rc::new(resized);
200    }
201
202    /// The reset behavior of the list.
203    pub fn reset_behavior(&self) -> ResetBehavior {
204        self.0.reset_behavior.get()
205    }
206
207    pub fn set_reset_behavior(&self, value: ResetBehavior) {
208        self.0.reset_behavior.set(value)
209    }
210
211    /// The validation routine that is invoked for every item before it is
212    /// added to the list.
213    pub fn validate(&self) -> Option<Rc<dyn Fn(&T)>>
214    where
215        T: 'static,
216    {
217        match self.0.validator.borrow().as_ref() {
218            None => None,
219            Some(ListValidator::Item(item_validator)) => Some(item_validator.validate.borrow().clone()),
220            Some(ListValidator::Other(other)) => {
221                let other = other.clone();
222                Some(Rc::new(move |item: &T| other.validate(item)))
223            }
224        }
225    }
226
227    /// Sets a validation routine that is invoked for every item before it is
228    /// added to the list; it rejects an item by panicking.
229    pub fn set_validate(&self, value: Option<Rc<dyn Fn(&T)>>)
230    where
231        T: 'static,
232    {
233        let Some(value) = value else {
234            self.set_validator(None);
235            return;
236        };
237
238        let current = self.0.validator.borrow().clone();
239        if let Some(ListValidator::Item(item_validator)) = current {
240            *item_validator.validate.borrow_mut() = value;
241        } else {
242            *self.0.validator.borrow_mut() =
243                Some(ListValidator::Item(Rc::new(ItemValidator { validate: RefCell::new(value) })));
244        }
245    }
246
247    /// The validator that is invoked for every item before it is added to the
248    /// list.
249    pub fn validator(&self) -> Option<Rc<dyn IFerroListItemValidator<T>>>
250    where
251        T: 'static,
252    {
253        match self.0.validator.borrow().as_ref()? {
254            ListValidator::Item(item_validator) => Some(item_validator.clone()),
255            ListValidator::Other(other) => Some(other.clone()),
256        }
257    }
258
259    pub fn set_validator(&self, value: Option<Rc<dyn IFerroListItemValidator<T>>>) {
260        *self.0.validator.borrow_mut() = value.map(ListValidator::Other);
261    }
262
263    /// The item at `index`. Panics if out of range.
264    #[inline]
265    pub fn get(&self, index: usize) -> T {
266        self.0.items.borrow()[index].clone()
267    }
268
269    /// The item at `index`, if in range.
270    #[inline]
271    pub fn try_get(&self, index: usize) -> Option<T> {
272        self.0.items.borrow().get(index).cloned()
273    }
274
275    /// Replaces the item at `index`.
276    pub fn set(&self, index: usize, value: T)
277    where
278        T: PartialEq,
279    {
280        self.validate_item(&value);
281        let old = self.get(index);
282        if old != value {
283            self.modify(|items| items[index] = value.clone());
284            self.notify(NotifyCollectionChangedEventArgs {
285                action: NotifyCollectionChangedAction::Replace,
286                new_items: std::slice::from_ref(&value),
287                old_items: std::slice::from_ref(&old),
288                new_starting_index: index as i32,
289                old_starting_index: index as i32,
290            });
291        }
292    }
293
294    /// A snapshot of the items. It stays valid and unchanged if the list is
295    /// modified.
296    #[inline]
297    pub fn snapshot(&self) -> Rc<Vec<T>> {
298        self.0.items.borrow().clone()
299    }
300
301    /// Upstream `InnerForTests` of the list that can be mutated while it is enumerated: the identity of the storage. For unit tests
302    /// only: it does not keep the storage alive, as a snapshot would.
303    #[cfg(test)]
304    pub(crate) fn inner_for_tests(&self) -> *const Vec<T> {
305        Rc::as_ptr(&self.0.items.borrow())
306    }
307
308    /// Copies the items into a vector.
309    pub fn to_vec(&self) -> Vec<T> {
310        self.0.items.borrow().as_ref().clone()
311    }
312
313    /// Calls `f` for each item of a snapshot of the list.
314    pub fn for_each(&self, mut f: impl FnMut(&T)) {
315        if self.is_empty() {
316            return;
317        }
318        for item in self.snapshot().iter() {
319            f(item);
320        }
321    }
322
323    /// Adds an item to the collection.
324    pub fn add(&self, item: T) {
325        self.validate_item(&item);
326        let index = self.count();
327        self.modify(|items| items.push(item.clone()));
328        self.notify_add(std::slice::from_ref(&item), index);
329    }
330
331    /// Adds multiple items to the collection.
332    pub fn add_range(&self, items: impl IntoIterator<Item = T>) {
333        self.insert_range(self.count(), items)
334    }
335
336    /// Inserts an item at the specified index.
337    pub fn insert(&self, index: usize, item: T) {
338        self.validate_item(&item);
339        self.modify(|items| items.insert(index, item.clone()));
340        self.notify_add(std::slice::from_ref(&item), index);
341    }
342
343    /// Inserts multiple items at the specified index.
344    pub fn insert_range(&self, index: usize, items: impl IntoIterator<Item = T>) {
345        let added: Vec<T> = items.into_iter().collect();
346        if added.is_empty() {
347            return;
348        }
349        for item in &added {
350            self.validate_item(item);
351        }
352        self.modify(|list| {
353            list.splice(index..index, added.iter().cloned());
354        });
355        self.notify_add(&added, index);
356    }
357
358    /// Moves an item to a new index.
359    pub fn move_item(&self, old_index: usize, new_index: usize) {
360        let item = self.get(old_index);
361        self.modify(|items| {
362            let item = items.remove(old_index);
363            items.insert(new_index, item);
364        });
365        self.notify(NotifyCollectionChangedEventArgs {
366            action: NotifyCollectionChangedAction::Move,
367            new_items: std::slice::from_ref(&item),
368            old_items: std::slice::from_ref(&item),
369            new_starting_index: new_index as i32,
370            old_starting_index: old_index as i32,
371        });
372    }
373
374    /// Moves multiple items to a new index.
375    pub fn move_range(&self, old_index: usize, count: usize, new_index: usize) {
376        let moved: Vec<T> = self.get_range(old_index, count);
377        let mut insert_at = new_index as isize;
378        if new_index > old_index {
379            insert_at -= count as isize - 1;
380        }
381        let insert_at = insert_at as usize;
382        self.modify(|items| {
383            items.drain(old_index..old_index + count);
384            items.splice(insert_at..insert_at, moved.iter().cloned());
385        });
386        self.notify(NotifyCollectionChangedEventArgs {
387            action: NotifyCollectionChangedAction::Move,
388            new_items: &moved,
389            old_items: &moved,
390            new_starting_index: new_index as i32,
391            old_starting_index: old_index as i32,
392        });
393    }
394
395    /// Removes the item at the specified index.
396    pub fn remove_at(&self, index: usize) {
397        let item = self.get(index);
398        self.modify(|items| {
399            items.remove(index);
400        });
401        self.notify_remove(std::slice::from_ref(&item), index);
402    }
403
404    /// Removes a range of elements from the collection.
405    pub fn remove_range(&self, index: usize, count: usize) {
406        if count == 0 {
407            return;
408        }
409        let removed: Vec<T> = self.0.items.borrow()[index..index + count].to_vec();
410        self.modify(|items| {
411            items.drain(index..index + count);
412        });
413        self.notify_remove(&removed, index);
414    }
415
416    /// Removes all items from the collection.
417    pub fn clear(&self) {
418        if self.count() > 0 {
419            let subscribed = self.has_collection_changed_subscribers();
420            let removed = subscribed && self.0.reset_behavior.get() == ResetBehavior::Remove;
421            // Upstream `_inner.Clear()`: the storage is cleared in place and keeps its capacity. While a
422            // snapshot is alive the storage is left to it (upstream `OnMutating` of the list that can be
423            // enumerated while it changes).
424            let old = {
425                let mut items = self.0.items.borrow_mut();
426                match Rc::get_mut(&mut items) {
427                    Some(inner) => {
428                        let old = if removed { inner.clone() } else { Vec::new() };
429                        inner.clear();
430                        Rc::new(old)
431                    }
432                    None => std::mem::replace(&mut *items, Rc::new(Vec::new())),
433                }
434            };
435            if subscribed {
436                match self.0.reset_behavior.get() {
437                    ResetBehavior::Reset => self.notify(NotifyCollectionChangedEventArgs {
438                        action: NotifyCollectionChangedAction::Reset,
439                        new_items: &[],
440                        old_items: &[],
441                        new_starting_index: -1,
442                        old_starting_index: -1,
443                    }),
444                    ResetBehavior::Remove => self.notify(NotifyCollectionChangedEventArgs {
445                        action: NotifyCollectionChangedAction::Remove,
446                        new_items: &[],
447                        old_items: &old,
448                        new_starting_index: -1,
449                        old_starting_index: 0,
450                    }),
451                }
452            }
453
454            self.notify_count_changed();
455        }
456    }
457
458    /// Copies the items into `array`, starting at `array_index`.
459    ///
460    /// # Panics
461    /// Panics if `array` is too small, as the managed original throws.
462    pub fn copy_to(&self, array: &mut [T], array_index: usize) {
463        let items = self.0.items.borrow();
464        assert!(
465            array_index <= array.len() && array.len() - array_index >= items.len(),
466            "Destination array was not long enough. Check the destination index, length, and the array's lower bounds."
467        );
468        array[array_index..array_index + items.len()].clone_from_slice(&items);
469    }
470
471    /// Gets a range of items from the collection.
472    pub fn get_range(&self, index: usize, count: usize) -> Vec<T> {
473        self.0.items.borrow()[index..index + count].to_vec()
474    }
475
476    /// Ensures that the capacity of the list is at least `capacity`.
477    pub fn ensure_capacity(&self, capacity: usize) {
478        // Adapted from List<T> implementation.
479        let current_capacity = self.capacity();
480        if current_capacity < capacity {
481            let mut new_capacity = if current_capacity == 0 { 4 } else { current_capacity * 2 };
482
483            if new_capacity < capacity {
484                new_capacity = capacity;
485            }
486
487            self.set_capacity(new_capacity);
488        }
489    }
490
491    fn validate_item(&self, item: &T) {
492        let validator = self.0.validator.borrow().clone();
493        match validator {
494            None => {}
495            Some(ListValidator::Item(validator)) => validator.validate(item),
496            Some(ListValidator::Other(validator)) => validator.validate(item),
497        }
498    }
499
500    fn modify(&self, f: impl FnOnce(&mut Vec<T>)) {
501        let mut items = self.0.items.borrow_mut();
502        f(Rc::make_mut(&mut items));
503    }
504
505    fn notify(&self, e: NotifyCollectionChangedEventArgs<'_, T>) {
506        raise_collection_changed(&self.0.collection_changed, &self.0.untyped_collection_changed, &e);
507    }
508
509    fn notify_add(&self, items: &[T], index: usize) {
510        self.notify(NotifyCollectionChangedEventArgs {
511            action: NotifyCollectionChangedAction::Add,
512            new_items: items,
513            old_items: &[],
514            new_starting_index: index as i32,
515            old_starting_index: -1,
516        });
517
518        self.notify_count_changed();
519    }
520
521    fn notify_count_changed(&self) {
522        self.0.property_changed.raise("Count");
523    }
524
525    fn notify_remove(&self, items: &[T], index: usize) {
526        self.notify(NotifyCollectionChangedEventArgs {
527            action: NotifyCollectionChangedAction::Remove,
528            new_items: &[],
529            old_items: items,
530            new_starting_index: -1,
531            old_starting_index: index as i32,
532        });
533
534        self.notify_count_changed();
535    }
536}
537
538/// Runs the typed and the untyped handlers of a collection changed event in
539/// subscription order (their tokens come from one counter).
540pub(super) fn raise_collection_changed<T>(
541    typed: &HandlerList<CollectionChangedHandler<T>>,
542    untyped: &Event<CollectionChange>,
543    e: &NotifyCollectionChangedEventArgs<'_, T>,
544) {
545    if typed.is_empty() && !untyped.has_handlers() {
546        return;
547    }
548    let typed = typed.snapshot();
549    let untyped = untyped.snapshot();
550    let change = CollectionChange {
551        action: e.action,
552        new_starting_index: e.new_starting_index,
553        new_count: e.new_items.len(),
554        old_starting_index: e.old_starting_index,
555        old_count: e.old_items.len(),
556    };
557    let (mut i, mut j) = (0, 0);
558    while i < typed.len() || j < untyped.len() {
559        if j == untyped.len() || (i < typed.len() && typed[i].0 < untyped[j].0) {
560            (typed[i].1)(e);
561            i += 1;
562        } else {
563            (untyped[j].1)(&change);
564            j += 1;
565        }
566    }
567}
568
569impl<T> INotifyCollectionChanged for FerroList<T> {
570    fn collection_changed(&self) -> &Event<CollectionChange> {
571        &self.0.untyped_collection_changed
572    }
573}
574
575impl<T> INotifyPropertyChanged for FerroList<T> {
576    fn property_changed(&self) -> &Event<str> {
577        &self.0.property_changed
578    }
579}
580
581impl<T: Clone + PartialEq> FerroList<T> {
582    /// Tests whether the collection contains an item.
583    pub fn contains(&self, item: &T) -> bool {
584        self.0.items.borrow().contains(item)
585    }
586
587    /// The index of an item in the collection.
588    pub fn index_of(&self, item: &T) -> Option<usize> {
589        self.0.items.borrow().iter().position(|i| i == item)
590    }
591
592    /// Removes an item from the collection. Returns true if it was present.
593    pub fn remove(&self, item: &T) -> bool {
594        match self.index_of(item) {
595            Some(index) => {
596                self.remove_at(index);
597                true
598            }
599            None => false,
600        }
601    }
602
603    /// Removes multiple items from the collection.
604    ///
605    /// Each run of adjacent items that are removed is notified as one
606    /// removal, from the end of the list to its start.
607    pub fn remove_all(&self, items: impl IntoIterator<Item = T>) {
608        // The original collects the items into a hash set. Every managed
609        // object can be hashed; Rust item types need not be, so the set is not
610        // ported and membership is a linear search by equality, which keeps
611        // the behaviour without a `Hash` bound.
612        let h_items: Vec<T> = items.into_iter().collect();
613        let mut counter = 0;
614
615        let mut i = self.count();
616        while i > 0 {
617            i -= 1;
618            let contained = h_items.contains(&self.0.items.borrow()[i]);
619            if contained {
620                counter += 1;
621            } else if counter > 0 {
622                self.remove_range(i + 1, counter);
623                counter = 0;
624            }
625        }
626
627        if counter > 0 {
628            self.remove_range(0, counter);
629        }
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    //! Not ported: `Can_CopyTo_Array_Of_Base_Type` (copies into an array of
636    //! a base element type through array covariance, which Rust does not
637    //! have), and the `*_With_Null_Should_Throw_Exception` tests (no null
638    //! arguments in Rust).
639
640    #[test]
641    fn capacity_is_read_and_set() {
642        let list = super::FerroList::from_items([1, 2, 3]);
643        list.set_capacity(16);
644        assert_eq!(list.capacity(), 16);
645        assert_eq!(list.to_vec(), vec![1, 2, 3]);
646        // A snapshot taken before keeps its items.
647        let snapshot = list.snapshot();
648        list.set_capacity(3);
649        assert_eq!(list.capacity(), 3);
650        assert_eq!(*snapshot, vec![1, 2, 3]);
651    }
652
653    #[test]
654    #[should_panic(expected = "capacity was less than the current size.")]
655    fn capacity_below_the_count_is_rejected() {
656        super::FerroList::from_items([1, 2, 3]).set_capacity(2);
657    }
658
659    use super::*;
660
661    fn tracked(list: &FerroList<i32>) -> Rc<RefCell<Vec<(NotifyCollectionChangedAction, Vec<i32>, Vec<i32>, i32, i32)>>> {
662        let log = Rc::new(RefCell::new(Vec::new()));
663        let l = log.clone();
664        list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, i32>| {
665            l.borrow_mut().push((
666                e.action,
667                e.new_items.to_vec(),
668                e.old_items.to_vec(),
669                e.new_starting_index,
670                e.old_starting_index,
671            ));
672        }));
673        log
674    }
675
676    type Logged = (NotifyCollectionChangedAction, Vec<i32>, Vec<i32>, i32, i32);
677
678    /// Upstream `AssertEvent`: the changes `action` raises.
679    fn assert_event(items: &FerroList<i32>, action: impl FnOnce(), expected_events: &[Logged]) {
680        let log = Rc::new(RefCell::new(Vec::new()));
681        let l = log.clone();
682        let token = items.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, i32>| {
683            l.borrow_mut().push((e.action, e.new_items.to_vec(), e.old_items.to_vec(), e.new_starting_index, e.old_starting_index));
684        }));
685        action();
686        items.remove_collection_changed(token);
687        assert_eq!(*log.borrow(), expected_events);
688    }
689
690    fn strings(items: impl IntoIterator<Item = String>) -> FerroList<String> {
691        FerroList::from_items(items)
692    }
693
694    fn removals(list: &FerroList<String>) -> Rc<RefCell<Vec<(NotifyCollectionChangedAction, i32, Vec<String>)>>> {
695        let log = Rc::new(RefCell::new(Vec::new()));
696        let l = log.clone();
697        list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, String>| {
698            l.borrow_mut().push((e.action, e.old_starting_index, e.old_items.to_vec()));
699        }));
700        log
701    }
702
703    #[test]
704    fn items_passed_to_constructor_should_appear_in_list() {
705        let items = [1, 2, 3];
706        let target = FerroList::from_items(items);
707
708        assert_eq!(target.to_vec(), items);
709    }
710
711    #[test]
712    #[should_panic]
713    fn insert_range_past_end_should_throw_exception() {
714        let target = FerroList::<i32>::new();
715
716        target.insert_range(1, vec![1]);
717    }
718
719    #[test]
720    fn move_should_move_one_item() {
721        let target = FerroList::from_items([1, 2, 3]);
722
723        assert_event(&target, || target.move_item(0, 1), &[(NotifyCollectionChangedAction::Move, vec![1], vec![1], 1, 0)]);
724
725        assert_eq!(target.to_vec(), [2, 1, 3]);
726    }
727
728    #[test]
729    fn move_should_update_collection() {
730        let target = FerroList::from_items([1, 2, 3]);
731
732        target.move_item(2, 0);
733
734        assert_eq!(target.to_vec(), [3, 1, 2]);
735    }
736
737    #[test]
738    fn move_range_should_update_collection() {
739        let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
740
741        target.move_range(4, 3, 0);
742
743        assert_eq!(target.to_vec(), [5, 6, 7, 1, 2, 3, 4, 8, 9, 10]);
744    }
745
746    #[test]
747    fn move_range_can_move_to_end() {
748        let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
749
750        target.move_range(0, 5, 9);
751
752        assert_eq!(target.to_vec(), [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]);
753    }
754
755    #[test]
756    fn move_range_raises_correct_collection_changed_event() {
757        let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
758
759        let moved = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
760        assert_event(
761            &target,
762            || target.move_range(0, 9, 9),
763            &[(NotifyCollectionChangedAction::Move, moved.clone(), moved, 9, 0)],
764        );
765
766        assert_eq!(target.to_vec(), [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
767    }
768
769    #[test]
770    fn move_range_should_move_one_item() {
771        let target = FerroList::from_items([1, 2, 3]);
772
773        assert_event(&target, || target.move_range(0, 1, 1), &[(NotifyCollectionChangedAction::Move, vec![1], vec![1], 1, 0)]);
774
775        assert_eq!(target.to_vec(), [2, 1, 3]);
776    }
777
778    #[test]
779    fn adding_item_should_raise_collection_changed() {
780        let target = FerroList::from_items([1, 2]);
781        let log = tracked(&target);
782
783        target.add(3);
784
785        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3], vec![], 2, -1)]);
786    }
787
788    #[test]
789    fn adding_items_should_raise_collection_changed() {
790        let target = FerroList::from_items([1, 2]);
791        let log = tracked(&target);
792
793        target.add_range([3, 4]);
794
795        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3, 4], vec![], 2, -1)]);
796    }
797
798    #[test]
799    fn add_range_i_enumerable_should_raise_count_property_changed() {
800        let target = FerroList::from_items([1, 2, 3, 4, 5]);
801        let raised = Rc::new(Cell::new(false));
802        let r = raised.clone();
803        let t = target.clone();
804
805        target.property_changed().add(Rc::new(move |e: &str| {
806            assert_eq!(e, "Count");
807            assert_eq!(t.count(), 7);
808            r.set(true);
809        }));
810
811        target.add_range(6..8);
812
813        assert!(raised.get());
814    }
815
816    #[test]
817    fn add_range_items_should_raise_correct_collection_changed() {
818        let target = FerroList::<Rc<i32>>::new();
819
820        let event_items = Rc::new(RefCell::new(Vec::new()));
821        let e = event_items.clone();
822
823        target.add_collection_changed(Rc::new(move |args: &NotifyCollectionChangedEventArgs<'_, Rc<i32>>| {
824            e.borrow_mut().extend(args.new_items.iter().cloned());
825        }));
826
827        target.add_range((0..10).map(Rc::new));
828
829        let event_items = event_items.borrow();
830        let target = target.to_vec();
831        assert_eq!(event_items.len(), target.len());
832        assert!(event_items.iter().zip(&target).all(|(a, b)| Rc::ptr_eq(a, b)));
833    }
834
835    #[test]
836    fn replacing_item_should_raise_collection_changed() {
837        let target = FerroList::from_items([1, 2]);
838        let log = tracked(&target);
839
840        target.set(1, 3);
841
842        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Replace, vec![3], vec![2], 1, 1)]);
843    }
844
845    #[test]
846    fn inserting_item_should_raise_collection_changed() {
847        let target = FerroList::from_items([1, 2]);
848        let log = tracked(&target);
849
850        target.insert(1, 3);
851
852        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3], vec![], 1, -1)]);
853    }
854
855    #[test]
856    fn inserting_items_should_raise_collection_changed() {
857        let target = FerroList::from_items([1, 2]);
858        let log = tracked(&target);
859
860        target.insert_range(1, [3, 4]);
861
862        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3, 4], vec![], 1, -1)]);
863    }
864
865    #[test]
866    fn removing_item_should_raise_collection_changed() {
867        let target = FerroList::from_items([1, 2, 3]);
868        let log = tracked(&target);
869
870        target.remove(&3);
871
872        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, vec![], vec![3], -1, 2)]);
873    }
874
875    #[test]
876    fn moving_item_should_raise_collection_changed() {
877        let target = FerroList::from_items([1, 2, 3]);
878        let log = tracked(&target);
879
880        target.move_item(2, 0);
881
882        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Move, vec![3], vec![3], 0, 2)]);
883    }
884
885    #[test]
886    fn moving_items_should_raise_collection_changed() {
887        let target = FerroList::from_items([1, 2, 3]);
888        let log = tracked(&target);
889
890        target.move_range(1, 2, 0);
891
892        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Move, vec![2, 3], vec![2, 3], 0, 1)]);
893    }
894
895    #[test]
896    fn clearing_items_should_raise_collection_changed_reset() {
897        let target = FerroList::from_items([1, 2, 3]);
898        let log = tracked(&target);
899
900        target.clear();
901
902        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Reset, vec![], vec![], -1, -1)]);
903    }
904
905    #[test]
906    fn clearing_items_should_raise_collection_changed_remove() {
907        let target = FerroList::from_items([1, 2, 3]);
908        target.set_reset_behavior(ResetBehavior::Remove);
909        let log = tracked(&target);
910
911        target.clear();
912
913        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, vec![], vec![1, 2, 3], -1, 0)]);
914    }
915
916    #[test]
917    fn can_copy_to_array_of_same_type() {
918        let target = FerroList::from_items(["foo", "bar", "baz"].map(String::from));
919        let mut result = vec![String::new(); 3];
920
921        target.copy_to(&mut result, 0);
922
923        assert_eq!(target.to_vec(), result);
924    }
925
926    #[test]
927    fn remove_all_should_send_single_notification_for_sequential_range() {
928        let target = strings((0..10).map(|x| format!("Item {x}")));
929        let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
930        let log = removals(&target);
931
932        target.remove_all(to_remove.clone());
933
934        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 5, to_remove.to_vec())]);
935    }
936
937    #[test]
938    fn remove_all_should_send_single_notification_for_sequential_range_with_duplicate_source_items() {
939        let target = strings((0..20).map(|x| format!("Item {}", x / 2)));
940        let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
941        let log = removals(&target);
942
943        target.remove_all(to_remove);
944
945        let expected = ["Item 5", "Item 5", "Item 6", "Item 6", "Item 7", "Item 7"].map(String::from).to_vec();
946        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 10, expected)]);
947    }
948
949    #[test]
950    fn remove_all_should_send_multiple_notifications_for_non_sequential_range() {
951        let target = strings((0..10).map(|x| format!("Item {x}")));
952        let to_remove = [["Item 2", "Item 3"].map(String::from), ["Item 5", "Item 6"].map(String::from)];
953        let log = removals(&target);
954
955        target.remove_all(to_remove.concat());
956
957        assert_eq!(
958            *log.borrow(),
959            vec![
960                (NotifyCollectionChangedAction::Remove, 5, to_remove[1].to_vec()),
961                (NotifyCollectionChangedAction::Remove, 2, to_remove[0].to_vec()),
962            ]
963        );
964    }
965
966    #[test]
967    fn remove_all_should_send_multiple_notifications_for_sequential_range_with_nonsequential_duplicate_source_items() {
968        let items: Vec<String> = (0..10).map(|x| format!("Item {x}")).collect();
969        let target = strings(items.iter().chain(&items).cloned());
970        let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
971        let log = removals(&target);
972
973        target.remove_all(to_remove.clone());
974
975        assert_eq!(
976            *log.borrow(),
977            vec![
978                (NotifyCollectionChangedAction::Remove, 15, to_remove.to_vec()),
979                (NotifyCollectionChangedAction::Remove, 5, to_remove.to_vec()),
980            ]
981        );
982    }
983
984    #[test]
985    fn remove_all_should_not_send_notification_for_items_not_present() {
986        let target = strings((0..10).map(|x| format!("Item {x}")));
987        let to_remove = ["Item 5", "Item 6", "Item 7", "Not present"].map(String::from);
988        let log = removals(&target);
989
990        target.remove_all(to_remove.clone());
991
992        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 5, to_remove[..3].to_vec())]);
993    }
994
995    #[test]
996    fn remove_all_should_handle_empty_list() {
997        let target = FerroList::<String>::new();
998        let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
999        let log = removals(&target);
1000
1001        target.remove_all(to_remove);
1002
1003        assert!(log.borrow().is_empty());
1004    }
1005
1006    /// Not from upstream: typed and untyped handlers run in subscription
1007    /// order, as the handlers of the single event of the original.
1008    #[test]
1009    fn typed_and_untyped_handlers_run_in_subscription_order() {
1010        let target = FerroList::from_items([1]);
1011        let log = Rc::new(RefCell::new(Vec::new()));
1012        let (a, b, c) = (log.clone(), log.clone(), log.clone());
1013        target.collection_changed().add(Rc::new(move |_: &CollectionChange| a.borrow_mut().push("untyped 1")));
1014        target.add_collection_changed(Rc::new(move |_: &NotifyCollectionChangedEventArgs<'_, i32>| b.borrow_mut().push("typed")));
1015        target.collection_changed().add(Rc::new(move |_: &CollectionChange| c.borrow_mut().push("untyped 2")));
1016
1017        target.add(2);
1018
1019        assert_eq!(*log.borrow(), ["untyped 1", "typed", "untyped 2"]);
1020    }
1021
1022    #[test]
1023    fn can_modify_list_while_enumerating_snapshot() {
1024        let target = FerroList::from_items([1, 2, 3]);
1025        let mut seen = Vec::new();
1026        target.for_each(|i| {
1027            seen.push(*i);
1028            target.remove(i);
1029        });
1030        assert_eq!(seen, vec![1, 2, 3]);
1031        assert!(target.is_empty());
1032    }
1033}