Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions crates/matrix-sdk-ui/tests/integration/timeline/focus_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,3 +753,144 @@ async fn test_focused_timeline_handles_other_thread_event_when_forcing_threaded_
assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]);
assert_eq!(item.as_event().unwrap().content().as_message().unwrap().body(), "Next");
}

#[async_test]
async fn test_focused_timeline_filters_out_threaded_events() {
let room_id = room_id!("!a98sd12bjh:example.org");
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let user_id = client.user_id().unwrap();

let prev_thread_event_id = event_id!("$prev:example.org");
let root_thread_id = event_id!("$root-id:example.org");

let f = EventFactory::new().room(room_id).sender(user_id);
let focus_event = f.text_msg("Hey").into_event();
let focus_event_id = focus_event.event_id().unwrap().clone();

// Mock the initial /context request to check if the event is in a thread.
let events_before = vec![
f.text_msg("Unrelated before 1").into_event(),
f.text_msg("Unrelated before 2").into_event(),
f.text_msg("Thread before").in_thread(root_thread_id, prev_thread_event_id).into_event(),
];
let events_after = vec![
f.text_msg("Unrelated after 1").into_event(),
f.text_msg("Unrelated after 2").into_event(),
f.text_msg("Thread after").in_thread(root_thread_id, prev_thread_event_id).into_event(),
];
server
.mock_room_event_context()
.room(room_id)
.ok(RoomContextResponseTemplate::new(focus_event)
.events_before(events_before)
.events_after(events_after)
.start("prev_token_1")
.end("next_token_1"))
.mock_once()
.mount()
.await;

let focus = TimelineFocus::Event {
target: focus_event_id,
num_context_events: 10,
thread_mode: TimelineEventFocusThreadMode::Automatic { hide_threaded_events: true },
};

let room = server.sync_joined_room(&client, room_id).await;
let timeline = TimelineBuilder::new(&room)
.with_focus(focus)
.build()
.await
.expect("Could not build focused timeline");

assert!(
timeline.live_back_pagination_status().await.is_none(),
"there should be no live back-pagination status for a focused timeline"
);

let (items, mut timeline_stream) = timeline.subscribe().await;
// The 2 unrelated events before + 2 after + the item + the date divider
assert_eq!(items.len(), 6);
assert!(items[0].is_date_divider());
assert_eq!(
items[1].as_event().unwrap().content().as_message().unwrap().body(),
"Unrelated before 2"
);
assert_eq!(
items[2].as_event().unwrap().content().as_message().unwrap().body(),
"Unrelated before 1"
);
assert_eq!(items[3].as_event().unwrap().content().as_message().unwrap().body(), "Hey");
assert_eq!(
items[4].as_event().unwrap().content().as_message().unwrap().body(),
"Unrelated after 1"
);
assert_eq!(
items[5].as_event().unwrap().content().as_message().unwrap().body(),
"Unrelated after 2"
);

// We paginate back once
server
.mock_room_messages()
.match_from("prev_token_1")
.ok(RoomMessagesResponseTemplate {
chunk: vec![
f.text_msg("Prev")
.sender(user_id)
.in_thread(root_thread_id, prev_thread_event_id)
.into_raw_timeline(),
f.text_msg("Prev no thread").sender(user_id).into_raw_timeline(),
],
start: "prev_token_1".to_owned(),
end: Some("prev_token_2".to_owned()),
state: Vec::new(),
delay: None,
})
.mock_once()
.mount()
.await;

let start_of_timeline =
timeline.paginate_backwards(10).await.expect("Could not paginate backwards");
assert!(!start_of_timeline);

// Only the non-threaded event is inserted at the start.
assert_let_timeout!(Some(timeline_updates) = timeline_stream.next());
assert_eq!(timeline_updates.len(), 1);
// The new item loaded is inserted at the start, just after the date divider.
assert_let!(VectorDiff::Insert { index: 1, value: item } = &timeline_updates[0]);
assert_eq!(item.as_event().unwrap().content().as_message().unwrap().body(), "Prev no thread");

// Then we paginate forwards
server
.mock_room_messages()
.match_from("next_token_1")
.ok(RoomMessagesResponseTemplate {
chunk: vec![
f.text_msg("Next no thread").sender(user_id).into_raw_timeline(),
f.text_msg("Next1")
.sender(user_id)
.in_thread(root_thread_id, prev_thread_event_id)
.into_raw_timeline(),
],
start: "next_token_1".to_owned(),
end: Some("next_token_2".to_owned()),
state: Vec::new(),
delay: None,
})
.mock_once()
.mount()
.await;

let end_of_timeline =
timeline.paginate_forwards(10).await.expect("Could not paginate forwards");
assert!(!end_of_timeline);

// Only the non-threaded event is pushed to the end.
assert_let_timeout!(Some(timeline_updates) = timeline_stream.next());
assert_eq!(timeline_updates.len(), 1);
assert_let!(VectorDiff::PushBack { value: item } = &timeline_updates[0]);
assert_eq!(item.as_event().unwrap().content().as_message().unwrap().body(), "Next no thread");
}
3 changes: 3 additions & 0 deletions crates/matrix-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ All notable changes to this project will be documented in this file.

