Skip to content

Commit 55549f6

Browse files
committed
test(sdk): Split timeline tests into smaller modules
1 parent cf359de commit 55549f6

File tree

7 files changed

+1004
-939
lines changed

7 files changed

+1004
-939
lines changed

crates/matrix-sdk/src/room/timeline/tests.rs

Lines changed: 0 additions & 939 deletions
This file was deleted.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use assert_matches::assert_matches;
2+
use futures_signals::signal_vec::VecDiff;
3+
use futures_util::StreamExt;
4+
use matrix_sdk_test::async_test;
5+
use ruma::{
6+
assign,
7+
events::{
8+
reaction::ReactionEventContent,
9+
relation::{Annotation, Replacement},
10+
room::{
11+
member::{MembershipState, RedactedRoomMemberEventContent, RoomMemberEventContent},
12+
message::{self, MessageType, RoomMessageEventContent},
13+
name::RoomNameEventContent,
14+
topic::RedactedRoomTopicEventContent,
15+
},
16+
FullStateEventContent,
17+
},
18+
};
19+
use serde_json::json;
20+
21+
use super::{TestTimeline, ALICE, BOB};
22+
use crate::room::timeline::{
23+
event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineItemContent,
24+
VirtualTimelineItem,
25+
};
26+
27+
#[async_test]
28+
async fn initial_events() {
29+
let timeline = TestTimeline::with_initial_events([
30+
(*ALICE, RoomMessageEventContent::text_plain("A").into()),
31+
(*BOB, RoomMessageEventContent::text_plain("B").into()),
32+
])
33+
.await;
34+
let mut stream = timeline.stream();
35+
36+
let items = assert_matches!(stream.next().await, Some(VecDiff::Replace { values }) => values);
37+
assert_eq!(items.len(), 3);
38+
assert_matches!(items[0].as_virtual().unwrap(), VirtualTimelineItem::DayDivider { .. });
39+
assert_eq!(items[1].as_event().unwrap().sender(), *ALICE);
40+
assert_eq!(items[2].as_event().unwrap().sender(), *BOB);
41+
}
42+
43+
#[async_test]
44+
async fn reaction_redaction() {
45+
let timeline = TestTimeline::new();
46+
let mut stream = timeline.stream();
47+
48+
timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("hi!")).await;
49+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
50+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
51+
let event = item.as_event().unwrap().as_remote().unwrap();
52+
assert_eq!(event.reactions().len(), 0);
53+
54+
let msg_event_id = &event.event_id;
55+
56+
let rel = Annotation::new(msg_event_id.to_owned(), "+1".to_owned());
57+
timeline.handle_live_message_event(&BOB, ReactionEventContent::new(rel)).await;
58+
let item =
59+
assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { index: 1, value }) => value);
60+
let event = item.as_event().unwrap().as_remote().unwrap();
61+
assert_eq!(event.reactions().len(), 1);
62+
63+
// TODO: After adding raw timeline items, check for one here
64+
65+
let reaction_event_id = event.event_id.as_ref();
66+
67+
timeline.handle_live_redaction(&BOB, reaction_event_id).await;
68+
let item =
69+
assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { index: 1, value }) => value);
70+
let event = item.as_event().unwrap().as_remote().unwrap();
71+
assert_eq!(event.reactions().len(), 0);
72+
}
73+
74+
#[async_test]
75+
async fn edit_redacted() {
76+
let timeline = TestTimeline::new();
77+
let mut stream = timeline.stream();
78+
79+
// Ruma currently fails to serialize most redacted events correctly
80+
timeline
81+
.handle_live_custom_event(json!({
82+
"content": {},
83+
"event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I",
84+
"origin_server_ts": 10,
85+
"sender": "@alice:example.org",
86+
"type": "m.room.message",
87+
"unsigned": {
88+
"redacted_because": {
89+
"content": {},
90+
"redacts": "$eeG0HA0FAZ37wP8kXlNkxx3K",
91+
"event_id": "$N6eUCBc3vu58PL8TobGaVQzM",
92+
"sender": "@alice:example.org",
93+
"origin_server_ts": 5,
94+
"type": "m.room.redaction",
95+
},
96+
},
97+
}))
98+
.await;
99+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
100+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
101+
102+
let redacted_event_id = item.as_event().unwrap().event_id().unwrap();
103+
104+
let edit = assign!(RoomMessageEventContent::text_plain(" * test"), {
105+
relates_to: Some(message::Relation::Replacement(Replacement::new(
106+
redacted_event_id.to_owned(),
107+
MessageType::text_plain("test"),
108+
))),
109+
});
110+
timeline.handle_live_message_event(&ALICE, edit).await;
111+
112+
assert_eq!(timeline.inner.items().len(), 2);
113+
}
114+
115+
#[async_test]
116+
async fn sticker() {
117+
let timeline = TestTimeline::new();
118+
let mut stream = timeline.stream();
119+
120+
timeline
121+
.handle_live_custom_event(json!({
122+
"content": {
123+
"body": "Happy sticker",
124+
"info": {
125+
"h": 398,
126+
"mimetype": "image/jpeg",
127+
"size": 31037,
128+
"w": 394
129+
},
130+
"url": "mxc://server.name/JWEIFJgwEIhweiWJE",
131+
},
132+
"event_id": "$143273582443PhrSn",
133+
"origin_server_ts": 143273582,
134+
"sender": "@alice:server.name",
135+
"type": "m.sticker",
136+
}))
137+
.await;
138+
139+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
140+
141+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
142+
assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::Sticker(_));
143+
}
144+
145+
#[async_test]
146+
async fn room_member() {
147+
let timeline = TestTimeline::new();
148+
let mut stream = timeline.stream();
149+
150+
let mut first_room_member_content = RoomMemberEventContent::new(MembershipState::Invite);
151+
first_room_member_content.displayname = Some("Alice".to_owned());
152+
timeline
153+
.handle_live_original_state_event_with_state_key(
154+
&BOB,
155+
ALICE.to_owned(),
156+
first_room_member_content.clone(),
157+
None,
158+
)
159+
.await;
160+
161+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
162+
163+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
164+
let membership = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::MembershipChange(ev) => ev);
165+
assert_matches!(membership.content(), FullStateEventContent::Original { .. });
166+
assert_matches!(membership.change(), Some(MembershipChange::Invited));
167+
168+
let mut second_room_member_content = RoomMemberEventContent::new(MembershipState::Join);
169+
second_room_member_content.displayname = Some("Alice".to_owned());
170+
timeline
171+
.handle_live_original_state_event_with_state_key(
172+
&ALICE,
173+
ALICE.to_owned(),
174+
second_room_member_content.clone(),
175+
Some(first_room_member_content),
176+
)
177+
.await;
178+
179+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
180+
let membership = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::MembershipChange(ev) => ev);
181+
assert_matches!(membership.content(), FullStateEventContent::Original { .. });
182+
assert_matches!(membership.change(), Some(MembershipChange::InvitationAccepted));
183+
184+
let mut third_room_member_content = RoomMemberEventContent::new(MembershipState::Join);
185+
third_room_member_content.displayname = Some("Alice In Wonderland".to_owned());
186+
timeline
187+
.handle_live_original_state_event_with_state_key(
188+
&ALICE,
189+
ALICE.to_owned(),
190+
third_room_member_content,
191+
Some(second_room_member_content),
192+
)
193+
.await;
194+
195+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
196+
let profile = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::ProfileChange(ev) => ev);
197+
assert_matches!(profile.displayname_change(), Some(_));
198+
assert_matches!(profile.avatar_url_change(), None);
199+
200+
timeline
201+
.handle_live_redacted_state_event_with_state_key(
202+
&ALICE,
203+
ALICE.to_owned(),
204+
RedactedRoomMemberEventContent::new(MembershipState::Join),
205+
)
206+
.await;
207+
208+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
209+
let membership = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::MembershipChange(ev) => ev);
210+
assert_matches!(membership.content(), FullStateEventContent::Redacted(_));
211+
assert_matches!(membership.change(), None);
212+
}
213+
214+
#[async_test]
215+
async fn other_state() {
216+
let timeline = TestTimeline::new();
217+
let mut stream = timeline.stream();
218+
219+
timeline
220+
.handle_live_original_state_event(
221+
&ALICE,
222+
RoomNameEventContent::new(Some("Alice's room".to_owned())),
223+
None,
224+
)
225+
.await;
226+
227+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
228+
229+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
230+
let ev = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::OtherState(ev) => ev);
231+
let full_content =
232+
assert_matches!(ev.content(), AnyOtherFullStateEventContent::RoomName(c) => c);
233+
let (content, prev_content) = assert_matches!(full_content, FullStateEventContent::Original { content, prev_content } => (content, prev_content));
234+
assert_eq!(content.name.as_ref().unwrap(), "Alice's room");
235+
assert_matches!(prev_content, None);
236+
237+
timeline.handle_live_redacted_state_event(&ALICE, RedactedRoomTopicEventContent::new()).await;
238+
239+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
240+
let ev = assert_matches!(item.as_event().unwrap().content(), TimelineItemContent::OtherState(ev) => ev);
241+
let full_content =
242+
assert_matches!(ev.content(), AnyOtherFullStateEventContent::RoomTopic(c) => c);
243+
assert_matches!(full_content, FullStateEventContent::Redacted(_));
244+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::{io, sync::Arc};
2+
3+
use assert_matches::assert_matches;
4+
use futures_signals::signal_vec::VecDiff;
5+
use futures_util::StreamExt;
6+
use matrix_sdk_test::async_test;
7+
use ruma::{
8+
event_id,
9+
events::{room::message::RoomMessageEventContent, AnyMessageLikeEventContent},
10+
};
11+
use serde_json::json;
12+
13+
use super::{TestTimeline, ALICE, BOB};
14+
use crate::{
15+
room::timeline::{event_item::EventSendState, EventTimelineItem},
16+
Error,
17+
};
18+
19+
#[async_test]
20+
async fn remote_echo_full_trip() {
21+
let timeline = TestTimeline::new();
22+
let mut stream = timeline.stream();
23+
24+
// Given a local event…
25+
let txn_id = timeline
26+
.handle_local_event(AnyMessageLikeEventContent::RoomMessage(
27+
RoomMessageEventContent::text_plain("echo"),
28+
))
29+
.await;
30+
31+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
32+
33+
// Scenario 1: The local event has not been sent yet to the server.
34+
{
35+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
36+
let event = item.as_event().unwrap().as_local().unwrap();
37+
assert_matches!(event.send_state, EventSendState::NotSentYet);
38+
}
39+
40+
// Scenario 2: The local event has not been sent to the server successfully, it
41+
// has failed. In this case, there is no event ID.
42+
{
43+
let some_io_error = Error::Io(io::Error::new(io::ErrorKind::Other, "this is a test"));
44+
timeline.inner.update_event_send_state(
45+
&txn_id,
46+
EventSendState::SendingFailed { error: Arc::new(some_io_error) },
47+
);
48+
49+
let item = assert_matches!(
50+
stream.next().await,
51+
Some(VecDiff::UpdateAt { value, index: 1 }) => value
52+
);
53+
let event = item.as_event().unwrap().as_local().unwrap();
54+
assert_matches!(event.send_state, EventSendState::SendingFailed { .. });
55+
}
56+
57+
// Scenario 3: The local event has been sent successfully to the server and an
58+
// event ID has been received as part of the server's response.
59+
let event_id = {
60+
let event_id = event_id!("$W6mZSLWMmfuQQ9jhZWeTxFIM");
61+
62+
timeline.inner.update_event_send_state(
63+
&txn_id,
64+
EventSendState::Sent { event_id: event_id.to_owned() },
65+
);
66+
67+
let item = assert_matches!(
68+
stream.next().await,
69+
Some(VecDiff::UpdateAt { value, index: 1 }) => value
70+
);
71+
let event = item.as_event().unwrap().as_local().unwrap();
72+
assert_matches!(event.send_state, EventSendState::Sent { .. });
73+
74+
event_id
75+
};
76+
77+
// Now, a sync has been run against the server, and an event with the same ID
78+
// comes in.
79+
timeline
80+
.handle_live_custom_event(json!({
81+
"content": {
82+
"body": "echo",
83+
"msgtype": "m.text",
84+
},
85+
"sender": &*ALICE,
86+
"event_id": event_id,
87+
"origin_server_ts": 5,
88+
"type": "m.room.message",
89+
}))
90+
.await;
91+
92+
// The local echo is removed
93+
assert_matches!(stream.next().await, Some(VecDiff::Pop { .. }));
94+
95+
// This day divider shouldn't be present, or the previous one should be
96+
// removed. There being a two day dividers in a row is a bug, but it's
97+
// non-trivial to fix and rare enough that we can fix it later (only happens
98+
// when the first message on a given day is a local echo).
99+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
100+
101+
// … and the remote echo is added.
102+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
103+
assert_matches!(item.as_event().unwrap(), EventTimelineItem::Remote(_));
104+
}
105+
106+
#[async_test]
107+
async fn remote_echo_new_position() {
108+
let timeline = TestTimeline::new();
109+
let mut stream = timeline.stream();
110+
111+
// Given a local event…
112+
let txn_id = timeline
113+
.handle_local_event(AnyMessageLikeEventContent::RoomMessage(
114+
RoomMessageEventContent::text_plain("echo"),
115+
))
116+
.await;
117+
118+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
119+
120+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
121+
let txn_id_from_event = item.as_event().unwrap().as_local().unwrap();
122+
assert_eq!(txn_id, *txn_id_from_event.transaction_id);
123+
124+
// … and another event that comes back before the remote echo
125+
timeline.handle_live_message_event(&BOB, RoomMessageEventContent::text_plain("test")).await;
126+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
127+
let _bob_message = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
128+
129+
// When the remote echo comes in…
130+
timeline
131+
.handle_live_custom_event(json!({
132+
"content": {
133+
"body": "echo",
134+
"msgtype": "m.text",
135+
},
136+
"sender": &*ALICE,
137+
"event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I",
138+
"origin_server_ts": 6,
139+
"type": "m.room.message",
140+
"unsigned": {
141+
"transaction_id": txn_id,
142+
},
143+
}))
144+
.await;
145+
146+
// … the local echo should be removed
147+
assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 1 }));
148+
149+
// … and the remote echo added
150+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
151+
assert_matches!(item.as_event().unwrap(), EventTimelineItem::Remote(_));
152+
}

0 commit comments

Comments
 (0)