use std::{
cmp::Ordering,
collections::{VecDeque, vec_deque::Iter},
iter::{Enumerate, Skip, Take},
ops::{Deref, RangeBounds},
sync::Arc,
};
use bitflags::bitflags;
use eyeball_im::{
ObservableVector, ObservableVectorEntries, ObservableVectorEntry, ObservableVectorTransaction,
ObservableVectorTransactionEntry, VectorSubscriber,
};
use imbl::Vector;
use ruma::EventId;
use super::{TimelineItem, metadata::EventMeta};
#[derive(Debug)]
pub struct ObservableItems {
items: ObservableVector<Arc<TimelineItem>>,
all_remote_events: AllRemoteEvents,
}
impl ObservableItems {
pub fn new() -> Self {
Self {
items: ObservableVector::with_capacity(32),
all_remote_events: AllRemoteEvents::default(),
}
}
pub fn all_remote_events(&self) -> &AllRemoteEvents {
&self.all_remote_events
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn subscribe(&self) -> VectorSubscriber<Arc<TimelineItem>> {
self.items.subscribe()
}
pub fn clone_items(&self) -> Vector<Arc<TimelineItem>> {
self.items.clone()
}
pub fn transaction(&mut self) -> ObservableItemsTransaction<'_> {
ObservableItemsTransaction {
items: self.items.transaction(),
all_remote_events: &mut self.all_remote_events,
}
}
pub fn replace(
&mut self,
timeline_item_index: usize,
timeline_item: Arc<TimelineItem>,
) -> Arc<TimelineItem> {
self.items.set(timeline_item_index, timeline_item)
}
pub fn entries(&mut self) -> ObservableItemsEntries<'_> {
ObservableItemsEntries(self.items.entries())
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(ObservableItemsEntry<'_>),
{
self.items.for_each(|entry| f(ObservableItemsEntry(entry)))
}
}
impl Deref for ObservableItems {
type Target = Vector<Arc<TimelineItem>>;
fn deref(&self) -> &Self::Target {
&self.items
}
}
pub struct ObservableItemsEntries<'a>(ObservableVectorEntries<'a, Arc<TimelineItem>>);
impl ObservableItemsEntries<'_> {
pub fn next(&mut self) -> Option<ObservableItemsEntry<'_>> {
self.0.next().map(ObservableItemsEntry)
}
}
#[derive(Debug)]
pub struct ObservableItemsEntry<'a>(ObservableVectorEntry<'a, Arc<TimelineItem>>);
impl ObservableItemsEntry<'_> {
pub fn replace(this: &mut Self, timeline_item: Arc<TimelineItem>) -> Arc<TimelineItem> {
ObservableVectorEntry::set(&mut this.0, timeline_item)
}
}
impl Deref for ObservableItemsEntry<'_> {
type Target = Arc<TimelineItem>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct ObservableItemsTransaction<'observable_items> {
items: ObservableVectorTransaction<'observable_items, Arc<TimelineItem>>,
all_remote_events: &'observable_items mut AllRemoteEvents,
}
impl<'observable_items> ObservableItemsTransaction<'observable_items> {
pub fn get(&self, timeline_item_index: usize) -> Option<&Arc<TimelineItem>> {
self.items.get(timeline_item_index)
}
pub fn all_remote_events(&self) -> &AllRemoteEvents {
self.all_remote_events
}
pub fn remove_remote_event(&mut self, event_index: usize) -> Option<EventMeta> {
self.all_remote_events.remove(event_index)
}
pub fn push_front_remote_event(&mut self, event_meta: EventMeta) {
self.all_remote_events.push_front(event_meta);
}
pub fn push_back_remote_event(&mut self, event_meta: EventMeta) {
self.all_remote_events.push_back(event_meta);
}
pub fn insert_remote_event(&mut self, event_index: usize, event_meta: EventMeta) {
self.all_remote_events.insert(event_index, event_meta);
}
pub fn get_remote_event_by_event_id_mut(
&mut self,
event_id: &EventId,
) -> Option<&mut EventMeta> {
self.all_remote_events.get_by_event_id_mut(event_id)
}
pub fn get_remote_event_by_event_id(&self, event_id: &EventId) -> Option<&EventMeta> {
self.all_remote_events.get_by_event_id(event_id)
}
pub fn position_by_event_id(&self, event_id: &EventId) -> Option<usize> {
self.all_remote_events.position_by_event_id(event_id)
}
pub fn replace(
&mut self,
timeline_item_index: usize,
timeline_item: Arc<TimelineItem>,
) -> Arc<TimelineItem> {
self.items.set(timeline_item_index, timeline_item)
}
pub fn remove(&mut self, timeline_item_index: usize) -> Arc<TimelineItem> {
let removed_timeline_item = self.items.remove(timeline_item_index);
self.all_remote_events.timeline_item_has_been_removed_at(timeline_item_index);
removed_timeline_item
}
pub fn insert(
&mut self,
timeline_item_index: usize,
timeline_item: Arc<TimelineItem>,
event_index: Option<usize>,
) {
self.items.insert(timeline_item_index, timeline_item);
self.all_remote_events.timeline_item_has_been_inserted_at(timeline_item_index, event_index);
}
pub fn push_front(&mut self, timeline_item: Arc<TimelineItem>, event_index: Option<usize>) {
self.items.push_front(timeline_item);
self.all_remote_events.timeline_item_has_been_inserted_at(0, event_index);
}
pub fn push_back(&mut self, timeline_item: Arc<TimelineItem>, event_index: Option<usize>) {
self.items.push_back(timeline_item);
self.all_remote_events
.timeline_item_has_been_inserted_at(self.items.len().saturating_sub(1), event_index);
}
pub fn push_local(&mut self, timeline_item: Arc<TimelineItem>) {
assert!(timeline_item.is_local_echo(), "The provided `timeline_item` is not a `Local`");
self.push_back(timeline_item, None);
}
pub fn push_date_divider(
&mut self,
timeline_item_index: usize,
timeline_item: Arc<TimelineItem>,
) {
assert!(
timeline_item.is_date_divider(),
"The provided `timeline_item` is not a `DateDivider`"
);
if timeline_item_index == 0 && !self.items.is_empty() {
assert!(
matches!(self.items.get(timeline_item_index), Some(timeline_item) if !timeline_item.is_timeline_start())
);
}
if timeline_item_index == self.len() {
self.push_back(timeline_item, None);
} else if timeline_item_index == 0 {
self.push_front(timeline_item, None);
} else {
self.insert(timeline_item_index, timeline_item, None);
}
}
pub fn push_timeline_start_if_missing(&mut self, timeline_item: Arc<TimelineItem>) {
assert!(
timeline_item.is_timeline_start(),
"The provided `timeline_item` is not a `TimelineStart`"
);
if self.get(0).is_some_and(|item| item.is_timeline_start()) {
return;
}
self.push_front(timeline_item, None);
}
pub fn clear(&mut self) {
self.items.clear();
self.all_remote_events.clear();
}
pub fn for_each<F>(&mut self, mut f: F)
where
F: FnMut(ObservableItemsTransactionEntry<'_, 'observable_items>),
{
self.items.for_each(|entry| {
f(ObservableItemsTransactionEntry { entry, all_remote_events: self.all_remote_events })
})
}
pub fn has_local(&self) -> bool {
matches!(self.items.last(), Some(timeline_item) if timeline_item.is_local_echo())
}
pub fn first_remotes_region_index(&self) -> usize {
if self.items.get(0).is_some_and(|item| item.is_timeline_start()) { 1 } else { 0 }
}
pub fn iter_remotes_region(&self) -> ObservableItemsTransactionIter<'_> {
ObservableItemsTransactionIterBuilder::new(&self.items).with_remotes().build()
}
pub fn iter_remotes_and_locals_regions(&self) -> ObservableItemsTransactionIter<'_> {
ObservableItemsTransactionIterBuilder::new(&self.items).with_remotes().with_locals().build()
}
pub fn iter_locals_region(&self) -> ObservableItemsTransactionIter<'_> {
ObservableItemsTransactionIterBuilder::new(&self.items).with_locals().build()
}
pub fn iter_all_regions(&self) -> ObservableItemsTransactionIter<'_> {
ObservableItemsTransactionIterBuilder::new(&self.items)
.with_start()
.with_remotes()
.with_locals()
.build()
}
#[allow(unused)] #[deprecated = "This method is now aliased to `Self::iter_all_regions`"]
pub fn iter(&self) -> ObservableItemsTransactionIter<'_> {
self.iter_all_regions()
}
pub fn commit(self) {
self.items.commit()
}
}
bitflags! {
struct Regions: u8 {
const START = 0b0000_0001;
const REMOTES = 0b0000_0010;
const LOCALS = 0b0000_0100;
}
}
struct ObservableItemsTransactionIterBuilder<'e> {
items: &'e ObservableVectorTransaction<'e, Arc<TimelineItem>>,
regions: Regions,
}
impl<'e> ObservableItemsTransactionIterBuilder<'e> {
fn new(items: &'e ObservableVectorTransaction<'e, Arc<TimelineItem>>) -> Self {
Self { items, regions: Regions::empty() }
}
fn with_start(mut self) -> Self {
self.regions.insert(Regions::START);
self
}
fn with_remotes(mut self) -> Self {
self.regions.insert(Regions::REMOTES);
self
}
fn with_locals(mut self) -> Self {
self.regions.insert(Regions::LOCALS);
self
}
#[allow(clippy::iter_skip_zero)]
fn build(self) -> ObservableItemsTransactionIter<'e> {
let size_of_start_region = if matches!(
self.items.get(0),
Some(first_timeline_item) if first_timeline_item.is_timeline_start()
) {
1
} else {
0
};
let size_of_locals_region = self
.items
.deref()
.iter()
.rev()
.take_while(|timeline_item| timeline_item.is_local_echo())
.count();
let size_of_remotes_region =
self.items.len() - size_of_start_region - size_of_locals_region;
let with_start = self.regions.contains(Regions::START);
let with_remotes = self.regions.contains(Regions::REMOTES);
let with_locals = self.regions.contains(Regions::LOCALS);
let iter = self.items.deref().iter().enumerate();
let inner = match (with_start, with_remotes, with_locals) {
(false, false, false) => iter.skip(0).take(0),
(true, false, false) => iter.skip(0).take(size_of_start_region),
(false, true, false) => iter.skip(size_of_start_region).take(size_of_remotes_region),
(true, true, false) => iter.skip(0).take(size_of_start_region + size_of_remotes_region),
(false, false, true) => {
iter.skip(size_of_start_region + size_of_remotes_region).take(size_of_locals_region)
}
(true, false, true) => unimplemented!(
"Iterating over the start and the locals regions is not implemented yet"
),
(false, true, true) => {
iter.skip(size_of_start_region).take(size_of_remotes_region + size_of_locals_region)
}
(true, true, true) => iter
.skip(0)
.take(size_of_start_region + size_of_remotes_region + size_of_locals_region),
};
ObservableItemsTransactionIter { inner }
}
}
pub(crate) struct ObservableItemsTransactionIter<'observable_items_transaction> {
#[allow(clippy::type_complexity)]
inner: Take<
Skip<
Enumerate<
imbl::vector::Iter<
'observable_items_transaction,
Arc<TimelineItem>,
imbl::shared_ptr::DefaultSharedPtr,
>,
>,
>,
>,
}
impl<'e> Iterator for ObservableItemsTransactionIter<'e> {
type Item = (usize, &'e Arc<TimelineItem>);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl ExactSizeIterator for ObservableItemsTransactionIter<'_> {
fn len(&self) -> usize {
self.inner.len()
}
}
impl DoubleEndedIterator for ObservableItemsTransactionIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back()
}
}
impl Deref for ObservableItemsTransaction<'_> {
type Target = Vector<Arc<TimelineItem>>;
fn deref(&self) -> &Self::Target {
&self.items
}
}
pub struct ObservableItemsTransactionEntry<'observable_transaction_items, 'observable_items> {
entry: ObservableVectorTransactionEntry<
'observable_transaction_items,
'observable_items,
Arc<TimelineItem>,
>,
all_remote_events: &'observable_transaction_items mut AllRemoteEvents,
}
impl ObservableItemsTransactionEntry<'_, '_> {
pub fn remove(this: Self) {
let entry_index = ObservableVectorTransactionEntry::index(&this.entry);
ObservableVectorTransactionEntry::remove(this.entry);
this.all_remote_events.timeline_item_has_been_removed_at(entry_index);
}
}
impl Deref for ObservableItemsTransactionEntry<'_, '_> {
type Target = Arc<TimelineItem>;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
#[cfg(test)]
mod observable_items_tests {
use std::ops::Not;
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use ruma::{
MilliSecondsSinceUnixEpoch,
events::room::message::{MessageType, TextMessageEventContent},
owned_user_id, uint,
};
use stream_assert::{assert_next_matches, assert_pending};
use super::*;
use crate::timeline::{
EventSendState, EventTimelineItem, Message, MsgLikeContent, MsgLikeKind, TimelineDetails,
TimelineItemContent, TimelineItemKind, TimelineUniqueId, VirtualTimelineItem,
controller::RemoteEventOrigin,
event_item::{EventTimelineItemKind, LocalEventTimelineItem, RemoteEventTimelineItem},
};
fn item(event_id: &str) -> Arc<TimelineItem> {
TimelineItem::new(
EventTimelineItem::new(
owned_user_id!("@ivan:mnt.io"),
TimelineDetails::Unavailable,
None,
None,
MilliSecondsSinceUnixEpoch(0u32.into()),
TimelineItemContent::MsgLike(MsgLikeContent {
kind: MsgLikeKind::Message(Message {
msgtype: MessageType::Text(TextMessageEventContent::plain("hello")),
edited: false,
mentions: None,
}),
reactions: Default::default(),
thread_root: None,
in_reply_to: None,
thread_summary: None,
}),
EventTimelineItemKind::Remote(RemoteEventTimelineItem {
event_id: event_id.parse().unwrap(),
transaction_id: None,
read_receipts: Default::default(),
is_own: false,
is_highlighted: false,
encryption_info: None,
original_json: None,
latest_edit_json: None,
origin: RemoteEventOrigin::Sync,
}),
false,
),
TimelineUniqueId(format!("__eid_{event_id}")),
)
}
fn local_item(transaction_id: &str) -> Arc<TimelineItem> {
TimelineItem::new(
EventTimelineItem::new(
owned_user_id!("@ivan:mnt.io"),
TimelineDetails::Unavailable,
None,
None,
MilliSecondsSinceUnixEpoch(0u32.into()),
TimelineItemContent::MsgLike(MsgLikeContent {
kind: MsgLikeKind::Message(Message {
msgtype: MessageType::Text(TextMessageEventContent::plain("hello")),
edited: false,
mentions: None,
}),
reactions: Default::default(),
thread_root: None,
in_reply_to: None,
thread_summary: None,
}),
EventTimelineItemKind::Local(LocalEventTimelineItem {
send_state: EventSendState::NotSentYet { progress: None },
transaction_id: transaction_id.into(),
send_handle: None,
}),
false,
),
TimelineUniqueId(format!("__tid_{transaction_id}")),
)
}
fn read_marker() -> Arc<TimelineItem> {
TimelineItem::read_marker()
}
fn event_meta(event_id: &str) -> EventMeta {
EventMeta {
event_id: event_id.parse().unwrap(),
thread_root_id: None,
timeline_item_index: None,
visible: false,
can_show_read_receipts: false,
}
}
macro_rules! assert_event_id {
( $timeline_item:expr, $event_id:literal $( , $message:expr )? $(,)? ) => {
assert_eq!($timeline_item.as_event().unwrap().event_id().unwrap().as_str(), $event_id $( , $message)? );
};
}
macro_rules! assert_transaction_id {
( $timeline_item:expr, $transaction_id:literal $( , $message:expr )? $(,)? ) => {
assert_eq!($timeline_item.as_event().unwrap().transaction_id().unwrap().as_str(), $transaction_id $( , $message)? );
};
}
macro_rules! assert_mapping {
( on $transaction:ident:
| event_id | event_index | timeline_item_index |
| $( - )+ | $( - )+ | $( - )+ |
$(
| $event_id:literal | $event_index:literal | $( $timeline_item_index:literal )? |
)+
) => {
let all_remote_events = $transaction .all_remote_events();
$(
// Remote event exists at this index…
assert_matches!(all_remote_events.0.get( $event_index ), Some(EventMeta { event_id, timeline_item_index, .. }) => {
assert_eq!(
event_id.as_str(),
$event_id ,
concat!("event #", $event_index, " should have ID ", $event_id)
);
#[allow(unused_variables)]
let timeline_item_index_is_expected = false;
$(
let timeline_item_index_is_expected = true;
let _ = $timeline_item_index;
)?
if timeline_item_index_is_expected.not() {
assert!(
timeline_item_index.is_none(),
concat!("event #", $event_index, " with ID ", $event_id, " should NOT map to a timeline item index" )
);
}
$(
assert_eq!(
*timeline_item_index,
Some( $timeline_item_index ),
concat!("event #", $event_index, " with ID ", $event_id, " should map to timeline item #", $timeline_item_index )
);
assert_matches!( $transaction .get( $timeline_item_index ), Some(timeline_item) => {
assert_event_id!(
timeline_item,
$event_id ,
concat!("timeline item #", $timeline_item_index, " should map to event ID ", $event_id )
);
});
)?
});
)*
}
}
#[test]
fn test_is_empty() {
let mut items = ObservableItems::new();
assert!(items.is_empty());
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.commit();
assert!(items.is_empty().not());
}
#[test]
fn test_subscribe() {
let mut items = ObservableItems::new();
let mut subscriber = items.subscribe().into_stream();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.commit();
assert_next_matches!(subscriber, VectorDiff::PushBack { value: event } => {
assert_event_id!(event, "$ev0");
});
}
#[test]
fn test_clone_items() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(item("$ev1"), Some(1));
transaction.commit();
let items = items.clone_items();
assert_eq!(items.len(), 2);
assert_event_id!(items[0], "$ev0");
assert_event_id!(items[1], "$ev1");
}
#[test]
fn test_replace() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.commit();
items.replace(0, item("$ev1"));
let items = items.clone_items();
assert_eq!(items.len(), 1);
assert_event_id!(items[0], "$ev1");
}
#[test]
fn test_entries() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back(item("$ev2"), Some(2));
transaction.commit();
let mut entries = items.entries();
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev0");
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev1");
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev2");
});
assert_matches!(entries.next(), None);
}
#[test]
fn test_entry_replace() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.commit();
let mut entries = items.entries();
assert_matches!(entries.next(), Some(mut entry) => {
assert_event_id!(entry, "$ev0");
ObservableItemsEntry::replace(&mut entry, item("$ev1"));
});
assert_matches!(entries.next(), None);
let mut entries = items.entries();
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev1");
});
assert_matches!(entries.next(), None);
}
#[test]
fn test_for_each() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back(item("$ev2"), Some(2));
transaction.commit();
let mut nth = 0;
items.for_each(|entry| {
match nth {
0 => {
assert_event_id!(entry, "$ev0");
}
1 => {
assert_event_id!(entry, "$ev1");
}
2 => {
assert_event_id!(entry, "$ev2");
}
_ => unreachable!(),
}
nth += 1;
});
}
#[test]
fn test_transaction_commit() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
drop(transaction);
assert!(items.is_empty());
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.commit();
assert!(items.is_empty().not());
}
#[test]
fn test_transaction_get() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
assert_matches!(transaction.get(0), Some(event) => {
assert_event_id!(event, "$ev0");
});
}
#[test]
fn test_transaction_replace() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.replace(0, item("$ev1"));
assert_matches!(transaction.get(0), Some(event) => {
assert_event_id!(event, "$ev1");
});
}
#[test]
fn test_transaction_insert() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back_remote_event(event_meta("$ev0"));
transaction.insert(0, item("$ev0"), Some(0));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 | }
transaction.insert(0, read_marker(), None);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 | }
transaction.push_back_remote_event(event_meta("$ev1"));
transaction.insert(2, item("$ev1"), Some(1));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 |
| "$ev1" | 1 | 2 | }
transaction.push_back_remote_event(event_meta("$ev2"));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | | }
transaction.push_back_remote_event(event_meta("$ev3"));
transaction.insert(3, item("$ev3"), Some(3));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | |
| "$ev3" | 3 | 3 | }
transaction.insert(3, item("$ev2"), Some(2));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | 3 | | "$ev3" | 3 | 4 | }
transaction.remove(0);
transaction.insert(2, read_marker(), None);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 | | "$ev1" | 1 | 1 | | "$ev2" | 2 | 3 |
| "$ev3" | 3 | 4 |
}
assert_eq!(transaction.len(), 5);
}
#[test]
fn test_transaction_push_front() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_front_remote_event(event_meta("$ev0"));
transaction.push_front(item("$ev0"), Some(0));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 | }
transaction.push_front(read_marker(), None);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 1 | }
transaction.push_front_remote_event(event_meta("$ev1"));
transaction.push_front(item("$ev1"), Some(0));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev1" | 0 | 0 | | "$ev0" | 1 | 2 | }
transaction.push_front_remote_event(event_meta("$ev2"));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev2" | 0 | |
| "$ev1" | 1 | 0 | | "$ev0" | 2 | 2 | }
transaction.push_front_remote_event(event_meta("$ev3"));
transaction.push_front(item("$ev3"), Some(0));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev3" | 0 | 0 | | "$ev2" | 1 | |
| "$ev1" | 2 | 1 | | "$ev0" | 3 | 3 | }
assert_eq!(transaction.len(), 4);
}
#[test]
fn test_transaction_push_back() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back_remote_event(event_meta("$ev0"));
transaction.push_back(item("$ev0"), Some(0));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 | }
transaction.push_back(read_marker(), None);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
}
transaction.push_back_remote_event(event_meta("$ev1"));
transaction.push_back(item("$ev1"), Some(1));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 2 | }
transaction.push_back_remote_event(event_meta("$ev2"));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | | }
transaction.push_back_remote_event(event_meta("$ev3"));
transaction.push_back(item("$ev3"), Some(3));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | |
| "$ev3" | 3 | 3 | }
assert_eq!(transaction.len(), 4);
}
#[test]
fn test_transaction_remove() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back_remote_event(event_meta("$ev0"));
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(read_marker(), None);
transaction.push_back_remote_event(event_meta("$ev1"));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back_remote_event(event_meta("$ev2"));
transaction.push_back_remote_event(event_meta("$ev3"));
transaction.push_back(item("$ev3"), Some(3));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | |
| "$ev3" | 3 | 3 |
}
transaction.remove(1);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 1 | | "$ev2" | 2 | |
| "$ev3" | 3 | 2 | }
transaction.remove(1);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | | | "$ev2" | 2 | |
| "$ev3" | 3 | 1 | }
transaction.remove(1);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | |
| "$ev2" | 2 | |
| "$ev3" | 3 | | }
transaction.remove(0);
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | | | "$ev1" | 1 | |
| "$ev2" | 2 | |
| "$ev3" | 3 | |
}
assert!(transaction.is_empty());
}
#[test]
fn test_transaction_clear() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back_remote_event(event_meta("$ev0"));
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(read_marker(), None);
transaction.push_back_remote_event(event_meta("$ev1"));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back_remote_event(event_meta("$ev2"));
transaction.push_back_remote_event(event_meta("$ev3"));
transaction.push_back(item("$ev3"), Some(3));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 2 |
| "$ev2" | 2 | |
| "$ev3" | 3 | 3 |
}
assert_eq!(transaction.all_remote_events().0.len(), 4);
assert_eq!(transaction.len(), 4);
transaction.clear();
assert!(transaction.all_remote_events().0.is_empty());
assert!(transaction.is_empty());
}
#[test]
fn test_transaction_for_each() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back(item("$ev2"), Some(2));
let mut nth = 0;
transaction.for_each(|entry| {
match nth {
0 => {
assert_event_id!(entry, "$ev0");
}
1 => {
assert_event_id!(entry, "$ev1");
}
2 => {
assert_event_id!(entry, "$ev2");
}
_ => unreachable!(),
}
nth += 1;
});
}
#[test]
fn test_transaction_for_each_remove() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back_remote_event(event_meta("$ev0"));
transaction.push_back(item("$ev0"), Some(0));
transaction.push_back_remote_event(event_meta("$ev1"));
transaction.push_back(item("$ev1"), Some(1));
transaction.push_back_remote_event(event_meta("$ev2"));
transaction.push_back(item("$ev2"), Some(2));
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev1" | 1 | 1 |
| "$ev2" | 2 | 2 |
}
transaction.for_each(|entry| {
if entry.as_event().unwrap().event_id().unwrap().as_str() == "$ev1" {
ObservableItemsTransactionEntry::remove(entry);
}
});
assert_mapping! {
on transaction:
| event_id | event_index | timeline_item_index |
|----------|-------------|---------------------|
| "$ev0" | 0 | 0 |
| "$ev2" | 2 | 1 | }
assert_eq!(transaction.all_remote_events().0.len(), 3);
assert_eq!(transaction.len(), 2);
}
#[test]
fn test_transaction_push_local() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), None);
transaction.push_local(local_item("t0"));
transaction.push_local(local_item("t1"));
transaction.commit();
let mut entries = items.entries();
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev0");
});
assert_matches!(entries.next(), Some(entry) => {
assert_transaction_id!(entry, "t0");
});
assert_matches!(entries.next(), Some(entry) => {
assert_transaction_id!(entry, "t1");
});
assert_matches!(entries.next(), None);
}
#[test]
#[should_panic]
fn test_transaction_push_local_panic_not_a_local() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_local(item("$ev0"));
}
#[test]
fn test_transaction_push_date_divider() {
let mut items = ObservableItems::new();
let mut stream = items.subscribe().into_stream();
let mut transaction = items.transaction();
transaction.push_date_divider(
0,
TimelineItem::new(
TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(
MilliSecondsSinceUnixEpoch(uint!(10)),
)),
TimelineUniqueId("__foo".to_owned()),
),
);
transaction.push_date_divider(
0,
TimelineItem::new(
TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(
MilliSecondsSinceUnixEpoch(uint!(20)),
)),
TimelineUniqueId("__bar".to_owned()),
),
);
transaction.push_date_divider(
1,
TimelineItem::new(
TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(
MilliSecondsSinceUnixEpoch(uint!(30)),
)),
TimelineUniqueId("__baz".to_owned()),
),
);
transaction.commit();
assert_next_matches!(stream, VectorDiff::PushBack { value: timeline_item } => {
assert_matches!(timeline_item.as_virtual(), Some(VirtualTimelineItem::DateDivider(ms)) => {
assert_eq!(u64::from(ms.0), 10);
});
});
assert_next_matches!(stream, VectorDiff::PushFront { value: timeline_item } => {
assert_matches!(timeline_item.as_virtual(), Some(VirtualTimelineItem::DateDivider(ms)) => {
assert_eq!(u64::from(ms.0), 20);
});
});
assert_next_matches!(stream, VectorDiff::Insert { index: 1, value: timeline_item } => {
assert_matches!(timeline_item.as_virtual(), Some(VirtualTimelineItem::DateDivider(ms)) => {
assert_eq!(u64::from(ms.0), 30);
});
});
assert_pending!(stream);
}
#[test]
#[should_panic]
fn test_transaction_push_date_divider_panic_not_a_date_divider() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_date_divider(0, item("$ev0"));
}
#[test]
#[should_panic]
fn test_transaction_push_date_divider_panic_not_in_remotes_region() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__id_start".to_owned()),
));
transaction.push_date_divider(
0,
TimelineItem::new(
VirtualTimelineItem::DateDivider(MilliSecondsSinceUnixEpoch(uint!(10))),
TimelineUniqueId("__date_divider".to_owned()),
),
);
}
#[test]
fn test_transaction_push_timeline_start_if_missing() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), None);
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__id_start".to_owned()),
));
transaction.push_back(item("$ev1"), None);
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__id_start_again".to_owned()),
));
transaction.commit();
let mut entries = items.entries();
assert_matches!(entries.next(), Some(entry) => {
assert!(entry.is_timeline_start());
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev0");
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev1");
});
assert_matches!(entries.next(), None);
}
#[test]
fn test_transaction_iter_all_regions() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__start".to_owned()),
));
transaction.push_back(item("$ev0"), None);
transaction.push_back(item("$ev1"), None);
transaction.push_back(item("$ev2"), None);
transaction.push_local(local_item("t0"));
transaction.push_local(local_item("t1"));
transaction.push_local(local_item("t2"));
let mut iter = transaction.iter_all_regions();
assert_matches!(iter.next(), Some((0, item)) => {
assert!(item.is_timeline_start());
});
assert_matches!(iter.next(), Some((1, item)) => {
assert_event_id!(item, "$ev0");
});
assert_matches!(iter.next(), Some((2, item)) => {
assert_event_id!(item, "$ev1");
});
assert_matches!(iter.next(), Some((3, item)) => {
assert_event_id!(item, "$ev2");
});
assert_matches!(iter.next(), Some((4, item)) => {
assert_transaction_id!(item, "t0");
});
assert_matches!(iter.next(), Some((5, item)) => {
assert_transaction_id!(item, "t1");
});
assert_matches!(iter.next(), Some((6, item)) => {
assert_transaction_id!(item, "t2");
});
assert!(iter.next().is_none());
}
#[test]
fn test_transaction_iter_remotes_regions() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__start".to_owned()),
));
transaction.push_back(item("$ev0"), None);
transaction.push_back(item("$ev1"), None);
transaction.push_back(item("$ev2"), None);
transaction.push_local(local_item("t0"));
transaction.push_local(local_item("t1"));
transaction.push_local(local_item("t2"));
let mut iter = transaction.iter_remotes_region();
assert_matches!(iter.next(), Some((1, item)) => {
assert_event_id!(item, "$ev0");
});
assert_matches!(iter.next(), Some((2, item)) => {
assert_event_id!(item, "$ev1");
});
assert_matches!(iter.next(), Some((3, item)) => {
assert_event_id!(item, "$ev2");
});
assert!(iter.next().is_none());
}
#[test]
fn test_transaction_iter_remotes_regions_with_no_start_region() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), None);
transaction.push_back(item("$ev1"), None);
transaction.push_back(item("$ev2"), None);
transaction.push_local(local_item("t0"));
transaction.push_local(local_item("t1"));
transaction.push_local(local_item("t2"));
let mut iter = transaction.iter_remotes_region();
assert_matches!(iter.next(), Some((0, item)) => {
assert_event_id!(item, "$ev0");
});
assert_matches!(iter.next(), Some((1, item)) => {
assert_event_id!(item, "$ev1");
});
assert_matches!(iter.next(), Some((2, item)) => {
assert_event_id!(item, "$ev2");
});
assert!(iter.next().is_none());
}
#[test]
fn test_transaction_iter_remotes_regions_with_no_locals_region() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_back(item("$ev0"), None);
transaction.push_back(item("$ev1"), None);
transaction.push_back(item("$ev2"), None);
let mut iter = transaction.iter_remotes_region();
assert_matches!(iter.next(), Some((0, item)) => {
assert_event_id!(item, "$ev0");
});
assert_matches!(iter.next(), Some((1, item)) => {
assert_event_id!(item, "$ev1");
});
assert_matches!(iter.next(), Some((2, item)) => {
assert_event_id!(item, "$ev2");
});
assert!(iter.next().is_none());
}
#[test]
fn test_transaction_iter_locals_region() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
transaction.push_timeline_start_if_missing(TimelineItem::new(
VirtualTimelineItem::TimelineStart,
TimelineUniqueId("__start".to_owned()),
));
transaction.push_back(item("$ev0"), None);
transaction.push_back(item("$ev1"), None);
transaction.push_back(item("$ev2"), None);
transaction.push_local(local_item("t0"));
transaction.push_local(local_item("t1"));
transaction.push_local(local_item("t2"));
let mut iter = transaction.iter_locals_region();
assert_matches!(iter.next(), Some((4, item)) => {
assert_transaction_id!(item, "t0");
});
assert_matches!(iter.next(), Some((5, item)) => {
assert_transaction_id!(item, "t1");
});
assert_matches!(iter.next(), Some((6, item)) => {
assert_transaction_id!(item, "t2");
});
assert!(iter.next().is_none());
}
}
#[derive(Clone, Debug, Default)]
pub struct AllRemoteEvents(VecDeque<EventMeta>);
impl AllRemoteEvents {
pub fn get(&self, event_index: usize) -> Option<&EventMeta> {
self.0.get(event_index)
}
pub fn iter(&self) -> Iter<'_, EventMeta> {
self.0.iter()
}
pub fn range<R>(&self, range: R) -> Iter<'_, EventMeta>
where
R: RangeBounds<usize>,
{
self.0.range(range)
}
fn clear(&mut self) {
self.0.clear();
}
fn push_front(&mut self, event_meta: EventMeta) {
if let Some(new_timeline_item_index) = event_meta.timeline_item_index {
self.increment_all_timeline_item_index_after(new_timeline_item_index);
}
self.0.push_front(event_meta)
}
fn push_back(&mut self, event_meta: EventMeta) {
if let Some(new_timeline_item_index) = event_meta.timeline_item_index {
self.increment_all_timeline_item_index_after(new_timeline_item_index);
}
self.0.push_back(event_meta)
}
fn insert(&mut self, event_index: usize, event_meta: EventMeta) {
if let Some(new_timeline_item_index) = event_meta.timeline_item_index {
self.increment_all_timeline_item_index_after(new_timeline_item_index);
}
self.0.insert(event_index, event_meta)
}
fn remove(&mut self, event_index: usize) -> Option<EventMeta> {
let event_meta = self.0.remove(event_index)?;
if let Some(removed_timeline_item_index) = event_meta.timeline_item_index {
self.decrement_all_timeline_item_index_after(removed_timeline_item_index);
}
Some(event_meta)
}
#[cfg(test)]
pub fn last(&self) -> Option<&EventMeta> {
self.0.back()
}
pub fn last_index(&self) -> Option<usize> {
self.0.len().checked_sub(1)
}
pub fn get_by_event_id_mut(&mut self, event_id: &EventId) -> Option<&mut EventMeta> {
self.0.iter_mut().rev().find(|event_meta| event_meta.event_id == event_id)
}
pub fn get_by_event_id(&self, event_id: &EventId) -> Option<&EventMeta> {
self.0.iter().rev().find(|event_meta| event_meta.event_id == event_id)
}
pub fn position_by_event_id(&self, event_id: &EventId) -> Option<usize> {
self.0
.iter()
.enumerate()
.rev()
.find_map(|(i, event_meta)| (event_meta.event_id == event_id).then_some(i))
}
fn increment_all_timeline_item_index_after(&mut self, new_timeline_item_index: usize) {
for event_meta in self.0.iter_mut().rev() {
if let Some(timeline_item_index) = event_meta.timeline_item_index.as_mut() {
if *timeline_item_index >= new_timeline_item_index {
*timeline_item_index += 1;
} else {
break;
}
}
}
}
fn decrement_all_timeline_item_index_after(&mut self, removed_timeline_item_index: usize) {
for event_meta in self.0.iter_mut().rev() {
if let Some(timeline_item_index) = event_meta.timeline_item_index.as_mut() {
if *timeline_item_index > removed_timeline_item_index {
*timeline_item_index -= 1;
} else {
break;
}
}
}
}
fn timeline_item_has_been_inserted_at(
&mut self,
new_timeline_item_index: usize,
event_index: Option<usize>,
) {
self.increment_all_timeline_item_index_after(new_timeline_item_index);
if let Some(event_index) = event_index
&& let Some(event_meta) = self.0.get_mut(event_index)
{
event_meta.timeline_item_index = Some(new_timeline_item_index);
}
}
fn timeline_item_has_been_removed_at(&mut self, timeline_item_index_to_remove: usize) {
for event_meta in self.0.iter_mut() {
let mut remove_timeline_item_index = false;
if let Some(timeline_item_index) = event_meta.timeline_item_index.as_mut() {
match (*timeline_item_index).cmp(&timeline_item_index_to_remove) {
Ordering::Equal => {
remove_timeline_item_index = true;
}
Ordering::Greater => {
*timeline_item_index -= 1;
}
Ordering::Less => {}
}
}
if remove_timeline_item_index {
event_meta.timeline_item_index = None;
}
}
}
}
#[cfg(test)]
mod all_remote_events_tests {
use assert_matches::assert_matches;
use ruma::event_id;
use super::{AllRemoteEvents, EventMeta};
fn event_meta(event_id: &str, timeline_item_index: Option<usize>) -> EventMeta {
EventMeta {
event_id: event_id.parse().unwrap(),
thread_root_id: None,
timeline_item_index,
visible: false,
can_show_read_receipts: false,
}
}
macro_rules! assert_events {
( $events:ident, [ $( ( $event_id:literal, $timeline_item_index:expr ) ),* $(,)? ] ) => {
let mut iter = $events .iter();
$(
assert_matches!(iter.next(), Some(EventMeta { event_id, timeline_item_index, .. }) => {
assert_eq!(event_id.as_str(), $event_id );
assert_eq!(*timeline_item_index, $timeline_item_index );
});
)*
assert!(iter.next().is_none(), "Not all events have been asserted");
}
}
#[test]
fn test_range() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", None));
events.push_back(event_meta("$ev1", None));
events.push_back(event_meta("$ev2", None));
assert_eq!(events.iter().count(), 3);
assert_eq!(events.range(..).count(), 3);
assert_eq!(events.range(1..).count(), 2);
assert_eq!(events.range(0..=1).count(), 2);
let mut some_events = events.range(1..);
assert_matches!(some_events.next(), Some(EventMeta { event_id, .. }) => {
assert_eq!(event_id.as_str(), "$ev1");
});
assert_matches!(some_events.next(), Some(EventMeta { event_id, .. }) => {
assert_eq!(event_id.as_str(), "$ev2");
});
assert!(some_events.next().is_none());
}
#[test]
fn test_clear() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", None));
events.push_back(event_meta("$ev1", None));
events.push_back(event_meta("$ev2", None));
assert_eq!(events.iter().count(), 3);
events.clear();
assert_eq!(events.iter().count(), 0);
}
#[test]
fn test_push_front() {
let mut events = AllRemoteEvents::default();
events.push_front(event_meta("$ev0", Some(1)));
events.push_front(event_meta("$ev1", None));
events.push_front(event_meta("$ev2", Some(0)));
events.push_front(event_meta("$ev3", Some(0)));
assert_events!(
events,
[
("$ev3", Some(0)),
("$ev2", Some(1)),
("$ev1", None),
("$ev0", Some(3)),
]
);
}
#[test]
fn test_push_back() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", None));
events.push_back(event_meta("$ev2", Some(1)));
events.push_back(event_meta("$ev3", Some(1)));
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev1", None),
("$ev2", Some(2)),
("$ev3", Some(1)),
]
);
}
#[test]
fn test_insert() {
let mut events = AllRemoteEvents::default();
events.insert(0, event_meta("$ev0", Some(0)));
events.insert(1, event_meta("$ev1", None));
events.insert(2, event_meta("$ev2", Some(1)));
events.insert(0, event_meta("$ev3", Some(0)));
assert_events!(
events,
[
("$ev3", Some(0)),
("$ev0", Some(1)),
("$ev1", None),
("$ev2", Some(2)),
]
);
}
#[test]
fn test_remove() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", Some(1)));
events.push_back(event_meta("$ev2", None));
events.push_back(event_meta("$ev3", Some(2)));
assert_events!(
events,
[("$ev0", Some(0)), ("$ev1", Some(1)), ("$ev2", None), ("$ev3", Some(2))]
);
events.remove(2); events.remove(1);
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev3", Some(1)),
]
);
}
#[test]
fn test_last() {
let mut events = AllRemoteEvents::default();
assert!(events.last().is_none());
assert!(events.last_index().is_none());
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", Some(1)));
assert_matches!(events.last(), Some(EventMeta { event_id, .. }) => {
assert_eq!(event_id.as_str(), "$ev1");
});
assert_eq!(events.last_index(), Some(1));
}
#[test]
fn test_get_by_event_by_mut() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", Some(1)));
assert!(events.get_by_event_id_mut(event_id!("$ev0")).is_some());
assert!(events.get_by_event_id_mut(event_id!("$ev42")).is_none());
}
#[test]
fn test_timeline_item_has_been_inserted_at() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", Some(1)));
events.push_back(event_meta("$ev2", None));
events.push_back(event_meta("$ev3", None));
events.push_back(event_meta("$ev4", Some(2)));
events.push_back(event_meta("$ev5", Some(3)));
events.push_back(event_meta("$ev6", None));
events.timeline_item_has_been_inserted_at(2, None);
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev1", Some(1)),
("$ev2", None),
("$ev3", None),
("$ev4", Some(3)),
("$ev5", Some(4)),
("$ev6", None),
]
);
events.timeline_item_has_been_inserted_at(5, Some(6));
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev1", Some(1)),
("$ev2", None),
("$ev3", None),
("$ev4", Some(3)),
("$ev5", Some(4)),
("$ev6", Some(5)),
]
);
}
#[test]
fn test_timeline_item_has_been_removed_at() {
let mut events = AllRemoteEvents::default();
events.push_back(event_meta("$ev0", Some(0)));
events.push_back(event_meta("$ev1", Some(1)));
events.push_back(event_meta("$ev2", None));
events.push_back(event_meta("$ev3", None));
events.push_back(event_meta("$ev4", Some(3)));
events.push_back(event_meta("$ev5", Some(4)));
events.push_back(event_meta("$ev6", None));
events.timeline_item_has_been_removed_at(2);
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev1", Some(1)),
("$ev2", None),
("$ev3", None),
("$ev4", Some(2)),
("$ev5", Some(3)),
("$ev6", None),
]
);
events.timeline_item_has_been_removed_at(2);
assert_events!(
events,
[
("$ev0", Some(0)),
("$ev1", Some(1)),
("$ev2", None),
("$ev3", None),
("$ev4", None),
("$ev5", Some(2)),
("$ev6", None),
]
);
events.timeline_item_has_been_removed_at(0);
assert_events!(
events,
[
("$ev0", None),
("$ev1", Some(0)),
("$ev2", None),
("$ev3", None),
("$ev4", None),
("$ev5", Some(1)),
("$ev6", None),
]
);
}
}