Skip to content

Commit 2a69868

Browse files
committed
look ma i'm writing tests and they found bugs
1 parent 11a1d75 commit 2a69868

File tree

4 files changed

+194
-7
lines changed

4 files changed

+194
-7
lines changed

crates/matrix-sdk-ui/src/timeline/event_handler.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,31 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
304304
}
305305
trace!("Handling remote event");
306306

307-
// Only add new timeline items if we're in the live mode, i.e. not in the
308-
// event-focused mode, *and* the event filter didn't filter this event out.
309-
self.is_live_timeline && *should_add
307+
// Retrieve the origin of the event.
308+
let origin = match position {
309+
TimelineItemPosition::End { origin }
310+
| TimelineItemPosition::Start { origin } => *origin,
311+
312+
TimelineItemPosition::Update(idx) => self
313+
.items
314+
.get(*idx)
315+
.and_then(|item| item.as_event())
316+
.and_then(|item| item.as_remote())
317+
.map_or(RemoteEventOrigin::Unknown, |item| item.origin),
318+
};
319+
320+
match origin {
321+
RemoteEventOrigin::Sync | RemoteEventOrigin::Unknown => {
322+
// If the event comes the sync (or is unknown), consider adding it only if
323+
// the timeline is in live mode; we don't want to display arbitrary sync
324+
// events in an event-focused timeline.
325+
self.is_live_timeline && *should_add
326+
}
327+
RemoteEventOrigin::Pagination | RemoteEventOrigin::Cache => {
328+
// Otherwise, forward the previous decision to add it.
329+
*should_add
330+
}
331+
}
310332
}
311333
};
312334

