1use super::IFerroListItemValidator;
2use crate::data::model::{CollectionChange, Event, INotifyCollectionChanged, INotifyPropertyChanged};
3use crate::utilities::{HandlerList, WeakEventSender};
4use std::cell::{Cell, RefCell};
5use std::rc::{Rc, Weak};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum NotifyCollectionChangedAction {
10 Add,
11 Remove,
12 Replace,
13 Move,
14 Reset,
15}
16
17pub struct NotifyCollectionChangedEventArgs<'a, T> {
22 pub action: NotifyCollectionChangedAction,
23 pub new_items: &'a [T],
24 pub old_items: &'a [T],
25 pub new_starting_index: i32,
26 pub old_starting_index: i32,
27}
28
29impl<T: Clone> NotifyCollectionChangedEventArgs<'_, T> {
30 pub fn share(&self) -> SharedNotifyCollectionChangedEventArgs<T> {
34 SharedNotifyCollectionChangedEventArgs(Rc::new(SharedNotifyCollectionChangedData {
35 action: self.action,
36 new_items: self.new_items.to_vec(),
37 old_items: self.old_items.to_vec(),
38 new_starting_index: self.new_starting_index,
39 old_starting_index: self.old_starting_index,
40 }))
41 }
42}
43
44struct SharedNotifyCollectionChangedData<T> {
45 action: NotifyCollectionChangedAction,
46 new_items: Vec<T>,
47 old_items: Vec<T>,
48 new_starting_index: i32,
49 old_starting_index: i32,
50}
51
52pub struct SharedNotifyCollectionChangedEventArgs<T>(Rc<SharedNotifyCollectionChangedData<T>>);
56
57impl<T> Clone for SharedNotifyCollectionChangedEventArgs<T> {
58 fn clone(&self) -> Self {
59 Self(self.0.clone())
60 }
61}
62
63impl<T> PartialEq for SharedNotifyCollectionChangedEventArgs<T> {
64 fn eq(&self, other: &Self) -> bool {
65 Rc::ptr_eq(&self.0, &other.0)
66 }
67}
68
69impl<T> SharedNotifyCollectionChangedEventArgs<T> {
70 pub fn action(&self) -> NotifyCollectionChangedAction {
72 self.0.action
73 }
74
75 pub fn new_items(&self) -> &[T] {
77 &self.0.new_items
78 }
79
80 pub fn old_items(&self) -> &[T] {
82 &self.0.old_items
83 }
84
85 pub fn new_starting_index(&self) -> i32 {
87 self.0.new_starting_index
88 }
89
90 pub fn old_starting_index(&self) -> i32 {
93 self.0.old_starting_index
94 }
95
96 pub fn as_args(&self) -> NotifyCollectionChangedEventArgs<'_, T> {
98 NotifyCollectionChangedEventArgs {
99 action: self.0.action,
100 new_items: &self.0.new_items,
101 old_items: &self.0.old_items,
102 new_starting_index: self.0.new_starting_index,
103 old_starting_index: self.0.old_starting_index,
104 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
110pub enum ResetBehavior {
111 #[default]
113 Reset,
114 Remove,
116}
117
118pub type CollectionChangedHandler<T> = dyn for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, T>);
120
121pub struct FerroList<T>(Rc<FerroListData<T>>);
130
131struct FerroListData<T> {
132 items: RefCell<Rc<Vec<T>>>,
133 collection_changed: HandlerList<CollectionChangedHandler<T>>,
134 untyped_collection_changed: Event<CollectionChange>,
139 property_changed: Event<str>,
140 validator: RefCell<Option<ListValidator<T>>>,
141 reset_behavior: Cell<ResetBehavior>,
142}
143
144enum ListValidator<T> {
148 Item(Rc<ItemValidator<T>>),
149 Other(Rc<dyn IFerroListItemValidator<T>>),
150}
151
152impl<T> Clone for ListValidator<T> {
153 fn clone(&self) -> Self {
154 match self {
155 Self::Item(validator) => Self::Item(validator.clone()),
156 Self::Other(validator) => Self::Other(validator.clone()),
157 }
158 }
159}
160
161struct ItemValidator<T> {
162 validate: RefCell<Rc<dyn Fn(&T)>>,
163}
164
165impl<T> IFerroListItemValidator<T> for ItemValidator<T> {
166 fn validate(&self, item: &T) {
167 let validate = self.validate.borrow().clone();
168 validate(item)
169 }
170}
171
172pub struct WeakFerroList<T>(Weak<FerroListData<T>>);
174
175impl<T> Clone for WeakFerroList<T> {
176 fn clone(&self) -> Self {
177 Self(self.0.clone())
178 }
179}
180
181impl<T: 'static> WeakEventSender for FerroList<T> {
182 type Weak = WeakFerroList<T>;
183
184 fn downgrade_sender(&self) -> WeakFerroList<T> {
185 WeakFerroList(Rc::downgrade(&self.0))
186 }
187
188 fn upgrade_sender(weak: &WeakFerroList<T>) -> Option<Self> {
189 weak.0.upgrade().map(FerroList)
190 }
191
192 fn sender_address(&self) -> usize {
193 Rc::as_ptr(&self.0) as *const () as usize
194 }
195}
196
197impl<T> Clone for FerroList<T> {
198 #[inline]
199 fn clone(&self) -> Self {
200 Self(self.0.clone())
201 }
202}
203
204impl<T> PartialEq for FerroList<T> {
205 #[inline]
206 fn eq(&self, other: &Self) -> bool {
207 self.ptr_eq(other)
208 }
209}
210
211impl<T> FerroList<T> {
212 #[inline]
214 pub fn ptr_eq(&self, other: &Self) -> bool {
215 Rc::ptr_eq(&self.0, &other.0)
216 }
217}
218
219impl<T> std::fmt::Debug for FerroList<T> {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 write!(f, "FerroList({})", self.0.items.borrow().len())
222 }
223}
224
225impl<T: Clone> Default for FerroList<T> {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231impl<T: Clone> FerroList<T> {
232 pub fn new() -> Self {
234 Self(Rc::new(FerroListData {
235 items: RefCell::new(Rc::new(Vec::new())),
236 collection_changed: HandlerList::with_ordered_ids(),
237 untyped_collection_changed: Event::with_ordered_ids(),
238 property_changed: Event::new(),
239 validator: RefCell::new(None),
240 reset_behavior: Cell::new(ResetBehavior::Reset),
241 }))
242 }
243
244 pub fn from_items(items: impl IntoIterator<Item = T>) -> Self {
246 let list = Self::new();
247 *list.0.items.borrow_mut() = Rc::new(items.into_iter().collect());
248 list
249 }
250
251 pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<T>>) -> u64 {
254 self.0.collection_changed.add(handler)
255 }
256
257 pub fn remove_collection_changed(&self, token: u64) -> bool {
259 self.0.collection_changed.remove(token)
260 }
261
262 pub fn has_collection_changed_subscribers(&self) -> bool {
264 !self.0.collection_changed.is_empty() || self.0.untyped_collection_changed.has_handlers()
265 }
266
267 #[inline]
269 pub fn count(&self) -> usize {
270 self.0.items.borrow().len()
271 }
272
273 #[inline]
274 pub fn is_empty(&self) -> bool {
275 self.0.items.borrow().is_empty()
276 }
277
278 pub fn capacity(&self) -> usize {
280 self.0.items.borrow().capacity()
281 }
282
283 pub fn set_capacity(&self, capacity: usize) {
289 let mut items = self.0.items.borrow_mut();
290 assert!(capacity >= items.len(), "capacity was less than the current size.");
291 if capacity == items.capacity() {
292 return;
293 }
294 let mut resized = Vec::with_capacity(capacity);
296 resized.extend(items.iter().cloned());
297 *items = Rc::new(resized);
298 }
299
300 pub fn reset_behavior(&self) -> ResetBehavior {
302 self.0.reset_behavior.get()
303 }
304
305 pub fn set_reset_behavior(&self, value: ResetBehavior) {
306 self.0.reset_behavior.set(value)
307 }
308
309 pub fn validate(&self) -> Option<Rc<dyn Fn(&T)>>
312 where
313 T: 'static,
314 {
315 match self.0.validator.borrow().as_ref() {
316 None => None,
317 Some(ListValidator::Item(item_validator)) => Some(item_validator.validate.borrow().clone()),
318 Some(ListValidator::Other(other)) => {
319 let other = other.clone();
320 Some(Rc::new(move |item: &T| other.validate(item)))
321 }
322 }
323 }
324
325 pub fn set_validate(&self, value: Option<Rc<dyn Fn(&T)>>)
328 where
329 T: 'static,
330 {
331 let Some(value) = value else {
332 self.set_validator(None);
333 return;
334 };
335
336 let current = self.0.validator.borrow().clone();
337 if let Some(ListValidator::Item(item_validator)) = current {
338 *item_validator.validate.borrow_mut() = value;
339 } else {
340 *self.0.validator.borrow_mut() =
341 Some(ListValidator::Item(Rc::new(ItemValidator { validate: RefCell::new(value) })));
342 }
343 }
344
345 pub fn validator(&self) -> Option<Rc<dyn IFerroListItemValidator<T>>>
348 where
349 T: 'static,
350 {
351 match self.0.validator.borrow().as_ref()? {
352 ListValidator::Item(item_validator) => Some(item_validator.clone()),
353 ListValidator::Other(other) => Some(other.clone()),
354 }
355 }
356
357 pub fn set_validator(&self, value: Option<Rc<dyn IFerroListItemValidator<T>>>) {
358 *self.0.validator.borrow_mut() = value.map(ListValidator::Other);
359 }
360
361 #[inline]
363 pub fn get(&self, index: usize) -> T {
364 self.0.items.borrow()[index].clone()
365 }
366
367 #[inline]
369 pub fn try_get(&self, index: usize) -> Option<T> {
370 self.0.items.borrow().get(index).cloned()
371 }
372
373 pub fn set(&self, index: usize, value: T)
375 where
376 T: PartialEq,
377 {
378 self.validate_item(&value);
379 let old = self.get(index);
380 if old != value {
381 self.modify(|items| items[index] = value.clone());
382 self.notify(NotifyCollectionChangedEventArgs {
383 action: NotifyCollectionChangedAction::Replace,
384 new_items: std::slice::from_ref(&value),
385 old_items: std::slice::from_ref(&old),
386 new_starting_index: index as i32,
387 old_starting_index: index as i32,
388 });
389 }
390 }
391
392 #[inline]
395 pub fn snapshot(&self) -> Rc<Vec<T>> {
396 self.0.items.borrow().clone()
397 }
398
399 #[cfg(test)]
402 pub(crate) fn inner_for_tests(&self) -> *const Vec<T> {
403 Rc::as_ptr(&self.0.items.borrow())
404 }
405
406 pub fn to_vec(&self) -> Vec<T> {
408 self.0.items.borrow().as_ref().clone()
409 }
410
411 pub fn for_each(&self, mut f: impl FnMut(&T)) {
413 if self.is_empty() {
414 return;
415 }
416 for item in self.snapshot().iter() {
417 f(item);
418 }
419 }
420
421 pub fn add(&self, item: T) {
423 self.validate_item(&item);
424 let index = self.count();
425 self.modify(|items| items.push(item.clone()));
426 self.notify_add(std::slice::from_ref(&item), index);
427 }
428
429 pub fn add_range(&self, items: impl IntoIterator<Item = T>) {
431 self.insert_range(self.count(), items)
432 }
433
434 pub fn insert(&self, index: usize, item: T) {
436 self.validate_item(&item);
437 self.modify(|items| items.insert(index, item.clone()));
438 self.notify_add(std::slice::from_ref(&item), index);
439 }
440
441 pub fn insert_range(&self, index: usize, items: impl IntoIterator<Item = T>) {
443 let added: Vec<T> = items.into_iter().collect();
444 if added.is_empty() {
445 return;
446 }
447 for item in &added {
448 self.validate_item(item);
449 }
450 self.modify(|list| {
451 list.splice(index..index, added.iter().cloned());
452 });
453 self.notify_add(&added, index);
454 }
455
456 pub fn move_item(&self, old_index: usize, new_index: usize) {
458 let item = self.get(old_index);
459 self.modify(|items| {
460 let item = items.remove(old_index);
461 items.insert(new_index, item);
462 });
463 self.notify(NotifyCollectionChangedEventArgs {
464 action: NotifyCollectionChangedAction::Move,
465 new_items: std::slice::from_ref(&item),
466 old_items: std::slice::from_ref(&item),
467 new_starting_index: new_index as i32,
468 old_starting_index: old_index as i32,
469 });
470 }
471
472 pub fn move_range(&self, old_index: usize, count: usize, new_index: usize) {
474 let moved: Vec<T> = self.get_range(old_index, count);
475 let mut insert_at = new_index as isize;
476 if new_index > old_index {
477 insert_at -= count as isize - 1;
478 }
479 let insert_at = insert_at as usize;
480 self.modify(|items| {
481 items.drain(old_index..old_index + count);
482 items.splice(insert_at..insert_at, moved.iter().cloned());
483 });
484 self.notify(NotifyCollectionChangedEventArgs {
485 action: NotifyCollectionChangedAction::Move,
486 new_items: &moved,
487 old_items: &moved,
488 new_starting_index: new_index as i32,
489 old_starting_index: old_index as i32,
490 });
491 }
492
493 pub fn remove_at(&self, index: usize) {
495 let item = self.get(index);
496 self.modify(|items| {
497 items.remove(index);
498 });
499 self.notify_remove(std::slice::from_ref(&item), index);
500 }
501
502 pub fn remove_range(&self, index: usize, count: usize) {
504 if count == 0 {
505 return;
506 }
507 let removed: Vec<T> = self.0.items.borrow()[index..index + count].to_vec();
508 self.modify(|items| {
509 items.drain(index..index + count);
510 });
511 self.notify_remove(&removed, index);
512 }
513
514 pub fn clear(&self) {
516 if self.count() > 0 {
517 let subscribed = self.has_collection_changed_subscribers();
518 let removed = subscribed && self.0.reset_behavior.get() == ResetBehavior::Remove;
519 let old = {
523 let mut items = self.0.items.borrow_mut();
524 match Rc::get_mut(&mut items) {
525 Some(inner) => {
526 let old = if removed { inner.clone() } else { Vec::new() };
527 inner.clear();
528 Rc::new(old)
529 }
530 None => std::mem::replace(&mut *items, Rc::new(Vec::new())),
531 }
532 };
533 if subscribed {
534 match self.0.reset_behavior.get() {
535 ResetBehavior::Reset => self.notify(NotifyCollectionChangedEventArgs {
536 action: NotifyCollectionChangedAction::Reset,
537 new_items: &[],
538 old_items: &[],
539 new_starting_index: -1,
540 old_starting_index: -1,
541 }),
542 ResetBehavior::Remove => self.notify(NotifyCollectionChangedEventArgs {
543 action: NotifyCollectionChangedAction::Remove,
544 new_items: &[],
545 old_items: &old,
546 new_starting_index: -1,
547 old_starting_index: 0,
548 }),
549 }
550 }
551
552 self.notify_count_changed();
553 }
554 }
555
556 pub fn copy_to(&self, array: &mut [T], array_index: usize) {
561 let items = self.0.items.borrow();
562 assert!(
563 array_index <= array.len() && array.len() - array_index >= items.len(),
564 "Destination array was not long enough. Check the destination index, length, and the array's lower bounds."
565 );
566 array[array_index..array_index + items.len()].clone_from_slice(&items);
567 }
568
569 pub fn get_range(&self, index: usize, count: usize) -> Vec<T> {
571 self.0.items.borrow()[index..index + count].to_vec()
572 }
573
574 pub fn ensure_capacity(&self, capacity: usize) {
576 let current_capacity = self.capacity();
578 if current_capacity < capacity {
579 let mut new_capacity = if current_capacity == 0 { 4 } else { current_capacity * 2 };
580
581 if new_capacity < capacity {
582 new_capacity = capacity;
583 }
584
585 self.set_capacity(new_capacity);
586 }
587 }
588
589 fn validate_item(&self, item: &T) {
590 let validator = self.0.validator.borrow().clone();
591 match validator {
592 None => {}
593 Some(ListValidator::Item(validator)) => validator.validate(item),
594 Some(ListValidator::Other(validator)) => validator.validate(item),
595 }
596 }
597
598 fn modify(&self, f: impl FnOnce(&mut Vec<T>)) {
599 let mut items = self.0.items.borrow_mut();
600 f(Rc::make_mut(&mut items));
601 }
602
603 fn notify(&self, e: NotifyCollectionChangedEventArgs<'_, T>) {
604 raise_collection_changed(&self.0.collection_changed, &self.0.untyped_collection_changed, &e);
605 }
606
607 fn notify_add(&self, items: &[T], index: usize) {
608 self.notify(NotifyCollectionChangedEventArgs {
609 action: NotifyCollectionChangedAction::Add,
610 new_items: items,
611 old_items: &[],
612 new_starting_index: index as i32,
613 old_starting_index: -1,
614 });
615
616 self.notify_count_changed();
617 }
618
619 fn notify_count_changed(&self) {
620 self.0.property_changed.raise("Count");
621 }
622
623 fn notify_remove(&self, items: &[T], index: usize) {
624 self.notify(NotifyCollectionChangedEventArgs {
625 action: NotifyCollectionChangedAction::Remove,
626 new_items: &[],
627 old_items: items,
628 new_starting_index: -1,
629 old_starting_index: index as i32,
630 });
631
632 self.notify_count_changed();
633 }
634}
635
636pub(super) fn raise_collection_changed<T>(
639 typed: &HandlerList<CollectionChangedHandler<T>>,
640 untyped: &Event<CollectionChange>,
641 e: &NotifyCollectionChangedEventArgs<'_, T>,
642) {
643 if typed.is_empty() && !untyped.has_handlers() {
644 return;
645 }
646 let typed = typed.snapshot();
647 let untyped = untyped.snapshot();
648 let change = CollectionChange {
649 action: e.action,
650 new_starting_index: e.new_starting_index,
651 new_count: e.new_items.len(),
652 old_starting_index: e.old_starting_index,
653 old_count: e.old_items.len(),
654 };
655 let (mut i, mut j) = (0, 0);
656 while i < typed.len() || j < untyped.len() {
657 if j == untyped.len() || (i < typed.len() && typed[i].0 < untyped[j].0) {
658 (typed[i].1)(e);
659 i += 1;
660 } else {
661 (untyped[j].1)(&change);
662 j += 1;
663 }
664 }
665}
666
667impl<T> INotifyCollectionChanged for FerroList<T> {
668 fn collection_changed(&self) -> &Event<CollectionChange> {
669 &self.0.untyped_collection_changed
670 }
671}
672
673impl<T> INotifyPropertyChanged for FerroList<T> {
674 fn property_changed(&self) -> &Event<str> {
675 &self.0.property_changed
676 }
677}
678
679impl<T: Clone + PartialEq> FerroList<T> {
680 pub fn contains(&self, item: &T) -> bool {
682 self.0.items.borrow().contains(item)
683 }
684
685 pub fn index_of(&self, item: &T) -> Option<usize> {
687 self.0.items.borrow().iter().position(|i| i == item)
688 }
689
690 pub fn remove(&self, item: &T) -> bool {
692 match self.index_of(item) {
693 Some(index) => {
694 self.remove_at(index);
695 true
696 }
697 None => false,
698 }
699 }
700
701 pub fn remove_all(&self, items: impl IntoIterator<Item = T>) {
706 let h_items: Vec<T> = items.into_iter().collect();
711 let mut counter = 0;
712
713 let mut i = self.count();
714 while i > 0 {
715 i -= 1;
716 let contained = h_items.contains(&self.0.items.borrow()[i]);
717 if contained {
718 counter += 1;
719 } else if counter > 0 {
720 self.remove_range(i + 1, counter);
721 counter = 0;
722 }
723 }
724
725 if counter > 0 {
726 self.remove_range(0, counter);
727 }
728 }
729}
730
731#[cfg(test)]
732mod tests {
733 #[test]
739 fn capacity_is_read_and_set() {
740 let list = super::FerroList::from_items([1, 2, 3]);
741 list.set_capacity(16);
742 assert_eq!(list.capacity(), 16);
743 assert_eq!(list.to_vec(), vec![1, 2, 3]);
744 let snapshot = list.snapshot();
746 list.set_capacity(3);
747 assert_eq!(list.capacity(), 3);
748 assert_eq!(*snapshot, vec![1, 2, 3]);
749 }
750
751 #[test]
752 #[should_panic(expected = "capacity was less than the current size.")]
753 fn capacity_below_the_count_is_rejected() {
754 super::FerroList::from_items([1, 2, 3]).set_capacity(2);
755 }
756
757 use super::*;
758
759 fn tracked(list: &FerroList<i32>) -> Rc<RefCell<Vec<(NotifyCollectionChangedAction, Vec<i32>, Vec<i32>, i32, i32)>>> {
760 let log = Rc::new(RefCell::new(Vec::new()));
761 let l = log.clone();
762 list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, i32>| {
763 l.borrow_mut().push((
764 e.action,
765 e.new_items.to_vec(),
766 e.old_items.to_vec(),
767 e.new_starting_index,
768 e.old_starting_index,
769 ));
770 }));
771 log
772 }
773
774 type Logged = (NotifyCollectionChangedAction, Vec<i32>, Vec<i32>, i32, i32);
775
776 fn assert_event(items: &FerroList<i32>, action: impl FnOnce(), expected_events: &[Logged]) {
778 let log = Rc::new(RefCell::new(Vec::new()));
779 let l = log.clone();
780 let token = items.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, i32>| {
781 l.borrow_mut().push((e.action, e.new_items.to_vec(), e.old_items.to_vec(), e.new_starting_index, e.old_starting_index));
782 }));
783 action();
784 items.remove_collection_changed(token);
785 assert_eq!(*log.borrow(), expected_events);
786 }
787
788 fn strings(items: impl IntoIterator<Item = String>) -> FerroList<String> {
789 FerroList::from_items(items)
790 }
791
792 fn removals(list: &FerroList<String>) -> Rc<RefCell<Vec<(NotifyCollectionChangedAction, i32, Vec<String>)>>> {
793 let log = Rc::new(RefCell::new(Vec::new()));
794 let l = log.clone();
795 list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, String>| {
796 l.borrow_mut().push((e.action, e.old_starting_index, e.old_items.to_vec()));
797 }));
798 log
799 }
800
801 #[test]
802 fn items_passed_to_constructor_should_appear_in_list() {
803 let items = [1, 2, 3];
804 let target = FerroList::from_items(items);
805
806 assert_eq!(target.to_vec(), items);
807 }
808
809 #[test]
810 #[should_panic]
811 fn insert_range_past_end_should_throw_exception() {
812 let target = FerroList::<i32>::new();
813
814 target.insert_range(1, vec![1]);
815 }
816
817 #[test]
818 fn move_should_move_one_item() {
819 let target = FerroList::from_items([1, 2, 3]);
820
821 assert_event(&target, || target.move_item(0, 1), &[(NotifyCollectionChangedAction::Move, vec![1], vec![1], 1, 0)]);
822
823 assert_eq!(target.to_vec(), [2, 1, 3]);
824 }
825
826 #[test]
827 fn move_should_update_collection() {
828 let target = FerroList::from_items([1, 2, 3]);
829
830 target.move_item(2, 0);
831
832 assert_eq!(target.to_vec(), [3, 1, 2]);
833 }
834
835 #[test]
836 fn move_range_should_update_collection() {
837 let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
838
839 target.move_range(4, 3, 0);
840
841 assert_eq!(target.to_vec(), [5, 6, 7, 1, 2, 3, 4, 8, 9, 10]);
842 }
843
844 #[test]
845 fn move_range_can_move_to_end() {
846 let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
847
848 target.move_range(0, 5, 9);
849
850 assert_eq!(target.to_vec(), [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]);
851 }
852
853 #[test]
854 fn move_range_raises_correct_collection_changed_event() {
855 let target = FerroList::from_items([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
856
857 let moved = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
858 assert_event(
859 &target,
860 || target.move_range(0, 9, 9),
861 &[(NotifyCollectionChangedAction::Move, moved.clone(), moved, 9, 0)],
862 );
863
864 assert_eq!(target.to_vec(), [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
865 }
866
867 #[test]
868 fn move_range_should_move_one_item() {
869 let target = FerroList::from_items([1, 2, 3]);
870
871 assert_event(&target, || target.move_range(0, 1, 1), &[(NotifyCollectionChangedAction::Move, vec![1], vec![1], 1, 0)]);
872
873 assert_eq!(target.to_vec(), [2, 1, 3]);
874 }
875
876 #[test]
877 fn adding_item_should_raise_collection_changed() {
878 let target = FerroList::from_items([1, 2]);
879 let log = tracked(&target);
880
881 target.add(3);
882
883 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3], vec![], 2, -1)]);
884 }
885
886 #[test]
887 fn adding_items_should_raise_collection_changed() {
888 let target = FerroList::from_items([1, 2]);
889 let log = tracked(&target);
890
891 target.add_range([3, 4]);
892
893 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3, 4], vec![], 2, -1)]);
894 }
895
896 #[test]
897 fn add_range_i_enumerable_should_raise_count_property_changed() {
898 let target = FerroList::from_items([1, 2, 3, 4, 5]);
899 let raised = Rc::new(Cell::new(false));
900 let r = raised.clone();
901 let t = target.clone();
902
903 target.property_changed().add(Rc::new(move |e: &str| {
904 assert_eq!(e, "Count");
905 assert_eq!(t.count(), 7);
906 r.set(true);
907 }));
908
909 target.add_range(6..8);
910
911 assert!(raised.get());
912 }
913
914 #[test]
915 fn add_range_items_should_raise_correct_collection_changed() {
916 let target = FerroList::<Rc<i32>>::new();
917
918 let event_items = Rc::new(RefCell::new(Vec::new()));
919 let e = event_items.clone();
920
921 target.add_collection_changed(Rc::new(move |args: &NotifyCollectionChangedEventArgs<'_, Rc<i32>>| {
922 e.borrow_mut().extend(args.new_items.iter().cloned());
923 }));
924
925 target.add_range((0..10).map(Rc::new));
926
927 let event_items = event_items.borrow();
928 let target = target.to_vec();
929 assert_eq!(event_items.len(), target.len());
930 assert!(event_items.iter().zip(&target).all(|(a, b)| Rc::ptr_eq(a, b)));
931 }
932
933 #[test]
934 fn replacing_item_should_raise_collection_changed() {
935 let target = FerroList::from_items([1, 2]);
936 let log = tracked(&target);
937
938 target.set(1, 3);
939
940 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Replace, vec![3], vec![2], 1, 1)]);
941 }
942
943 #[test]
944 fn inserting_item_should_raise_collection_changed() {
945 let target = FerroList::from_items([1, 2]);
946 let log = tracked(&target);
947
948 target.insert(1, 3);
949
950 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3], vec![], 1, -1)]);
951 }
952
953 #[test]
954 fn inserting_items_should_raise_collection_changed() {
955 let target = FerroList::from_items([1, 2]);
956 let log = tracked(&target);
957
958 target.insert_range(1, [3, 4]);
959
960 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, vec![3, 4], vec![], 1, -1)]);
961 }
962
963 #[test]
964 fn removing_item_should_raise_collection_changed() {
965 let target = FerroList::from_items([1, 2, 3]);
966 let log = tracked(&target);
967
968 target.remove(&3);
969
970 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, vec![], vec![3], -1, 2)]);
971 }
972
973 #[test]
974 fn moving_item_should_raise_collection_changed() {
975 let target = FerroList::from_items([1, 2, 3]);
976 let log = tracked(&target);
977
978 target.move_item(2, 0);
979
980 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Move, vec![3], vec![3], 0, 2)]);
981 }
982
983 #[test]
984 fn moving_items_should_raise_collection_changed() {
985 let target = FerroList::from_items([1, 2, 3]);
986 let log = tracked(&target);
987
988 target.move_range(1, 2, 0);
989
990 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Move, vec![2, 3], vec![2, 3], 0, 1)]);
991 }
992
993 #[test]
994 fn clearing_items_should_raise_collection_changed_reset() {
995 let target = FerroList::from_items([1, 2, 3]);
996 let log = tracked(&target);
997
998 target.clear();
999
1000 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Reset, vec![], vec![], -1, -1)]);
1001 }
1002
1003 #[test]
1004 fn clearing_items_should_raise_collection_changed_remove() {
1005 let target = FerroList::from_items([1, 2, 3]);
1006 target.set_reset_behavior(ResetBehavior::Remove);
1007 let log = tracked(&target);
1008
1009 target.clear();
1010
1011 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, vec![], vec![1, 2, 3], -1, 0)]);
1012 }
1013
1014 #[test]
1015 fn can_copy_to_array_of_same_type() {
1016 let target = FerroList::from_items(["foo", "bar", "baz"].map(String::from));
1017 let mut result = vec![String::new(); 3];
1018
1019 target.copy_to(&mut result, 0);
1020
1021 assert_eq!(target.to_vec(), result);
1022 }
1023
1024 #[test]
1025 fn remove_all_should_send_single_notification_for_sequential_range() {
1026 let target = strings((0..10).map(|x| format!("Item {x}")));
1027 let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
1028 let log = removals(&target);
1029
1030 target.remove_all(to_remove.clone());
1031
1032 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 5, to_remove.to_vec())]);
1033 }
1034
1035 #[test]
1036 fn remove_all_should_send_single_notification_for_sequential_range_with_duplicate_source_items() {
1037 let target = strings((0..20).map(|x| format!("Item {}", x / 2)));
1038 let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
1039 let log = removals(&target);
1040
1041 target.remove_all(to_remove);
1042
1043 let expected = ["Item 5", "Item 5", "Item 6", "Item 6", "Item 7", "Item 7"].map(String::from).to_vec();
1044 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 10, expected)]);
1045 }
1046
1047 #[test]
1048 fn remove_all_should_send_multiple_notifications_for_non_sequential_range() {
1049 let target = strings((0..10).map(|x| format!("Item {x}")));
1050 let to_remove = [["Item 2", "Item 3"].map(String::from), ["Item 5", "Item 6"].map(String::from)];
1051 let log = removals(&target);
1052
1053 target.remove_all(to_remove.concat());
1054
1055 assert_eq!(
1056 *log.borrow(),
1057 vec![
1058 (NotifyCollectionChangedAction::Remove, 5, to_remove[1].to_vec()),
1059 (NotifyCollectionChangedAction::Remove, 2, to_remove[0].to_vec()),
1060 ]
1061 );
1062 }
1063
1064 #[test]
1065 fn remove_all_should_send_multiple_notifications_for_sequential_range_with_nonsequential_duplicate_source_items() {
1066 let items: Vec<String> = (0..10).map(|x| format!("Item {x}")).collect();
1067 let target = strings(items.iter().chain(&items).cloned());
1068 let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
1069 let log = removals(&target);
1070
1071 target.remove_all(to_remove.clone());
1072
1073 assert_eq!(
1074 *log.borrow(),
1075 vec![
1076 (NotifyCollectionChangedAction::Remove, 15, to_remove.to_vec()),
1077 (NotifyCollectionChangedAction::Remove, 5, to_remove.to_vec()),
1078 ]
1079 );
1080 }
1081
1082 #[test]
1083 fn remove_all_should_not_send_notification_for_items_not_present() {
1084 let target = strings((0..10).map(|x| format!("Item {x}")));
1085 let to_remove = ["Item 5", "Item 6", "Item 7", "Not present"].map(String::from);
1086 let log = removals(&target);
1087
1088 target.remove_all(to_remove.clone());
1089
1090 assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Remove, 5, to_remove[..3].to_vec())]);
1091 }
1092
1093 #[test]
1094 fn remove_all_should_handle_empty_list() {
1095 let target = FerroList::<String>::new();
1096 let to_remove = ["Item 5", "Item 6", "Item 7"].map(String::from);
1097 let log = removals(&target);
1098
1099 target.remove_all(to_remove);
1100
1101 assert!(log.borrow().is_empty());
1102 }
1103
1104 #[test]
1107 fn typed_and_untyped_handlers_run_in_subscription_order() {
1108 let target = FerroList::from_items([1]);
1109 let log = Rc::new(RefCell::new(Vec::new()));
1110 let (a, b, c) = (log.clone(), log.clone(), log.clone());
1111 target.collection_changed().add(Rc::new(move |_: &CollectionChange| a.borrow_mut().push("untyped 1")));
1112 target.add_collection_changed(Rc::new(move |_: &NotifyCollectionChangedEventArgs<'_, i32>| b.borrow_mut().push("typed")));
1113 target.collection_changed().add(Rc::new(move |_: &CollectionChange| c.borrow_mut().push("untyped 2")));
1114
1115 target.add(2);
1116
1117 assert_eq!(*log.borrow(), ["untyped 1", "typed", "untyped 2"]);
1118 }
1119
1120 #[test]
1121 fn can_modify_list_while_enumerating_snapshot() {
1122 let target = FerroList::from_items([1, 2, 3]);
1123 let mut seen = Vec::new();
1124 target.for_each(|i| {
1125 seen.push(*i);
1126 target.remove(i);
1127 });
1128 assert_eq!(seen, vec![1, 2, 3]);
1129 assert!(target.is_empty());
1130 }
1131}