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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum ResetBehavior {
13 #[default]
15 Reset,
16 Remove,
18}
19
20pub type CollectionChangedHandler<T> = dyn for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, T>);
22
23pub struct FerroList<T>(Rc<FerroListData<T>>);
32
33struct FerroListData<T> {
34 items: RefCell<Rc<Vec<T>>>,
35 collection_changed: HandlerList<CollectionChangedHandler<T>>,
36 untyped_collection_changed: Event<CollectionChange>,
41 property_changed: Event<str>,
42 validator: RefCell<Option<ListValidator<T>>>,
43 reset_behavior: Cell<ResetBehavior>,
44}
45
46enum 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
74pub 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 #[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 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 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 pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<T>>) -> u64 {
156 self.0.collection_changed.add(handler)
157 }
158
159 pub fn remove_collection_changed(&self, token: u64) -> bool {
161 self.0.collection_changed.remove(token)
162 }
163
164 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 #[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 pub fn capacity(&self) -> usize {
182 self.0.items.borrow().capacity()
183 }
184
185 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 let mut resized = Vec::with_capacity(capacity);
198 resized.extend(items.iter().cloned());
199 *items = Rc::new(resized);
200 }
201
202 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 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 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 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 #[inline]
265 pub fn get(&self, index: usize) -> T {
266 self.0.items.borrow()[index].clone()
267 }
268
269 #[inline]
271 pub fn try_get(&self, index: usize) -> Option<T> {
272 self.0.items.borrow().get(index).cloned()
273 }
274
275 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 #[inline]
297 pub fn snapshot(&self) -> Rc<Vec<T>> {
298 self.0.items.borrow().clone()
299 }
300
301 #[cfg(test)]
304 pub(crate) fn inner_for_tests(&self) -> *const Vec<T> {
305 Rc::as_ptr(&self.0.items.borrow())
306 }
307
308 pub fn to_vec(&self) -> Vec<T> {
310 self.0.items.borrow().as_ref().clone()
311 }
312
313 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 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 pub fn add_range(&self, items: impl IntoIterator<Item = T>) {
333 self.insert_range(self.count(), items)
334 }
335
336 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 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 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 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 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 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 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 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 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 pub fn get_range(&self, index: usize, count: usize) -> Vec<T> {
473 self.0.items.borrow()[index..index + count].to_vec()
474 }
475
476 pub fn ensure_capacity(&self, capacity: usize) {
478 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
538pub(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 pub fn contains(&self, item: &T) -> bool {
584 self.0.items.borrow().contains(item)
585 }
586
587 pub fn index_of(&self, item: &T) -> Option<usize> {
589 self.0.items.borrow().iter().position(|i| i == item)
590 }
591
592 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 pub fn remove_all(&self, items: impl IntoIterator<Item = T>) {
608 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 #[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 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 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 #[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}