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::logged_in_client_with_server,
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 fn set_latest_event_value(room: &mut RoomListItem, latest_event_value: LatestEventValue) {
167 let mut room_info = room.clone_info();
168 room_info.set_latest_event(latest_event_value);
169 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::LATEST_EVENT);
170 room.refresh_cached_data();
171 }
172
173 fn set_recency_stamp(room: &mut RoomListItem, recency_stamp: RoomRecencyStamp) {
174 let mut room_info = room.clone_info();
175 room_info.update_recency_stamp(recency_stamp);
176 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::RECENCY_STAMP);
177 room.refresh_cached_data();
178 }
179
180 #[async_test]
181 async fn test_extract_scores_with_none() {
182 let (client, server) = logged_in_client_with_server().await;
183 let [mut room_a, mut room_b] =
184 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
185
186 set_recency_stamp(&mut room_a, 1.into());
187 set_recency_stamp(&mut room_b, 2.into());
188
189 {
193 set_latest_event_value(&mut room_a, none());
194 set_latest_event_value(&mut room_b, none());
195
196 assert_eq!(extract_scores(&room_a, &room_b), (Some(1), Some(2)));
197 }
198
199 {
203 set_latest_event_value(&mut room_a, none());
204 set_latest_event_value(&mut room_b, remote(3));
205
206 assert_eq!(extract_scores(&room_a, &room_b), (None, Some(3)));
207 }
208
209 {
213 set_latest_event_value(&mut room_a, remote(3));
214 set_latest_event_value(&mut room_b, none());
215
216 assert_eq!(extract_scores(&room_a, &room_b), (Some(3), None));
217 }
218 }
219
220 #[async_test]
221 async fn test_extract_scores_with_remote_or_local() {
222 let (client, server) = logged_in_client_with_server().await;
223 let [mut room_a, mut room_b] =
224 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
225
226 set_recency_stamp(&mut room_a, 1.into());
227 set_recency_stamp(&mut room_b, 2.into());
228
229 {
233 for latest_event_value_a in [remote(3), local_is_sending(3), local_cannot_be_sent(3)] {
234 for latest_event_value_b in
235 [remote(4), local_is_sending(4), local_cannot_be_sent(4)]
236 {
237 set_latest_event_value(&mut room_a, latest_event_value_a.clone());
238 set_latest_event_value(&mut room_b, latest_event_value_b);
239
240 assert_eq!(extract_scores(&room_a, &room_b), (Some(3), Some(4)));
241 }
242 }
243 }
244 }
245
246 #[async_test]
247 async fn test_with_two_scores() {
248 let (client, server) = logged_in_client_with_server().await;
249 let [room_a, room_b] =
250 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
251
252 {
254 assert_eq!(
257 cmp(|_left, _right| (Some(1), Some(2)), &room_a, &room_b),
258 Ordering::Greater
259 );
260 }
261
262 {
264 assert_eq!(cmp(|_left, _right| (Some(2), Some(1)), &room_a, &room_b), Ordering::Less);
267 }
268
269 {
271 assert_eq!(cmp(|_left, _right| (Some(1), Some(1)), &room_a, &room_b), Ordering::Equal);
272 }
273 }
274
275 #[async_test]
276 async fn test_with_one_score() {
277 let (client, server) = logged_in_client_with_server().await;
278 let [room_a, room_b] =
279 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
280
281 {
284 assert_eq!(cmp(|_left, _right| (Some(1), None), &room_a, &room_b), Ordering::Less);
285 }
286
287 {
290 assert_eq!(cmp(|_left, _right| (None, Some(1)), &room_a, &room_b), Ordering::Greater);
291 }
292 }
293
294 #[async_test]
295 async fn test_with_zero_score() {
296 let (client, server) = logged_in_client_with_server().await;
297 let [room_a, room_b] =
298 new_rooms([room_id!("!a:b.c"), room_id!("!d:e.f")], &client, &server).await;
299
300 {
302 assert_eq!(cmp(|_left, _right| (None, None), &room_a, &room_b), Ordering::Equal);
303 }
304 }
305
306 prop_compose! {
307 fn arb_score()(score in any::<Option<u64>>()) -> Option<Score> {
308 score
309 }
310 }
311
312 proptest! {
318 #![proptest_config(ProptestConfig::with_cases(10_000))]
321 #[test]
322 fn test_cmp_reflexive(score in arb_score()) {
323 let result = cmp_impl(score, score);
324 assert!(result == Ordering::Less || result == Ordering::Equal);
325 }
326
327 #[test]
329 fn test_cmp_transitive(a in arb_score(), b in arb_score(), c in arb_score()) {
330 use Ordering::*;
331
332 let a_b_comparison = cmp_impl(a, b);
333 let b_c_comparison = cmp_impl(b, c);
334 let a_c_comparison = cmp_impl(a, c);
335
336 if matches!(a_b_comparison, Less | Equal) && matches!(b_c_comparison, Less | Equal) {
337 assert!(a_c_comparison == Less || a_c_comparison == Equal);
338 }
339 }
340
341 #[test]
343 fn test_cmp_antisymetric(a in arb_score(), b in arb_score()) {
344 use Ordering::*;
345
346 let a_b_comparison = cmp_impl(a, b);
347 let b_a_comparison = cmp_impl(b, a);
348
349 eprintln!("a.cmp(b) = {a_b_comparison:?}");
350 eprintln!("b.cmp(a) = {b_a_comparison:?}");
351
352 if matches!(a_b_comparison, Less | Equal) && matches!(b_a_comparison, Less | Equal) {
353 assert!(a == b);
354 }
355 }
356
357 #[test]
359 fn test_cmp_reverse(a in arb_score(), b in arb_score()) {
360 use Ordering::*;
361
362 let a_b_comparison = cmp_impl(a, b);
363 let b_a_comparison = cmp_impl(b, a);
364
365 if a_b_comparison == Less {
366 assert_eq!(b_a_comparison, Greater);
367 } else if a_b_comparison == Greater {
368 assert_eq!(b_a_comparison, Less);
369 } else {
370 assert_eq!(a_b_comparison, Equal);
371 assert_eq!(b_a_comparison, Equal);
372 }
373 }
374 }
375}