### Bugfix

- When threads are enabled, a focused event timeline is used and the focused event is not part of a thread,
hide other threaded events by default like it happens on the live focus timeline.
([#6519](https://github.com/matrix-org/matrix-rust-sdk/pull/6519))
- Add a recursion limit attribute that raises it from the default value of 128 to 256.
([#6489](https://github.com/matrix-org/matrix-rust-sdk/pull/6489))
- Reject invalid edits as candidates for the latest event.
Expand Down
49 changes: 43 additions & 6 deletions crates/matrix-sdk/src/event_cache/caches/event_focused/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub enum EventFocusThreadMode {
pub(crate) enum EventFocusedPaginationMode {
/// Standard room pagination (for all events as for an unthreaded/main room
/// linked chunk).
Room,
Room { hide_thread_events: bool },

/// Threaded pagination (the focused event is part of a thread).
Thread {
Expand Down Expand Up @@ -210,12 +210,27 @@ impl EventFocusedCacheInner {
self.add_initial_events_with_gaps(thread_events, backward_token, forward_token);
} else {
trace!("focused event is not part of a thread, setting up room pagination");
self.pagination_mode = EventFocusedPaginationMode::Room;

let backward_token = tokens.previous.into_token();
let forward_token = tokens.next.into_token();

self.add_initial_events_with_gaps(result.events.clone(), backward_token, forward_token);
let hide_thread_events =
matches!(thread_mode, EventFocusThreadMode::Automatic) && thread_root.is_none();

self.pagination_mode = EventFocusedPaginationMode::Room { hide_thread_events };

let events = if hide_thread_events {
result
.events
.iter()
.filter(|event| extract_thread_root(event.raw()).is_none())
.cloned()
.collect()
} else {
result.events.clone()
};

self.add_initial_events_with_gaps(events, backward_token, forward_token);
}

self.propagate_changes();
Expand Down Expand Up @@ -304,7 +319,7 @@ impl EventFocusedCacheInner {

// Fetch events based on pagination mode.
let (mut events, new_token) = match &self.pagination_mode {
EventFocusedPaginationMode::Room => {
EventFocusedPaginationMode::Room { .. } => {
Self::fetch_room_backwards(&room, num_events, &token).await?
}
EventFocusedPaginationMode::Thread { thread_root } => {
Expand All @@ -319,6 +334,17 @@ impl EventFocusedCacheInner {
let hit_end = new_token.is_none();
let new_gap = new_token.map(|t| Gap { token: t });

let hide_thread_events = match &self.pagination_mode {
EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
EventFocusedPaginationMode::Thread { .. } => false,
};

let events = if hide_thread_events {
events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
} else {
events
};

// Replace the gap and insert the new events.
self.chunk.push_backwards_pagination_events(Some(gap_id), new_gap, &events);

Expand Down Expand Up @@ -405,7 +431,7 @@ impl EventFocusedCacheInner {

// Fetch events based on pagination mode.
let (events, new_token) = match &self.pagination_mode {
EventFocusedPaginationMode::Room => {
EventFocusedPaginationMode::Room { .. } => {
Self::fetch_room_forwards(&room, num_events, &token).await?
}
EventFocusedPaginationMode::Thread { thread_root } => {
Expand All @@ -416,6 +442,17 @@ impl EventFocusedCacheInner {
let hit_end = new_token.is_none();
let new_gap = new_token.map(|t| Gap { token: t });

let hide_thread_events = match &self.pagination_mode {
EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
EventFocusedPaginationMode::Thread { .. } => false,
};

let events = if hide_thread_events {
events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
} else {
events
};

// Replace the gap and insert new events.
self.chunk.push_forwards_pagination_events(Some(gap_id), new_gap, &events);

Expand Down Expand Up @@ -497,7 +534,7 @@ impl EventFocusedCache {
inner: Arc::new(RwLock::new(EventFocusedCacheInner {
room,
focused_event_id,
pagination_mode: EventFocusedPaginationMode::Room,
pagination_mode: EventFocusedPaginationMode::Room { hide_thread_events: false },
chunk: EventLinkedChunk::new(),
sender: Sender::new(32),
linked_chunk_update_sender,
Expand Down
Loading