matrix_sdk_ui/room_list_service/sorters/
recency.rs1use std::cmp::Ordering;
16
17use super::{RoomListItem, Sorter};
18
19fn cmp<F>(scores: F, left: &RoomListItem, right: &RoomListItem) -> Ordering
20where
21 F: Fn(&RoomListItem, &RoomListItem) -> (Option<Score>, Option<Score>),
22{
23 let (a, b) = scores(left, right);
24 cmp_impl(a, b)
25}
26
27fn cmp_impl(a: Option<Score>, b: Option<Score>) -> Ordering {
28 match (a, b) {
29 (Some(left), Some(right)) => left.cmp(&right).reverse(),
30 (Some(_), None) => Ordering::Less,
31 (None, Some(_)) => Ordering::Greater,
32 (None, None) => Ordering::Equal,
33 }
34}
35
36pub fn new_sorter() -> impl Sorter {
44 |left, right| -> Ordering { cmp(extract_scores, left, right) }
45}
46
47type Score = u64;
51
52fn extract_scores(left: &RoomListItem, right: &RoomListItem) -> (Option<Score>, Option<Score>) {
64 if left.cached_latest_event_timestamp.is_some() || right.cached_latest_event_timestamp.is_some()
92 {
93 (
94 left.cached_latest_event_timestamp.map(|ts| ts.get().into()),
95 right.cached_latest_event_timestamp.map(|ts| ts.get().into()),
96 )
97 }
98 else {
100 (left.cached_recency_stamp.map(Into::into), right.cached_recency_stamp.map(Into::into))
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use matrix_sdk::{
107 RoomRecencyStamp,
108 latest_events::{LatestEventValue, LocalLatestEventValue, RemoteLatestEventValue},
109 store::SerializableEventContent,
110 test_utils::mocks::MatrixMockServer,
111 };
112 use matrix_sdk_base::RoomInfoNotableUpdateReasons;
113 use matrix_sdk_test::async_test;
114 use proptest::prelude::*;
115 use ruma::{
116 MilliSecondsSinceUnixEpoch,
117 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
118 room_id,
119 serde::Raw,
120 };
121 use serde_json::json;
122
123 use super::{super::super::filters::new_rooms, *};
124
125 fn none() -> LatestEventValue {
126 LatestEventValue::None
127 }
128
129 fn remote(origin_server_ts: u64) -> LatestEventValue {
130 LatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
131 Raw::from_json_string(
132 json!({
133 "content": RoomMessageEventContent::text_plain("raclette"),
134 "type": "m.room.message",
135 "event_id": "$ev0",
136 "room_id": "!r0",
137 "origin_server_ts": origin_server_ts,
138 "sender": "@mnt_io:matrix.org",
139 })
140 .to_string(),
141 )
142 .unwrap(),
143 ))
144 }
145
146 fn local_is_sending(origin_server_ts: u32) -> LatestEventValue {
147 LatestEventValue::LocalIsSending(LocalLatestEventValue {
148 timestamp: MilliSecondsSinceUnixEpoch(origin_server_ts.into()),
149 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
150 RoomMessageEventContent::text_plain("raclette"),
151 ))
152 .unwrap(),
153 })
154 }
155
156 fn local_cannot_be_sent(origin_server_ts: u32) -> LatestEventValue {
157 LatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
158 timestamp: MilliSecondsSinceUnixEpoch(origin_server_ts.into()),
159 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
160 RoomMessageEventContent::text_plain("raclette"),
161 ))
162 .unwrap(),
163 })
164 }
165
166 async fn set_latest_event_value(room: &mut RoomListItem, latest_event_value: LatestEventValue) {
167 room.update_room_info(|mut info| {
168 info.set_latest_event(latest_event_value);
169 (info, RoomInfoNotableUpdateReasons::LATEST_EVENT)
170 })
171 .await;
172 room.refresh_cached_data();
173 }
174
175 async fn set_recency_stamp(room: &mut RoomListItem, recency_stamp: RoomRecencyStamp) {
176 room.update_room_info(|mut info| {
177 info.update_recency_stamp(recency_stamp);
178 (info, RoomInfoNotableUpdateReasons::RECENCY_STAMP)
179 })
180 .await;
181 room.refresh_cached_data();
182 }
183
184 #[async_test]
185 async fn test_extract_scores_with_none() {
186 let server = MatrixMockServer::new().await;
187 let client = server.client_builder().build().await;
188 let [mut room_a, mut room_b] =
189 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
190
191 set_recency_stamp(&mut room_a, 1.into()).await;
192 set_recency_stamp(&mut room_b, 2.into()).await;
193
194 {
198 set_latest_event_value(&mut room_a, none()).await;
199 set_latest_event_value(&mut room_b, none()).await;
200
201 assert_eq!(extract_scores(&room_a, &room_b), (Some(1), Some(2)));
202 }
203
204 {
208 set_latest_event_value(&mut room_a, none()).await;
209 set_latest_event_value(&mut room_b, remote(3)).await;
210
211 assert_eq!(extract_scores(&room_a, &room_b), (None, Some(3)));
212 }
213
214 {
218 set_latest_event_value(&mut room_a, remote(3)).await;
219 set_latest_event_value(&mut room_b, none()).await;
220
221 assert_eq!(extract_scores(&room_a, &room_b), (Some(3), None));
222 }
223 }
224
225 #[async_test]
226 async fn test_extract_scores_with_remote_or_local() {
227 let server = MatrixMockServer::new().await;
228 let client = server.client_builder().build().await;
229 let [mut room_a, mut room_b] =
230 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
231
232 set_recency_stamp(&mut room_a, 1.into()).await;
233 set_recency_stamp(&mut room_b, 2.into()).await;
234
235 {
239 for latest_event_value_a in [remote(3), local_is_sending(3), local_cannot_be_sent(3)] {
240 for latest_event_value_b in
241 [remote(4), local_is_sending(4), local_cannot_be_sent(4)]
242 {
243 set_latest_event_value(&mut room_a, latest_event_value_a.clone()).await;
244 set_latest_event_value(&mut room_b, latest_event_value_b).await;
245
246 assert_eq!(extract_scores(&room_a, &room_b), (Some(3), Some(4)));
247 }
248 }
249 }
250 }
251
252 #[async_test]
253 async fn test_with_two_scores() {
254 let server = MatrixMockServer::new().await;
255 let client = server.client_builder().build().await;
256 let [room_a, room_b] =
257 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
258
259 {
261 assert_eq!(
264 cmp(|_left, _right| (Some(1), Some(2)), &room_a, &room_b),
265 Ordering::Greater
266 );
267 }
268
269 {
271 assert_eq!(cmp(|_left, _right| (Some(2), Some(1)), &room_a, &room_b), Ordering::Less);
274 }
275
276 {
278 assert_eq!(cmp(|_left, _right| (Some(1), Some(1)), &room_a, &room_b), Ordering::Equal);
279 }
280 }
281
282 #[async_test]
283 async fn test_with_one_score() {
284 let server = MatrixMockServer::new().await;
285 let client = server.client_builder().build().await;
286 let [room_a, room_b] =
287 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
288
289 {
292 assert_eq!(cmp(|_left, _right| (Some(1), None), &room_a, &room_b), Ordering::Less);
293 }
294
295 {
298 assert_eq!(cmp(|_left, _right| (None, Some(1)), &room_a, &room_b), Ordering::Greater);
299 }
300 }
301
302 #[async_test]
303 async fn test_with_zero_score() {
304 let server = MatrixMockServer::new().await;
305 let client = server.client_builder().build().await;
306 let [room_a, room_b] =
307 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
308
309 {
311 assert_eq!(cmp(|_left, _right| (None, None), &room_a, &room_b), Ordering::Equal);
312 }
313 }
314
315 prop_compose! {
316 fn arb_score()(score in any::<Option<u64>>()) -> Option<Score> {
317 score
318 }
319 }
320
321 proptest! {
327 #![proptest_config(ProptestConfig::with_cases(10_000))]
330 #[test]
331 fn test_cmp_reflexive(score in arb_score()) {
332 let result = cmp_impl(score, score);
333 assert!(result == Ordering::Less || result == Ordering::Equal);
334 }
335
336 #[test]
338 fn test_cmp_transitive(a in arb_score(), b in arb_score(), c in arb_score()) {
339 use Ordering::*;
340
341 let a_b_comparison = cmp_impl(a, b);
342 let b_c_comparison = cmp_impl(b, c);
343 let a_c_comparison = cmp_impl(a, c);
344
345 if matches!(a_b_comparison, Less | Equal) && matches!(b_c_comparison, Less | Equal) {
346 assert!(a_c_comparison == Less || a_c_comparison == Equal);
347 }
348 }
349
350 #[test]
352 fn test_cmp_antisymetric(a in arb_score(), b in arb_score()) {
353 use Ordering::*;
354
355 let a_b_comparison = cmp_impl(a, b);
356 let b_a_comparison = cmp_impl(b, a);
357
358 eprintln!("a.cmp(b) = {a_b_comparison:?}");
359 eprintln!("b.cmp(a) = {b_a_comparison:?}");
360
361 if matches!(a_b_comparison, Less | Equal) && matches!(b_a_comparison, Less | Equal) {
362 assert!(a == b);
363 }
364 }
365
366 #[test]
368 fn test_cmp_reverse(a in arb_score(), b in arb_score()) {
369 use Ordering::*;
370
371 let a_b_comparison = cmp_impl(a, b);
372 let b_a_comparison = cmp_impl(b, a);
373
374 if a_b_comparison == Less {
375 assert_eq!(b_a_comparison, Greater);
376 } else if a_b_comparison == Greater {
377 assert_eq!(b_a_comparison, Less);
378 } else {
379 assert_eq!(a_b_comparison, Equal);
380 assert_eq!(b_a_comparison, Equal);
381 }
382 }
383 }
384}