diff --git a/eyeball-im-util/CHANGELOG.md b/eyeball-im-util/CHANGELOG.md index b48f2e1..1c29c8c 100644 --- a/eyeball-im-util/CHANGELOG.md +++ b/eyeball-im-util/CHANGELOG.md @@ -1,3 +1,11 @@ +# unreleased + +- Remove lifetime parameter from `SortBy` +- Allow `SortBy::new` and `VectorObserverExt::sort_by` to accept a callable + directly, instead of through a reference (references still work since `&F` + also implements `Fn(X) -> Y` if `F` does) +- Add `Sort`, `SortByKey` adapters and corresponding `VectorObserverExt` methods + # 0.5.3 - Add the `SortBy` adapter diff --git a/eyeball-im-util/src/vector.rs b/eyeball-im-util/src/vector.rs index 8e14009..59b6760 100644 --- a/eyeball-im-util/src/vector.rs +++ b/eyeball-im-util/src/vector.rs @@ -13,7 +13,7 @@ use self::ops::{VectorDiffContainerFamilyMember, VectorDiffContainerOps}; pub use self::{ filter::{Filter, FilterMap}, limit::{EmptyLimitStream, Limit}, - sort::SortBy, + sort::{Sort, SortBy, SortByKey}, traits::{ BatchedVectorSubscriber, VectorDiffContainer, VectorObserver, VectorObserverExt, VectorSubscriberExt, diff --git a/eyeball-im-util/src/vector/sort.rs b/eyeball-im-util/src/vector/sort.rs index 557225a..9c7933a 100644 --- a/eyeball-im-util/src/vector/sort.rs +++ b/eyeball-im-util/src/vector/sort.rs @@ -25,24 +25,11 @@ pin_project! { /// use eyeball_im::{ObservableVector, VectorDiff}; /// use eyeball_im_util::vector::VectorObserverExt; /// use imbl::vector; - /// use std::cmp::Ordering; /// use stream_assert::{assert_closed, assert_next_eq, assert_pending}; /// - /// // A comparison function that is used to sort our - /// // `ObservableVector` values. - /// fn cmp(left: &T, right: &T) -> Ordering - /// where - /// T: Ord, - /// { - /// left.cmp(right) - /// } - /// - /// # fn main() { /// // Our vector. /// let mut ob = ObservableVector::::new(); - /// let (values, mut sub) = ob.subscribe().sort_by(&cmp); - /// // ^^^^ - /// // | our comparison function + /// let (values, mut sub) = ob.subscribe().sort(); /// /// assert!(values.is_empty()); /// assert_pending!(sub); @@ -53,11 +40,11 @@ pin_project! { /// assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'd', 'e'] }); /// /// // Let's recap what we have. `ob` is our `ObservableVector`, - /// // `sub` is the “sorted view”/“sorted stream” of `ob`: + /// // `sub` is the “sorted view” / “sorted stream” of `ob`: /// // | `ob` | d b e | /// // | `sub` | b d e | /// - /// // Append other multiple values. + /// // Append multiple other values. /// ob.append(vector!['f', 'g', 'a', 'c']); /// // We get three `VectorDiff`s! /// assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); @@ -73,25 +60,174 @@ pin_project! { /// // | with `VectorDiff::Insert { index: 2, .. }` /// // with `VectorDiff::PushFront { .. }` /// - /// // Technically, `SortBy` emits `VectorDiff`s that mimic a sorted `Vector`. + /// // Technically, `Sort` emits `VectorDiff`s that mimic a sorted `Vector`. /// /// drop(ob); /// assert_closed!(sub); - /// # } /// ``` /// /// [`ObservableVector`]: eyeball_im::ObservableVector - pub struct SortBy<'a, S, F> + pub struct Sort where S: Stream, S::Item: VectorDiffContainer, { - // The main stream to poll items from. #[pin] + inner: SortImpl, + } +} + +impl Sort +where + S: Stream, + S::Item: VectorDiffContainer, + VectorDiffContainerStreamElement: Ord, +{ + /// Create a new `Sort` with the given (unsorted) initial values and stream + /// of `VectorDiff` updates for those values. + pub fn new( + initial_values: Vector>, inner_stream: S, + ) -> (Vector>, Self) { + let (initial_sorted, inner) = SortImpl::new(initial_values, inner_stream, Ord::cmp); + (initial_sorted, Self { inner }) + } +} + +impl Stream for Sort +where + S: Stream, + S::Item: VectorDiffContainer, + VectorDiffContainerStreamElement: Ord, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + self.project().inner.poll_next(cx, Ord::cmp) + } +} + +pin_project! { + /// A [`VectorDiff`] stream adapter that presents a sorted view of the + /// underlying [`ObservableVector`] items. + /// + /// Sorting is done using a custom comparison function. Otherwise this + /// adapter works exactly like [`Sort`], see that type's documentation for + /// details on how this adapter operates. + /// + /// [`ObservableVector`]: eyeball_im::ObservableVector + pub struct SortBy + where + S: Stream, + S::Item: VectorDiffContainer, + { + #[pin] + inner: SortImpl, // The comparison function to sort items. - compare: &'a F, + compare: F, + } +} + +impl SortBy +where + S: Stream, + S::Item: VectorDiffContainer, + F: Fn(&VectorDiffContainerStreamElement, &VectorDiffContainerStreamElement) -> Ordering, +{ + /// Create a new `SortBy` with the given (unsorted) initial values, stream + /// of `VectorDiff` updates for those values, and the comparison function. + pub fn new( + initial_values: Vector>, + inner_stream: S, + compare: F, + ) -> (Vector>, Self) { + let (initial_sorted, inner) = SortImpl::new(initial_values, inner_stream, &compare); + (initial_sorted, Self { inner, compare }) + } +} + +impl Stream for SortBy +where + S: Stream, + S::Item: VectorDiffContainer, + F: Fn(&VectorDiffContainerStreamElement, &VectorDiffContainerStreamElement) -> Ordering, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx, &*this.compare) + } +} + +pin_project! { + /// A [`VectorDiff`] stream adapter that presents a sorted view of the + /// underlying [`ObservableVector`] items. + /// + /// Sorting is done by transforming items to a key with a custom function + /// and comparing those. Otherwise this adapter works exactly like [`Sort`], + /// see that type's documentation for details on how this adapter operates. + /// + /// [`ObservableVector`]: eyeball_im::ObservableVector + pub struct SortByKey + where + S: Stream, + S::Item: VectorDiffContainer, + { + #[pin] + inner: SortImpl, + + // The function to convert an item to a key used for comparison. + key_fn: F, + } +} + +impl SortByKey +where + S: Stream, + S::Item: VectorDiffContainer, + F: Fn(&VectorDiffContainerStreamElement) -> K, + K: Ord, +{ + /// Create a new `SortByKey` with the given (unsorted) initial values, + /// stream of `VectorDiff` updates for those values, and the key function. + pub fn new( + initial_values: Vector>, + inner_stream: S, + key_fn: F, + ) -> (Vector>, Self) { + let (initial_sorted, inner) = + SortImpl::new(initial_values, inner_stream, |a, b| key_fn(a).cmp(&key_fn(b))); + (initial_sorted, Self { inner, key_fn }) + } +} + +impl Stream for SortByKey +where + S: Stream, + S::Item: VectorDiffContainer, + F: Fn(&VectorDiffContainerStreamElement) -> K, + K: Ord, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + let this = self.project(); + let key_fn = &*this.key_fn; + this.inner.poll_next(cx, |a, b| key_fn(a).cmp(&key_fn(b))) + } +} + +pin_project! { + pub struct SortImpl + where + S: Stream, + S::Item: VectorDiffContainer, + { + // The main stream to poll items from. + #[pin] + inner_stream: S, // This is the **sorted** buffered vector. buffered_vector: Vector<(UnsortedIndex, VectorDiffContainerStreamElement)>, @@ -105,19 +241,22 @@ pin_project! { } } -impl<'a, S, F> SortBy<'a, S, F> +impl SortImpl where S: Stream, S::Item: VectorDiffContainer, - F: Fn(&VectorDiffContainerStreamElement, &VectorDiffContainerStreamElement) -> Ordering, { - /// Create a new `SortBy` with the given (unsorted) initial values, stream - /// of `VectorDiff` updates for those values, and the comparison function. - pub fn new( + fn new( initial_values: Vector>, inner_stream: S, - compare: &'a F, - ) -> (Vector>, Self) { + compare: F, + ) -> (Vector>, Self) + where + F: Fn( + &VectorDiffContainerStreamElement, + &VectorDiffContainerStreamElement, + ) -> Ordering, + { let mut initial_values = initial_values.into_iter().enumerate().collect::>(); initial_values.sort_by(|(_, left), (_, right)| compare(left, right)); @@ -125,23 +264,24 @@ where initial_values.iter().map(|(_, value)| value.clone()).collect(), Self { inner_stream, - compare, buffered_vector: initial_values, ready_values: Default::default(), }, ) } -} -impl<'a, S, F> Stream for SortBy<'a, S, F> -where - S: Stream, - S::Item: VectorDiffContainer, - F: Fn(&VectorDiffContainerStreamElement, &VectorDiffContainerStreamElement) -> Ordering, -{ - type Item = S::Item; - - fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_next( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + compare: F, + ) -> Poll> + where + F: Fn( + &VectorDiffContainerStreamElement, + &VectorDiffContainerStreamElement, + ) -> Ordering + + Copy, + { let mut this = self.project(); loop { @@ -157,7 +297,7 @@ where // Consume and apply the diffs if possible. let ready = diffs.push_into_sort_buf(this.ready_values, |diff| { - handle_diff_and_update_buffered_vector(diff, this.compare, this.buffered_vector) + handle_diff_and_update_buffered_vector(diff, compare, this.buffered_vector) }); if let Some(diff) = ready { @@ -178,7 +318,7 @@ where /// `Iterator::position` is used. fn handle_diff_and_update_buffered_vector( diff: VectorDiff, - compare: &F, + compare: F, buffered_vector: &mut Vector<(usize, T)>, ) -> SmallVec<[VectorDiff; 2]> where diff --git a/eyeball-im-util/src/vector/traits.rs b/eyeball-im-util/src/vector/traits.rs index 599bc5a..e24c705 100644 --- a/eyeball-im-util/src/vector/traits.rs +++ b/eyeball-im-util/src/vector/traits.rs @@ -12,7 +12,7 @@ use super::{ ops::{ VecVectorDiffFamily, VectorDiffContainerFamily, VectorDiffContainerOps, VectorDiffFamily, }, - EmptyLimitStream, Filter, FilterMap, Limit, SortBy, + EmptyLimitStream, Filter, FilterMap, Limit, Sort, SortBy, SortByKey, }; /// Abstraction over stream items that the adapters in this module can deal @@ -160,16 +160,39 @@ where Limit::dynamic_with_initial_limit(items, stream, initial_limit, limit_stream) } - /// Sort the observed values with `compare`. + /// Sort the observed values. + /// + /// See [`Sort`] for more details. + fn sort(self) -> (Vector, Sort) + where + T: Ord, + { + let (items, stream) = self.into_parts(); + Sort::new(items, stream) + } + + /// Sort the observed values with the given comparison function. /// /// See [`SortBy`] for more details. - fn sort_by(self, compare: &F) -> (Vector, SortBy<'_, Self::Stream, F>) + fn sort_by(self, compare: F) -> (Vector, SortBy) where F: Fn(&T, &T) -> Ordering, { let (items, stream) = self.into_parts(); SortBy::new(items, stream, compare) } + + /// Sort the observed values with the given key function. + /// + /// See [`SortBy`] for more details. + fn sort_by_key(self, key_fn: F) -> (Vector, SortByKey) + where + F: Fn(&T) -> K, + K: Ord, + { + let (items, stream) = self.into_parts(); + SortByKey::new(items, stream, key_fn) + } } impl VectorObserverExt for O diff --git a/eyeball-im-util/tests/it/main.rs b/eyeball-im-util/tests/it/main.rs index a69bf91..749e075 100644 --- a/eyeball-im-util/tests/it/main.rs +++ b/eyeball-im-util/tests/it/main.rs @@ -2,3 +2,5 @@ mod filter; mod filter_map; mod limit; mod sort; +mod sort_by; +mod sort_by_key; diff --git a/eyeball-im-util/tests/it/sort.rs b/eyeball-im-util/tests/it/sort.rs index 604c29c..509f073 100644 --- a/eyeball-im-util/tests/it/sort.rs +++ b/eyeball-im-util/tests/it/sort.rs @@ -1,20 +1,12 @@ use eyeball_im::{ObservableVector, VectorDiff}; use eyeball_im_util::vector::VectorObserverExt; use imbl::vector; -use std::cmp::Ordering; use stream_assert::{assert_closed, assert_next_eq, assert_pending}; -fn cmp(left: &T, right: &T) -> Ordering -where - T: Ord, -{ - left.cmp(right) -} - #[test] fn new() { let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert_eq!(values, vector!['a', 'b', 'c', 'd']); assert_pending!(sub); @@ -26,7 +18,7 @@ fn new() { #[test] fn append() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -66,7 +58,7 @@ fn append() { #[test] fn clear() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -91,7 +83,7 @@ fn clear() { #[test] fn push_front() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -125,7 +117,7 @@ fn push_front() { #[test] fn push_back() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -159,7 +151,7 @@ fn push_back() { #[test] fn insert() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -198,7 +190,7 @@ fn insert() { #[test] fn pop_front() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -243,7 +235,7 @@ fn pop_front() { #[test] fn pop_back() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -294,7 +286,7 @@ fn pop_back() { #[test] fn remove() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -325,7 +317,7 @@ fn remove() { #[test] fn set() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -380,7 +372,7 @@ fn set() { #[test] fn truncate() { let mut ob = ObservableVector::::new(); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -413,7 +405,7 @@ fn truncate() { #[test] fn reset() { let mut ob = ObservableVector::::with_capacity(1); - let (values, mut sub) = ob.subscribe().sort_by(&cmp); + let (values, mut sub) = ob.subscribe().sort(); assert!(values.is_empty()); assert_pending!(sub); @@ -428,7 +420,7 @@ fn reset() { ob.push_back('f'); assert_next_eq!(sub, VectorDiff::Reset { values: vector!['a', 'b', 'c', 'd', 'f'] }); - // Items in the vector have been inserted and are not sorted. + // Items in the vector have been inserted and are not sorted. assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); drop(ob); diff --git a/eyeball-im-util/tests/it/sort_by.rs b/eyeball-im-util/tests/it/sort_by.rs new file mode 100644 index 0000000..f0ae341 --- /dev/null +++ b/eyeball-im-util/tests/it/sort_by.rs @@ -0,0 +1,402 @@ +use eyeball_im::{ObservableVector, VectorDiff}; +use eyeball_im_util::vector::VectorObserverExt; +use imbl::vector; +use std::cmp::Ordering; +use stream_assert::{assert_closed, assert_next_eq, assert_pending}; + +/// Reversed sorting function. +/// +/// `sort_by(rev_cmp)` is equivalent to `sort_by_key(std::cmp::Reverse)` +fn rev_cmp(left: &T, right: &T) -> Ordering +where + T: Ord, +{ + right.cmp(left) +} + +#[test] +fn new() { + let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert_eq!(values, vector!['d', 'c', 'b', 'a']); + assert_pending!(sub); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn append() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append on an empty vector. + ob.append(vector!['d', 'e', 'e']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'e', 'd'] }); + + // Append on an non-empty vector. + ob.append(vector!['f', 'g', 'c']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'g' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['c'] }); + + // Another append. + ob.append(vector!['i', 'h', 'j', 'a', 'b']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'j' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'i' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'h' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'a'] }); + + // Items in the vector have been appended and are not sorted. + assert_eq!(*ob, vector!['d', 'e', 'e', 'f', 'g', 'c', 'i', 'h', 'j', 'a', 'b']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn clear() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + ob.append(vector!['b', 'a', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['c', 'b', 'a'] }); + + assert_eq!(*ob, vector!['b', 'a', 'c']); + + // Let's clear it. + ob.clear(); + + assert_next_eq!(sub, VectorDiff::Clear); + + // Items in the vector has been cleared out. + assert!((*ob).is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn push_front() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Push front on an empty vector. + ob.push_front('b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Push front on non-empty vector. + // The new item should appear at the end. + ob.push_front('a'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); + + // Another push front. + // The new item should appear at the beginning. + ob.push_front('d'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); + + // Another push front. + // The new item should appear in the middle. + ob.push_front('c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'c' }); + + // Items in the vector have been pushed front and are not sorted. + assert_eq!(*ob, vector!['c', 'd', 'a', 'b']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn push_back() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Push back on an empty vector. + ob.push_back('b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Push back on non-empty vector. + // The new item should appear at the end. + ob.push_back('a'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); + + // Another push back. + // The new item should appear at the beginning. + ob.push_back('d'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); + + // Another push back. + // The new item should appear in the middle. + ob.push_back('c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'c' }); + + // Items in the vector have been pushed back and are not sorted. + assert_eq!(*ob, vector!['b', 'a', 'd', 'c']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn insert() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Insert on an empty vector. + ob.insert(0, 'b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Insert on non-empty vector. + // The new item should appear at the end. + ob.insert(1, 'a'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); + + // Another insert. + // The new item should appear at the beginning. + ob.insert(1, 'd'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); + + // Another insert. + // The new item should appear at the beginning. + ob.insert(1, 'e'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'e' }); + + // Another insert. + // The new item should appear in the middle. + ob.insert(3, 'c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); + + // Items in the vector have been inserted and are not sorted. + assert_eq!(*ob, vector!['b', 'e', 'd', 'c', 'a']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn pop_front() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'd', 'c', 'b', 'a'] }); + + // Pop front once. + assert_eq!(ob.pop_front(), Some('e')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop front again. + assert_eq!(ob.pop_front(), Some('b')); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + + // Pop front again. + assert_eq!(ob.pop_front(), Some('a')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop front again. + assert_eq!(ob.pop_front(), Some('d')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop front again. + assert_eq!(ob.pop_front(), Some('c')); + assert_next_eq!(sub, VectorDiff::PopFront); + + assert!(ob.is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn pop_back() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c', 'f']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['f', 'e', 'd', 'c', 'b', 'a'] }); + + // Pop back once. + // `f` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_back(), Some('f')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop back again. + assert_eq!(ob.pop_back(), Some('c')); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + + // Pop back again. + assert_eq!(ob.pop_back(), Some('d')); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + + // Pop back again. + assert_eq!(ob.pop_back(), Some('a')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop back again. + assert_eq!(ob.pop_back(), Some('b')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop back again. + assert_eq!(ob.pop_back(), Some('e')); + assert_next_eq!(sub, VectorDiff::PopFront); + + assert!(ob.is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn remove() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'd', 'c', 'b', 'a'] }); + + // Remove `a`. + ob.remove(2); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Remove `e`. + ob.remove(0); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Remove `c`. + ob.remove(2); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + + // Items in the vector have been removed and are not sorted. + assert_eq!(*ob, vector!['b', 'd']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn set() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['d', 'e', 'b', 'g']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['g', 'e', 'd', 'b'] }); + + // Same value. + ob.set(0, 'd'); + assert_next_eq!(sub, VectorDiff::Set { index: 2, value: 'd' }); + + // Different value but position stays the same. + ob.set(0, 'c'); + assert_next_eq!(sub, VectorDiff::Set { index: 2, value: 'c' }); + + // This time sorting moves the value. + // Another value, that is moved to the right. + ob.set(0, 'f'); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); + + // Same operation, at another index, just for fun. + ob.set(2, 'f'); + assert_next_eq!(sub, VectorDiff::Remove { index: 3 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); + + // Items in the vector have been updated and are not sorted. + assert_eq!(*ob, vector!['f', 'e', 'f', 'g']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn truncate() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['c', 'd', 'a']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['d', 'c', 'a'] }); + + // Append other items. + ob.append(vector!['b', 'e', 'f']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'f' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'e' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 4, value: 'b' }); + + // Truncate. + ob.truncate(2); + assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); + + // Items in the vector have been truncated and are not sorted. + assert_eq!(*ob, vector!['c', 'd']); + + // Append other items. + ob.append(vector!['b', 'x', 'y']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'y' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'x' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['b'] }); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn reset() { + let mut ob = ObservableVector::::with_capacity(1); + let (values, mut sub) = ob.subscribe().sort_by(&rev_cmp); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['c', 'd', 'a']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['d', 'c', 'a'] }); + + // Push back a bunch of items 3 times, so that it overflows the capacity, and we + // get a reset! + ob.push_back('b'); + ob.push_back('f'); + assert_next_eq!(sub, VectorDiff::Reset { values: vector!['f', 'd', 'c', 'b', 'a'] }); + + // Items in the vector have been inserted and are not sorted. + assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); + + drop(ob); + assert_closed!(sub); +} diff --git a/eyeball-im-util/tests/it/sort_by_key.rs b/eyeball-im-util/tests/it/sort_by_key.rs new file mode 100644 index 0000000..686f18d --- /dev/null +++ b/eyeball-im-util/tests/it/sort_by_key.rs @@ -0,0 +1,420 @@ +use eyeball_im::{ObservableVector, VectorDiff}; +use eyeball_im_util::vector::VectorObserverExt; +use imbl::vector; +use stream_assert::{assert_closed, assert_next_eq, assert_pending}; + +#[test] +fn new() { + let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert_eq!(values, vector!['a', 'b', 'c', 'd']); + assert_pending!(sub); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn append() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append on an empty vector. + ob.append(vector!['d', 'a', 'e']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'd', 'e'] }); + + // Append on an non-empty vector. + ob.append(vector!['f', 'g', 'b']); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['f', 'g'] }); + + // Another append. + // This time, it contains a duplicated new item + an insert + new items to be + // appended. + ob.append(vector!['i', 'h', 'c', 'j', 'a']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); + assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'c' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['h', 'i', 'j'] }); + + // Another append. + // This time, with two new items that are a duplication of the last item. + ob.append(vector!['k', 'l', 'j', 'm', 'j']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['j', 'j', 'k', 'l', 'm'] }); + + // Items in the vector have been appended and are not sorted. + assert_eq!( + *ob, + vector!['d', 'a', 'e', 'f', 'g', 'b', 'i', 'h', 'c', 'j', 'a', 'k', 'l', 'j', 'm', 'j'] + ); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn clear() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + ob.append(vector!['b', 'a', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c'] }); + + assert_eq!(*ob, vector!['b', 'a', 'c']); + + // Let's clear it. + ob.clear(); + + assert_next_eq!(sub, VectorDiff::Clear); + + // Items in the vector has been cleared out. + assert!((*ob).is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn push_front() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Push front on an empty vector. + ob.push_front('b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Push front on non-empty vector. + // The new item should appear at position 0. + ob.push_front('a'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); + + // Another push front. + // The new item should appear at last position. + ob.push_front('d'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); + + // Another push front. + // The new item should appear in the middle. + ob.push_front('c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); + + // Items in the vector have been pushed front and are not sorted. + assert_eq!(*ob, vector!['c', 'd', 'a', 'b']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn push_back() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Push back on an empty vector. + ob.push_back('b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Push back on non-empty vector. + // The new item should appear at position 0. + ob.push_back('a'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); + + // Another push back. + // The new item should appear at last position. + ob.push_back('d'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); + + // Another push back. + // The new item should appear in the middle. + ob.push_back('c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); + + // Items in the vector have been pushed back and are not sorted. + assert_eq!(*ob, vector!['b', 'a', 'd', 'c']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn insert() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Insert on an empty vector. + ob.insert(0, 'b'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + + // Insert on non-empty vector. + // The new item should appear at position 0. + ob.insert(1, 'a'); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); + + // Another insert. + // The new item should appear at last position. + ob.insert(1, 'd'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); + + // Another insert. + // The new item should appear at last position. + ob.insert(1, 'e'); + assert_next_eq!(sub, VectorDiff::PushBack { value: 'e' }); + + // Another insert. + // The new item should appear in the middle. + ob.insert(3, 'c'); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); + + // Items in the vector have been inserted and are not sorted. + assert_eq!(*ob, vector!['b', 'e', 'd', 'c', 'a']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn pop_front() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); + + // Pop front once. + // `e` is at the last sorted position, so it generates a `VectorDiff::PopBack`. + assert_eq!(ob.pop_front(), Some('e')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop front again. + // `b` is at the second sorted position, so it generates a `VectorDiff::Remove`. + assert_eq!(ob.pop_front(), Some('b')); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + + // Pop front again. + // `a` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_front(), Some('a')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop front again. + // `d` is at the last sorted position, so it generates a `VectorDiff::PopBack`. + assert_eq!(ob.pop_front(), Some('d')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop front again. + // `c` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_front(), Some('c')); + assert_next_eq!(sub, VectorDiff::PopFront); + + assert!(ob.is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn pop_back() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c', 'f']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e', 'f'] }); + + // Pop back once. + // `f` is at the last sorted position, so it generates a `VectorDiff::PopBack`. + assert_eq!(ob.pop_back(), Some('f')); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Pop back again. + // `c` is at the third sorted position, so it generates a `VectorDiff::Remove`. + assert_eq!(ob.pop_back(), Some('c')); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + + // Pop back again. + // `d` is at the third sorted position, so it generates a `VectorDiff::Remove`. + assert_eq!(ob.pop_back(), Some('d')); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + + // Pop back again. + // `a` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_back(), Some('a')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop back again. + // `b` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_back(), Some('b')); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Pop back again. + // `e` is at the first sorted position, so it generates a + // `VectorDiff::PopFront`. + assert_eq!(ob.pop_back(), Some('e')); + assert_next_eq!(sub, VectorDiff::PopFront); + + assert!(ob.is_empty()); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn remove() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['e', 'b', 'a', 'd', 'c']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); + + // Remove `a`. + ob.remove(2); + assert_next_eq!(sub, VectorDiff::PopFront); + + // Remove `e`. + ob.remove(0); + assert_next_eq!(sub, VectorDiff::PopBack); + + // Remove `c`. + ob.remove(2); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + + // Items in the vector have been removed and are not sorted. + assert_eq!(*ob, vector!['b', 'd']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn set() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['d', 'e', 'b', 'g']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'd', 'e', 'g'] }); + + // Same value. + ob.set(0, 'd'); + assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'd' }); + + // Another value, that is sorted at the same index. + ob.set(0, 'c'); + assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'c' }); + + // Another value, that is moved to the left. + ob.set(0, 'a'); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 'a' }); + + // Another value, that is moved to the right. + ob.set(0, 'f'); + assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'f' }); + + // Another value, that is moved to the right-most position. + ob.set(0, 'h'); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'h' }); + + // Same operation, at another index, just for fun. + ob.set(2, 'f'); + assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); + + // Items in the vector have been updated and are not sorted. + assert_eq!(*ob, vector!['h', 'e', 'f', 'g']); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn truncate() { + let mut ob = ObservableVector::::new(); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['c', 'd', 'a']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); + + // Append other items. + ob.append(vector!['b', 'e', 'f']); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'f'] }); + + // Truncate. + ob.truncate(2); + assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); + + // Items in the vector have been truncated and are not sorted. + assert_eq!(*ob, vector!['c', 'd']); + + // Append other items. + ob.append(vector!['b', 'x', 'y']); + assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['x', 'y'] }); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn reset() { + let mut ob = ObservableVector::::with_capacity(1); + let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); + + assert!(values.is_empty()); + assert_pending!(sub); + + // Append a bunch of items. + ob.append(vector!['c', 'd', 'a']); + assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); + + // Push back a bunch of items 3 times, so that it overflows the capacity, and we + // get a reset! + ob.push_back('b'); + ob.push_back('f'); + assert_next_eq!(sub, VectorDiff::Reset { values: vector!['a', 'b', 'c', 'd', 'f'] }); + + // Items in the vector have been inserted and are not sorted. + assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); + + drop(ob); + assert_closed!(sub); +}