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<_>) =
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};
411
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 strass::assert_let;
427 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
428 use wiremock::ResponseTemplate;
429
430 use crate::spaces::{
431 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
432 };
433
434 #[async_test]
435 async fn test_room_list_pagination() {
436 let server = MatrixMockServer::new().await;
437 let client = server.client_builder().build().await;
438 let user_id = client.user_id().unwrap();
439 let space_service = SpaceService::new(client.clone()).await;
440 let factory = EventFactory::new();
441
442 server.mock_room_state_encryption().plain().mount().await;
443
444 let parent_space_id = room_id!("!parent_space:example.org");
445 let child_space_id_1 = room_id!("!1:example.org");
446 let child_space_id_2 = room_id!("!2:example.org");
447
448 server
449 .sync_room(
450 &client,
451 JoinedRoomBuilder::new(parent_space_id)
452 .add_state_event(
453 factory
454 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
455 .sender(user_id),
456 )
457 .add_state_event(
458 factory
459 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
460 .sender(user_id),
461 ),
462 )
463 .await;
464
465 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
466
467 assert_let!(Some(parent_space) = room_list.space());
470 assert_eq!(parent_space.children_count, 2);
471
472 assert_matches!(
474 room_list.pagination_state(),
475 SpaceRoomListPaginationState::Idle { end_reached: false }
476 );
477
478 assert_eq!(room_list.rooms().await, vec![]);
480
481 let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
484 pin_mut!(pagination_state_subscriber);
485 assert_pending!(pagination_state_subscriber);
486
487 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
488 pin_mut!(rooms_subscriber);
489 assert_pending!(rooms_subscriber);
490
491 server
493 .mock_get_hierarchy()
494 .ok_with_room_ids_and_children_state(
495 vec![child_space_id_1, child_space_id_2],
496 vec![(room_id!("!child:example.org"), vec![])],
497 )
498 .mount()
499 .await;
500
501 room_list.paginate().await.unwrap();
502
503 assert_next_matches!(
505 pagination_state_subscriber,
506 SpaceRoomListPaginationState::Idle { end_reached: true }
507 );
508
509 assert_next_eq!(
511 rooms_subscriber,
512 vec![
513 VectorDiff::PushBack {
514 value: SpaceRoom::new_from_summary(
515 &RoomSummary::new(
516 child_space_id_1.to_owned(),
517 JoinRuleSummary::Public,
518 false,
519 uint!(1),
520 false,
521 ),
522 None,
523 1,
524 vec![],
525 false,
526 )
527 .await
528 },
529 VectorDiff::PushBack {
530 value: SpaceRoom::new_from_summary(
531 &RoomSummary::new(
532 child_space_id_2.to_owned(),
533 JoinRuleSummary::Public,
534 false,
535 uint!(1),
536 false,
537 ),
538 None,
539 1,
540 vec![],
541 false,
542 )
543 .await,
544 }
545 ]
546 );
547 }
548
549 #[async_test]
550 async fn test_room_state_updates() {
551 let server = MatrixMockServer::new().await;
552 let client = server.client_builder().build().await;
553 let space_service = SpaceService::new(client.clone()).await;
554
555 let parent_space_id = room_id!("!parent_space:example.org");
556 let child_room_id_1 = room_id!("!1:example.org");
557 let child_room_id_2 = room_id!("!2:example.org");
558
559 server
560 .mock_get_hierarchy()
561 .ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
562 .mount()
563 .await;
564
565 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
566
567 room_list.paginate().await.unwrap();
568
569 assert_eq!(room_list.rooms().await.first().unwrap().room_id, child_room_id_1);
571 assert_eq!(room_list.rooms().await.last().unwrap().room_id, child_room_id_2);
572
573 assert_eq!(room_list.rooms().await.first().unwrap().state, None);
575 assert_eq!(room_list.rooms().await.last().unwrap().state, None);
576
577 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
578 pin_mut!(rooms_subscriber);
579 assert_pending!(rooms_subscriber);
580
581 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
583
584 assert_ready!(rooms_subscriber);
586 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Joined));
587 assert_eq!(room_list.rooms().await.last().unwrap().state, None);
588
589 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
591 assert_ready!(rooms_subscriber);
592 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Joined));
593 assert_eq!(room_list.rooms().await.last().unwrap().state, Some(RoomState::Joined));
594
595 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
597 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
598 assert_ready!(rooms_subscriber);
599 assert_eq!(room_list.rooms().await.first().unwrap().state, Some(RoomState::Left));
600 assert_eq!(room_list.rooms().await.last().unwrap().state, Some(RoomState::Left));
601 }
602
603 #[async_test]
604 async fn test_parent_space_updates() {
605 let server = MatrixMockServer::new().await;
606 let client = server.client_builder().build().await;
607 let user_id = client.user_id().unwrap();
608 let space_service = SpaceService::new(client.clone()).await;
609 let factory = EventFactory::new();
610
611 server.mock_room_state_encryption().plain().mount().await;
612
613 let parent_space_id = room_id!("!parent_space:example.org");
614 let child_space_id_1 = room_id!("!1:example.org");
615 let child_space_id_2 = room_id!("!2:example.org");
616
617 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
619 assert!(room_list.space().is_none());
620
621 let parent_space_subscriber = room_list.subscribe_to_space_updates();
622 pin_mut!(parent_space_subscriber);
623 assert_pending!(parent_space_subscriber);
624
625 server
626 .mock_get_hierarchy()
627 .ok_with_room_ids_and_children_state(
628 vec![parent_space_id, child_space_id_1, child_space_id_2],
629 vec![(
630 room_id!("!child:example.org"),
631 vec![server_name!("matrix-client.example.org")],
632 )],
633 )
634 .mount()
635 .await;
636
637 room_list.paginate().await.unwrap();
639 assert_let!(Some(parent_space) = room_list.space());
640 assert_eq!(parent_space.room_id, parent_space_id);
641
642 assert_next_eq!(parent_space_subscriber, Some(parent_space));
644
645 server
648 .sync_room(
649 &client,
650 JoinedRoomBuilder::new(parent_space_id)
651 .add_state_event(
652 factory
653 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
654 .sender(user_id),
655 )
656 .add_state_event(
657 factory
658 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
659 .sender(user_id),
660 ),
661 )
662 .await;
663
664 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
665
666 assert_let!(Some(parent_space) = room_list.space());
669 assert_eq!(parent_space.children_count, 2);
670 }
671
672 #[async_test]
673 async fn test_parent_space_room_info_update() {
674 let server = MatrixMockServer::new().await;
675 let client = server.client_builder().build().await;
676 let user_id = client.user_id().unwrap();
677 let space_service = SpaceService::new(client.clone()).await;
678 let factory = EventFactory::new();
679
680 server.mock_room_state_encryption().plain().mount().await;
681
682 let parent_space_id = room_id!("!parent_space:example.org");
683
684 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
685
686 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
687 assert_let!(Some(parent_space) = room_list.space());
688
689 let parent_space_subscriber = room_list.subscribe_to_space_updates();
691 pin_mut!(parent_space_subscriber);
692 assert_pending!(parent_space_subscriber);
693
694 server
696 .sync_room(
697 &client,
698 JoinedRoomBuilder::new(parent_space_id)
699 .add_state_event(factory.room_topic("New room topic").sender(user_id))
700 .add_state_event(factory.room_name("New room name").sender(user_id)),
701 )
702 .await;
703
704 let mut updated_parent_space = parent_space.clone();
705 updated_parent_space.topic = Some("New room topic".to_owned());
706 updated_parent_space.name = Some("New room name".to_owned());
707 updated_parent_space.display_name = "New room name".to_owned();
708
709 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
711 }
712
713 #[async_test]
714 async fn test_via_retrieval() {
715 let server = MatrixMockServer::new().await;
716 let client = server.client_builder().build().await;
717 let space_service = SpaceService::new(client.clone()).await;
718
719 server.mock_room_state_encryption().plain().mount().await;
720
721 let parent_space_id = room_id!("!parent_space:example.org");
722 let child_space_id_1 = room_id!("!1:example.org");
723 let child_space_id_2 = room_id!("!2:example.org");
724
725 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
726
727 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
728 pin_mut!(rooms_subscriber);
729
730 server
732 .mock_get_hierarchy()
733 .ok_with_room_ids_and_children_state(
734 vec![parent_space_id, child_space_id_1, child_space_id_2],
735 vec![
736 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
737 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
738 ],
739 )
740 .mount()
741 .await;
742
743 room_list.paginate().await.unwrap();
744
745 assert_next_eq!(
747 rooms_subscriber,
748 vec![
749 VectorDiff::PushBack {
750 value: SpaceRoom::new_from_summary(
751 &RoomSummary::new(
752 child_space_id_1.to_owned(),
753 JoinRuleSummary::Public,
754 false,
755 uint!(1),
756 false,
757 ),
758 None,
759 2,
760 vec![owned_server_name!("matrix-client.example.org")],
761 false,
762 )
763 .await
764 },
765 VectorDiff::PushBack {
766 value: SpaceRoom::new_from_summary(
767 &RoomSummary::new(
768 child_space_id_2.to_owned(),
769 JoinRuleSummary::Public,
770 false,
771 uint!(1),
772 false,
773 ),
774 None,
775 2,
776 vec![owned_server_name!("other-matrix-client.example.org")],
777 false,
778 )
779 .await,
780 }
781 ]
782 );
783 }
784
785 #[async_test]
786 async fn test_suggested_field() {
787 let server = MatrixMockServer::new().await;
788 let client = server.client_builder().build().await;
789 let space_service = SpaceService::new(client.clone()).await;
790
791 server.mock_room_state_encryption().plain().mount().await;
792
793 let parent_space_id = room_id!("!parent_space:example.org");
794 let suggested_child = room_id!("!suggested:example.org");
795 let not_suggested_child = room_id!("!not_suggested:example.org");
796
797 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
798
799 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates().await;
800 pin_mut!(rooms_subscriber);
801
802 let children_state = vec![
805 json!({
806 "type": "m.space.child",
807 "state_key": suggested_child,
808 "content": { "via": ["example.org"], "suggested": true },
809 "sender": "@admin:example.org",
810 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
811 }),
812 json!({
813 "type": "m.space.child",
814 "state_key": not_suggested_child,
815 "content": { "via": ["example.org"], "suggested": false },
816 "sender": "@admin:example.org",
817 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
818 }),
819 ];
820
821 server
822 .mock_get_hierarchy()
823 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
824 "rooms": [
825 {
826 "room_id": parent_space_id,
827 "num_joined_members": 1,
828 "world_readable": false,
829 "guest_can_join": false,
830 "children_state": children_state
831 },
832 {
833 "room_id": suggested_child,
834 "num_joined_members": 5,
835 "world_readable": false,
836 "guest_can_join": false,
837 "children_state": []
838 },
839 {
840 "room_id": not_suggested_child,
841 "num_joined_members": 3,
842 "world_readable": false,
843 "guest_can_join": false,
844 "children_state": []
845 },
846 ]
847 })))
848 .mount()
849 .await;
850
851 room_list.paginate().await.unwrap();
852
853 let rooms_diff = assert_next_matches!(rooms_subscriber, diff => diff);
854 assert_eq!(rooms_diff.len(), 2);
855
856 let mut rooms_by_id = HashMap::new();
858 for diff in rooms_diff {
859 if let VectorDiff::PushBack { value } = diff {
860 rooms_by_id.insert(value.room_id.clone(), value);
861 } else {
862 panic!("Expected PushBack, got {:?}", diff);
863 }
864 }
865
866 let suggested_room = rooms_by_id.get(suggested_child).expect("suggested child not found");
868 assert!(
869 suggested_room.suggested,
870 "Room with 'suggested: true' should have suggested == true"
871 );
872
873 let not_suggested_room =
875 rooms_by_id.get(not_suggested_child).expect("not-suggested child not found");
876 assert!(
877 !not_suggested_room.suggested,
878 "Room with 'suggested: false' should have suggested == false"
879 );
880 }
881
882 #[async_test]
883 async fn test_room_list_sorting() {
884 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
885
886 assert_eq!(
889 SpaceRoomList::compare_rooms(
890 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
891 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
892 &children_state,
893 ),
894 Ordering::Less
895 );
896
897 assert_eq!(
898 SpaceRoomList::compare_rooms(
899 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
900 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
901 &children_state,
902 ),
903 Ordering::Greater
904 );
905
906 assert_eq!(
909 SpaceRoomList::compare_rooms(
910 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
911 &make_space_room(
912 owned_room_id!("!Marțolea:a.b"),
913 None,
914 Some(0),
915 &mut children_state
916 ),
917 &children_state,
918 ),
919 Ordering::Greater
920 );
921
922 assert_eq!(
924 SpaceRoomList::compare_rooms(
925 &make_space_room(
926 owned_room_id!("!Joiana:a.b"),
927 Some("last"),
928 Some(123),
929 &mut children_state
930 ),
931 &make_space_room(
932 owned_room_id!("!Mioara:a.b"),
933 Some("first"),
934 Some(234),
935 &mut children_state
936 ),
937 &children_state,
938 ),
939 Ordering::Greater
940 );
941
942 assert_eq!(
944 SpaceRoomList::compare_rooms(
945 &make_space_room(
946 owned_room_id!("!Joiana:a.b"),
947 Some("Same pasture"),
948 Some(1),
949 &mut children_state
950 ),
951 &make_space_room(
952 owned_room_id!("!Mioara:a.b"),
953 Some("Same pasture"),
954 Some(0),
955 &mut children_state
956 ),
957 &children_state,
958 ),
959 Ordering::Greater
960 );
961
962 assert_eq!(
965 SpaceRoomList::compare_rooms(
966 &make_space_room(
967 owned_room_id!("!Joiana:a.b"),
968 Some("same_pasture"),
969 Some(0),
970 &mut children_state
971 ),
972 &make_space_room(
973 owned_room_id!("!Mioara:a.b"),
974 Some("same_pasture"),
975 Some(0),
976 &mut children_state
977 ),
978 &children_state,
979 ),
980 Ordering::Less
981 );
982
983 assert_eq!(
986 SpaceRoomList::compare_rooms(
987 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
988 &make_space_room(
989 owned_room_id!("!Sâmbotina:a.b"),
990 None,
991 Some(0),
992 &mut children_state
993 ),
994 &children_state,
995 ),
996 Ordering::Greater
997 );
998
999 assert_eq!(
1002 SpaceRoomList::compare_rooms(
1003 &make_space_room(
1004 owned_room_id!("!Sâmbotina:a.b"),
1005 None,
1006 Some(1),
1007 &mut children_state
1008 ),
1009 &make_space_room(
1010 owned_room_id!("!Dumana:a.b"),
1011 Some("Some pasture"),
1012 Some(1),
1013 &mut children_state
1014 ),
1015 &children_state,
1016 ),
1017 Ordering::Greater
1018 );
1019 }
1020
1021 #[async_test]
1022 async fn test_reset() {
1023 let server = MatrixMockServer::new().await;
1024 let client = server.client_builder().build().await;
1025 let space_service = SpaceService::new(client.clone()).await;
1026
1027 let parent_space_id = room_id!("!parent_space:example.org");
1028 let child_space_id_1 = room_id!("!1:example.org");
1029
1030 server
1031 .mock_get_hierarchy()
1032 .ok_with_room_ids(vec![child_space_id_1])
1033 .expect(2)
1034 .mount()
1035 .await;
1036
1037 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
1038
1039 room_list.paginate().await.unwrap();
1040
1041 assert_eq!(room_list.rooms().await.len(), 1);
1043
1044 room_list.reset().await;
1046
1047 assert_eq!(room_list.rooms().await.len(), 0);
1049 assert_matches!(
1050 room_list.pagination_state(),
1051 SpaceRoomListPaginationState::Idle { end_reached: false }
1052 );
1053
1054 room_list.paginate().await.unwrap();
1056 assert_eq!(room_list.rooms().await.len(), 1);
1057 }
1058
1059 fn make_space_room(
1060 room_id: OwnedRoomId,
1061 order: Option<&str>,
1062 origin_server_ts: Option<u32>,
1063 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
1064 ) -> SpaceRoom {
1065 if let Some(origin_server_ts) = origin_server_ts {
1066 children_state.insert(
1067 room_id.clone(),
1068 hierarchy_space_child_event(&room_id, order, origin_server_ts),
1069 );
1070 }
1071 SpaceRoom {
1072 room_id,
1073 canonical_alias: None,
1074 name: Some("New room name".to_owned()),
1075 display_name: "Empty room".to_owned(),
1076 topic: None,
1077 avatar_url: None,
1078 room_type: None,
1079 num_joined_members: 0,
1080 join_rule: None,
1081 world_readable: None,
1082 guest_can_join: false,
1083 is_direct: None,
1084 children_count: 0,
1085 state: None,
1086 heroes: None,
1087 via: vec![],
1088 suggested: false,
1089 is_dm: None,
1090 }
1091 }
1092
1093 fn hierarchy_space_child_event(
1094 room_id: &RoomId,
1095 order: Option<&str>,
1096 origin_server_ts: u32,
1097 ) -> HierarchySpaceChildEvent {
1098 let mut json = json!({
1099 "content": {
1100 "via": []
1101 },
1102 "origin_server_ts": origin_server_ts,
1103 "sender": "@bob:a.b",
1104 "state_key": room_id.to_string(),
1105 "type": "m.space.child"
1106 });
1107
1108 if let Some(order) = order {
1109 json["content"]["order"] = json!(order);
1110 }
1111
1112 from_value::<HierarchySpaceChildEvent>(json).unwrap()
1113 }
1114}