1use std::{cmp::Ordering, collections::HashMap, pin::pin, sync::Arc};
16
17use eyeball::{ObservableWriteGuard, SharedObservable, Subscriber};
18use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
19use futures_util::future::join_all;
20use imbl::Vector;
21use itertools::Itertools;
22use matrix_sdk::{
23 Client, Error, locks::Mutex, paginators::PaginationToken, task_monitor::BackgroundTaskHandle,
24};
25use ruma::{
26 OwnedRoomId,
27 api::client::space::get_hierarchy,
28 events::space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
29 uint,
30};
31use tokio::sync::Mutex as AsyncMutex;
32use tracing::{error, warn};
33
34use crate::spaces::SpaceRoom;
35
36#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub enum SpaceRoomListPaginationState {
39 Idle { end_reached: bool },
40 Loading,
41}
42
43pub struct SpaceRoomList {
105 client: Client,
106
107 space_id: OwnedRoomId,
108
109 space: SharedObservable<Option<SpaceRoom>>,
110
111 children_state: Mutex<Option<HashMap<OwnedRoomId, HierarchySpaceChildEvent>>>,
112
113 token: AsyncMutex<PaginationToken>,
114
115 pagination_state: SharedObservable<SpaceRoomListPaginationState>,
116
117 rooms: Arc<AsyncMutex<ObservableVector<SpaceRoom>>>,
118
119 _space_update_handle: Option<BackgroundTaskHandle>,
120
121 _room_update_handle: BackgroundTaskHandle,
122}
123
124impl SpaceRoomList {
125 pub async fn new(client: Client, space_id: OwnedRoomId) -> Self {
127 let rooms = Arc::new(AsyncMutex::new(ObservableVector::<SpaceRoom>::new()));
128
129 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
130
131 let room_update_handle = client
132 .task_monitor()
133 .spawn_infinite_task("space_room_list::room_updates", {
134 let client = client.clone();
135 let rooms = rooms.clone();
136
137 async move {
138 let mut all_room_updates_receiver = pin!(all_room_updates_receiver);
139
140 loop {
141 match all_room_updates_receiver.recv().await {
142 Ok(updates) => {
143 if updates.is_empty() {
144 continue;
145 }
146
147 let mut mutable_rooms = rooms.lock().await;
148
149 for updated_room_id in updates.iter_all_room_ids() {
150 if let Some((position, room)) = mutable_rooms
151 .clone()
152 .iter()
153 .find_position(|room| &room.room_id == updated_room_id)
154 && let Some(updated_room) = client.get_room(updated_room_id)
155 {
156 mutable_rooms.set(
157 position,
158 SpaceRoom::new_from_known(
159 &updated_room,
160 room.children_count,
161 )
162 .await,
163 );
164 }
165 }
166 }
167 Err(err) => {
168 error!("error when listening to room updates: {err}");
169 }
170 }
171 }
172 }
173 })
174 .abort_on_drop();
175
176 let space_observable = SharedObservable::new(None);
177
178 let (space_room, space_update_handle) = if let Some(parent) = client.get_room(&space_id) {
179 let children_count = parent
180 .get_state_events_static::<SpaceChildEventContent>()
181 .await
182 .map_or(0, |c| c.len() as u64);
183
184 let mut subscriber = parent.subscribe_info();
185 let space_update_handle = client
186 .task_monitor()
187 .spawn_infinite_task("space_room_list::space_update", {
188 let client = client.clone();
189 let space_id = space_id.clone();
190 let space_observable = space_observable.clone();
191 async move {
192 while subscriber.next().await.is_some() {
193 if let Some(room) = client.get_room(&space_id) {
194 space_observable.set(Some(
195 SpaceRoom::new_from_known(&room, children_count).await,
196 ));
197 }
198 }
199 }
200 })
201 .abort_on_drop();
202
203 (
204 Some(SpaceRoom::new_from_known(&parent, children_count).await),
205 Some(space_update_handle),
206 )
207 } else {
208 (None, None)
209 };
210
211 space_observable.set(space_room);
212
213 Self {
214 client,
215 space_id,
216 space: space_observable,
217 children_state: Mutex::new(None),
218 token: AsyncMutex::new(None.into()),
219 pagination_state: SharedObservable::new(SpaceRoomListPaginationState::Idle {
220 end_reached: false,
221 }),
222 rooms,
223 _space_update_handle: space_update_handle,
224 _room_update_handle: room_update_handle,
225 }
226 }
227
228 pub fn space(&self) -> Option<SpaceRoom> {
230 self.space.get()
231 }
232
233 pub fn subscribe_to_space_updates(&self) -> Subscriber<Option<SpaceRoom>> {
235 self.space.subscribe()
236 }
237
238 pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
240 self.pagination_state.get()
241 }
242
243 pub fn subscribe_to_pagination_state_updates(
245 &self,
246 ) -> Subscriber<SpaceRoomListPaginationState> {
247 self.pagination_state.subscribe()
248 }
249
250 pub async fn rooms(&self) -> Vec<SpaceRoom> {
252 self.rooms.lock().await.iter().cloned().collect_vec()
253 }
254
255 pub async fn subscribe_to_room_updates(
257 &self,
258 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
259 self.rooms.lock().await.subscribe().into_values_and_batched_stream()
260 }
261
262 pub async fn paginate(&self) -> Result<(), Error> {
265 {
266 let mut pagination_state = self.pagination_state.write();
267
268 match *pagination_state {
269 SpaceRoomListPaginationState::Idle { end_reached } if end_reached => {
270 return Ok(());
271 }
272 SpaceRoomListPaginationState::Loading => {
273 return Ok(());
274 }
275 _ => {}
276 }
277
278 ObservableWriteGuard::set(&mut pagination_state, SpaceRoomListPaginationState::Loading);
279 }
280
281 let mut request = get_hierarchy::v1::Request::new(self.space_id.clone());
282 request.max_depth = Some(uint!(1)); let mut pagination_token = self.token.lock().await;
285
286 if let PaginationToken::HasMore(ref token) = *pagination_token {
287 request.from = Some(token.clone());
288 }
289
290 match self.client.send(request).await {
291 Ok(result) => {
292 *pagination_token = match &result.next_batch {
293 Some(val) => PaginationToken::HasMore(val.clone()),
294 None => PaginationToken::HitEnd,
295 };
296
297 let (space, children): (Vec<_>, Vec<_>) =
301 result.rooms.into_iter().partition(|f| f.summary.room_id == self.space_id);
302
303 if let Some(room) = space.first() {
304 let mut children_state =
305 HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
306 for child_state in &room.children_state {
307 match child_state.deserialize() {
308 Ok(child) => {
309 children_state.insert(child.state_key.clone(), child);
310 }
311 Err(error) => {
312 warn!("Failed deserializing space child event: {error}");
313 }
314 }
315 }
316 *self.children_state.lock() = Some(children_state);
317
318 let space_is_none = self.space.read().is_none();
319 if space_is_none {
320 let space_room = SpaceRoom::new_from_summary(
321 &room.summary,
322 self.client.get_room(&room.summary.room_id),
323 room.children_state.len() as u64,
324 vec![],
325 false,
326 )
327 .await;
328 let mut space = self.space.write();
329 ObservableWriteGuard::set(&mut space, Some(space_room));
330 }
331 }
332
333 let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
334 let mut rooms = self.rooms.lock().await;
335
336 join_all(children.into_iter().map(|room| {
337 let children_state = children_state.clone();
338 async move {
339 let child_state = children_state.get(&room.summary.room_id);
340 let via =
341 child_state.map(|state| state.content.via.clone()).unwrap_or_default();
342 let suggested =
343 child_state.map(|state| state.content.suggested).unwrap_or(false);
344 SpaceRoom::new_from_summary(
345 &room.summary,
346 self.client.get_room(&room.summary.room_id),
347 room.children_state.len() as u64,
348 via,
349 suggested,
350 )
351 .await
352 }
353 }))
354 .await
355 .into_iter()
356 .sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
357 .for_each(|room| rooms.push_back(room));
358
359 self.pagination_state.set(SpaceRoomListPaginationState::Idle {
360 end_reached: result.next_batch.is_none(),
361 });
362
363 Ok(())
364 }
365 Err(err) => {
366 self.pagination_state
367 .set(SpaceRoomListPaginationState::Idle { end_reached: false });
368 Err(err.into())
369 }
370 }
371 }
372
373 pub async fn reset(&self) {
382 let mut pagination_token = self.token.lock().await;
383 *pagination_token = None.into();
384
385 self.rooms.lock().await.clear();
386 self.children_state.lock().take();
387
388 self.pagination_state.set(SpaceRoomListPaginationState::Idle { end_reached: false });
389 }
390
391 fn compare_rooms(
394 a: &SpaceRoom,
395 b: &SpaceRoom,
396 children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
397 ) -> Ordering {
398 let a_state = children_state.get(&a.room_id);
399 let b_state = children_state.get(&b.room_id);
400
401 SpaceRoom::compare_rooms(
402 (&a.room_id, a_state.map(Into::into).as_ref()),
403 (&b.room_id, b_state.map(Into::into).as_ref()),
404 )
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use std::{assert_matches, cmp::Ordering, collections::HashMap, pin::pin};
411
412 use eyeball_im::VectorDiff;
413 use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
414 use matrix_sdk_test::{
415 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
416 };
417 use ruma::{
418 MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId,
419 events::space::child::HierarchySpaceChildEvent,
420 owned_room_id, owned_server_name,
421 room::{JoinRuleSummary, RoomSummary},
422 room_id, server_name, uint,
423 };
424 use serde_json::{from_value, json};
425 use strass::assert_let;
426 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
427
428 use crate::spaces::{
429 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
430 };
431
432 #[async_test]
433 async fn test_room_list_pagination() {
434 let server = MatrixMockServer::new().await;
435 let client = server.client_builder().build().await;
436 let user_id = client.user_id().unwrap();
437 let space_service = SpaceService::new(client.clone()).await;
438 let factory = EventFactory::new();
439
440 server.mock_room_state_encryption().plain().mount().await;
441
442 let parent_space_id = room_id!("!parent_space:example.org");
443 let child_space_id_1 = room_id!("!1:example.org");
444 let child_space_id_2 = room_id!("!2:example.org");
445
446 server
447 .sync_room(
448 &client,
449 JoinedRoomBuilder::new(parent_space_id)
450 .add_state_event(
451 factory
452 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
453 .sender(user_id),
454 )
455 .add_state_event(
456 factory
457 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
458 .sender(user_id),
459 ),
460 )
461 .await;
462
463 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
464
465 assert_let!(Some(parent_space) = room_list.space());
468 assert_eq!(parent_space.children_count, 2);
469
470 assert_matches!(
472 room_list.pagination_state(),
473 SpaceRoomListPaginationState::Idle { end_reached: false }
474 );
475
476 assert_eq!(room_list.rooms().await, vec![]);
478
479 let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
482 let mut pagination_state_subscriber = pin!(pagination_state_subscriber);
483 assert_pending!(pagination_state_subscriber);
484
485 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
486 let mut rooms_subscriber = pin!(rooms_subscriber);
487 assert_pending!(rooms_subscriber);
488
489 server
491 .mock_get_hierarchy()
492 .ok_with_room_ids_and_children_state(
493 vec![child_space_id_1, child_space_id_2],
494 vec![(room_id!("!child:example.org"), vec![])],
495 )
496 .mount()
497 .await;
498
499 room_list.paginate().await.unwrap();
500
501 assert_next_matches!(
503 pagination_state_subscriber,
504 SpaceRoomListPaginationState::Idle { end_reached: true }
505 );
506
507 assert_next_eq!(
509 rooms_subscriber,
510 vec![
511 VectorDiff::PushBack {
512 value: SpaceRoom::new_from_summary(
513 &RoomSummary::new(
514 child_space_id_1.to_owned(),
515 JoinRuleSummary::Public,
516 false,
517 uint!(1),
518 false,
519 ),
520 None,
521 1,
522 vec![],
523 false,
524 )
525 .await
526 },
527 VectorDiff::PushBack {
528 value: SpaceRoom::new_from_summary(
529 &RoomSummary::new(
530 child_space_id_2.to_owned(),
531 JoinRuleSummary::Public,
532 false,
533 uint!(1),
534 false,
535 ),
536 None,
537 1,
538 vec![],
539 false,
540 )
541 .await,
542 }
543 ]
544 );
545 }
546
547 #[async_test]
548 async fn test_room_state_updates() {
549 let server = MatrixMockServer::new().await;
550 let client = server.client_builder().build().await;
551 let space_service = SpaceService::new(client.clone()).await;
552
553 let parent_space_id = room_id!("!parent_space:example.org");
554 let child_room_id_1 = room_id!("!1:example.org");
555 let child_room_id_2 = room_id!("!2:example.org");
556
557 server
558 .mock_get_hierarchy()
559 .ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
560 .mount()
561 .await;
562
563 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
564
565 room_list.paginate().await.unwrap();
566
567 assert_eq!(room_list.rooms().await.first().unwrap().room_id, child_room_id_1);
569 assert_eq!(room_list.rooms().await.last().unwrap().room_id, child_room_id_2);
570
571 assert_eq!(room_list.rooms().await.first().unwrap().state, None);
573 assert_eq!(room_list.rooms().await.last().unwrap().state, None);
574
575 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
576 let mut rooms_subscriber = pin!(rooms_subscriber);
577 assert_pending!(rooms_subscriber);
578
579 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
581
582 assert_ready!(rooms_subscriber);
584 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Joined));
585 assert_eq!(room_list.rooms().await.last().unwrap().state, None);
586
587 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
589 assert_ready!(rooms_subscriber);
590 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Joined));
591 assert_eq!(room_list.rooms().await.last().unwrap().state, Some(RoomState::Joined));
592
593 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
595 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
596 assert_ready!(rooms_subscriber);
597 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Left));
598 assert_eq!(room_list.rooms().await.last().unwrap().state, Some(RoomState::Left));
599 }
600
601 #[async_test]
602 async fn test_parent_space_updates() {
603 let server = MatrixMockServer::new().await;
604 let client = server.client_builder().build().await;
605 let user_id = client.user_id().unwrap();
606 let space_service = SpaceService::new(client.clone()).await;
607 let factory = EventFactory::new();
608
609 server.mock_room_state_encryption().plain().mount().await;
610
611 let parent_space_id = room_id!("!parent_space:example.org");
612 let child_space_id_1 = room_id!("!1:example.org");
613 let child_space_id_2 = room_id!("!2:example.org");
614
615 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
617 assert!(room_list.space().is_none());
618
619 let parent_space_subscriber = room_list.subscribe_to_space_updates();
620 let mut parent_space_subscriber = pin!(parent_space_subscriber);
621 assert_pending!(parent_space_subscriber);
622
623 server
624 .mock_get_hierarchy()
625 .ok_with_room_ids_and_children_state(
626 vec![parent_space_id, child_space_id_1, child_space_id_2],
627 vec![(
628 room_id!("!child:example.org"),
629 vec![server_name!("matrix-client.example.org")],
630 )],
631 )
632 .mount()
633 .await;
634
635 room_list.paginate().await.unwrap();
637 assert_let!(Some(parent_space) = room_list.space());
638 assert_eq!(parent_space.room_id, parent_space_id);
639
640 assert_next_eq!(parent_space_subscriber, Some(parent_space));
642
643 server
646 .sync_room(
647 &client,
648 JoinedRoomBuilder::new(parent_space_id)
649 .add_state_event(
650 factory
651 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
652 .sender(user_id),
653 )
654 .add_state_event(
655 factory
656 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
657 .sender(user_id),
658 ),
659 )
660 .await;
661
662 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
663
664 assert_let!(Some(parent_space) = room_list.space());
667 assert_eq!(parent_space.children_count, 2);
668 }
669
670 #[async_test]
671 async fn test_parent_space_room_info_update() {
672 let server = MatrixMockServer::new().await;
673 let client = server.client_builder().build().await;
674 let user_id = client.user_id().unwrap();
675 let space_service = SpaceService::new(client.clone()).await;
676 let factory = EventFactory::new();
677
678 server.mock_room_state_encryption().plain().mount().await;
679
680 let parent_space_id = room_id!("!parent_space:example.org");
681
682 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
683
684 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
685 assert_let!(Some(parent_space) = room_list.space());
686
687 let parent_space_subscriber = room_list.subscribe_to_space_updates();
689 let mut parent_space_subscriber = pin!(parent_space_subscriber);
690 assert_pending!(parent_space_subscriber);
691
692 server
694 .sync_room(
695 &client,
696 JoinedRoomBuilder::new(parent_space_id)
697 .add_state_event(factory.room_topic("New room topic").sender(user_id))
698 .add_state_event(factory.room_name("New room name").sender(user_id)),
699 )
700 .await;
701
702 let mut updated_parent_space = parent_space.clone();
703 updated_parent_space.topic = Some("New room topic".to_owned());
704 updated_parent_space.name = Some("New room name".to_owned());
705 updated_parent_space.display_name = "New room name".to_owned();
706
707 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
709 }
710
711 #[async_test]
712 async fn test_via_retrieval() {
713 let server = MatrixMockServer::new().await;
714 let client = server.client_builder().build().await;
715 let space_service = SpaceService::new(client.clone()).await;
716
717 server.mock_room_state_encryption().plain().mount().await;
718
719 let parent_space_id = room_id!("!parent_space:example.org");
720 let child_space_id_1 = room_id!("!1:example.org");
721 let child_space_id_2 = room_id!("!2:example.org");
722
723 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
724
725 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
726 let mut rooms_subscriber = pin!(rooms_subscriber);
727
728 server
730 .mock_get_hierarchy()
731 .ok_with_room_ids_and_children_state(
732 vec![parent_space_id, child_space_id_1, child_space_id_2],
733 vec![
734 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
735 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
736 ],
737 )
738 .mount()
739 .await;
740
741 room_list.paginate().await.unwrap();
742
743 assert_next_eq!(
745 rooms_subscriber,
746 vec![
747 VectorDiff::PushBack {
748 value: SpaceRoom::new_from_summary(
749 &RoomSummary::new(
750 child_space_id_1.to_owned(),
751 JoinRuleSummary::Public,
752 false,
753 uint!(1),
754 false,
755 ),
756 None,
757 2,
758 vec![owned_server_name!("matrix-client.example.org")],
759 false,
760 )
761 .await
762 },
763 VectorDiff::PushBack {
764 value: SpaceRoom::new_from_summary(
765 &RoomSummary::new(
766 child_space_id_2.to_owned(),
767 JoinRuleSummary::Public,
768 false,
769 uint!(1),
770 false,
771 ),
772 None,
773 2,
774 vec![owned_server_name!("other-matrix-client.example.org")],
775 false,
776 )
777 .await,
778 }
779 ]
780 );
781 }
782
783 #[async_test]
784 async fn test_suggested_field() {
785 let server = MatrixMockServer::new().await;
786 let client = server.client_builder().build().await;
787 let space_service = SpaceService::new(client.clone()).await;
788
789 server.mock_room_state_encryption().plain().mount().await;
790
791 let parent_space_id = room_id!("!parent_space:example.org");
792 let suggested_child = room_id!("!suggested:example.org");
793 let not_suggested_child = room_id!("!not_suggested:example.org");
794
795 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
796
797 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
798 let mut rooms_subscriber = pin!(rooms_subscriber);
799
800 let children_state = vec![
803 json!({
804 "type": "m.space.child",
805 "state_key": suggested_child,
806 "content": { "via": ["example.org"], "suggested": true },
807 "sender": "@admin:example.org",
808 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
809 }),
810 json!({
811 "type": "m.space.child",
812 "state_key": not_suggested_child,
813 "content": { "via": ["example.org"], "suggested": false },
814 "sender": "@admin:example.org",
815 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
816 }),
817 ];
818
819 server
820 .mock_get_hierarchy()
821 .ok_json(json!({
822 "rooms": [
823 {
824 "room_id": parent_space_id,
825 "num_joined_members": 1,
826 "world_readable": false,
827 "guest_can_join": false,
828 "children_state": children_state
829 },
830 {
831 "room_id": suggested_child,
832 "num_joined_members": 5,
833 "world_readable": false,
834 "guest_can_join": false,
835 "children_state": []
836 },
837 {
838 "room_id": not_suggested_child,
839 "num_joined_members": 3,
840 "world_readable": false,
841 "guest_can_join": false,
842 "children_state": []
843 },
844 ]
845 }))
846 .mount()
847 .await;
848
849 room_list.paginate().await.unwrap();
850
851 let rooms_diff = assert_next_matches!(rooms_subscriber, diff => diff);
852 assert_eq!(rooms_diff.len(), 2);
853
854 let mut rooms_by_id = HashMap::new();
856 for diff in rooms_diff {
857 if let VectorDiff::PushBack { value } = diff {
858 rooms_by_id.insert(value.room_id.clone(), value);
859 } else {
860 panic!("Expected PushBack, got {:?}", diff);
861 }
862 }
863
864 let suggested_room = rooms_by_id.get(suggested_child).expect("suggested child not found");
866 assert!(
867 suggested_room.suggested,
868 "Room with 'suggested: true' should have suggested == true"
869 );
870
871 let not_suggested_room =
873 rooms_by_id.get(not_suggested_child).expect("not-suggested child not found");
874 assert!(
875 !not_suggested_room.suggested,
876 "Room with 'suggested: false' should have suggested == false"
877 );
878 }
879
880 #[async_test]
881 async fn test_room_list_sorting() {
882 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
883
884 assert_eq!(
887 SpaceRoomList::compare_rooms(
888 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
889 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
890 &children_state,
891 ),
892 Ordering::Less
893 );
894
895 assert_eq!(
896 SpaceRoomList::compare_rooms(
897 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
898 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
899 &children_state,
900 ),
901 Ordering::Greater
902 );
903
904 assert_eq!(
907 SpaceRoomList::compare_rooms(
908 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
909 &make_space_room(
910 owned_room_id!("!Marțolea:a.b"),
911 None,
912 Some(0),
913 &mut children_state
914 ),
915 &children_state,
916 ),
917 Ordering::Greater
918 );
919
920 assert_eq!(
922 SpaceRoomList::compare_rooms(
923 &make_space_room(
924 owned_room_id!("!Joiana:a.b"),
925 Some("last"),
926 Some(123),
927 &mut children_state
928 ),
929 &make_space_room(
930 owned_room_id!("!Mioara:a.b"),
931 Some("first"),
932 Some(234),
933 &mut children_state
934 ),
935 &children_state,
936 ),
937 Ordering::Greater
938 );
939
940 assert_eq!(
942 SpaceRoomList::compare_rooms(
943 &make_space_room(
944 owned_room_id!("!Joiana:a.b"),
945 Some("Same pasture"),
946 Some(1),
947 &mut children_state
948 ),
949 &make_space_room(
950 owned_room_id!("!Mioara:a.b"),
951 Some("Same pasture"),
952 Some(0),
953 &mut children_state
954 ),
955 &children_state,
956 ),
957 Ordering::Greater
958 );
959
960 assert_eq!(
963 SpaceRoomList::compare_rooms(
964 &make_space_room(
965 owned_room_id!("!Joiana:a.b"),
966 Some("same_pasture"),
967 Some(0),
968 &mut children_state
969 ),
970 &make_space_room(
971 owned_room_id!("!Mioara:a.b"),
972 Some("same_pasture"),
973 Some(0),
974 &mut children_state
975 ),
976 &children_state,
977 ),
978 Ordering::Less
979 );
980
981 assert_eq!(
984 SpaceRoomList::compare_rooms(
985 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
986 &make_space_room(
987 owned_room_id!("!Sâmbotina:a.b"),
988 None,
989 Some(0),
990 &mut children_state
991 ),
992 &children_state,
993 ),
994 Ordering::Greater
995 );
996
997 assert_eq!(
1000 SpaceRoomList::compare_rooms(
1001 &make_space_room(
1002 owned_room_id!("!Sâmbotina:a.b"),
1003 None,
1004 Some(1),
1005 &mut children_state
1006 ),
1007 &make_space_room(
1008 owned_room_id!("!Dumana:a.b"),
1009 Some("Some pasture"),
1010 Some(1),
1011 &mut children_state
1012 ),
1013 &children_state,
1014 ),
1015 Ordering::Greater
1016 );
1017 }
1018
1019 #[async_test]
1020 async fn test_reset() {
1021 let server = MatrixMockServer::new().await;
1022 let client = server.client_builder().build().await;
1023 let space_service = SpaceService::new(client.clone()).await;
1024
1025 let parent_space_id = room_id!("!parent_space:example.org");
1026 let child_space_id_1 = room_id!("!1:example.org");
1027
1028 server
1029 .mock_get_hierarchy()
1030 .ok_with_room_ids(vec![child_space_id_1])
1031 .expect(2)
1032 .mount()
1033 .await;
1034
1035 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
1036
1037 room_list.paginate().await.unwrap();
1038
1039 assert_eq!(room_list.rooms().await.len(), 1);
1041
1042 room_list.reset().await;
1044
1045 assert_eq!(room_list.rooms().await.len(), 0);
1047 assert_matches!(
1048 room_list.pagination_state(),
1049 SpaceRoomListPaginationState::Idle { end_reached: false }
1050 );
1051
1052 room_list.paginate().await.unwrap();
1054 assert_eq!(room_list.rooms().await.len(), 1);
1055 }
1056
1057 fn make_space_room(
1058 room_id: OwnedRoomId,
1059 order: Option<&str>,
1060 origin_server_ts: Option<u32>,
1061 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
1062 ) -> SpaceRoom {
1063 if let Some(origin_server_ts) = origin_server_ts {
1064 children_state.insert(
1065 room_id.clone(),
1066 hierarchy_space_child_event(&room_id, order, origin_server_ts),
1067 );
1068 }
1069 SpaceRoom {
1070 room_id,
1071 canonical_alias: None,
1072 name: Some("New room name".to_owned()),
1073 display_name: "Empty room".to_owned(),
1074 topic: None,
1075 avatar_url: None,
1076 room_type: None,
1077 num_joined_members: 0,
1078 join_rule: None,
1079 world_readable: None,
1080 guest_can_join: false,
1081 is_direct: None,
1082 children_count: 0,
1083 state: None,
1084 heroes: None,
1085 via: vec![],
1086 suggested: false,
1087 is_dm: None,
1088 }
1089 }
1090
1091 fn hierarchy_space_child_event(
1092 room_id: &RoomId,
1093 order: Option<&str>,
1094 origin_server_ts: u32,
1095 ) -> HierarchySpaceChildEvent {
1096 let mut json = json!({
1097 "content": {
1098 "via": []
1099 },
1100 "origin_server_ts": origin_server_ts,
1101 "sender": "@bob:a.b",
1102 "state_key": room_id.to_string(),
1103 "type": "m.space.child"
1104 });
1105
1106 if let Some(order) = order {
1107 json["content"]["order"] = json!(order);
1108 }
1109
1110 from_value::<HierarchySpaceChildEvent>(json).unwrap()
1111 }
1112}