sdk-ui: add Timeline::pin_event and Timeline::unpin_event

This commit is contained in:
Jorge Martín
2024-07-23 10:29:25 +02:00
committed by Andy Balaam
parent 15bf675e5e
commit ab0494549e
6 changed files with 223 additions and 2 deletions

View File

@@ -44,7 +44,7 @@ pub(in crate::timeline) struct RemoteEventTimelineItem {
/// Note that currently this ignores threads.
pub read_receipts: IndexMap<OwnedUserId, Receipt>,
/// Whether the event has been sent by the the logged-in user themselves.
/// Whether the event has been sent by the logged-in user themselves.
pub is_own: bool,
/// Whether the item should be highlighted in the timeline.

View File

@@ -170,7 +170,7 @@ impl TimelineInnerState {
timestamp: MilliSecondsSinceUnixEpoch::now(),
is_own_event: true,
read_receipts: Default::default(),
// An event sent by ourself is never matched against push rules.
// An event sent by ourselves is never matched against push rules.
is_highlighted: false,
flow: Flow::Local { txn_id, send_handle },
};

View File

@@ -50,6 +50,7 @@ use ruma::{
AddMentions, ForwardThread, OriginalRoomMessageEvent, RoomMessageEventContent,
RoomMessageEventContentWithoutRelation,
},
pinned_events::RoomPinnedEventsEventContent,
redaction::RoomRedactionEventContent,
},
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
@@ -857,6 +858,42 @@ impl Timeline {
Ok(false)
}
}
/// Adds a new pinned event by sending an updated `m.room.pinned_events`
/// event containing the new event id.
///
/// Returns `true` if we sent the request, `false` if the event was already
/// pinned.
pub async fn pin_event(&self, event_id: &EventId) -> Result<bool> {
let mut pinned_events = self.room().pinned_events();
let event_id = event_id.to_owned();
if pinned_events.contains(&event_id) {
Ok(false)
} else {
pinned_events.push(event_id);
let content = RoomPinnedEventsEventContent::new(pinned_events);
self.room().send_state_event(content).await?;
Ok(true)
}
}
/// Adds a new pinned event by sending an updated `m.room.pinned_events`
/// event without the event id we want to remove.
///
/// Returns `true` if we sent the request, `false` if the event wasn't
/// pinned.
pub async fn unpin_event(&self, event_id: &EventId) -> Result<bool> {
let mut pinned_events = self.room().pinned_events();
let event_id = event_id.to_owned();
if let Some(idx) = pinned_events.iter().position(|e| *e == *event_id) {
pinned_events.remove(idx);
let content = RoomPinnedEventsEventContent::new(pinned_events);
self.room().send_state_event(content).await?;
Ok(true)
} else {
Ok(false)
}
}
}
/// Test helpers, likely not very useful in production.

View File

