Skip to content

Commit

Permalink
timeline: add integration test showing the issue with editing or abor…
Browse files Browse the repository at this point in the history
…ting with a stale local echo
  • Loading branch information
bnjbvr committed Jul 3, 2024
1 parent 86dd0b5 commit acd21d9
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 2 deletions.
2 changes: 1 addition & 1 deletion crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ impl<T> TimelineDetails<T> {
matches!(self, Self::Unavailable)
}

pub(crate) fn is_ready(&self) -> bool {
pub fn is_ready(&self) -> bool {
matches!(self, Self::Ready(_))
}
}
Expand Down
92 changes: 91 additions & 1 deletion testing/matrix-sdk-integration-testing/src/tests/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use matrix_sdk::ruma::{
events::{relation::Annotation, room::message::RoomMessageEventContent},
EventId, MilliSecondsSinceUnixEpoch, UserId,
};
use matrix_sdk_ui::timeline::{EventTimelineItem, RoomExt, TimelineItem};
use matrix_sdk_ui::timeline::{EventSendState, EventTimelineItem, RoomExt, TimelineItem};
use tokio::{
spawn,
task::JoinHandle,
Expand Down Expand Up @@ -249,3 +249,93 @@ async fn assert_event_is_updated(

event.to_owned()
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_stale_local_echo_time_abort_edit() {
// Set up sync for user Alice, and create a room.
let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await.unwrap();

let alice_clone = alice.clone();
let alice_sync = spawn(async move {
alice_clone.sync(Default::default()).await.expect("sync failed!");
});

debug!("Creating room…");
let room = alice
.create_room(assign!(CreateRoomRequest::new(), {
is_direct: true,
}))
.await
.unwrap();

// Create a timeline for this room, filtering out all non-message items.
let timeline = room.timeline().await.unwrap();
let (items, mut stream) = timeline
.subscribe_filter_map(|item| {
item.as_event()
.and_then(|item| item.content().as_message().is_some().then(|| item.clone()))
})
.await;

assert!(items.is_empty());

// Send message.
debug!("Sending initial message…");
timeline.send(RoomMessageEventContent::text_plain("hi!").into()).await.unwrap();

// Receiving the local echo for the message.
let vector_diff = timeout(Duration::from_secs(5), stream.next()).await.unwrap().unwrap();
let local_echo = assert_matches!(vector_diff, VectorDiff::PushBack { value } => value);

assert!(local_echo.is_local_echo());
assert!(local_echo.is_editable());
assert_matches!(local_echo.send_state(), Some(EventSendState::NotSentYet));
assert_eq!(local_echo.content().as_message().unwrap().body(), "hi!");

let mut has_sender_profile = local_echo.sender_profile().is_ready();

// It is then sent. The timeline stream can be racy here:
//
// - either the local echo is marked as sent *before*, and we receive an update
// for this before
// the remote echo.
// - or the remote echo comes up faster.
//
// Handle both orderings.
while let Ok(Some(vector_diff)) = timeout(Duration::from_secs(1), stream.next()).await {
let VectorDiff::Set { index: 0, value: echo } = vector_diff else {
panic!("unexpected diff: {vector_diff:#?}");
};

if echo.is_local_echo() {
// If the sender profile wasn't available, we may receive an update about it;
// ignore it.
if !has_sender_profile && echo.sender_profile().is_ready() {
has_sender_profile = true;
continue;
}
assert_matches!(echo.send_state(), Some(EventSendState::Sent { .. }));
}
assert!(echo.is_editable());
assert_eq!(echo.content().as_message().unwrap().body(), "hi!");
}

// Now do a crime: try to edit the local echo.
let edit_info = local_echo.edit_info().unwrap();
let did_edit = timeline
.edit(RoomMessageEventContent::text_plain("bonjour").into(), edit_info)
.await
.unwrap();

// The edit works on the local echo and applies to the remote echo \o/.
assert!(did_edit);

let vector_diff = timeout(Duration::from_secs(5), stream.next()).await.unwrap().unwrap();
let remote_echo = assert_matches!(vector_diff, VectorDiff::Set { index: 0, value } => value);
assert!(!remote_echo.is_local_echo());
assert!(remote_echo.is_editable());

assert_eq!(remote_echo.content().as_message().unwrap().body(), "bonjour");

alice_sync.abort();
}

0 comments on commit acd21d9

Please sign in to comment.