matrix_sdk_ui/timeline/
date_dividers.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Algorithm to adjust (insert/replace/remove) date dividers after new events
16//! have been received from any source.
17
18use std::{fmt::Display, sync::Arc};
19
20use chrono::{Datelike, Local, TimeZone};
21use ruma::MilliSecondsSinceUnixEpoch;
22use tracing::{error, event_enabled, instrument, trace, warn, Level};
23
24use super::{
25    controller::{ObservableItemsTransaction, TimelineMetadata},
26    DateDividerMode, TimelineItem, TimelineItemKind, VirtualTimelineItem,
27};
28
29#[derive(Debug, PartialEq)]
30struct Date {
31    year: i32,
32    month: u32,
33    day: u32,
34}
35
36impl Date {
37    fn is_same_month_as(&self, date: Date) -> bool {
38        self.year == date.year && self.month == date.month
39    }
40}
41
42/// Converts a timestamp since Unix Epoch to a year, month and day.
43fn timestamp_to_date(ts: MilliSecondsSinceUnixEpoch) -> Date {
44    let datetime = Local
45        .timestamp_millis_opt(ts.0.into())
46        // Only returns `None` if date is after Dec 31, 262143 BCE.
47        .single()
48        // Fallback to the current date to avoid issues with malicious
49        // homeservers.
50        .unwrap_or_else(Local::now);
51
52    Date { year: datetime.year(), month: datetime.month(), day: datetime.day() }
53}
54
55/// Algorithm ensuring that date dividers are adjusted correctly, according to
56/// new items that have been inserted.
57pub(super) struct DateDividerAdjuster {
58    /// The list of recorded operations to apply, after analyzing the latest
59    /// items.
60    ops: Vec<DateDividerOperation>,
61
62    /// A boolean indicating whether the struct has been used and thus must be
63    /// mark unused manually by calling [`Self::run`].
64    consumed: bool,
65
66    mode: DateDividerMode,
67}
68
69impl Drop for DateDividerAdjuster {
70    fn drop(&mut self) {
71        // Only run the assert if we're not currently panicking.
72        if !std::thread::panicking() && !self.consumed {
73            error!("a DateDividerAdjuster has not been consumed with run()");
74        }
75    }
76}
77
78/// A descriptor for a previous item.
79struct PrevItemDesc<'a> {
80    /// The index of the item in the `self.items` array.
81    item_index: usize,
82
83    /// The previous timeline item.
84    item: &'a Arc<TimelineItem>,
85
86    // The insert position of the operation in the `ops` array.
87    insert_op_at: usize,
88}
89
90impl DateDividerAdjuster {
91    pub fn new(mode: DateDividerMode) -> Self {
92        Self {
93            ops: Default::default(),
94            // The adjuster starts as consumed, and it will be marked no consumed iff it's used
95            // with `mark_used`.
96            consumed: true,
97            mode,
98        }
99    }
100
101    /// Marks this [`DateDividerAdjuster`] as used, which means it'll require a
102    /// call to [`DateDividerAdjuster::run`] before getting dropped.
103    pub fn mark_used(&mut self) {
104        // Mark the adjuster as needing to be consumed.
105        self.consumed = false;
106    }
107
108    /// Ensures that date separators are properly inserted/removed when needs
109    /// be.
110    #[instrument(skip_all)]
111    pub fn run(&mut self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
112        // We're going to record vector operations like inserting, replacing and
113        // removing date dividers. Since we may remove or insert new items,
114        // recorded offsets will change as we're iterating over the array. The
115        // only way this is possible is because we're recording operations
116        // happening in non-decreasing order of the indices, i.e. we can't do an
117        // operation on index I and then on any index J<I later.
118        //
119        // Note we can't "just" iterate in reverse order, because we may have a
120        // `Remove(i)` followed by a `Replace((i+1) -1)`, which wouldn't do what
121        // we want, if running in reverse order.
122        //
123        // Also note that we can remove a few items at position J, then later decide to
124        // replace/remove an item (in `handle_event`) at position I, with I<J. That
125        // would break the above invariant (that operations happen in
126        // non-decreasing order of the indices), so we must record the insert
127        // position for an operation related to the previous item.
128
129        let mut prev_item: Option<PrevItemDesc<'_>> = None;
130        let mut latest_event_ts = None;
131
132        for (i, item) in items.iter().enumerate() {
133            match item.kind() {
134                TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) => {
135                    // Record what the last alive item pair is only if we haven't removed the date
136                    // divider.
137                    if !self.handle_date_divider(i, *ts, prev_item.as_ref().map(|desc| desc.item)) {
138                        prev_item = Some(PrevItemDesc {
139                            item_index: i,
140                            item,
141                            insert_op_at: self.ops.len(),
142                        });
143                    }
144                }
145
146                TimelineItemKind::Event(event) => {
147                    let ts = event.timestamp();
148
149                    self.handle_event(i, ts, prev_item, latest_event_ts);
150
151                    prev_item =
152                        Some(PrevItemDesc { item_index: i, item, insert_op_at: self.ops.len() });
153                    latest_event_ts = Some(ts);
154                }
155
156                TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
157                | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
158                    // Nothing to do.
159                }
160            }
161        }
162
163        // Also chase trailing date dividers explicitly, by iterating from the end to
164        // the start. Since they wouldn't be the prev_item of anything, we
165        // wouldn't analyze them in the previous loop.
166        for (i, item) in items.iter().enumerate().rev() {
167            if item.is_date_divider() {
168                // The item is a trailing date divider: remove it, if it wasn't already
169                // scheduled for deletion.
170                if !self
171                    .ops
172                    .iter()
173                    .any(|op| matches!(op, DateDividerOperation::Remove(j) if i == *j))
174                {
175                    trace!("removing trailing date divider @ {i}");
176
177                    // Find the index at which to insert the removal operation. It must be before
178                    // any other operation on a bigger index, to maintain the
179                    // non-decreasing invariant.
180                    let index =
181                        self.ops.iter().position(|op| op.index() > i).unwrap_or(self.ops.len());
182
183                    self.ops.insert(index, DateDividerOperation::Remove(i));
184                }
185            }
186
187            if item.is_event() {
188                // Stop as soon as we run into the first (trailing) event.
189                break;
190            }
191        }
192
193        // Only record the initial state if we've enabled the trace log level, and not
194        // otherwise.
195        let initial_state =
196            if event_enabled!(Level::TRACE) { Some(items.iter().cloned().collect()) } else { None };
197
198        self.process_ops(items, meta);
199
200        // Then check invariants.
201        if let Some(report) = self.check_invariants(items, initial_state) {
202            warn!("Errors encountered when checking invariants.");
203            warn!("{report}");
204            #[cfg(any(debug_assertions, test))]
205            panic!("There was an error checking date separator invariants");
206        }
207
208        self.consumed = true;
209    }
210
211    /// Decides what to do with a date divider.
212    ///
213    /// Returns whether it's been removed or not.
214    #[inline]
215    fn handle_date_divider(
216        &mut self,
217        i: usize,
218        ts: MilliSecondsSinceUnixEpoch,
219        prev_item: Option<&Arc<TimelineItem>>,
220    ) -> bool {
221        let Some(prev_item) = prev_item else {
222            // No interesting item prior to the date divider: it must be the first one,
223            // nothing to do.
224            return false;
225        };
226
227        match prev_item.kind() {
228            TimelineItemKind::Event(event) => {
229                // This date divider is preceded by an event.
230                if self.is_same_date_divider_group_as(event.timestamp(), ts) {
231                    // The event has the same date as the date divider: remove the current date
232                    // divider.
233                    trace!("removing date divider following event with same timestamp @ {i}");
234                    self.ops.push(DateDividerOperation::Remove(i));
235                    return true;
236                }
237            }
238
239            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(_)) => {
240                trace!("removing duplicate date divider @ {i}");
241                // This date divider is preceded by another one: remove the current one.
242                self.ops.push(DateDividerOperation::Remove(i));
243                return true;
244            }
245
246            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
247            | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
248                // Nothing to do.
249            }
250        }
251
252        false
253    }
254
255    #[inline]
256    fn handle_event(
257        &mut self,
258        i: usize,
259        ts: MilliSecondsSinceUnixEpoch,
260        prev_item_desc: Option<PrevItemDesc<'_>>,
261        latest_event_ts: Option<MilliSecondsSinceUnixEpoch>,
262    ) {
263        let Some(PrevItemDesc { item_index, insert_op_at, item }) = prev_item_desc else {
264            // The event was the first item, so there wasn't any date divider before it:
265            // insert one.
266            trace!("inserting the first date divider @ {}", i);
267            self.ops.push(DateDividerOperation::Insert(i, ts));
268            return;
269        };
270
271        match item.kind() {
272            TimelineItemKind::Event(prev_event) => {
273                // The event is preceded by another event. If they're not the same date,
274                // insert a date divider.
275                let prev_ts = prev_event.timestamp();
276
277                if !self.is_same_date_divider_group_as(prev_ts, ts) {
278                    trace!(
279                        "inserting date divider @ {} between two events with different dates",
280                        i
281                    );
282                    self.ops.push(DateDividerOperation::Insert(i, ts));
283                }
284            }
285
286            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(prev_ts)) => {
287                let event_date = timestamp_to_date(ts);
288
289                // The event is preceded by a date divider.
290                if timestamp_to_date(*prev_ts) != event_date {
291                    // The date divider is wrong. Should we replace it with the correct value, or
292                    // remove it entirely?
293                    if let Some(last_event_ts) = latest_event_ts {
294                        if timestamp_to_date(last_event_ts) == event_date {
295                            // There's a previous event with the same date: remove the divider.
296                            trace!("removed date divider @ {item_index} between two events that have the same date");
297                            self.ops.insert(insert_op_at, DateDividerOperation::Remove(item_index));
298                            return;
299                        }
300                    }
301
302                    // There's no previous event or there's one with a different date: replace
303                    // the current divider.
304                    trace!("replacing date divider @ {item_index} with new timestamp from event");
305                    self.ops.insert(insert_op_at, DateDividerOperation::Replace(item_index, ts));
306                }
307            }
308
309            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
310            | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
311                // Nothing to do.
312            }
313        }
314    }
315
316    fn process_ops(&self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
317        // Record the deletion offset.
318        let mut offset = 0i64;
319        // Remember what the maximum index was, so we can assert that it's
320        // non-decreasing.
321        let mut max_i = 0;
322
323        for op in &self.ops {
324            match *op {
325                DateDividerOperation::Insert(i, ts) => {
326                    assert!(i >= max_i, "trying to insert at {i} < max_i={max_i}");
327
328                    let at = (i64::try_from(i).unwrap() + offset)
329                        .min(i64::try_from(items.len()).unwrap());
330                    assert!(at >= 0);
331                    let at = at as usize;
332
333                    let item = meta.new_timeline_item(VirtualTimelineItem::DateDivider(ts));
334
335                    // Keep push semantics, if we're inserting at the front or the back.
336                    if at == items.len() {
337                        items.push_back(item, None);
338                    } else if at == 0 {
339                        items.push_front(item, None);
340                    } else {
341                        items.insert(at, item, None);
342                    }
343
344                    offset += 1;
345                    max_i = i;
346                }
347
348                DateDividerOperation::Replace(i, ts) => {
349                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
350
351                    let at = i64::try_from(i).unwrap() + offset;
352                    assert!(at >= 0);
353                    let at = at as usize;
354
355                    let replaced = &items[at];
356                    if !replaced.is_date_divider() {
357                        error!("we replaced a non date-divider @ {i}: {:?}", replaced.kind());
358                    }
359
360                    let unique_id = replaced.unique_id();
361                    let item = TimelineItem::new(
362                        VirtualTimelineItem::DateDivider(ts),
363                        unique_id.to_owned(),
364                    );
365
366                    items.replace(at, item);
367                    max_i = i;
368                }
369
370                DateDividerOperation::Remove(i) => {
371                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
372
373                    let at = i64::try_from(i).unwrap() + offset;
374                    assert!(at >= 0);
375
376                    let removed = items.remove(at as usize);
377                    if !removed.is_date_divider() {
378                        error!("we removed a non date-divider @ {i}: {:?}", removed.kind());
379                    }
380
381                    offset -= 1;
382                    max_i = i;
383                }
384            }
385        }
386    }
387
388    /// Checks the invariants that must hold at any time after inserting date
389    /// dividers.
390    ///
391    /// Returns a report if and only if there was at least one error.
392    fn check_invariants<'a, 'o>(
393        &mut self,
394        items: &'a ObservableItemsTransaction<'o>,
395        initial_state: Option<Vec<Arc<TimelineItem>>>,
396    ) -> Option<DateDividerInvariantsReport<'a, 'o>> {
397        let mut report = DateDividerInvariantsReport {
398            initial_state,
399            errors: Vec::new(),
400            operations: std::mem::take(&mut self.ops),
401            final_state: items,
402        };
403
404        // Assert invariants.
405        // 1. The timeline starts with a date divider, if it's not only virtual items.
406        {
407            let mut i = 0;
408            while let Some(item) = items.get(i) {
409                if let Some(virt) = item.as_virtual() {
410                    if matches!(virt, VirtualTimelineItem::DateDivider(_)) {
411                        // We found a date divider among the first virtual items: stop here.
412                        break;
413                    }
414                } else {
415                    // We found an event, but we didn't have a date divider: report an error.
416                    report.errors.push(DateDividerInsertError::FirstItemNotDateDivider);
417                    break;
418                }
419                i += 1;
420            }
421        }
422
423        // 2. There are no two date dividers following each other.
424        {
425            let mut prev_was_date_divider = false;
426            for (i, item) in items.iter().enumerate() {
427                if item.is_date_divider() {
428                    if prev_was_date_divider {
429                        report.errors.push(DateDividerInsertError::DuplicateDateDivider { at: i });
430                    }
431                    prev_was_date_divider = true;
432                } else {
433                    prev_was_date_divider = false;
434                }
435            }
436        };
437
438        // 3. There's no trailing date divider.
439        if let Some(last) = items.last() {
440            if last.is_date_divider() {
441                report.errors.push(DateDividerInsertError::TrailingDateDivider);
442            }
443        }
444
445        // 4. Items are properly separated with date dividers.
446        {
447            let mut prev_event_ts = None;
448            let mut prev_date_divider_ts = None;
449
450            for (i, item) in items.iter().enumerate() {
451                if let Some(ev) = item.as_event() {
452                    let ts = ev.timestamp();
453
454                    // We have the same date as the previous event we've seen.
455                    if let Some(prev_ts) = prev_event_ts {
456                        if !self.is_same_date_divider_group_as(prev_ts, ts) {
457                            report.errors.push(
458                                DateDividerInsertError::MissingDateDividerBetweenEvents { at: i },
459                            );
460                        }
461                    }
462
463                    // There is a date divider before us, and it's the same date as our timestamp.
464                    if let Some(prev_ts) = prev_date_divider_ts {
465                        if !self.is_same_date_divider_group_as(prev_ts, ts) {
466                            report.errors.push(
467                                DateDividerInsertError::InconsistentDateAfterPreviousDateDivider {
468                                    at: i,
469                                },
470                            );
471                        }
472                    } else {
473                        report
474                            .errors
475                            .push(DateDividerInsertError::MissingDateDividerBeforeEvent { at: i });
476                    }
477
478                    prev_event_ts = Some(ts);
479                } else if let TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) =
480                    item.kind()
481                {
482                    // The previous date divider is for a different date.
483                    if let Some(prev_ts) = prev_date_divider_ts {
484                        if self.is_same_date_divider_group_as(prev_ts, *ts) {
485                            report
486                                .errors
487                                .push(DateDividerInsertError::DuplicateDateDivider { at: i });
488                        }
489                    }
490
491                    prev_event_ts = None;
492                    prev_date_divider_ts = Some(*ts);
493                }
494            }
495        }
496
497        // 5. If there was a read marker at the beginning, there should be one at the
498        //    end.
499        if let Some(state) = &report.initial_state {
500            if state.iter().any(|item| item.is_read_marker())
501                && !report.final_state.iter().any(|item| item.is_read_marker())
502            {
503                report.errors.push(DateDividerInsertError::ReadMarkerDisappeared);
504            }
505        }
506
507        if report.errors.is_empty() {
508            None
509        } else {
510            Some(report)
511        }
512    }
513
514    /// Returns whether the two dates for the given timestamps are the same or
515    /// not.
516    fn is_same_date_divider_group_as(
517        &self,
518        lhs: MilliSecondsSinceUnixEpoch,
519        rhs: MilliSecondsSinceUnixEpoch,
520    ) -> bool {
521        match self.mode {
522            DateDividerMode::Daily => timestamp_to_date(lhs) == timestamp_to_date(rhs),
523            DateDividerMode::Monthly => {
524                timestamp_to_date(lhs).is_same_month_as(timestamp_to_date(rhs))
525            }
526        }
527    }
528}
529
530#[derive(Debug)]
531enum DateDividerOperation {
532    Insert(usize, MilliSecondsSinceUnixEpoch),
533    Replace(usize, MilliSecondsSinceUnixEpoch),
534    Remove(usize),
535}
536
537impl DateDividerOperation {
538    fn index(&self) -> usize {
539        match self {
540            DateDividerOperation::Insert(i, _)
541            | DateDividerOperation::Replace(i, _)
542            | DateDividerOperation::Remove(i) => *i,
543        }
544    }
545}
546
547/// A report returned by [`DateDividerAdjuster::check_invariants`].
548struct DateDividerInvariantsReport<'a, 'o> {
549    /// Initial state before inserting the items.
550    initial_state: Option<Vec<Arc<TimelineItem>>>,
551    /// The operations that have been applied on the list.
552    operations: Vec<DateDividerOperation>,
553    /// Final state after inserting the date dividers.
554    final_state: &'a ObservableItemsTransaction<'o>,
555    /// Errors encountered in the algorithm.
556    errors: Vec<DateDividerInsertError>,
557}
558
559impl Display for DateDividerInvariantsReport<'_, '_> {
560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561        // Write all the items of a slice of timeline items.
562        fn write_items(
563            f: &mut std::fmt::Formatter<'_>,
564            items: &[Arc<TimelineItem>],
565        ) -> std::fmt::Result {
566            for (i, item) in items.iter().enumerate() {
567                if let TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) = item.kind()
568                {
569                    writeln!(f, "#{i} --- {}", ts.0)?;
570                } else if let Some(event) = item.as_event() {
571                    // id: timestamp
572                    writeln!(
573                        f,
574                        "#{i} {}: {}",
575                        event
576                            .event_id()
577                            .map_or_else(|| "(no event id)".to_owned(), |id| id.to_string()),
578                        event.timestamp().0
579                    )?;
580                } else {
581                    writeln!(f, "#{i} (other virtual item)")?;
582                }
583            }
584
585            Ok(())
586        }
587
588        if let Some(initial_state) = &self.initial_state {
589            writeln!(f, "Initial state:")?;
590            write_items(f, initial_state)?;
591
592            writeln!(f, "\nOperations to apply:")?;
593            for op in &self.operations {
594                match *op {
595                    DateDividerOperation::Insert(i, ts) => writeln!(f, "insert @ {i}: {}", ts.0)?,
596                    DateDividerOperation::Replace(i, ts) => writeln!(f, "replace @ {i}: {}", ts.0)?,
597                    DateDividerOperation::Remove(i) => writeln!(f, "remove @ {i}")?,
598                }
599            }
600
601            writeln!(f, "\nFinal state:")?;
602            write_items(f, self.final_state.iter().cloned().collect::<Vec<_>>().as_slice())?;
603
604            writeln!(f)?;
605        }
606
607        for err in &self.errors {
608            writeln!(f, "{err}")?;
609        }
610
611        Ok(())
612    }
613}
614
615#[derive(Debug, thiserror::Error)]
616enum DateDividerInsertError {
617    /// The first item isn't a date divider.
618    #[error("The first item isn't a date divider")]
619    FirstItemNotDateDivider,
620
621    /// There are two date dividers for the same date.
622    #[error("Duplicate date divider @ {at}.")]
623    DuplicateDateDivider { at: usize },
624
625    /// The last item is a date divider.
626    #[error("The last item is a date divider.")]
627    TrailingDateDivider,
628
629    /// Two events are following each other but they have different dates
630    /// without a date divider between them.
631    #[error("Missing date divider between events @ {at}")]
632    MissingDateDividerBetweenEvents { at: usize },
633
634    /// Some event is missing a date divider before it.
635    #[error("Missing date divider before event @ {at}")]
636    MissingDateDividerBeforeEvent { at: usize },
637
638    /// An event and the previous date divider aren't focused on the same date.
639    #[error("Event @ {at} and the previous date divider aren't targeting the same date")]
640    InconsistentDateAfterPreviousDateDivider { at: usize },
641
642    /// The read marker has been removed.
643    #[error("The read marker has been removed")]
644    ReadMarkerDisappeared,
645}
646
647#[cfg(test)]
648mod tests {
649    use assert_matches2::assert_let;
650    use ruma::{owned_event_id, owned_user_id, uint, MilliSecondsSinceUnixEpoch};
651
652    use super::{super::controller::ObservableItems, DateDividerAdjuster};
653    use crate::timeline::{
654        controller::TimelineMetadata,
655        date_dividers::timestamp_to_date,
656        event_item::{EventTimelineItemKind, RemoteEventTimelineItem},
657        DateDividerMode, EventTimelineItem, MsgLikeContent, TimelineItemContent,
658        VirtualTimelineItem,
659    };
660
661    fn event_with_ts(timestamp: MilliSecondsSinceUnixEpoch) -> EventTimelineItem {
662        let event_kind = EventTimelineItemKind::Remote(RemoteEventTimelineItem {
663            event_id: owned_event_id!("$1"),
664            transaction_id: None,
665            read_receipts: Default::default(),
666            is_own: false,
667            is_highlighted: false,
668            encryption_info: None,
669            original_json: None,
670            latest_edit_json: None,
671            origin: crate::timeline::event_item::RemoteEventOrigin::Sync,
672        });
673        EventTimelineItem::new(
674            owned_user_id!("@alice:example.org"),
675            crate::timeline::TimelineDetails::Pending,
676            timestamp,
677            TimelineItemContent::MsgLike(MsgLikeContent::redacted()),
678            event_kind,
679            false,
680        )
681    }
682
683    fn test_metadata() -> TimelineMetadata {
684        TimelineMetadata::new(owned_user_id!("@a:b.c"), ruma::RoomVersionId::V11, None, None, false)
685    }
686
687    #[test]
688    fn test_no_trailing_date_divider() {
689        let mut items = ObservableItems::new();
690        let mut txn = items.transaction();
691
692        let mut meta = test_metadata();
693
694        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
695        let timestamp_next_day =
696            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
697
698        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
699        txn.push_back(
700            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
701            None,
702        );
703        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
704
705        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
706        adjuster.run(&mut txn, &mut meta);
707
708        txn.commit();
709
710        let mut iter = items.iter();
711
712        assert_let!(Some(item) = iter.next());
713        assert!(item.is_date_divider());
714
715        assert_let!(Some(item) = iter.next());
716        assert!(item.is_remote_event());
717
718        assert_let!(Some(item) = iter.next());
719        assert!(item.is_read_marker());
720
721        assert!(iter.next().is_none());
722    }
723
724    #[test]
725    fn test_read_marker_in_between_event_and_date_divider() {
726        let mut items = ObservableItems::new();
727        let mut txn = items.transaction();
728
729        let mut meta = test_metadata();
730
731        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
732        let timestamp_next_day =
733            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
734        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
735
736        let event = event_with_ts(timestamp);
737        txn.push_back(meta.new_timeline_item(event.clone()), None);
738        txn.push_back(
739            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
740            None,
741        );
742        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
743        txn.push_back(meta.new_timeline_item(event), None);
744
745        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
746        adjuster.run(&mut txn, &mut meta);
747
748        txn.commit();
749
750        let mut iter = items.iter();
751
752        assert!(iter.next().unwrap().is_date_divider());
753        assert!(iter.next().unwrap().is_remote_event());
754        assert!(iter.next().unwrap().is_read_marker());
755        assert!(iter.next().unwrap().is_remote_event());
756        assert!(iter.next().is_none());
757    }
758
759    #[test]
760    fn test_read_marker_in_between_date_dividers() {
761        let mut items = ObservableItems::new();
762        let mut txn = items.transaction();
763
764        let mut meta = test_metadata();
765
766        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
767        let timestamp_next_day =
768            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
769        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
770
771        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
772        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
773        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
774        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
775        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
776        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
777
778        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
779        adjuster.run(&mut txn, &mut meta);
780
781        txn.commit();
782
783        let mut iter = items.iter();
784
785        assert!(iter.next().unwrap().is_date_divider());
786        assert!(iter.next().unwrap().is_remote_event());
787        assert!(iter.next().unwrap().is_read_marker());
788        assert!(iter.next().unwrap().is_date_divider());
789        assert!(iter.next().unwrap().is_remote_event());
790        assert!(iter.next().is_none());
791    }
792
793    #[test]
794    fn test_remove_all_date_dividers() {
795        let mut items = ObservableItems::new();
796        let mut txn = items.transaction();
797
798        let mut meta = test_metadata();
799
800        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
801        let timestamp_next_day =
802            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
803        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
804
805        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
806        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
807        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
808        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
809
810        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
811        adjuster.run(&mut txn, &mut meta);
812
813        txn.commit();
814
815        let mut iter = items.iter();
816
817        assert!(iter.next().unwrap().is_date_divider());
818        assert!(iter.next().unwrap().is_remote_event());
819        assert!(iter.next().unwrap().is_remote_event());
820        assert!(iter.next().is_none());
821    }
822
823    #[test]
824    fn test_event_read_marker_spurious_date_divider() {
825        let mut items = ObservableItems::new();
826        let mut txn = items.transaction();
827
828        let mut meta = test_metadata();
829
830        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
831
832        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
833        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
834        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
835
836        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
837        adjuster.run(&mut txn, &mut meta);
838
839        txn.commit();
840
841        let mut iter = items.iter();
842
843        assert!(iter.next().unwrap().is_date_divider());
844        assert!(iter.next().unwrap().is_remote_event());
845        assert!(iter.next().unwrap().is_read_marker());
846        assert!(iter.next().is_none());
847    }
848
849    #[test]
850    fn test_multiple_trailing_date_dividers() {
851        let mut items = ObservableItems::new();
852        let mut txn = items.transaction();
853
854        let mut meta = test_metadata();
855
856        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
857
858        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
859        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
860        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
861
862        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
863        adjuster.run(&mut txn, &mut meta);
864
865        txn.commit();
866
867        let mut iter = items.iter();
868
869        assert!(iter.next().unwrap().is_read_marker());
870        assert!(iter.next().is_none());
871    }
872
873    #[test]
874    fn test_start_with_read_marker() {
875        let mut items = ObservableItems::new();
876        let mut txn = items.transaction();
877
878        let mut meta = test_metadata();
879        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
880
881        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
882        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
883
884        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
885        adjuster.run(&mut txn, &mut meta);
886
887        txn.commit();
888
889        let mut iter = items.iter();
890
891        assert!(iter.next().unwrap().is_read_marker());
892        assert!(iter.next().unwrap().is_date_divider());
893        assert!(iter.next().unwrap().is_remote_event());
894        assert!(iter.next().is_none());
895    }
896
897    #[test]
898    fn test_daily_divider_mode() {
899        let mut items = ObservableItems::new();
900        let mut txn = items.transaction();
901
902        let mut meta = test_metadata();
903
904        txn.push_back(
905            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(0)))),
906            None,
907        );
908        txn.push_back(
909            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))), // One day later
910            None,
911        );
912        txn.push_back(
913            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), // One month later
914            None,
915        );
916
917        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
918        adjuster.run(&mut txn, &mut meta);
919
920        txn.commit();
921
922        let mut iter = items.iter();
923
924        assert!(iter.next().unwrap().is_date_divider());
925        assert!(iter.next().unwrap().is_remote_event());
926        assert!(iter.next().unwrap().is_date_divider());
927        assert!(iter.next().unwrap().is_remote_event());
928        assert!(iter.next().unwrap().is_date_divider());
929        assert!(iter.next().unwrap().is_remote_event());
930        assert!(iter.next().is_none());
931    }
932
933    #[test]
934    fn test_monthly_divider_mode() {
935        let mut items = ObservableItems::new();
936        let mut txn = items.transaction();
937
938        let mut meta = test_metadata();
939
940        txn.push_back(
941            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(0)))),
942            None,
943        );
944        txn.push_back(
945            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))), // One day later
946            None,
947        );
948        txn.push_back(
949            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), // One month later
950            None,
951        );
952
953        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Monthly);
954        adjuster.run(&mut txn, &mut meta);
955
956        txn.commit();
957
958        let mut iter = items.iter();
959
960        assert!(iter.next().unwrap().is_date_divider());
961        assert!(iter.next().unwrap().is_remote_event());
962        assert!(iter.next().unwrap().is_remote_event());
963        assert!(iter.next().unwrap().is_date_divider());
964        assert!(iter.next().unwrap().is_remote_event());
965        assert!(iter.next().is_none());
966    }
967}