crates/matrix-sdk-ui/tests/integration/main.rs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use itertools::Itertools as _;
16+
use matrix_sdk::deserialized_responses::TimelineEvent;
1517
use matrix_sdk_test::test_json;
18+
use ruma::{events::AnyStateEvent, serde::Raw, EventId, RoomId};
1619
use serde::Serialize;
20+
use serde_json::json;
1721
use wiremock::{
1822
matchers::{header, method, path, path_regex, query_param, query_param_is_missing},
1923
Mock, MockServer, ResponseTemplate,
@@ -32,22 +36,74 @@ matrix_sdk_test::init_tracing_for_tests!();
3236
/// an optional `since` param that returns a 200 status code with the given
3337
/// response body.
3438
async fn mock_sync(server: &MockServer, response_body: impl Serialize, since: Option<String>) {
35-
let mut builder = Mock::given(method("GET"))
39+
let mut mock_builder = Mock::given(method("GET"))
3640
.and(path("/_matrix/client/r0/sync"))
3741
.and(header("authorization", "Bearer 1234"));
3842

3943
if let Some(since) = since {
40-
builder = builder.and(query_param("since", since));
44+
mock_builder = mock_builder.and(query_param("since", since));
4145
} else {
42-
builder = builder.and(query_param_is_missing("since"));
46+
mock_builder = mock_builder.and(query_param_is_missing("since"));
4347
}
4448

45-
builder
49+
mock_builder
4650
.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
4751
.mount(server)
4852
.await;
4953
}
5054

55+
/// Mocks the /context endpoint
56+
///
57+
/// Note: pass `events_before` in the normal order, I'll revert the order for
58+
/// you.
59+
async fn mock_context(
60+
server: &MockServer,
61+
room_id: &RoomId,
62+
event_id: &EventId,
63+
prev_batch_token: Option<String>,
64+
events_before: Vec<TimelineEvent>,
65+
event: TimelineEvent,
66+
events_after: Vec<TimelineEvent>,
67+
next_batch_token: Option<String>,
68+
state: Vec<Raw<AnyStateEvent>>,
69+
) {
70+
Mock::given(method("GET"))
71+
.and(path(format!("/_matrix/client/r0/rooms/{room_id}/context/{event_id}")))
72+
.and(header("authorization", "Bearer 1234"))
73+
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
74+
"events_before": events_before.into_iter().rev().map(|ev| ev.event).collect_vec(),
75+
"event": event.event,
76+
"events_after": events_after.into_iter().map(|ev| ev.event).collect_vec(),
77+
"state": state,
78+
"prev_batch_token": prev_batch_token,
79+
"next_batch_token": next_batch_token
80+
})))
81+
.mount(&server)
82+
.await;
83+
}
84+
85+
async fn mock_messages(
86+
server: &MockServer,
87+
start: String,
88+
end: Option<String>,
89+
chunk: Vec<TimelineEvent>,
90+
state: Vec<Raw<AnyStateEvent>>,
91+
) {
92+
Mock::given(method("GET"))
93+
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$"))
94+
.and(header("authorization", "Bearer 1234"))
95+
.and(query_param("from", start.clone()))
96+
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
97+
"start": start,
98+
"end": end,
99+
"chunk": chunk.into_iter().map(|ev| ev.event).collect_vec(),
100+
"state": state,
101+
})))
102+
.expect(1)
103+
.mount(&server)
104+
.await;
105+
}
106+
51107
/// Mount a Mock on the given server to handle the `GET
52108
/// /rooms/.../state/m.room.encryption` endpoint with an option whether it
53109
/// should return an encryption event or not.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2024 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Tests specific to a timeline focused on an event.
16+
17+
use std::time::Duration;
18+
19+
use assert_matches::assert_matches;
20+
use assert_matches2::assert_let;
21+
use eyeball_im::VectorDiff;
22+
use futures_util::StreamExt;
23+
use matrix_sdk::{
24+
config::SyncSettings,
25+
test_utils::{events::EventFactory, logged_in_client_with_server},
26+
};
27+
use matrix_sdk_test::{
28+
async_test, sync_timeline_event, JoinedRoomBuilder, RoomAccountDataTestEvent, StateTestEvent,
29+
SyncResponseBuilder, ALICE, BOB,
30+
};
31+
use matrix_sdk_ui::{
32+
timeline::{RoomExt, TimelineFocus, TimelineItemContent, VirtualTimelineItem},
33+
Timeline,
34+
};
35+
use ruma::{event_id, room_id, user_id};
36+
use stream_assert::assert_pending;
37+
38+
use crate::{mock_context, mock_sync};
39+
40+
#[async_test]
41+
async fn test_new_focused() {
42+
let room_id = room_id!("!a98sd12bjh:example.org");
43+
let (client, server) = logged_in_client_with_server().await;
44+
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
45+
46+
let mut ev_builder = SyncResponseBuilder::new();
47+
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
48+
49+
// Mark the room as joined.
50+
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
51+
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
52+
server.reset().await;
53+
54+
let factory = EventFactory::new().room(room_id);
55+
let target_event = event_id!("$1");
56+
57+
mock_context(
58+
&server,
59+
room_id,
60+
target_event,
61+
Some("prev1".to_owned()),
62+
vec![
63+
factory.text_msg("i tried so hard").sender(*ALICE).into_timeline(),
64+
factory.text_msg("and got so far").sender(*ALICE).into_timeline(),
65+
],
66+
factory.text_msg("in the end").sender(*BOB).into_timeline(),
67+
vec![
68+
factory.text_msg("it doesn't even").sender(*ALICE).into_timeline(),
69+
factory.text_msg("matter").sender(*ALICE).into_timeline(),
70+
],
71+
Some("next1".to_owned()),
72+
vec![],
73+
)
74+
.await;
75+
76+
let room = client.get_room(room_id).unwrap();
77+
let timeline = Timeline::builder(&room)
78+
.with_focus(TimelineFocus::Event {
79+
target: target_event.to_owned(),
80+
num_context_events: 20,
81+
})
82+
.build()
83+
.await
84+
.unwrap();
85+
86+
server.reset().await;
87+
88+
let (items, mut timeline_stream) = timeline.subscribe().await;
89+
90+
assert_eq!(items.len(), 5 + 1); // event items + a day divider
91+
assert!(items[0].is_day_divider());
92+
assert_eq!(
93+
items[1].as_event().unwrap().content().as_message().unwrap().body(),
94+
"i tried so hard"
95+
);
96+
assert_eq!(
97+
items[2].as_event().unwrap().content().as_message().unwrap().body(),
98+
"and got so far"
99+
);
100+
assert_eq!(items[3].as_event().unwrap().content().as_message().unwrap().body(), "in the end");
101+
assert_eq!(
102+
items[4].as_event().unwrap().content().as_message().unwrap().body(),
103+
"it doesn't even"
104+
);
105+
assert_eq!(items[5].as_event().unwrap().content().as_message().unwrap().body(), "matter");
106+
107+
assert_pending!(timeline_stream);
108+
}

crates/matrix-sdk-ui/tests/integration/timeline/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::mock_sync;
3030

3131
mod echo;
3232
mod edit;
33+
mod focus_event;
3334
mod pagination;
3435
mod profiles;
3536
mod queue;

0 commit comments

Comments
 (0)