1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
16
17use eyeball::{ObservableWriteGuard, SharedObservable, Subscriber};
18use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
19use futures_util::pin_mut;
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<Mutex<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(Mutex::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_background_task("space_room_list::room_updates", {
134 let client = client.clone();
135 let rooms = rooms.clone();
136
137 async move {
138 pin_mut!(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();
148
149 updates.iter_all_room_ids().for_each(|updated_room_id| {
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 );
163 }
164 })
165 }
166 Err(err) => {
167 error!("error when listening to room updates: {err}");
168 }
169 }
170 }
171 }
172 })
173 .abort_on_drop();
174
175 let space_observable = SharedObservable::new(None);
176
177 let (space_room, space_update_handle) = if let Some(parent) = client.get_room(&space_id) {
178 let children_count = parent
179 .get_state_events_static::<SpaceChildEventContent>()
180 .await
181 .map_or(0, |c| c.len() as u64);
182
183 let mut subscriber = parent.subscribe_info();
184 let space_update_handle = client
185 .task_monitor()
186 .spawn_background_task("space_room_list::space_update", {
187 let client = client.clone();
188 let space_id = space_id.clone();
189 let space_observable = space_observable.clone();
190 async move {
191 while subscriber.next().await.is_some() {
192 if let Some(room) = client.get_room(&space_id) {
193 space_observable
194 .set(Some(SpaceRoom::new_from_known(&room, children_count)));
195 }
196 }
197 }
198 })
199 .abort_on_drop();
200
201 (Some(SpaceRoom::new_from_known(&parent, children_count)), Some(space_update_handle))
202 } else {
203 (None, None)
204 };
205
206 space_observable.set(space_room);
207
208 Self {
209 client,
210 space_id,
211 space: space_observable,
212 children_state: Mutex::new(None),
213 token: AsyncMutex::new(None.into()),
214 pagination_state: SharedObservable::new(SpaceRoomListPaginationState::Idle {
215 end_reached: false,
216 }),
217 rooms,
218 _space_update_handle: space_update_handle,
219 _room_update_handle: room_update_handle,
220 }
221 }
222
223 pub fn space(&self) -> Option<SpaceRoom> {
225 self.space.get()
226 }
227
228 pub fn subscribe_to_space_updates(&self) -> Subscriber<Option<SpaceRoom>> {
230 self.space.subscribe()
231 }
232
233 pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
235 self.pagination_state.get()
236 }
237
238 pub fn subscribe_to_pagination_state_updates(
240 &self,
241 ) -> Subscriber<SpaceRoomListPaginationState> {
242 self.pagination_state.subscribe()
243 }
244
245 pub fn rooms(&self) -> Vec<SpaceRoom> {
247 self.rooms.lock().iter().cloned().collect_vec()
248 }
249
250 pub fn subscribe_to_room_updates(
252 &self,
253 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
254 self.rooms.lock().subscribe().into_values_and_batched_stream()
255 }
256
257 pub async fn paginate(&self) -> Result<(), Error> {
260 {
261 let mut pagination_state = self.pagination_state.write();
262
263 match *pagination_state {
264 SpaceRoomListPaginationState::Idle { end_reached } if end_reached => {
265 return Ok(());
266 }
267 SpaceRoomListPaginationState::Loading => {
268 return Ok(());
269 }
270 _ => {}
271 }
272
273 ObservableWriteGuard::set(&mut pagination_state, SpaceRoomListPaginationState::Loading);
274 }
275
276 let mut request = get_hierarchy::v1::Request::new(self.space_id.clone());
277 request.max_depth = Some(uint!(1)); let mut pagination_token = self.token.lock().await;
280
281 if let PaginationToken::HasMore(ref token) = *pagination_token {
282 request.from = Some(token.clone());
283 }
284
285 match self.client.send(request).await {
286 Ok(result) => {
287 *pagination_token = match &result.next_batch {
288 Some(val) => PaginationToken::HasMore(val.clone()),
289 None => PaginationToken::HitEnd,
290 };
291
292 let mut rooms = self.rooms.lock();
293
294 let (space, children): (Vec<_>, Vec<_>) =
297 result.rooms.into_iter().partition(|f| f.summary.room_id == self.space_id);
298
299 if let Some(room) = space.first() {
300 let mut children_state =
301 HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
302 for child_state in &room.children_state {
303 match child_state.deserialize() {
304 Ok(child) => {
305 children_state.insert(child.state_key.clone(), child.clone());
306 }
307 Err(error) => {
308 warn!("Failed deserializing space child event: {error}");
309 }
310 }
311 }
312 *self.children_state.lock() = Some(children_state);
313
314 let mut space = self.space.write();
315 if space.is_none() {
316 ObservableWriteGuard::set(
317 &mut space,
318 Some(SpaceRoom::new_from_summary(
319 &room.summary,
320 self.client.get_room(&room.summary.room_id),
321 room.children_state.len() as u64,
322 vec![],
323 )),
324 );
325 }
326 }
327
328 let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
329
330 children
331 .iter()
332 .map(|room| {
333 let via = children_state
334 .get(&room.summary.room_id)
335 .map(|state| state.content.via.clone());
336
337 SpaceRoom::new_from_summary(
338 &room.summary,
339 self.client.get_room(&room.summary.room_id),
340 room.children_state.len() as u64,
341 via.unwrap_or_default(),
342 )
343 })
344 .sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
345 .for_each(|room| rooms.push_back(room));
346
347 self.pagination_state.set(SpaceRoomListPaginationState::Idle {
348 end_reached: result.next_batch.is_none(),
349 });
350
351 Ok(())
352 }
353 Err(err) => {
354 self.pagination_state
355 .set(SpaceRoomListPaginationState::Idle { end_reached: false });
356 Err(err.into())
357 }
358 }
359 }
360
361 pub async fn reset(&self) {
370 let mut pagination_token = self.token.lock().await;
371 *pagination_token = None.into();
372
373 self.rooms.lock().clear();
374 self.children_state.lock().take();
375
376 self.pagination_state.set(SpaceRoomListPaginationState::Idle { end_reached: false });
377 }
378
379 fn compare_rooms(
382 a: &SpaceRoom,
383 b: &SpaceRoom,
384 children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
385 ) -> Ordering {
386 let a_state = children_state.get(&a.room_id);
387 let b_state = children_state.get(&b.room_id);
388
389 SpaceRoom::compare_rooms(a, b, a_state.map(Into::into), b_state.map(Into::into))
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use std::{cmp::Ordering, collections::HashMap};
396
397 use assert_matches2::{assert_let, assert_matches};
398 use eyeball_im::VectorDiff;
399 use futures_util::pin_mut;
400 use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
401 use matrix_sdk_test::{
402 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
403 };
404 use ruma::{
405 OwnedRoomId, RoomId,
406 events::space::child::HierarchySpaceChildEvent,
407 owned_room_id, owned_server_name,
408 room::{JoinRuleSummary, RoomSummary},
409 room_id, server_name, uint,
410 };
411 use serde_json::{from_value, json};
412 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
413
414 use crate::spaces::{
415 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
416 };
417
418 #[async_test]
419 async fn test_room_list_pagination() {
420 let server = MatrixMockServer::new().await;
421 let client = server.client_builder().build().await;
422 let user_id = client.user_id().unwrap();
423 let space_service = SpaceService::new(client.clone()).await;
424 let factory = EventFactory::new();
425
426 server.mock_room_state_encryption().plain().mount().await;
427
428 let parent_space_id = room_id!("!parent_space:example.org");
429 let child_space_id_1 = room_id!("!1:example.org");
430 let child_space_id_2 = room_id!("!2:example.org");
431
432 server
433 .sync_room(
434 &client,
435 JoinedRoomBuilder::new(parent_space_id)
436 .add_state_event(
437 factory
438 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
439 .sender(user_id),
440 )
441 .add_state_event(
442 factory
443 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
444 .sender(user_id),
445 ),
446 )
447 .await;
448
449 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
450
451 assert_let!(Some(parent_space) = room_list.space());
453 assert_eq!(parent_space.children_count, 2);
454
455 assert_matches!(
457 room_list.pagination_state(),
458 SpaceRoomListPaginationState::Idle { end_reached: false }
459 );
460
461 assert_eq!(room_list.rooms(), vec![]);
463
464 let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
467 pin_mut!(pagination_state_subscriber);
468 assert_pending!(pagination_state_subscriber);
469
470 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
471 pin_mut!(rooms_subscriber);
472 assert_pending!(rooms_subscriber);
473
474 server
476 .mock_get_hierarchy()
477 .ok_with_room_ids_and_children_state(
478 vec![child_space_id_1, child_space_id_2],
479 vec![(room_id!("!child:example.org"), vec![])],
480 )
481 .mount()
482 .await;
483
484 room_list.paginate().await.unwrap();
485
486 assert_next_matches!(
488 pagination_state_subscriber,
489 SpaceRoomListPaginationState::Idle { end_reached: true }
490 );
491
492 assert_next_eq!(
494 rooms_subscriber,
495 vec![
496 VectorDiff::PushBack {
497 value: SpaceRoom::new_from_summary(
498 &RoomSummary::new(
499 child_space_id_1.to_owned(),
500 JoinRuleSummary::Public,
501 false,
502 uint!(1),
503 false,
504 ),
505 None,
506 1,
507 vec![],
508 )
509 },
510 VectorDiff::PushBack {
511 value: SpaceRoom::new_from_summary(
512 &RoomSummary::new(
513 child_space_id_2.to_owned(),
514 JoinRuleSummary::Public,
515 false,
516 uint!(1),
517 false,
518 ),
519 None,
520 1,
521 vec![],
522 ),
523 }
524 ]
525 );
526 }
527
528 #[async_test]
529 async fn test_room_state_updates() {
530 let server = MatrixMockServer::new().await;
531 let client = server.client_builder().build().await;
532 let space_service = SpaceService::new(client.clone()).await;
533
534 let parent_space_id = room_id!("!parent_space:example.org");
535 let child_room_id_1 = room_id!("!1:example.org");
536 let child_room_id_2 = room_id!("!2:example.org");
537
538 server
539 .mock_get_hierarchy()
540 .ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
541 .mount()
542 .await;
543
544 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
545
546 room_list.paginate().await.unwrap();
547
548 assert_eq!(room_list.rooms().first().unwrap().room_id, child_room_id_1);
550 assert_eq!(room_list.rooms().last().unwrap().room_id, child_room_id_2);
551
552 assert_eq!(room_list.rooms().first().unwrap().state, None);
554 assert_eq!(room_list.rooms().last().unwrap().state, None);
555
556 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
557 pin_mut!(rooms_subscriber);
558 assert_pending!(rooms_subscriber);
559
560 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
562
563 assert_ready!(rooms_subscriber);
565 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
566 assert_eq!(room_list.rooms().last().unwrap().state, None);
567
568 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
570 assert_ready!(rooms_subscriber);
571 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
572 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Joined));
573
574 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
576 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
577 assert_ready!(rooms_subscriber);
578 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Left));
579 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Left));
580 }
581
582 #[async_test]
583 async fn test_parent_space_updates() {
584 let server = MatrixMockServer::new().await;
585 let client = server.client_builder().build().await;
586 let user_id = client.user_id().unwrap();
587 let space_service = SpaceService::new(client.clone()).await;
588 let factory = EventFactory::new();
589
590 server.mock_room_state_encryption().plain().mount().await;
591
592 let parent_space_id = room_id!("!parent_space:example.org");
593 let child_space_id_1 = room_id!("!1:example.org");
594 let child_space_id_2 = room_id!("!2:example.org");
595
596 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
598 assert!(room_list.space().is_none());
599
600 let parent_space_subscriber = room_list.subscribe_to_space_updates();
601 pin_mut!(parent_space_subscriber);
602 assert_pending!(parent_space_subscriber);
603
604 server
605 .mock_get_hierarchy()
606 .ok_with_room_ids_and_children_state(
607 vec![parent_space_id, child_space_id_1, child_space_id_2],
608 vec![(
609 room_id!("!child:example.org"),
610 vec![server_name!("matrix-client.example.org")],
611 )],
612 )
613 .mount()
614 .await;
615
616 room_list.paginate().await.unwrap();
618 assert_let!(Some(parent_space) = room_list.space());
619 assert_eq!(parent_space.room_id, parent_space_id);
620
621 assert_next_eq!(parent_space_subscriber, Some(parent_space));
623
624 server
627 .sync_room(
628 &client,
629 JoinedRoomBuilder::new(parent_space_id)
630 .add_state_event(
631 factory
632 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
633 .sender(user_id),
634 )
635 .add_state_event(
636 factory
637 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
638 .sender(user_id),
639 ),
640 )
641 .await;
642
643 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
644
645 assert_let!(Some(parent_space) = room_list.space());
647 assert_eq!(parent_space.children_count, 2);
648 }
649
650 #[async_test]
651 async fn test_parent_space_room_info_update() {
652 let server = MatrixMockServer::new().await;
653 let client = server.client_builder().build().await;
654 let user_id = client.user_id().unwrap();
655 let space_service = SpaceService::new(client.clone()).await;
656 let factory = EventFactory::new();
657
658 server.mock_room_state_encryption().plain().mount().await;
659
660 let parent_space_id = room_id!("!parent_space:example.org");
661
662 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
663
664 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
665 assert_let!(Some(parent_space) = room_list.space());
666
667 let parent_space_subscriber = room_list.subscribe_to_space_updates();
669 pin_mut!(parent_space_subscriber);
670 assert_pending!(parent_space_subscriber);
671
672 server
674 .sync_room(
675 &client,
676 JoinedRoomBuilder::new(parent_space_id)
677 .add_state_event(factory.room_topic("New room topic").sender(user_id))
678 .add_state_event(factory.room_name("New room name").sender(user_id)),
679 )
680 .await;
681
682 let mut updated_parent_space = parent_space.clone();
683 updated_parent_space.topic = Some("New room topic".to_owned());
684 updated_parent_space.name = Some("New room name".to_owned());
685 updated_parent_space.display_name = "New room name".to_owned();
686
687 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
689 }
690
691 #[async_test]
692 async fn test_via_retrieval() {
693 let server = MatrixMockServer::new().await;
694 let client = server.client_builder().build().await;
695 let space_service = SpaceService::new(client.clone()).await;
696
697 server.mock_room_state_encryption().plain().mount().await;
698
699 let parent_space_id = room_id!("!parent_space:example.org");
700 let child_space_id_1 = room_id!("!1:example.org");
701 let child_space_id_2 = room_id!("!2:example.org");
702
703 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
704
705 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
706 pin_mut!(rooms_subscriber);
707
708 server
710 .mock_get_hierarchy()
711 .ok_with_room_ids_and_children_state(
712 vec![parent_space_id, child_space_id_1, child_space_id_2],
713 vec![
714 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
715 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
716 ],
717 )
718 .mount()
719 .await;
720
721 room_list.paginate().await.unwrap();
722
723 assert_next_eq!(
725 rooms_subscriber,
726 vec![
727 VectorDiff::PushBack {
728 value: SpaceRoom::new_from_summary(
729 &RoomSummary::new(
730 child_space_id_1.to_owned(),
731 JoinRuleSummary::Public,
732 false,
733 uint!(1),
734 false,
735 ),
736 None,
737 2,
738 vec![owned_server_name!("matrix-client.example.org")],
739 )
740 },
741 VectorDiff::PushBack {
742 value: SpaceRoom::new_from_summary(
743 &RoomSummary::new(
744 child_space_id_2.to_owned(),
745 JoinRuleSummary::Public,
746 false,
747 uint!(1),
748 false,
749 ),
750 None,
751 2,
752 vec![owned_server_name!("other-matrix-client.example.org")],
753 ),
754 }
755 ]
756 );
757 }
758
759 #[async_test]
760 async fn test_room_list_sorting() {
761 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
762
763 assert_eq!(
765 SpaceRoomList::compare_rooms(
766 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
767 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
768 &children_state,
769 ),
770 Ordering::Less
771 );
772
773 assert_eq!(
774 SpaceRoomList::compare_rooms(
775 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
776 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
777 &children_state,
778 ),
779 Ordering::Greater
780 );
781
782 assert_eq!(
785 SpaceRoomList::compare_rooms(
786 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
787 &make_space_room(
788 owned_room_id!("!Marțolea:a.b"),
789 None,
790 Some(0),
791 &mut children_state
792 ),
793 &children_state,
794 ),
795 Ordering::Greater
796 );
797
798 assert_eq!(
800 SpaceRoomList::compare_rooms(
801 &make_space_room(
802 owned_room_id!("!Joiana:a.b"),
803 Some("last"),
804 Some(123),
805 &mut children_state
806 ),
807 &make_space_room(
808 owned_room_id!("!Mioara:a.b"),
809 Some("first"),
810 Some(234),
811 &mut children_state
812 ),
813 &children_state,
814 ),
815 Ordering::Greater
816 );
817
818 assert_eq!(
820 SpaceRoomList::compare_rooms(
821 &make_space_room(
822 owned_room_id!("!Joiana:a.b"),
823 Some("Same pasture"),
824 Some(1),
825 &mut children_state
826 ),
827 &make_space_room(
828 owned_room_id!("!Mioara:a.b"),
829 Some("Same pasture"),
830 Some(0),
831 &mut children_state
832 ),
833 &children_state,
834 ),
835 Ordering::Greater
836 );
837
838 assert_eq!(
841 SpaceRoomList::compare_rooms(
842 &make_space_room(
843 owned_room_id!("!Joiana:a.b"),
844 Some("same_pasture"),
845 Some(0),
846 &mut children_state
847 ),
848 &make_space_room(
849 owned_room_id!("!Mioara:a.b"),
850 Some("same_pasture"),
851 Some(0),
852 &mut children_state
853 ),
854 &children_state,
855 ),
856 Ordering::Less
857 );
858
859 assert_eq!(
862 SpaceRoomList::compare_rooms(
863 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
864 &make_space_room(
865 owned_room_id!("!Sâmbotina:a.b"),
866 None,
867 Some(0),
868 &mut children_state
869 ),
870 &children_state,
871 ),
872 Ordering::Greater
873 );
874
875 assert_eq!(
878 SpaceRoomList::compare_rooms(
879 &make_space_room(
880 owned_room_id!("!Sâmbotina:a.b"),
881 None,
882 Some(1),
883 &mut children_state
884 ),
885 &make_space_room(
886 owned_room_id!("!Dumana:a.b"),
887 Some("Some pasture"),
888 Some(1),
889 &mut children_state
890 ),
891 &children_state,
892 ),
893 Ordering::Greater
894 );
895 }
896
897 #[async_test]
898 async fn test_reset() {
899 let server = MatrixMockServer::new().await;
900 let client = server.client_builder().build().await;
901 let space_service = SpaceService::new(client.clone()).await;
902
903 let parent_space_id = room_id!("!parent_space:example.org");
904 let child_space_id_1 = room_id!("!1:example.org");
905
906 server
907 .mock_get_hierarchy()
908 .ok_with_room_ids(vec![child_space_id_1])
909 .expect(2)
910 .mount()
911 .await;
912
913 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
914
915 room_list.paginate().await.unwrap();
916
917 assert_eq!(room_list.rooms().len(), 1);
919
920 room_list.reset().await;
922
923 assert_eq!(room_list.rooms().len(), 0);
925 assert_matches!(
926 room_list.pagination_state(),
927 SpaceRoomListPaginationState::Idle { end_reached: false }
928 );
929
930 room_list.paginate().await.unwrap();
932 assert_eq!(room_list.rooms().len(), 1);
933 }
934
935 fn make_space_room(
936 room_id: OwnedRoomId,
937 order: Option<&str>,
938 origin_server_ts: Option<u32>,
939 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
940 ) -> SpaceRoom {
941 if let Some(origin_server_ts) = origin_server_ts {
942 children_state.insert(
943 room_id.clone(),
944 hierarchy_space_child_event(&room_id, order, origin_server_ts),
945 );
946 }
947 SpaceRoom {
948 room_id,
949 canonical_alias: None,
950 name: Some("New room name".to_owned()),
951 display_name: "Empty room".to_owned(),
952 topic: None,
953 avatar_url: None,
954 room_type: None,
955 num_joined_members: 0,
956 join_rule: None,
957 world_readable: None,
958 guest_can_join: false,
959 is_direct: None,
960 children_count: 0,
961 state: None,
962 heroes: None,
963 via: vec![],
964 }
965 }
966
967 fn hierarchy_space_child_event(
968 room_id: &RoomId,
969 order: Option<&str>,
970 origin_server_ts: u32,
971 ) -> HierarchySpaceChildEvent {
972 let mut json = json!({
973 "content": {
974 "via": []
975 },
976 "origin_server_ts": origin_server_ts,
977 "sender": "@bob:a.b",
978 "state_key": room_id.to_string(),
979 "type": "m.space.child"
980 });
981
982 if let Some(order) = order {
983 json["content"]["order"] = json!(order);
984 }
985
986 from_value::<HierarchySpaceChildEvent>(json).unwrap()
987 }
988}