1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
16
17use eyeball::{ObservableWriteGuard, SharedObservable, Subscriber};
18use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
19use futures_util::{future::join_all, 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<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 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().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<_>) =
300 result.rooms.into_iter().partition(|f| f.summary.room_id == self.space_id);
301
302 if let Some(room) = space.first() {
303 let mut children_state =
304 HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
305 for child_state in &room.children_state {
306 match child_state.deserialize() {
307 Ok(child) => {
308 children_state.insert(child.state_key.clone(), child.clone());
309 }
310 Err(error) => {
311 warn!("Failed deserializing space child event: {error}");
312 }
313 }
314 }
315 *self.children_state.lock() = Some(children_state);
316
317 let space_is_none = self.space.read().is_none();
318 if space_is_none {
319 let space_room = SpaceRoom::new_from_summary(
320 &room.summary,
321 self.client.get_room(&room.summary.room_id),
322 room.children_state.len() as u64,
323 vec![],
324 false,
325 )
326 .await;
327 let mut space = self.space.write();
328 ObservableWriteGuard::set(&mut space, Some(space_room));
329 }
330 }
331
332 let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
333 let mut rooms = self.rooms.lock().await;
334
335 join_all(children.into_iter().map(|room| {
336 let children_state = children_state.clone();
337 async move {
338 let child_state = children_state.get(&room.summary.room_id);
339 let via =
340 child_state.map(|state| state.content.via.clone()).unwrap_or_default();
341 let suggested =
342 child_state.map(|state| state.content.suggested).unwrap_or(false);
343 SpaceRoom::new_from_summary(
344 &room.summary,
345 self.client.get_room(&room.summary.room_id),
346 room.children_state.len() as u64,
347 via,
348 suggested,
349 )
350 .await
351 }
352 }))
353 .await
354 .into_iter()
355 .sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
356 .for_each(|room| rooms.push_back(room));
357
358 self.pagination_state.set(SpaceRoomListPaginationState::Idle {
359 end_reached: result.next_batch.is_none(),
360 });
361
362 Ok(())
363 }
364 Err(err) => {
365 self.pagination_state
366 .set(SpaceRoomListPaginationState::Idle { end_reached: false });
367 Err(err.into())
368 }
369 }
370 }
371
372 pub async fn reset(&self) {
381 let mut pagination_token = self.token.lock().await;
382 *pagination_token = None.into();
383
384 self.rooms.lock().await.clear();
385 self.children_state.lock().take();
386
387 self.pagination_state.set(SpaceRoomListPaginationState::Idle { end_reached: false });
388 }
389
390 fn compare_rooms(
393 a: &SpaceRoom,
394 b: &SpaceRoom,
395 children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
396 ) -> Ordering {
397 let a_state = children_state.get(&a.room_id);
398 let b_state = children_state.get(&b.room_id);
399
400 SpaceRoom::compare_rooms(
401 (&a.room_id, a_state.map(Into::into).as_ref()),
402 (&b.room_id, b_state.map(Into::into).as_ref()),
403 )
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use std::{cmp::Ordering, collections::HashMap};
410
411 use assert_matches2::{assert_let, assert_matches};
412 use eyeball_im::VectorDiff;
413 use futures_util::pin_mut;
414 use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
415 use matrix_sdk_test::{
416 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
417 };
418 use ruma::{
419 MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId,
420 events::space::child::HierarchySpaceChildEvent,
421 owned_room_id, owned_server_name,
422 room::{JoinRuleSummary, RoomSummary},
423 room_id, server_name, uint,
424 };
425 use serde_json::{from_value, json};
426 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
427 use wiremock::ResponseTemplate;
428
429 use crate::spaces::{
430 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
431 };
432
433 #[async_test]
434 async fn test_room_list_pagination() {
435 let server = MatrixMockServer::new().await;
436 let client = server.client_builder().build().await;
437 let user_id = client.user_id().unwrap();
438 let space_service = SpaceService::new(client.clone()).await;
439 let factory = EventFactory::new();
440
441 server.mock_room_state_encryption().plain().mount().await;
442
443 let parent_space_id = room_id!("!parent_space:example.org");
444 let child_space_id_1 = room_id!("!1:example.org");
445 let child_space_id_2 = room_id!("!2:example.org");
446
447 server
448 .sync_room(
449 &client,
450 JoinedRoomBuilder::new(parent_space_id)
451 .add_state_event(
452 factory
453 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
454 .sender(user_id),
455 )
456 .add_state_event(
457 factory
458 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
459 .sender(user_id),
460 ),
461 )
462 .await;
463
464 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
465
466 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 pin_mut!(pagination_state_subscriber);
483 assert_pending!(pagination_state_subscriber);
484
485 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
486 pin_mut!(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 pin_mut!(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 pin_mut!(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());
666 assert_eq!(parent_space.children_count, 2);
667 }
668
669 #[async_test]
670 async fn test_parent_space_room_info_update() {
671 let server = MatrixMockServer::new().await;
672 let client = server.client_builder().build().await;
673 let user_id = client.user_id().unwrap();
674 let space_service = SpaceService::new(client.clone()).await;
675 let factory = EventFactory::new();
676
677 server.mock_room_state_encryption().plain().mount().await;
678
679 let parent_space_id = room_id!("!parent_space:example.org");
680
681 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
682
683 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
684 assert_let!(Some(parent_space) = room_list.space());
685
686 let parent_space_subscriber = room_list.subscribe_to_space_updates();
688 pin_mut!(parent_space_subscriber);
689 assert_pending!(parent_space_subscriber);
690
691 server
693 .sync_room(
694 &client,
695 JoinedRoomBuilder::new(parent_space_id)
696 .add_state_event(factory.room_topic("New room topic").sender(user_id))
697 .add_state_event(factory.room_name("New room name").sender(user_id)),
698 )
699 .await;
700
701 let mut updated_parent_space = parent_space.clone();
702 updated_parent_space.topic = Some("New room topic".to_owned());
703 updated_parent_space.name = Some("New room name".to_owned());
704 updated_parent_space.display_name = "New room name".to_owned();
705
706 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
708 }
709
710 #[async_test]
711 async fn test_via_retrieval() {
712 let server = MatrixMockServer::new().await;
713 let client = server.client_builder().build().await;
714 let space_service = SpaceService::new(client.clone()).await;
715
716 server.mock_room_state_encryption().plain().mount().await;
717
718 let parent_space_id = room_id!("!parent_space:example.org");
719 let child_space_id_1 = room_id!("!1:example.org");
720 let child_space_id_2 = room_id!("!2:example.org");
721
722 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
723
724 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
725 pin_mut!(rooms_subscriber);
726
727 server
729 .mock_get_hierarchy()
730 .ok_with_room_ids_and_children_state(
731 vec![parent_space_id, child_space_id_1, child_space_id_2],
732 vec![
733 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
734 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
735 ],
736 )
737 .mount()
738 .await;
739
740 room_list.paginate().await.unwrap();
741
742 assert_next_eq!(
744 rooms_subscriber,
745 vec![
746 VectorDiff::PushBack {
747 value: SpaceRoom::new_from_summary(
748 &RoomSummary::new(
749 child_space_id_1.to_owned(),
750 JoinRuleSummary::Public,
751 false,
752 uint!(1),
753 false,
754 ),
755 None,
756 2,
757 vec![owned_server_name!("matrix-client.example.org")],
758 false,
759 )
760 .await
761 },
762 VectorDiff::PushBack {
763 value: SpaceRoom::new_from_summary(
764 &RoomSummary::new(
765 child_space_id_2.to_owned(),
766 JoinRuleSummary::Public,
767 false,
768 uint!(1),
769 false,
770 ),
771 None,
772 2,
773 vec![owned_server_name!("other-matrix-client.example.org")],
774 false,
775 )
776 .await,
777 }
778 ]
779 );
780 }
781
782 #[async_test]
783 async fn test_suggested_field() {
784 let server = MatrixMockServer::new().await;
785 let client = server.client_builder().build().await;
786 let space_service = SpaceService::new(client.clone()).await;
787
788 server.mock_room_state_encryption().plain().mount().await;
789
790 let parent_space_id = room_id!("!parent_space:example.org");
791 let suggested_child = room_id!("!suggested:example.org");
792 let not_suggested_child = room_id!("!not_suggested:example.org");
793
794 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
795
796 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
797 pin_mut!(rooms_subscriber);
798
799 let children_state = vec![
801 json!({
802 "type": "m.space.child",
803 "state_key": suggested_child,
804 "content": { "via": ["example.org"], "suggested": true },
805 "sender": "@admin:example.org",
806 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
807 }),
808 json!({
809 "type": "m.space.child",
810 "state_key": not_suggested_child,
811 "content": { "via": ["example.org"], "suggested": false },
812 "sender": "@admin:example.org",
813 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
814 }),
815 ];
816
817 server
818 .mock_get_hierarchy()
819 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
820 "rooms": [
821 {
822 "room_id": parent_space_id,
823 "num_joined_members": 1,
824 "world_readable": false,
825 "guest_can_join": false,
826 "children_state": children_state
827 },
828 {
829 "room_id": suggested_child,
830 "num_joined_members": 5,
831 "world_readable": false,
832 "guest_can_join": false,
833 "children_state": []
834 },
835 {
836 "room_id": not_suggested_child,
837 "num_joined_members": 3,
838 "world_readable": false,
839 "guest_can_join": false,
840 "children_state": []
841 },
842 ]
843 })))
844 .mount()
845 .await;
846
847 room_list.paginate().await.unwrap();
848
849 let rooms_diff = assert_next_matches!(rooms_subscriber, diff => diff);
850 assert_eq!(rooms_diff.len(), 2);
851
852 let mut rooms_by_id = HashMap::new();
854 for diff in rooms_diff {
855 if let VectorDiff::PushBack { value } = diff {
856 rooms_by_id.insert(value.room_id.clone(), value);
857 } else {
858 panic!("Expected PushBack, got {:?}", diff);
859 }
860 }
861
862 let suggested_room = rooms_by_id.get(suggested_child).expect("suggested child not found");
864 assert!(
865 suggested_room.suggested,
866 "Room with 'suggested: true' should have suggested == true"
867 );
868
869 let not_suggested_room =
871 rooms_by_id.get(not_suggested_child).expect("not-suggested child not found");
872 assert!(
873 !not_suggested_room.suggested,
874 "Room with 'suggested: false' should have suggested == false"
875 );
876 }
877
878 #[async_test]
879 async fn test_room_list_sorting() {
880 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
881
882 assert_eq!(
884 SpaceRoomList::compare_rooms(
885 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
886 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
887 &children_state,
888 ),
889 Ordering::Less
890 );
891
892 assert_eq!(
893 SpaceRoomList::compare_rooms(
894 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
895 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
896 &children_state,
897 ),
898 Ordering::Greater
899 );
900
901 assert_eq!(
904 SpaceRoomList::compare_rooms(
905 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
906 &make_space_room(
907 owned_room_id!("!Marțolea:a.b"),
908 None,
909 Some(0),
910 &mut children_state
911 ),
912 &children_state,
913 ),
914 Ordering::Greater
915 );
916
917 assert_eq!(
919 SpaceRoomList::compare_rooms(
920 &make_space_room(
921 owned_room_id!("!Joiana:a.b"),
922 Some("last"),
923 Some(123),
924 &mut children_state
925 ),
926 &make_space_room(
927 owned_room_id!("!Mioara:a.b"),
928 Some("first"),
929 Some(234),
930 &mut children_state
931 ),
932 &children_state,
933 ),
934 Ordering::Greater
935 );
936
937 assert_eq!(
939 SpaceRoomList::compare_rooms(
940 &make_space_room(
941 owned_room_id!("!Joiana:a.b"),
942 Some("Same pasture"),
943 Some(1),
944 &mut children_state
945 ),
946 &make_space_room(
947 owned_room_id!("!Mioara:a.b"),
948 Some("Same pasture"),
949 Some(0),
950 &mut children_state
951 ),
952 &children_state,
953 ),
954 Ordering::Greater
955 );
956
957 assert_eq!(
960 SpaceRoomList::compare_rooms(
961 &make_space_room(
962 owned_room_id!("!Joiana:a.b"),
963 Some("same_pasture"),
964 Some(0),
965 &mut children_state
966 ),
967 &make_space_room(
968 owned_room_id!("!Mioara:a.b"),
969 Some("same_pasture"),
970 Some(0),
971 &mut children_state
972 ),
973 &children_state,
974 ),
975 Ordering::Less
976 );
977
978 assert_eq!(
981 SpaceRoomList::compare_rooms(
982 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
983 &make_space_room(
984 owned_room_id!("!Sâmbotina:a.b"),
985 None,
986 Some(0),
987 &mut children_state
988 ),
989 &children_state,
990 ),
991 Ordering::Greater
992 );
993
994 assert_eq!(
997 SpaceRoomList::compare_rooms(
998 &make_space_room(
999 owned_room_id!("!Sâmbotina:a.b"),
1000 None,
1001 Some(1),
1002 &mut children_state
1003 ),
1004 &make_space_room(
1005 owned_room_id!("!Dumana:a.b"),
1006 Some("Some pasture"),
1007 Some(1),
1008 &mut children_state
1009 ),
1010 &children_state,
1011 ),
1012 Ordering::Greater
1013 );
1014 }
1015
1016 #[async_test]
1017 async fn test_reset() {
1018 let server = MatrixMockServer::new().await;
1019 let client = server.client_builder().build().await;
1020 let space_service = SpaceService::new(client.clone()).await;
1021
1022 let parent_space_id = room_id!("!parent_space:example.org");
1023 let child_space_id_1 = room_id!("!1:example.org");
1024
1025 server
1026 .mock_get_hierarchy()
1027 .ok_with_room_ids(vec![child_space_id_1])
1028 .expect(2)
1029 .mount()
1030 .await;
1031
1032 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
1033
1034 room_list.paginate().await.unwrap();
1035
1036 assert_eq!(room_list.rooms().await.len(), 1);
1038
1039 room_list.reset().await;
1041
1042 assert_eq!(room_list.rooms().await.len(), 0);
1044 assert_matches!(
1045 room_list.pagination_state(),
1046 SpaceRoomListPaginationState::Idle { end_reached: false }
1047 );
1048
1049 room_list.paginate().await.unwrap();
1051 assert_eq!(room_list.rooms().await.len(), 1);
1052 }
1053
1054 fn make_space_room(
1055 room_id: OwnedRoomId,
1056 order: Option<&str>,
1057 origin_server_ts: Option<u32>,
1058 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
1059 ) -> SpaceRoom {
1060 if let Some(origin_server_ts) = origin_server_ts {
1061 children_state.insert(
1062 room_id.clone(),
1063 hierarchy_space_child_event(&room_id, order, origin_server_ts),
1064 );
1065 }
1066 SpaceRoom {
1067 room_id,
1068 canonical_alias: None,
1069 name: Some("New room name".to_owned()),
1070 display_name: "Empty room".to_owned(),
1071 topic: None,
1072 avatar_url: None,
1073 room_type: None,
1074 num_joined_members: 0,
1075 join_rule: None,
1076 world_readable: None,
1077 guest_can_join: false,
1078 is_direct: None,
1079 children_count: 0,
1080 state: None,
1081 heroes: None,
1082 via: vec![],
1083 suggested: false,
1084 is_dm: None,
1085 }
1086 }
1087
1088 fn hierarchy_space_child_event(
1089 room_id: &RoomId,
1090 order: Option<&str>,
1091 origin_server_ts: u32,
1092 ) -> HierarchySpaceChildEvent {
1093 let mut json = json!({
1094 "content": {
1095 "via": []
1096 },
1097 "origin_server_ts": origin_server_ts,
1098 "sender": "@bob:a.b",
1099 "state_key": room_id.to_string(),
1100 "type": "m.space.child"
1101 });
1102
1103 if let Some(order) = order {
1104 json["content"]["order"] = json!(order);
1105 }
1106
1107 from_value::<HierarchySpaceChildEvent>(json).unwrap()
1108 }
1109}