matrix_sdk_ui/spaces/
leave.rs1use matrix_sdk::{Client, RoomState, room::RoomMemberRole};
16use ruma::{Int, OwnedRoomId};
17
18use crate::spaces::{Error, SpaceRoom};
19
20#[derive(Debug, Clone)]
23pub struct LeaveSpaceRoom {
24 pub space_room: SpaceRoom,
26 pub is_last_admin: bool,
29}
30
31pub struct LeaveSpaceHandle {
39 client: Client,
40 rooms: Vec<LeaveSpaceRoom>,
41}
42
43impl LeaveSpaceHandle {
44 pub(crate) async fn new(client: Client, room_ids: Vec<OwnedRoomId>) -> Self {
45 let mut rooms = Vec::new();
46
47 for room_id in &room_ids {
48 let Some(room) = client.get_room(room_id) else {
49 continue;
50 };
51
52 if room.state() != RoomState::Joined {
53 continue;
54 }
55
56 let users_to_power_levels = room.users_with_power_levels().await;
57
58 let is_last_admin = users_to_power_levels
59 .iter()
60 .filter(|(_, power_level)| {
61 let Some(power_level) = Int::new(**power_level) else {
62 return false;
63 };
64
65 RoomMemberRole::suggested_role_for_power_level(power_level.into())
66 == RoomMemberRole::Administrator
67 })
68 .map(|p| p.0)
69 .collect::<Vec<_>>()
70 == vec![room.own_user_id()];
71
72 rooms.push(LeaveSpaceRoom {
73 space_room: SpaceRoom::new_from_known(&room, 0),
74 is_last_admin,
75 });
76 }
77
78 Self { client, rooms }
79 }
80
81 pub fn rooms(&self) -> &Vec<LeaveSpaceRoom> {
84 &self.rooms
85 }
86
87 pub async fn leave(&self, filter: impl FnMut(&LeaveSpaceRoom) -> bool) -> Result<(), Error> {
89 for room in self.rooms.clone().into_iter().filter(filter) {
90 if let Some(room) = self.client.get_room(&room.space_room.room_id) {
91 room.leave().await.map_err(Error::LeaveSpace)?;
92 } else {
93 return Err(Error::RoomNotFound(room.space_room.room_id));
94 }
95 }
96
97 Ok(())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use std::collections::BTreeMap;
104
105 use matrix_sdk::test_utils::mocks::MatrixMockServer;
106 use matrix_sdk_test::{
107 InvitedRoomBuilder, JoinedRoomBuilder, LeftRoomBuilder, async_test,
108 event_factory::EventFactory,
109 };
110 use ruma::{RoomVersionId, owned_user_id, room_id};
111
112 use crate::spaces::SpaceService;
113
114 #[async_test]
115 async fn test_leaving() {
116 let server = MatrixMockServer::new().await;
117 let client = server.client_builder().build().await;
118 let user_id = client.user_id().unwrap();
119 let space_service = SpaceService::new(client.clone());
120 let factory = EventFactory::new().sender(user_id);
121
122 server.mock_room_state_encryption().plain().mount().await;
123
124 let parent_space_id = room_id!("!parent_space:example.org");
127 let child_space_id_1 = room_id!("!child_space_1:example.org");
128 let child_space_id_2 = room_id!("!child_space_2:example.org");
129 let left_room_id = room_id!("!left_room:example.org");
130 let invited_room_id = room_id!("!invited_room:example.org");
131
132 let mut power_levels = BTreeMap::from([
133 (user_id.to_owned(), 100.into()),
134 (owned_user_id!("@some_non_admin:a.b"), 50.into()),
135 ]);
136
137 server
138 .sync_room(
139 &client,
140 JoinedRoomBuilder::new(child_space_id_1)
141 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
142 .add_state_event(
143 factory
144 .space_parent(parent_space_id.to_owned(), child_space_id_1.to_owned()),
145 )
146 .add_state_event(
147 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
148 ),
149 )
150 .await;
151
152 power_levels.insert(owned_user_id!("@some_other_admin:a.b"), 100.into());
153
154 server
155 .sync_room(
156 &client,
157 JoinedRoomBuilder::new(child_space_id_2)
158 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
159 .add_state_event(
160 factory
161 .space_parent(parent_space_id.to_owned(), child_space_id_2.to_owned()),
162 )
163 .add_state_event(
164 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
165 ),
166 )
167 .await;
168
169 server.sync_room(&client, LeftRoomBuilder::new(invited_room_id)).await;
170 server.sync_room(&client, InvitedRoomBuilder::new(invited_room_id)).await;
171
172 server
173 .sync_room(
174 &client,
175 JoinedRoomBuilder::new(parent_space_id)
176 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
177 .add_state_event(
178 factory
179 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned()),
180 )
181 .add_state_event(
182 factory
183 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned()),
184 )
185 .add_state_event(
186 factory.space_child(parent_space_id.to_owned(), left_room_id.to_owned()),
187 )
188 .add_state_event(
189 factory.space_child(parent_space_id.to_owned(), invited_room_id.to_owned()),
190 ),
191 )
192 .await;
193
194 server.mock_room_leave().ok(room_id!("!does_not_matter:a:b")).mount().await;
195
196 assert!(!space_service.joined_spaces().await.is_empty());
197
198 let handle = space_service.leave_space(parent_space_id).await.unwrap();
199
200 let rooms = handle.rooms();
201
202 let child_room_1 = &rooms[0];
203 assert!(child_room_1.is_last_admin);
204
205 let child_room_2 = &rooms[1];
206 assert!(!child_room_2.is_last_admin);
207
208 let room_ids = rooms.iter().map(|r| r.space_room.room_id.clone()).collect::<Vec<_>>();
209 assert_eq!(room_ids, vec![child_space_id_1, child_space_id_2, parent_space_id]);
210
211 handle.leave(|room| room_ids.contains(&room.space_room.room_id)).await.unwrap();
212
213 assert!(space_service.joined_spaces().await.is_empty());
214 }
215}