From f47c2ba1252e6312a44bc817d49b24067ab42e75 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 24 May 2023 17:53:58 +0200 Subject: [PATCH] !bar --- crates/matrix-sdk-ui/Cargo.toml | 13 +- crates/matrix-sdk-ui/src/lib.rs | 6 +- crates/matrix-sdk-ui/src/room_list/mod.rs | 169 ++++++++++++++++++ crates/matrix-sdk-ui/src/roomlist/mod.rs | 33 ---- .../matrix-sdk-ui/tests/integration/main.rs | 2 + .../tests/integration/room_list.rs | 107 +++++++++++ .../integration/timeline/sliding_sync.rs | 2 +- 7 files changed, 294 insertions(+), 38 deletions(-) create mode 100644 crates/matrix-sdk-ui/src/room_list/mod.rs delete mode 100644 crates/matrix-sdk-ui/src/roomlist/mod.rs create mode 100644 crates/matrix-sdk-ui/tests/integration/room_list.rs diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index af767afe6..b3abd3ad7 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -4,17 +4,20 @@ version = "0.6.0" edition = "2021" [features] -default = ["e2e-encryption", "native-tls", "experimental-roomlist"] +default = ["e2e-encryption", "native-tls", "experimental-room-list"] e2e-encryption = ["matrix-sdk/e2e-encryption"] native-tls = ["matrix-sdk/native-tls"] rustls-tls = ["matrix-sdk/rustls-tls"] -experimental-roomlist = ["experimental-sliding-sync"] +experimental-room-list = ["experimental-sliding-sync", "dep:async-stream"] experimental-sliding-sync = ["matrix-sdk/experimental-sliding-sync"] +testing = [] + [dependencies] +async-stream = { workspace = true, optional = true } async-trait = { workspace = true } chrono = "0.4.23" eyeball-im = { workspace = true } @@ -23,6 +26,7 @@ futures-util = { workspace = true } imbl = { version = "2.0.0", features = ["serde"] } indexmap = "1.9.1" matrix-sdk = { version = "0.6.2", path = "../matrix-sdk", default-features = false } +matrix-sdk-base = { version = "0.6.1", path = "../matrix-sdk-base" } mime = "0.3.16" once_cell = { workspace = true } pin-project-lite = "0.2.9" @@ -35,8 +39,13 @@ tracing = { workspace = true, features = ["attributes"] } [dev-dependencies] anyhow = { workspace = true } +assert-json-diff = "2.0" assert_matches = { workspace = true } ctor = { workspace = true } matrix-sdk-test = { version = "0.6.0", path = "../../testing/matrix-sdk-test" } tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } wiremock = "0.5.13" + +[[test]] +name = "integration" +required-features = ["testing"] \ No newline at end of file diff --git a/crates/matrix-sdk-ui/src/lib.rs b/crates/matrix-sdk-ui/src/lib.rs index 94ded1e70..a08debc48 100644 --- a/crates/matrix-sdk-ui/src/lib.rs +++ b/crates/matrix-sdk-ui/src/lib.rs @@ -14,8 +14,10 @@ mod events; -#[cfg(feature = "experimental-roomlist")] -pub mod roomlist; +#[cfg(feature = "experimental-room-list")] +pub mod room_list; pub mod timeline; +#[cfg(feature = "experimental-room-list")] +pub use self::room_list::RoomList; pub use self::timeline::Timeline; diff --git a/crates/matrix-sdk-ui/src/room_list/mod.rs b/crates/matrix-sdk-ui/src/room_list/mod.rs new file mode 100644 index 000000000..eb6a4244e --- /dev/null +++ b/crates/matrix-sdk-ui/src/room_list/mod.rs @@ -0,0 +1,169 @@ +//! `RoomList` API. + +use async_stream::stream; +use async_trait::async_trait; +use futures_util::{pin_mut, Stream, StreamExt}; +use matrix_sdk::{ + Client, Error, Result, SlidingSync, SlidingSyncList, SlidingSyncMode, UpdateSummary, +}; +use once_cell::sync::Lazy; + +pub const ALL_ROOMS_LIST_NAME: &str = "all_rooms"; +pub const VISIBLE_ROOMS_LIST_NAME: &str = "visible_rooms"; + +#[derive(Debug)] +pub struct RoomList { + sliding_sync: SlidingSync, +} + +impl RoomList { + pub async fn new(client: Client) -> Result { + Ok(Self { + sliding_sync: client + .sliding_sync() + .storage_key(Some("matrix-sdk-ui-roomlist".to_string())) + .add_cached_list( + SlidingSyncList::builder(ALL_ROOMS_LIST_NAME) + .sync_mode(SlidingSyncMode::new_selective()), + ) + .await? + .build() + .await?, + }) + } + + pub fn sync(&self) -> impl Stream> + '_ { + stream! { + let sync = self.sliding_sync.sync(); + pin_mut!(sync); + + let mut state = State::LoadFirstRooms; + + while let Some(update_summary) = sync.next().await { + state = state.next(update_summary, &self.sliding_sync).await?; + + // if let State::Failure = state { + // yield Err(()); + // // transform into a custom `MyError::SyncFailure` + // } else { + yield Ok(()); + // } + } + } + } + + #[cfg(feature = "testing")] + pub fn sliding_sync(&self) -> &SlidingSync { + &self.sliding_sync + } +} + +enum State { + LoadFirstRooms, + LoadAllRooms, + Enjoy, + Failure, +} + +impl State { + async fn next( + self, + update_summary: Result, + sliding_sync: &SlidingSync, + ) -> Result { + use State::*; + + if update_summary.is_err() { + return Ok(Failure); + } + + let (next_state, actions) = match self { + LoadFirstRooms => (LoadAllRooms, Actions::first_rooms_are_loaded()), + LoadAllRooms => (Enjoy, Actions::none()), + Enjoy => (Enjoy, Actions::none()), + Failure => (Failure, Actions::none()), + }; + + for action in actions.iter() { + action.run(sliding_sync).await?; + } + + Ok(next_state) + } +} + +#[async_trait] +trait Action { + async fn run(&self, sliding_sync: &SlidingSync) -> Result<(), Error>; +} + +struct AddVisibleRoomsList; + +#[async_trait] +impl Action for AddVisibleRoomsList { + async fn run(&self, sliding_sync: &SlidingSync) -> Result<(), Error> { + sliding_sync + .add_list( + SlidingSyncList::builder(VISIBLE_ROOMS_LIST_NAME) + .sync_mode(SlidingSyncMode::new_selective()), + ) + .await?; + + Ok(()) + } +} + +struct ChangeAllRoomsListToSelectiveSyncMode; + +#[async_trait] +impl Action for ChangeAllRoomsListToSelectiveSyncMode { + async fn run(&self, sliding_sync: &SlidingSync) -> Result<(), Error> { + sliding_sync + .on_list("all_rooms", |list| list.set_sync_mode(SlidingSyncMode::new_growing(20, None))) + .unwrap() // transform into a custom `MyError::UnknownList` + ?; + + Ok(()) + } +} + +type OneAction = Box; +type ManyActions = Vec; + +struct Actions { + actions: &'static Lazy, +} + +macro_rules! actions { + ( + $( + $action_group_name:ident => [ + $( $action_name:ident ),* $(,)? + ] + ),* + $(,)? + ) => { + $( + fn $action_group_name () -> Self { + static ACTIONS: Lazy = Lazy::new(|| { + vec![ + $( Box::new( $action_name ) ),* + ] + }); + + Self { actions: &ACTIONS } + } + )* + }; +} + +impl Actions { + actions! { + none => [], + first_rooms_are_loaded => [ChangeAllRoomsListToSelectiveSyncMode, AddVisibleRoomsList], + } + + fn iter(&self) -> &[OneAction] { + self.actions.as_slice() + } +} diff --git a/crates/matrix-sdk-ui/src/roomlist/mod.rs b/crates/matrix-sdk-ui/src/roomlist/mod.rs deleted file mode 100644 index 7dd95a3cd..000000000 --- a/crates/matrix-sdk-ui/src/roomlist/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! `RoomList` API. - -use matrix_sdk::{Client, Result, SlidingSync, SlidingSyncList}; - -#[derive(Debug)] -pub struct RoomList { - sliding_sync: SlidingSync, -} - -impl RoomList { - pub async fn new(client: Client) -> Result { - Ok(Self { - sliding_sync: client - .sliding_sync() - .storage_key(Some("matrix-sdk-ui-roomlist".to_string())) - .add_cached_list(SlidingSyncList::builder("all_rooms")) - .await? - .add_list(SlidingSyncList::builder("visible_rooms")) - .build() - .await?, - }) - } - - pub fn sync(&self) {} -} - -#[cfg(test)] -mod tests { - #[tokio::test] - async fn test_has_two_lists() { - // let - } -} diff --git a/crates/matrix-sdk-ui/tests/integration/main.rs b/crates/matrix-sdk-ui/tests/integration/main.rs index 0823b3db8..5d36604b4 100644 --- a/crates/matrix-sdk-ui/tests/integration/main.rs +++ b/crates/matrix-sdk-ui/tests/integration/main.rs @@ -7,6 +7,8 @@ use wiremock::{ Mock, MockServer, ResponseTemplate, }; +#[cfg(feature = "experimental-room-list")] +mod room_list; mod timeline; #[cfg(all(test, not(target_arch = "wasm32")))] diff --git a/crates/matrix-sdk-ui/tests/integration/room_list.rs b/crates/matrix-sdk-ui/tests/integration/room_list.rs new file mode 100644 index 000000000..165e310ef --- /dev/null +++ b/crates/matrix-sdk-ui/tests/integration/room_list.rs @@ -0,0 +1,107 @@ +use anyhow::{Context, Result}; +use futures_util::{pin_mut, StreamExt}; +use matrix_sdk_test::async_test; +use matrix_sdk_ui::{room_list, RoomList}; +use serde_json::json; +use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate}; + +use crate::logged_in_client; + +async fn new_room_list() -> Result<(MockServer, RoomList)> { + let (client, server) = logged_in_client().await; + let room_list = RoomList::new(client).await?; + + Ok((server, room_list)) +} + +#[derive(Copy, Clone)] +struct SlidingSyncMatcher; + +impl Match for SlidingSyncMatcher { + fn matches(&self, request: &Request) -> bool { + request.url.path() == "/_matrix/client/unstable/org.matrix.msc3575/sync" + && request.method == Method::Post + } +} + +macro_rules! sync_then_assert_request_and_fake_response { + ( + [$server:ident, $room_list_sync_stream:ident] + request = { $( $request_json:tt )* }, + response = { $( $response_json:tt )* } + $(,)? + ) => { + { + let matcher = SlidingSyncMatcher; + + let _mock_guard = Mock::given(matcher) + .respond_with(ResponseTemplate::new(200).set_body_json( + json!({ $( $response_json )* }) + )) + .mount_as_scoped(&$server) + .await; + + let next = $room_list_sync_stream.next().await.context("`sync` trip")??; + + for request in $server.received_requests().await.expect("Request recording has been disabled").iter().rev() { + if matcher.matches(request) { + let json_value = serde_json::from_slice::(&request.body).unwrap(); + + if let Err(error) = assert_json_diff::assert_json_matches_no_panic( + &json_value, + &json!({ $( $response_json )* }), + assert_json_diff::Config::new(assert_json_diff::CompareMode::Inclusive), + ) { + dbg!(json_value); + panic!("{}", error); + } + } + } + + next + } + }; +} + +#[async_test] +async fn test_one_list() -> Result<()> { + let (_, room_list) = new_room_list().await?; + let sliding_sync = room_list.sliding_sync(); + + assert_eq!(sliding_sync.on_list(room_list::ALL_ROOMS_LIST_NAME, |_list| ()), Some(())); + assert_eq!(sliding_sync.on_list(room_list::VISIBLE_ROOMS_LIST_NAME, |_list| ()), None); + + Ok(()) +} + +#[async_test] +async fn test_foo() -> Result<()> { + let (server, room_list) = new_room_list().await?; + + let sync = room_list.sync(); + pin_mut!(sync); + + sync_then_assert_request_and_fake_response! { + [server, sync] + request = { + "lists": { + "all_rooms": { + "ranges": [], + } + }, + }, + response = { + "pos": "foo", + "lists": {}, + "rooms": {}, + "extensions": {}, + }, + }; + + assert_eq!( + room_list.sliding_sync().on_list(room_list::VISIBLE_ROOMS_LIST_NAME, |_list| ()), + Some(()) + ); + + Ok(()) +} diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs index 360075314..b9f650c83 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/sliding_sync.rs @@ -28,7 +28,7 @@ macro_rules! receive_response { .mount_as_scoped(&$server) .await; - let next = $sliding_sync_stream.next().await.context("`stream` trip")??; + let next = $sliding_sync_stream.next().await.context("`sync` trip")??; next }