refactor(ui): Add ObservableItemsTransaction::push_local.

This patch adds the `push_local` method on `ObservableItemsTransaction`
to add semantics and hardcode the invariant in a single place for the
different timeline items.
This commit is contained in:
Ivan Enderlin
2025-05-06 13:35:14 +02:00
parent 4e501e88ee
commit 55ea80b485
2 changed files with 88 additions and 6 deletions

View File

@@ -303,6 +303,23 @@ impl<'observable_items> ObservableItemsTransaction<'observable_items> {
.timeline_item_has_been_inserted_at(self.items.len().saturating_sub(1), event_index);
}
/// Push a new [`Local`] timeline item.
///
/// # Invariant
///
/// A [`Local`] is always the last item.
///
/// # Panics
///
/// It panics if the provided `timeline_item` is not a [`Local`].
///
/// [`Local`]: super::EventTimelineItemKind::Local
pub fn push_local(&mut self, timeline_item: Arc<TimelineItem>) {
assert!(timeline_item.is_local_echo());
self.push_back(timeline_item, None);
}
/// Push a new [`TimelineStart`] virtual timeline item.
///
/// # Invariant
@@ -413,8 +430,8 @@ mod observable_items_tests {
use super::*;
use crate::timeline::{
controller::{EventTimelineItemKind, RemoteEventOrigin},
event_item::RemoteEventTimelineItem,
EventTimelineItem, Message, MsgLikeContent, MsgLikeKind, TimelineDetails,
event_item::{LocalEventTimelineItem, RemoteEventTimelineItem},
EventSendState, EventTimelineItem, Message, MsgLikeContent, MsgLikeKind, TimelineDetails,
TimelineItemContent, TimelineUniqueId, VirtualTimelineItem,
};
@@ -448,7 +465,35 @@ mod observable_items_tests {
}),
false,
),
TimelineUniqueId(format!("__id_{event_id}")),
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,
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,
transaction_id: transaction_id.into(),
send_handle: None,
}),
false,
),
TimelineUniqueId(format!("__tid_{transaction_id}")),
)
}
@@ -466,6 +511,12 @@ mod observable_items_tests {
};
}
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 |
@@ -1181,6 +1232,37 @@ mod observable_items_tests {
assert_eq!(transaction.len(), 2);
}
#[test]
fn test_transaction_push_local() {
let mut items = ObservableItems::new();
let mut transaction = items.transaction();
// Push a remote item.
transaction.push_back(item("$ev0"), None);
// Push a local item.
transaction.push_local(local_item("t0"));
// Push another local item.
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]
fn test_transaction_push_timeline_start_if_missing() {
let mut items = ObservableItems::new();
@@ -1213,10 +1295,10 @@ mod observable_items_tests {
assert!(entry.is_timeline_start());
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev0");
assert_event_id!(entry, "$ev0");
});
assert_matches!(entries.next(), Some(entry) => {
assert_event_id!(entry, "$ev1");
assert_event_id!(entry, "$ev1");
});
assert_matches!(entries.next(), None);
}

View File

@@ -1115,7 +1115,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
let item = self.meta.new_timeline_item(item);
self.items.push_back(item, None);
self.items.push_local(item);
}
Flow::Remote {