feat(ffi): Implement RoomList::invites

feat(ffi): Implement `RoomList::invites`
This commit is contained in:
Ivan Enderlin
2023-06-16 13:31:53 +02:00
committed by GitHub
4 changed files with 358 additions and 16 deletions

View File

@@ -149,6 +149,24 @@ impl RoomList {
})
}
async fn invites(
&self,
listener: Box<dyn RoomListEntriesListener>,
) -> Result<RoomListEntriesResult, RoomListError> {
let (entries, entries_stream) = self.inner.invites().await.map_err(RoomListError::from)?;
Ok(RoomListEntriesResult {
entries: entries.into_iter().map(Into::into).collect(),
entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
pin_mut!(entries_stream);
while let Some(diff) = entries_stream.next().await {
listener.on_update(diff.into());
}
}))),
})
}
async fn apply_input(&self, input: RoomListInput) -> Result<(), RoomListError> {
self.inner.apply_input(input.into()).await.map_err(Into::into)
}

View File

@@ -230,6 +230,19 @@ impl RoomList {
.ok_or_else(|| Error::UnknownList(ALL_ROOMS_LIST_NAME.to_owned()))
}
/// Get all previous invites, in addition to a [`Stream`] to invites.
///
/// Invites are taking the form of `RoomListEntry`, it's like a “sub” room
/// list.
pub async fn invites(
&self,
) -> Result<(Vector<RoomListEntry>, impl Stream<Item = VectorDiff<RoomListEntry>>), Error> {
self.sliding_sync
.on_list(INVITES_LIST_NAME, |list| ready(list.room_list_stream()))
.await
.ok_or_else(|| Error::UnknownList(INVITES_LIST_NAME.to_owned()))
}
/// Pass an [`Input`] onto the state machine.
pub async fn apply_input(&self, input: Input) -> Result<(), Error> {
use Input::*;

View File

@@ -13,6 +13,7 @@ use super::Error;
pub const ALL_ROOMS_LIST_NAME: &str = "all_rooms";
pub const VISIBLE_ROOMS_LIST_NAME: &str = "visible_rooms";
pub const INVITES_LIST_NAME: &str = "invites";
/// The state of the [`super::RoomList`]' state machine.
#[derive(Clone, Debug, PartialEq)]
@@ -130,6 +131,36 @@ impl Action for SetAllRoomsListToGrowingSyncMode {
}
}
struct AddInvitesList;
#[async_trait]
impl Action for AddInvitesList {
async fn run(&self, sliding_sync: &SlidingSync) -> Result<(), Error> {
sliding_sync
.add_list(
SlidingSyncList::builder(INVITES_LIST_NAME)
.sync_mode(SlidingSyncMode::new_growing(100))
.timeline_limit(0)
.required_state(vec![
(StateEventType::RoomAvatar, "".to_owned()),
(StateEventType::RoomEncryption, "".to_owned()),
(StateEventType::RoomMember, "$ME".to_owned()),
(StateEventType::RoomCanonicalAlias, "".to_owned()),
])
.filters(Some(assign!(SyncRequestListFilters::default(), {
is_invite: Some(true),
is_tombstoned: Some(false),
not_room_types: vec!["m.space".to_owned()],
}))),
)
.await
.map_err(Error::SlidingSync)?;
Ok(())
}
}
/// Type alias to represent one action.
type OneAction = Box<dyn Action + Send + Sync>;
@@ -169,7 +200,7 @@ macro_rules! actions {
impl Actions {
actions! {
none => [],
first_rooms_are_loaded => [SetAllRoomsListToGrowingSyncMode, AddVisibleRoomsList],
first_rooms_are_loaded => [SetAllRoomsListToGrowingSyncMode, AddVisibleRoomsList, AddInvitesList],
refresh_lists => [SetAllRoomsListToGrowingSyncMode],
}
@@ -303,4 +334,29 @@ mod tests {
Ok(())
}
#[async_test]
async fn test_action_add_invitess_list() -> Result<(), Error> {
let room_list = new_room_list().await?;
let sliding_sync = room_list.sliding_sync();
// List is absent.
assert_eq!(sliding_sync.on_list(INVITES_LIST_NAME, |_list| ready(())).await, None);
// Run the action!
AddInvitesList.run(sliding_sync).await?;
// List is present!
assert_eq!(
sliding_sync
.on_list(INVITES_LIST_NAME, |list| ready(matches!(
list.sync_mode(),
SlidingSyncMode::Growing { batch_size, .. } if batch_size == 100
)))
.await,
Some(true)
);
Ok(())
}
}

View File

@@ -8,7 +8,7 @@ use matrix_sdk_test::async_test;
use matrix_sdk_ui::{
room_list::{
EntriesLoadingState, Error, Input, RoomListEntry, State, ALL_ROOMS_LIST_NAME as ALL_ROOMS,
VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS,
INVITES_LIST_NAME as INVITES, VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS,
},
timeline::{TimelineItem, VirtualTimelineItem},
RoomList,
@@ -326,6 +326,22 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"sort": ["by_recency", "by_name"],
"timeline_limit": 20,
},
INVITES: {
"ranges": [[0, 99]],
"required_state": [
["m.room.avatar", ""],
["m.room.encryption", ""],
["m.room.member", "$ME"],
["m.room.canonical_alias", ""],
],
"filters": {
"is_invite": true,
"is_tombstoned": false,
"not_room_types": ["m.space"],
},
"sort": ["by_recency", "by_name"],
"timeline_limit": 0,
},
},
},
respond with = {
@@ -341,6 +357,10 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 2,
"ops": [],
},
},
"rooms": {
// let's ignore them for now
@@ -366,6 +386,9 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 1]],
}
},
},
respond with = {
@@ -381,6 +404,10 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 3,
"ops": [],
},
},
"rooms": {
// let's ignore them for now
@@ -401,6 +428,9 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 2]],
},
},
},
respond with = {
@@ -416,6 +446,10 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 0,
"ops": [],
}
},
"rooms": {
// let's ignore them for now
@@ -434,7 +468,10 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"ranges": [[0, 199]],
},
VISIBLE_ROOMS: {
"ranges": [],
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 0]],
},
},
},
@@ -451,6 +488,10 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 0,
"ops": [],
},
},
"rooms": {
// let's ignore them for now
@@ -466,8 +507,8 @@ async fn test_sync_from_init_to_enjoy() -> Result<(), Error> {
Ok(())
}
#[async_test]
#[async_test]
async fn test_sync_resumes_from_previous_state() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;
@@ -515,6 +556,9 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> {
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {
@@ -528,6 +572,10 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 0,
"ops": [],
},
},
"rooms": {},
},
@@ -550,6 +598,9 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> {
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 0]],
},
},
},
respond with = {
@@ -563,6 +614,10 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> {
"count": 0,
"ops": [],
},
INVITES: {
"count": 0,
"ops": [],
},
},
"rooms": {},
},
@@ -644,6 +699,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// Hello new list.
"ranges": [[0, 19]],
},
INVITES: {
// Hello new list.
"ranges": [[0, 99]],
},
},
},
respond with = (code 400) {
@@ -677,6 +736,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// We have set a viewport, which reflects here.
"ranges": [[5, 10]],
},
INVITES: {
// The range hasn't been modified due to previous error.
"ranges": [[0, 99]],
},
},
},
respond with = {
@@ -685,6 +748,9 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
ALL_ROOMS: {
"count": 110,
},
INVITES: {
"count": 3,
}
},
"rooms": {},
},
@@ -706,6 +772,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// Despites the error, the range is kept.
"ranges": [[5, 10]],
},
INVITES: {
// Despites the error, the range has made progress.
"ranges": [[0, 2]],
},
},
},
respond with = (code 400) {
@@ -735,6 +805,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// Despites the error, the range is kept.
"ranges": [[5, 10]],
},
INVITES: {
// Despites the error, the range is kept.
"ranges": [[0, 2]],
}
},
},
respond with = {
@@ -743,6 +817,9 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
ALL_ROOMS: {
"count": 110,
},
INVITES: {
"count": 0,
},
},
"rooms": {},
},
@@ -763,6 +840,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// No error. The range is still here.
"ranges": [[5, 10]],
},
INVITES: {
// The range is making progress.
"ranges": [[0, 0]],
},
},
},
respond with = {
@@ -792,6 +873,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// The range is still here.
"ranges": [[5, 10]],
},
INVITES: {
// The range is kept as it was.
"ranges": [[0, 0]],
},
},
},
respond with = (code 400) {
@@ -823,6 +908,10 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> {
// The range is still here.
"ranges": [[5, 10]],
},
INVITES: {
// The range is kept as it was.
"ranges": [[0, 0]],
},
},
},
respond with = {
@@ -905,7 +994,7 @@ async fn test_entries_stream() -> Result<(), Error> {
set[1] [ F("!r1:bar.org") ];
set[2] [ F("!r2:bar.org") ];
pending;
}
};
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
@@ -913,13 +1002,14 @@ async fn test_entries_stream() -> Result<(), Error> {
assert request = {
"lists": {
ALL_ROOMS: {
"ranges": [
[0, 9],
],
"ranges": [[0, 9]],
},
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {
@@ -939,15 +1029,15 @@ async fn test_entries_stream() -> Result<(), Error> {
{
"op": "INSERT",
"index": 0,
"room_id": "!r3:bar.org"
"room_id": "!r3:bar.org",
},
],
},
VISIBLE_ROOMS: {
"count": 0,
"ops": [
// let's ignore them for now
],
},
INVITES: {
"count": 0,
},
},
"rooms": {
@@ -966,7 +1056,7 @@ async fn test_entries_stream() -> Result<(), Error> {
remove[0];
insert[0] [ F("!r3:bar.org") ];
pending;
}
};
Ok(())
}
@@ -1046,6 +1136,9 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> {
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {
@@ -1068,9 +1161,9 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> {
},
VISIBLE_ROOMS: {
"count": 0,
"ops": [
// let's ignore them for now
],
},
INVITES: {
"count": 0,
},
},
"rooms": {
@@ -1109,6 +1202,162 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> {
Ok(())
}
#[async_test]
async fn test_invites_stream() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;
let sync = room_list.sync();
pin_mut!(sync);
// The invites aren't accessible yet.
assert!(room_list.invites().await.is_err());
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
states = Init => FirstRooms,
assert request = {
"lists": {
ALL_ROOMS: {
"ranges": [[0, 19]],
},
},
},
respond with = {
"pos": "0",
"lists": {
ALL_ROOMS: {
"count": 0,
},
},
"rooms": {},
},
};
// The invites aren't accessible yet.
assert!(room_list.invites().await.is_err());
let room_id_0 = room_id!("!r0:bar.org");
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
states = FirstRooms => AllRooms,
assert request = {
"lists": {
ALL_ROOMS: {
"ranges": [[0, 0]],
},
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {
"pos": "1",
"lists": {
ALL_ROOMS: {
"count": 0,
},
VISIBLE_ROOMS: {
"count": 0,
},
INVITES: {
"count": 1,
"ops": [
{
"op": "SYNC",
"range": [0, 0],
"room_ids": [
room_id_0,
],
},
],
},
},
"rooms": {
room_id_0: {
"name": "Invitation for Room #0",
"initial": true,
},
},
},
};
let (previous_invites, invites_stream) = room_list.invites().await?;
pin_mut!(invites_stream);
assert_eq!(previous_invites.len(), 1);
assert_matches!(&previous_invites[0], RoomListEntry::Filled(room_id) => {
assert_eq!(room_id, room_id_0);
});
assert_entries_stream! {
[invites_stream]
pending;
};
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
states = AllRooms => CarryOn,
assert request = {
"lists": {
ALL_ROOMS: {
"ranges": [[0, 0]],
},
VISIBLE_ROOMS: {
"ranges": [[0, 19]],
},
INVITES: {
"ranges": [[0, 0]],
},
},
},
respond with = {
"pos": "2",
"lists": {
ALL_ROOMS: {
"count": 0,
},
VISIBLE_ROOMS: {
"count": 0,
},
INVITES: {
"count": 1,
"ops": [
{
"op": "DELETE",
"index": 0,
},
{
"op": "INSERT",
"index": 0,
"room_id": "!r1:bar.org",
},
],
},
},
"rooms": {
"!r1:bar.org": {
"name": "Invitation for Room #1",
"initial": true,
},
},
},
};
assert_entries_stream! {
[invites_stream]
remove[0];
insert[0] [ F("!r1:bar.org") ];
pending;
};
Ok(())
}
#[async_test]
async fn test_room() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;
@@ -1634,6 +1883,9 @@ async fn test_input_viewport() -> Result<(), Error> {
"ranges": [[0, 19]],
"timeline_limit": 20,
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {
@@ -1657,6 +1909,9 @@ async fn test_input_viewport() -> Result<(), Error> {
"ranges": [[10, 15], [20, 25]],
"timeline_limit": 20,
},
INVITES: {
"ranges": [[0, 99]],
},
},
},
respond with = {