atagen

matrix-rust-sdk

public
Star0
Watch0

Commit 9b9b16982dca

tarball

atagen <boss@atagen.co>2026-10-25 03:19 UTC

3 files changed+139-0
timeline: handle replacement event cache snapshots
Modified file: crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs+29 additions
@@ -213,6 +213,23 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
213213 self.clear();
214214 }
215215
216+ VectorDiff::Reset { values: events } => {
217+ recycled_timeline_ids.clear();
218+ self.clear();
219+ for event in events {
220+ self.handle_remote_event(
221+ event,
222+ TimelineItemPosition::End { origin },
223+ room_data_provider,
224+ settings,
225+ &mut date_divider_adjuster,
226+ &mut cached_profiles,
227+ None,
228+ )
229+ .await;
230+ }
231+ }
232+
216233 v => unimplemented!("{v:?}"),
217234 }
218235 }
@@ -429,6 +446,18 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
429446 // place.
430447 }
431448
449+ VectorDiff::Reset { values: events } => {
450+ for event in events {
451+ self.handle_remote_aggregation(
452+ event,
453+ TimelineItemPosition::End { origin },
454+ room_data_provider,
455+ &mut date_divider_adjuster,
456+ )
457+ .await;
458+ }
459+ }
460+
432461 v => unimplemented!("{v:?}"),
433462 }
434463 }
Modified file: crates/matrix-sdk-ui/tests/integration/timeline/mod.rs+1 additions
@@ -56,6 +56,7 @@ mod queue;
5656 mod reactions;
5757 mod read_receipts;
5858 mod redecryption;
59+mod reseed;
5960 mod replies;
6061 mod subscribe;
6162 mod thread;
Added file: crates/matrix-sdk-ui/tests/integration/timeline/reseed.rs+109 additions
@@ -0,0 +1,109 @@
1+use std::time::Duration;
2+
3+use assert_matches::assert_matches;
4+use futures_util::StreamExt;
5+use matrix_sdk::test_utils::mocks::MatrixMockServer;
6+use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};
7+use matrix_sdk_ui::timeline::{EventSendState, RoomExt};
8+use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id};
9+use tokio::time::timeout;
10+
11+#[async_test]
12+async fn test_timeline_survives_room_cache_reseed() {
13+ let server = MatrixMockServer::new().await;
14+ let client = server.client_builder().build().await;
15+ client.send_queue().set_enabled(false).await;
16+
17+ server.mock_room_state_encryption().plain().mount().await;
18+
19+ let room_id = room_id!("!a98sd12bjh:example.org");
20+ let room = server.sync_joined_room(&client, room_id).await;
21+ let timeline = room.timeline().await.unwrap();
22+ let (_, mut timeline_stream) = timeline.subscribe().await;
23+ let event_factory = EventFactory::new().room(room_id);
24+
25+ let old_event_id = event_id!("$before_reseed");
26+ server
27+ .sync_room(
28+ &client,
29+ JoinedRoomBuilder::new(room_id).add_timeline_event(
30+ event_factory.text_msg("old history").sender(&ALICE).event_id(old_event_id),
31+ ),
32+ )
33+ .await;
34+
35+ timeout(Duration::from_secs(5), async {
36+ loop {
37+ let items = timeline.items().await;
38+ if items.iter().any(|item| {
39+ item.as_event().is_some_and(|event| event.event_id() == Some(old_event_id))
40+ }) {
41+ break;
42+ }
43+ assert!(timeline_stream.next().await.is_some());
44+ }
45+ })
46+ .await
47+ .unwrap();
48+
49+ timeline.send(RoomMessageEventContent::text_plain("pending local echo").into()).await.unwrap();
50+
51+ timeout(Duration::from_secs(5), async {
52+ loop {
53+ let items = timeline.items().await;
54+ if items.iter().any(|item| {
55+ item.as_event().is_some_and(|event| {
56+ event
57+ .content()
58+ .as_message()
59+ .is_some_and(|message| message.body() == "pending local echo")
60+ })
61+ }) {
62+ break;
63+ }
64+ assert!(timeline_stream.next().await.is_some());
65+ }
66+ })
67+ .await
68+ .unwrap();
69+
70+ let new_event_id = event_id!("$after_reseed");
71+ let new_event =
72+ event_factory.text_msg("recent history").sender(&ALICE).event_id(new_event_id).into_event();
73+ let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
74+ room_event_cache
75+ .reseed(vec![new_event], Some("older".into()))
76+ .await
77+ .unwrap();
78+
79+ timeout(Duration::from_secs(5), async {
80+ loop {
81+ let items = timeline.items().await;
82+ if items.iter().any(|item| {
83+ item.as_event().is_some_and(|event| event.event_id() == Some(new_event_id))
84+ }) {
85+ break;
86+ }
87+ assert!(timeline_stream.next().await.is_some());
88+ }
89+ })
90+ .await
91+ .unwrap();
92+
93+ let items = timeline.items().await;
94+ assert!(!items.iter().any(|item| {
95+ item.as_event().is_some_and(|event| event.event_id() == Some(old_event_id))
96+ }));
97+
98+ let local_echo = items
99+ .iter()
100+ .filter_map(|item| item.as_event())
101+ .find(|event| {
102+ event
103+ .content()
104+ .as_message()
105+ .is_some_and(|message| message.body() == "pending local echo")
106+ })
107+ .unwrap();
108+ assert_matches!(local_echo.send_state(), Some(EventSendState::NotSentYet { .. }));
109+}