mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-04 22:15:44 -04:00
timeline: update replies when a message has been edited
This commit is contained in:
@@ -68,6 +68,7 @@ use crate::{
|
||||
controller::PendingEdit,
|
||||
event_item::{ReactionInfo, ReactionStatus},
|
||||
reactions::PendingReaction,
|
||||
RepliedToEvent,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -511,9 +512,33 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
|
||||
) {
|
||||
if let Some((item_pos, item)) = rfind_event_by_id(self.items, &replacement.event_id) {
|
||||
let edit_json = self.ctx.flow.raw_event().cloned();
|
||||
if let Some(new_item) = self.apply_msg_edit(&item, replacement, edit_json) {
|
||||
if let Some(new_item) = self.apply_msg_edit(&item, replacement.new_content, edit_json) {
|
||||
trace!("Applied edit");
|
||||
self.items.set(item_pos, TimelineItem::new(new_item, item.internal_id.to_owned()));
|
||||
|
||||
let internal_id = item.internal_id.to_owned();
|
||||
|
||||
// Update all events that replied to this message with the edited content.
|
||||
self.items.for_each(|mut entry| {
|
||||
let Some(event_item) = entry.as_event() else { return };
|
||||
let Some(message) = event_item.content.as_message() else { return };
|
||||
let Some(in_reply_to) = message.in_reply_to() else { return };
|
||||
if replacement.event_id == in_reply_to.event_id {
|
||||
let in_reply_to = InReplyToDetails {
|
||||
event_id: in_reply_to.event_id.clone(),
|
||||
event: TimelineDetails::Ready(Box::new(
|
||||
RepliedToEvent::from_timeline_item(&new_item),
|
||||
)),
|
||||
};
|
||||
let new_reply_content =
|
||||
TimelineItemContent::Message(message.with_in_reply_to(in_reply_to));
|
||||
let new_reply_item =
|
||||
entry.with_kind(event_item.with_content(new_reply_content, None));
|
||||
ObservableVectorTransactionEntry::set(&mut entry, new_reply_item);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the event itself.
|
||||
self.items.set(item_pos, TimelineItem::new(new_item, internal_id));
|
||||
self.result.items_updated += 1;
|
||||
}
|
||||
} else if let Flow::Remote { position, raw_event, .. } = &self.ctx.flow {
|
||||
@@ -589,7 +614,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
|
||||
fn apply_msg_edit(
|
||||
&self,
|
||||
item: &EventTimelineItem,
|
||||
replacement: Replacement<RoomMessageEventContentWithoutRelation>,
|
||||
new_content: RoomMessageEventContentWithoutRelation,
|
||||
edit_json: Option<Raw<AnySyncTimelineEvent>>,
|
||||
) -> Option<EventTimelineItem> {
|
||||
if self.ctx.sender != item.sender() {
|
||||
@@ -609,7 +634,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
|
||||
};
|
||||
|
||||
let mut new_msg = msg.clone();
|
||||
new_msg.apply_edit(replacement.new_content);
|
||||
new_msg.apply_edit(new_content);
|
||||
|
||||
let mut new_item = item.with_content(TimelineItemContent::Message(new_msg), edit_json);
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ async fn test_send_reply_edit() {
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
timeline
|
||||
let edited = timeline
|
||||
.edit(
|
||||
&reply_item,
|
||||
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
|
||||
@@ -419,6 +419,7 @@ async fn test_send_reply_edit() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(edited);
|
||||
|
||||
// Let the send queue handle the event.
|
||||
yield_now().await;
|
||||
@@ -444,6 +445,130 @@ async fn test_send_reply_edit() {
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_edit_to_replied_updates_reply() {
|
||||
let room_id = room_id!("!a98sd12bjh:example.org");
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
mock_encryption_state(&server, false).await;
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let timeline = room.timeline().await.unwrap();
|
||||
let (_, mut timeline_stream) =
|
||||
timeline.subscribe_filter_map(|item| item.as_event().cloned()).await;
|
||||
|
||||
let f = EventFactory::new();
|
||||
let event_id = event_id!("$original_event");
|
||||
let user_id = client.user_id().unwrap();
|
||||
|
||||
// When a room has two messages, one is a reply to the other…
|
||||
sync_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_event(f.text_msg("bonjour").sender(user_id).event_id(event_id))
|
||||
.add_timeline_event(f.text_msg("hi back").reply_to(event_id).sender(*ALICE))
|
||||
.add_timeline_event(f.text_msg("yo").reply_to(event_id).sender(*BOB)),
|
||||
);
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
// (I see all the messages in the timeline.)
|
||||
let replied_to_item = assert_next_matches!(timeline_stream, VectorDiff::PushBack { value } => {
|
||||
assert_eq!(value.content().as_message().unwrap().body(), "bonjour");
|
||||
assert!(value.is_editable());
|
||||
value
|
||||
});
|
||||
|
||||
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value: reply_item } => {
|
||||
let reply_message = reply_item.content().as_message().unwrap();
|
||||
assert_eq!(reply_message.body(), "hi back");
|
||||
|
||||
let in_reply_to = reply_message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id);
|
||||
|
||||
assert_let!(TimelineDetails::Ready(replied_to) = &in_reply_to.event);
|
||||
assert_eq!(replied_to.content().as_message().unwrap().body(), "bonjour");
|
||||
});
|
||||
|
||||
assert_next_matches!(timeline_stream, VectorDiff::PushBack { value: reply_item } => {
|
||||
let reply_message = reply_item.content().as_message().unwrap();
|
||||
assert_eq!(reply_message.body(), "yo");
|
||||
|
||||
let in_reply_to = reply_message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id);
|
||||
|
||||
assert_let!(TimelineDetails::Ready(replied_to) = &in_reply_to.event);
|
||||
assert_eq!(replied_to.content().as_message().unwrap().body(), "bonjour");
|
||||
});
|
||||
|
||||
mock_encryption_state(&server, false).await;
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$edit_event" })),
|
||||
)
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
// If I edit the first message,…
|
||||
let edited = timeline
|
||||
.edit(
|
||||
&replied_to_item,
|
||||
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
|
||||
"hello world",
|
||||
)),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(edited);
|
||||
|
||||
yield_now().await; // let the send queue handle the edit.
|
||||
|
||||
// The reply events are updated with the edited replied-to content.
|
||||
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 1, value } => {
|
||||
let reply_message = value.content().as_message().unwrap();
|
||||
assert_eq!(reply_message.body(), "hi back");
|
||||
assert!(!reply_message.is_edited());
|
||||
|
||||
let in_reply_to = reply_message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id);
|
||||
assert_let!(TimelineDetails::Ready(replied_to) = &in_reply_to.event);
|
||||
assert_eq!(replied_to.content().as_message().unwrap().body(), "hello world");
|
||||
});
|
||||
|
||||
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 2, value } => {
|
||||
let reply_message = value.content().as_message().unwrap();
|
||||
assert_eq!(reply_message.body(), "yo");
|
||||
assert!(!reply_message.is_edited());
|
||||
|
||||
let in_reply_to = reply_message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id);
|
||||
assert_let!(TimelineDetails::Ready(replied_to) = &in_reply_to.event);
|
||||
assert_eq!(replied_to.content().as_message().unwrap().body(), "hello world");
|
||||
});
|
||||
|
||||
// And the edit happens.
|
||||
assert_next_matches!(timeline_stream, VectorDiff::Set { index: 0, value } => {
|
||||
let msg = value.content().as_message().unwrap();
|
||||
assert_eq!(msg.body(), "hello world");
|
||||
assert!(msg.is_edited());
|
||||
});
|
||||
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_send_edit_poll() {
|
||||
let room_id = room_id!("!a98sd12bjh:example.org");
|
||||
|
||||
Reference in New Issue
Block a user