@@ -507,3 +507,169 @@ async fn test_duplicate_maintains_correct_order() {
let content = items[3].as_event().unwrap().content().as_message().unwrap().body();
assert_eq!(content, "C");
}
#[async_test]
async fn test_pin_event_is_sent_successfully() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(false).await;
assert!(!timeline.items().await.is_empty());
// Pinning a remote event succeeds.
setup
.mock_response(ResponseTemplate::new(200).set_body_json(json!({
"event_id": "$42"
})))
.await;
let event_id = setup.event_id();
assert!(timeline.pin_event(event_id).await.unwrap());
setup.reset_server().await;
}
#[async_test]
async fn test_pin_event_is_returning_false_because_is_already_pinned() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(true).await;
assert!(!timeline.items().await.is_empty());
let event_id = setup.event_id();
assert!(!timeline.pin_event(event_id).await.unwrap());
setup.reset_server().await;
}
#[async_test]
async fn test_pin_event_is_returning_an_error() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(false).await;
assert!(!timeline.items().await.is_empty());
// Pinning a remote event fails.
setup.mock_response(ResponseTemplate::new(400)).await;
let event_id = setup.event_id();
assert!(timeline.pin_event(event_id).await.is_err());
setup.reset_server().await;
}
#[async_test]
async fn test_unpin_event_is_sent_successfully() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(true).await;
assert!(!timeline.items().await.is_empty());
// Unpinning a remote event succeeds.
setup
.mock_response(ResponseTemplate::new(200).set_body_json(json!({
"event_id": "$42"
})))
.await;
let event_id = setup.event_id();
assert!(timeline.unpin_event(event_id).await.unwrap());
setup.reset_server().await;
}
#[async_test]
async fn test_unpin_event_is_returning_false_because_is_not_pinned() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(false).await;
assert!(!timeline.items().await.is_empty());
let event_id = setup.event_id();
assert!(!timeline.unpin_event(event_id).await.unwrap());
setup.reset_server().await;
}
#[async_test]
async fn test_unpin_event_is_returning_an_error() {
let mut setup = PinningTestSetup::new().await;
let timeline = setup.timeline().await;
setup.mock_sync(true).await;
assert!(!timeline.items().await.is_empty());
// Unpinning a remote event fails.
setup.mock_response(ResponseTemplate::new(400)).await;
let event_id = setup.event_id();
assert!(timeline.unpin_event(event_id).await.is_err());
setup.reset_server().await;
}
struct PinningTestSetup<'a> {
event_id: &'a ruma::EventId,
room_id: &'a ruma::RoomId,
client: matrix_sdk::Client,
server: wiremock::MockServer,
sync_settings: SyncSettings,
sync_builder: SyncResponseBuilder,
}
impl PinningTestSetup<'_> {
async fn new() -> Self {
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();
let event_id = event_id!("$a");
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;
Self { event_id, room_id, client, server, sync_settings, sync_builder }
}
async fn timeline(&self) -> matrix_sdk_ui::Timeline {
let room = self.client.get_room(self.room_id).unwrap();
room.timeline().await.unwrap()
}
async fn reset_server(&self) {
self.server.reset().await;
}
async fn mock_response(&self, response: ResponseTemplate) {
Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.room.pinned_events/.*?"))
.and(header("authorization", "Bearer 1234"))
.respond_with(response)
.mount(&self.server)
.await;
}
async fn mock_sync(&mut self, is_using_pinned_state_event: bool) {
let f = EventFactory::new().sender(user_id!("@a:b.c"));
let mut joined_room_builder = JoinedRoomBuilder::new(self.room_id)
.add_timeline_event(f.text_msg("A").event_id(self.event_id).into_raw_sync());
if is_using_pinned_state_event {
joined_room_builder =
joined_room_builder.add_state_event(StateTestEvent::RoomPinnedEvents);
}
self.sync_builder.add_joined_room(joined_room_builder);
mock_sync(&self.server, self.sync_builder.build_json_sync_response(), None).await;
let _response = self.client.sync_once(self.sync_settings.clone()).await.unwrap();
}
fn event_id(&self) -> &ruma::EventId {
self.event_id
}
}

View File

@@ -28,6 +28,7 @@ pub enum StateTestEvent {
RedactedState,
RoomAvatar,
RoomName,
RoomPinnedEvents,
RoomTopic,
Custom(JsonValue),
}
@@ -53,6 +54,7 @@ impl StateTestEvent {
Self::RedactedState => test_json::sync_events::REDACTED_STATE.to_owned(),
Self::RoomAvatar => test_json::sync_events::ROOM_AVATAR.to_owned(),
Self::RoomName => test_json::sync_events::NAME.to_owned(),
Self::RoomPinnedEvents => test_json::sync_events::PINNED_EVENTS.to_owned(),
Self::RoomTopic => test_json::sync_events::TOPIC.to_owned(),
Self::Custom(json) => json,
}

View File

@@ -317,6 +317,22 @@ pub static NAME_STRIPPED: Lazy<JsonValue> = Lazy::new(|| {
})
});
pub static PINNED_EVENTS: Lazy<JsonValue> = Lazy::new(|| {
json!({
"content": {
"pinned": [ "$a" ]
},
"event_id": "$15139375513VdeRF:localhost",
"origin_server_ts": 151393755,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.pinned_events",
"unsigned": {
"age": 703422
}
})
});
pub static POWER_LEVELS: Lazy<JsonValue> = Lazy::new(|| {
json!({
"content": {