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_infinite_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_infinite_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 false,
324 )),
325 );
326 }
327 }
328
329 let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
330
331 children
332 .iter()
333 .map(|room| {
334 let child_state = children_state.get(&room.summary.room_id);
335 let via =
336 child_state.map(|state| state.content.via.clone()).unwrap_or_default();
337 let suggested =
338 child_state.map(|state| state.content.suggested).unwrap_or(false);
339
340 SpaceRoom::new_from_summary(
341 &room.summary,
342 self.client.get_room(&room.summary.room_id),
343 room.children_state.len() as u64,
344 via,
345 suggested,
346 )
347 })
348 .sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
349 .for_each(|room| rooms.push_back(room));
350
351 self.pagination_state.set(SpaceRoomListPaginationState::Idle {
352 end_reached: result.next_batch.is_none(),
353 });
354
355 Ok(())
356 }
357 Err(err) => {
358 self.pagination_state
359 .set(SpaceRoomListPaginationState::Idle { end_reached: false });
360 Err(err.into())
361 }
362 }
363 }
364
365 pub async fn reset(&self) {
374 let mut pagination_token = self.token.lock().await;
375 *pagination_token = None.into();
376
377 self.rooms.lock().clear();
378 self.children_state.lock().take();
379
380 self.pagination_state.set(SpaceRoomListPaginationState::Idle { end_reached: false });
381 }
382
383 fn compare_rooms(
386 a: &SpaceRoom,
387 b: &SpaceRoom,
388 children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
389 ) -> Ordering {
390 let a_state = children_state.get(&a.room_id);
391 let b_state = children_state.get(&b.room_id);
392
393 SpaceRoom::compare_rooms(
394 (&a.room_id, a_state.map(Into::into).as_ref()),
395 (&b.room_id, b_state.map(Into::into).as_ref()),
396 )
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use std::{cmp::Ordering, collections::HashMap};
403
404 use assert_matches2::{assert_let, assert_matches};
405 use eyeball_im::VectorDiff;
406 use futures_util::pin_mut;
407 use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
408 use matrix_sdk_test::{
409 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
410 };
411 use ruma::{
412 MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId,
413 events::space::child::HierarchySpaceChildEvent,
414 owned_room_id, owned_server_name,
415 room::{JoinRuleSummary, RoomSummary},
416 room_id, server_name, uint,
417 };
418 use serde_json::{from_value, json};
419 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
420 use wiremock::ResponseTemplate;
421
422 use crate::spaces::{
423 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
424 };
425
426 #[async_test]
427 async fn test_room_list_pagination() {
428 let server = MatrixMockServer::new().await;
429 let client = server.client_builder().build().await;
430 let user_id = client.user_id().unwrap();
431 let space_service = SpaceService::new(client.clone()).await;
432 let factory = EventFactory::new();
433
434 server.mock_room_state_encryption().plain().mount().await;
435
436 let parent_space_id = room_id!("!parent_space:example.org");
437 let child_space_id_1 = room_id!("!1:example.org");
438 let child_space_id_2 = room_id!("!2:example.org");
439
440 server
441 .sync_room(
442 &client,
443 JoinedRoomBuilder::new(parent_space_id)
444 .add_state_event(
445 factory
446 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
447 .sender(user_id),
448 )
449 .add_state_event(
450 factory
451 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
452 .sender(user_id),
453 ),
454 )
455 .await;
456
457 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
458
459 assert_let!(Some(parent_space) = room_list.space());
461 assert_eq!(parent_space.children_count, 2);
462
463 assert_matches!(
465 room_list.pagination_state(),
466 SpaceRoomListPaginationState::Idle { end_reached: false }
467 );
468
469 assert_eq!(room_list.rooms(), vec![]);
471
472 let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
475 pin_mut!(pagination_state_subscriber);
476 assert_pending!(pagination_state_subscriber);
477
478 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
479 pin_mut!(rooms_subscriber);
480 assert_pending!(rooms_subscriber);
481
482 server
484 .mock_get_hierarchy()
485 .ok_with_room_ids_and_children_state(
486 vec![child_space_id_1, child_space_id_2],
487 vec![(room_id!("!child:example.org"), vec![])],
488 )
489 .mount()
490 .await;
491
492 room_list.paginate().await.unwrap();
493
494 assert_next_matches!(
496 pagination_state_subscriber,
497 SpaceRoomListPaginationState::Idle { end_reached: true }
498 );
499
500 assert_next_eq!(
502 rooms_subscriber,
503 vec![
504 VectorDiff::PushBack {
505 value: SpaceRoom::new_from_summary(
506 &RoomSummary::new(
507 child_space_id_1.to_owned(),
508 JoinRuleSummary::Public,
509 false,
510 uint!(1),
511 false,
512 ),
513 None,
514 1,
515 vec![],
516 false,
517 )
518 },
519 VectorDiff::PushBack {
520 value: SpaceRoom::new_from_summary(
521 &RoomSummary::new(
522 child_space_id_2.to_owned(),
523 JoinRuleSummary::Public,
524 false,
525 uint!(1),
526 false,
527 ),
528 None,
529 1,
530 vec![],
531 false,
532 ),
533 }
534 ]
535 );
536 }
537
538 #[async_test]
539 async fn test_room_state_updates() {
540 let server = MatrixMockServer::new().await;
541 let client = server.client_builder().build().await;
542 let space_service = SpaceService::new(client.clone()).await;
543
544 let parent_space_id = room_id!("!parent_space:example.org");
545 let child_room_id_1 = room_id!("!1:example.org");
546 let child_room_id_2 = room_id!("!2:example.org");
547
548 server
549 .mock_get_hierarchy()
550 .ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
551 .mount()
552 .await;
553
554 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
555
556 room_list.paginate().await.unwrap();
557
558 assert_eq!(room_list.rooms().first().unwrap().room_id, child_room_id_1);
560 assert_eq!(room_list.rooms().last().unwrap().room_id, child_room_id_2);
561
562 assert_eq!(room_list.rooms().first().unwrap().state, None);
564 assert_eq!(room_list.rooms().last().unwrap().state, None);
565
566 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
567 pin_mut!(rooms_subscriber);
568 assert_pending!(rooms_subscriber);
569
570 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
572
573 assert_ready!(rooms_subscriber);
575 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
576 assert_eq!(room_list.rooms().last().unwrap().state, None);
577
578 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
580 assert_ready!(rooms_subscriber);
581 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
582 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Joined));
583
584 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
586 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
587 assert_ready!(rooms_subscriber);
588 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Left));
589 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Left));
590 }
591
592 #[async_test]
593 async fn test_parent_space_updates() {
594 let server = MatrixMockServer::new().await;
595 let client = server.client_builder().build().await;
596 let user_id = client.user_id().unwrap();
597 let space_service = SpaceService::new(client.clone()).await;
598 let factory = EventFactory::new();
599
600 server.mock_room_state_encryption().plain().mount().await;
601
602 let parent_space_id = room_id!("!parent_space:example.org");
603 let child_space_id_1 = room_id!("!1:example.org");
604 let child_space_id_2 = room_id!("!2:example.org");
605
606 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
608 assert!(room_list.space().is_none());
609
610 let parent_space_subscriber = room_list.subscribe_to_space_updates();
611 pin_mut!(parent_space_subscriber);
612 assert_pending!(parent_space_subscriber);
613
614 server
615 .mock_get_hierarchy()
616 .ok_with_room_ids_and_children_state(
617 vec![parent_space_id, child_space_id_1, child_space_id_2],
618 vec![(
619 room_id!("!child:example.org"),
620 vec![server_name!("matrix-client.example.org")],
621 )],
622 )
623 .mount()
624 .await;
625
626 room_list.paginate().await.unwrap();
628 assert_let!(Some(parent_space) = room_list.space());
629 assert_eq!(parent_space.room_id, parent_space_id);
630
631 assert_next_eq!(parent_space_subscriber, Some(parent_space));
633
634 server
637 .sync_room(
638 &client,
639 JoinedRoomBuilder::new(parent_space_id)
640 .add_state_event(
641 factory
642 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
643 .sender(user_id),
644 )
645 .add_state_event(
646 factory
647 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
648 .sender(user_id),
649 ),
650 )
651 .await;
652
653 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
654
655 assert_let!(Some(parent_space) = room_list.space());
657 assert_eq!(parent_space.children_count, 2);
658 }
659
660 #[async_test]
661 async fn test_parent_space_room_info_update() {
662 let server = MatrixMockServer::new().await;
663 let client = server.client_builder().build().await;
664 let user_id = client.user_id().unwrap();
665 let space_service = SpaceService::new(client.clone()).await;
666 let factory = EventFactory::new();
667
668 server.mock_room_state_encryption().plain().mount().await;
669
670 let parent_space_id = room_id!("!parent_space:example.org");
671
672 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
673
674 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
675 assert_let!(Some(parent_space) = room_list.space());
676
677 let parent_space_subscriber = room_list.subscribe_to_space_updates();
679 pin_mut!(parent_space_subscriber);
680 assert_pending!(parent_space_subscriber);
681
682 server
684 .sync_room(
685 &client,
686 JoinedRoomBuilder::new(parent_space_id)
687 .add_state_event(factory.room_topic("New room topic").sender(user_id))
688 .add_state_event(factory.room_name("New room name").sender(user_id)),
689 )
690 .await;
691
692 let mut updated_parent_space = parent_space.clone();
693 updated_parent_space.topic = Some("New room topic".to_owned());
694 updated_parent_space.name = Some("New room name".to_owned());
695 updated_parent_space.display_name = "New room name".to_owned();
696
697 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
699 }
700
701 #[async_test]
702 async fn test_via_retrieval() {
703 let server = MatrixMockServer::new().await;
704 let client = server.client_builder().build().await;
705 let space_service = SpaceService::new(client.clone()).await;
706
707 server.mock_room_state_encryption().plain().mount().await;
708
709 let parent_space_id = room_id!("!parent_space:example.org");
710 let child_space_id_1 = room_id!("!1:example.org");
711 let child_space_id_2 = room_id!("!2:example.org");
712
713 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
714
715 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
716 pin_mut!(rooms_subscriber);
717
718 server
720 .mock_get_hierarchy()
721 .ok_with_room_ids_and_children_state(
722 vec![parent_space_id, child_space_id_1, child_space_id_2],
723 vec![
724 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
725 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
726 ],
727 )
728 .mount()
729 .await;
730
731 room_list.paginate().await.unwrap();
732
733 assert_next_eq!(
735 rooms_subscriber,
736 vec![
737 VectorDiff::PushBack {
738 value: SpaceRoom::new_from_summary(
739 &RoomSummary::new(
740 child_space_id_1.to_owned(),
741 JoinRuleSummary::Public,
742 false,
743 uint!(1),
744 false,
745 ),
746 None,
747 2,
748 vec![owned_server_name!("matrix-client.example.org")],
749 false,
750 )
751 },
752 VectorDiff::PushBack {
753 value: SpaceRoom::new_from_summary(
754 &RoomSummary::new(
755 child_space_id_2.to_owned(),
756 JoinRuleSummary::Public,
757 false,
758 uint!(1),
759 false,
760 ),
761 None,
762 2,
763 vec![owned_server_name!("other-matrix-client.example.org")],
764 false,
765 ),
766 }
767 ]
768 );
769 }
770
771 #[async_test]
772 async fn test_suggested_field() {
773 let server = MatrixMockServer::new().await;
774 let client = server.client_builder().build().await;
775 let space_service = SpaceService::new(client.clone()).await;
776
777 server.mock_room_state_encryption().plain().mount().await;
778
779 let parent_space_id = room_id!("!parent_space:example.org");
780 let suggested_child = room_id!("!suggested:example.org");
781 let not_suggested_child = room_id!("!not_suggested:example.org");
782
783 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
784
785 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
786 pin_mut!(rooms_subscriber);
787
788 let children_state = vec![
790 json!({
791 "type": "m.space.child",
792 "state_key": suggested_child,
793 "content": { "via": ["example.org"], "suggested": true },
794 "sender": "@admin:example.org",
795 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
796 }),
797 json!({
798 "type": "m.space.child",
799 "state_key": not_suggested_child,
800 "content": { "via": ["example.org"], "suggested": false },
801 "sender": "@admin:example.org",
802 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
803 }),
804 ];
805
806 server
807 .mock_get_hierarchy()
808 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
809 "rooms": [
810 {
811 "room_id": parent_space_id,
812 "num_joined_members": 1,
813 "world_readable": false,
814 "guest_can_join": false,
815 "children_state": children_state
816 },
817 {
818 "room_id": suggested_child,
819 "num_joined_members": 5,
820 "world_readable": false,
821 "guest_can_join": false,
822 "children_state": []
823 },
824 {
825 "room_id": not_suggested_child,
826 "num_joined_members": 3,
827 "world_readable": false,
828 "guest_can_join": false,
829 "children_state": []
830 },
831 ]
832 })))
833 .mount()
834 .await;
835
836 room_list.paginate().await.unwrap();
837
838 let rooms_diff = assert_next_matches!(rooms_subscriber, diff => diff);
839 assert_eq!(rooms_diff.len(), 2);
840
841 let mut rooms_by_id = HashMap::new();
843 for diff in rooms_diff {
844 if let VectorDiff::PushBack { value } = diff {
845 rooms_by_id.insert(value.room_id.clone(), value);
846 } else {
847 panic!("Expected PushBack, got {:?}", diff);
848 }
849 }
850
851 let suggested_room = rooms_by_id.get(suggested_child).expect("suggested child not found");
853 assert!(
854 suggested_room.suggested,
855 "Room with 'suggested: true' should have suggested == true"
856 );
857
858 let not_suggested_room =
860 rooms_by_id.get(not_suggested_child).expect("not-suggested child not found");
861 assert!(
862 !not_suggested_room.suggested,
863 "Room with 'suggested: false' should have suggested == false"
864 );
865 }
866
867 #[async_test]
868 async fn test_room_list_sorting() {
869 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
870
871 assert_eq!(
873 SpaceRoomList::compare_rooms(
874 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
875 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
876 &children_state,
877 ),
878 Ordering::Less
879 );
880
881 assert_eq!(
882 SpaceRoomList::compare_rooms(
883 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
884 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
885 &children_state,
886 ),
887 Ordering::Greater
888 );
889
890 assert_eq!(
893 SpaceRoomList::compare_rooms(
894 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
895 &make_space_room(
896 owned_room_id!("!Marțolea:a.b"),
897 None,
898 Some(0),
899 &mut children_state
900 ),
901 &children_state,
902 ),
903 Ordering::Greater
904 );
905
906 assert_eq!(
908 SpaceRoomList::compare_rooms(
909 &make_space_room(
910 owned_room_id!("!Joiana:a.b"),
911 Some("last"),
912 Some(123),
913 &mut children_state
914 ),
915 &make_space_room(
916 owned_room_id!("!Mioara:a.b"),
917 Some("first"),
918 Some(234),
919 &mut children_state
920 ),
921 &children_state,
922 ),
923 Ordering::Greater
924 );
925
926 assert_eq!(
928 SpaceRoomList::compare_rooms(
929 &make_space_room(
930 owned_room_id!("!Joiana:a.b"),
931 Some("Same pasture"),
932 Some(1),
933 &mut children_state
934 ),
935 &make_space_room(
936 owned_room_id!("!Mioara:a.b"),
937 Some("Same pasture"),
938 Some(0),
939 &mut children_state
940 ),
941 &children_state,
942 ),
943 Ordering::Greater
944 );
945
946 assert_eq!(
949 SpaceRoomList::compare_rooms(
950 &make_space_room(
951 owned_room_id!("!Joiana:a.b"),
952 Some("same_pasture"),
953 Some(0),
954 &mut children_state
955 ),
956 &make_space_room(
957 owned_room_id!("!Mioara:a.b"),
958 Some("same_pasture"),
959 Some(0),
960 &mut children_state
961 ),
962 &children_state,
963 ),
964 Ordering::Less
965 );
966
967 assert_eq!(
970 SpaceRoomList::compare_rooms(
971 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
972 &make_space_room(
973 owned_room_id!("!Sâmbotina:a.b"),
974 None,
975 Some(0),
976 &mut children_state
977 ),
978 &children_state,
979 ),
980 Ordering::Greater
981 );
982
983 assert_eq!(
986 SpaceRoomList::compare_rooms(
987 &make_space_room(
988 owned_room_id!("!Sâmbotina:a.b"),
989 None,
990 Some(1),
991 &mut children_state
992 ),
993 &make_space_room(
994 owned_room_id!("!Dumana:a.b"),
995 Some("Some pasture"),
996 Some(1),
997 &mut children_state
998 ),
999 &children_state,
1000 ),
1001 Ordering::Greater
1002 );
1003 }
1004
1005 #[async_test]
1006 async fn test_reset() {
1007 let server = MatrixMockServer::new().await;
1008 let client = server.client_builder().build().await;
1009 let space_service = SpaceService::new(client.clone()).await;
1010
1011 let parent_space_id = room_id!("!parent_space:example.org");
1012 let child_space_id_1 = room_id!("!1:example.org");
1013
1014 server
1015 .mock_get_hierarchy()
1016 .ok_with_room_ids(vec![child_space_id_1])
1017 .expect(2)
1018 .mount()
1019 .await;
1020
1021 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
1022
1023 room_list.paginate().await.unwrap();
1024
1025 assert_eq!(room_list.rooms().len(), 1);
1027
1028 room_list.reset().await;
1030
1031 assert_eq!(room_list.rooms().len(), 0);
1033 assert_matches!(
1034 room_list.pagination_state(),
1035 SpaceRoomListPaginationState::Idle { end_reached: false }
1036 );
1037
1038 room_list.paginate().await.unwrap();
1040 assert_eq!(room_list.rooms().len(), 1);
1041 }
1042
1043 fn make_space_room(
1044 room_id: OwnedRoomId,
1045 order: Option<&str>,
1046 origin_server_ts: Option<u32>,
1047 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
1048 ) -> SpaceRoom {
1049 if let Some(origin_server_ts) = origin_server_ts {
1050 children_state.insert(
1051 room_id.clone(),
1052 hierarchy_space_child_event(&room_id, order, origin_server_ts),
1053 );
1054 }
1055 SpaceRoom {
1056 room_id,
1057 canonical_alias: None,
1058 name: Some("New room name".to_owned()),
1059 display_name: "Empty room".to_owned(),
1060 topic: None,
1061 avatar_url: None,
1062 room_type: None,
1063 num_joined_members: 0,
1064 join_rule: None,
1065 world_readable: None,
1066 guest_can_join: false,
1067 is_direct: None,
1068 children_count: 0,
1069 state: None,
1070 heroes: None,
1071 via: vec![],
1072 suggested: false,
1073 }
1074 }
1075
1076 fn hierarchy_space_child_event(
1077 room_id: &RoomId,
1078 order: Option<&str>,
1079 origin_server_ts: u32,
1080 ) -> HierarchySpaceChildEvent {
1081 let mut json = json!({
1082 "content": {
1083 "via": []
1084 },
1085 "origin_server_ts": origin_server_ts,
1086 "sender": "@bob:a.b",
1087 "state_key": room_id.to_string(),
1088 "type": "m.space.child"
1089 });
1090
1091 if let Some(order) = order {
1092 json["content"]["order"] = json!(order);
1093 }
1094
1095 from_value::<HierarchySpaceChildEvent>(json).unwrap()
1096 }
1097}