Skip to content

Commit

Permalink
sdk-ui: add Timeline::pin_event and Timeline::unpin_event
Browse files Browse the repository at this point in the history
  • Loading branch information
jmartinesp authored and andybalaam committed Jul 26, 2024
1 parent 15bf675 commit ab04945
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 2 deletions.
2 changes: 1 addition & 1 deletion crates/matrix-sdk-ui/src/timeline/event_item/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk-ui/src/timeline/inner/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
};
Expand Down
37 changes: 37 additions & 0 deletions crates/matrix-sdk-ui/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use ruma::{
AddMentions, ForwardThread, OriginalRoomMessageEvent, RoomMessageEventContent,
RoomMessageEventContentWithoutRelation,
},
pinned_events::RoomPinnedEventsEventContent,
redaction::RoomRedactionEventContent,
},
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
Expand Down Expand Up @@ -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.
Expand Down
166 changes: 166 additions & 0 deletions crates/matrix-sdk-ui/tests/integration/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 2 additions & 0 deletions testing/matrix-sdk-test/src/sync_builder/test_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub enum StateTestEvent {
RedactedState,
RoomAvatar,
RoomName,
RoomPinnedEvents,
RoomTopic,
Custom(JsonValue),
}
Expand All @@ -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,
}
Expand Down
16 changes: 16 additions & 0 deletions testing/matrix-sdk-test/src/test_json/sync_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down

0 comments on commit ab04945

Please sign in to comment.