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                    // Nothing to do.
158                }
159            }
160        }
161
162        // Also chase trailing date dividers explicitly, by iterating from the end to
163        // the start. Since they wouldn't be the prev_item of anything, we
164        // wouldn't analyze them in the previous loop.
165        for (i, item) in items.iter().enumerate().rev() {
166            if item.is_date_divider() {
167                // The item is a trailing date divider: remove it, if it wasn't already
168                // scheduled for deletion.
169                if !self
170                    .ops
171                    .iter()
172                    .any(|op| matches!(op, DateDividerOperation::Remove(j) if i == *j))
173                {
174                    trace!("removing trailing date divider @ {i}");
175
176                    // Find the index at which to insert the removal operation. It must be before
177                    // any other operation on a bigger index, to maintain the
178                    // non-decreasing invariant.
179                    let index =
180                        self.ops.iter().position(|op| op.index() > i).unwrap_or(self.ops.len());
181
182                    self.ops.insert(index, DateDividerOperation::Remove(i));
183                }
184            }
185
186            if item.is_event() {
187                // Stop as soon as we run into the first (trailing) event.
188                break;
189            }
190        }
191
192        // Only record the initial state if we've enabled the trace log level, and not
193        // otherwise.
194        let initial_state =
195            if event_enabled!(Level::TRACE) { Some(items.iter().cloned().collect()) } else { None };
196
197        self.process_ops(items, meta);
198
199        // Then check invariants.
200        if let Some(report) = self.check_invariants(items, initial_state) {
201            warn!("Errors encountered when checking invariants.");
202            warn!("{report}");
203            #[cfg(any(debug_assertions, test))]
204            panic!("There was an error checking date separator invariants");
205        }
206
207        self.consumed = true;
208    }
209
210    /// Decides what to do with a date divider.
211    ///
212    /// Returns whether it's been removed or not.
213    #[inline]
214    fn handle_date_divider(
215        &mut self,
216        i: usize,
217        ts: MilliSecondsSinceUnixEpoch,
218        prev_item: Option<&Arc<TimelineItem>>,
219    ) -> bool {
220        let Some(prev_item) = prev_item else {
221            // No interesting item prior to the date divider: it must be the first one,
222            // nothing to do.
223            return false;
224        };
225
226        match prev_item.kind() {
227            TimelineItemKind::Event(event) => {
228                // This date divider is preceded by an event.
229                if self.is_same_date_divider_group_as(event.timestamp(), ts) {
230                    // The event has the same date as the date divider: remove the current date
231                    // divider.
232                    trace!("removing date divider following event with same timestamp @ {i}");
233                    self.ops.push(DateDividerOperation::Remove(i));
234                    return true;
235                }
236            }
237
238            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(_)) => {
239                trace!("removing duplicate date divider @ {i}");
240                // This date divider is preceded by another one: remove the current one.
241                self.ops.push(DateDividerOperation::Remove(i));
242                return true;
243            }
244
245            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
246                // Nothing to do for read markers.
247            }
248        }
249
250        false
251    }
252
253    #[inline]
254    fn handle_event(
255        &mut self,
256        i: usize,
257        ts: MilliSecondsSinceUnixEpoch,
258        prev_item_desc: Option<PrevItemDesc<'_>>,
259        latest_event_ts: Option<MilliSecondsSinceUnixEpoch>,
260    ) {
261        let Some(PrevItemDesc { item_index, insert_op_at, item }) = prev_item_desc else {
262            // The event was the first item, so there wasn't any date divider before it:
263            // insert one.
264            trace!("inserting the first date divider @ {}", i);
265            self.ops.push(DateDividerOperation::Insert(i, ts));
266            return;
267        };
268
269        match item.kind() {
270            TimelineItemKind::Event(prev_event) => {
271                // The event is preceded by another event. If they're not the same date,
272                // insert a date divider.
273                let prev_ts = prev_event.timestamp();
274
275                if !self.is_same_date_divider_group_as(prev_ts, ts) {
276                    trace!(
277                        "inserting date divider @ {} between two events with different dates",
278                        i
279                    );
280                    self.ops.push(DateDividerOperation::Insert(i, ts));
281                }
282            }
283
284            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(prev_ts)) => {
285                let event_date = timestamp_to_date(ts);
286
287                // The event is preceded by a date divider.
288                if timestamp_to_date(*prev_ts) != event_date {
289                    // The date divider is wrong. Should we replace it with the correct value, or
290                    // remove it entirely?
291                    if let Some(last_event_ts) = latest_event_ts {
292                        if timestamp_to_date(last_event_ts) == event_date {
293                            // There's a previous event with the same date: remove the divider.
294                            trace!("removed date divider @ {item_index} between two events that have the same date");
295                            self.ops.insert(insert_op_at, DateDividerOperation::Remove(item_index));
296                            return;
297                        }
298                    }
299
300                    // There's no previous event or there's one with a different date: replace
301                    // the current divider.
302                    trace!("replacing date divider @ {item_index} with new timestamp from event");
303                    self.ops.insert(insert_op_at, DateDividerOperation::Replace(item_index, ts));
304                }
305            }
306
307            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
308                // Nothing to do.
309            }
310        }
311    }
312
313    fn process_ops(&self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
314        // Record the deletion offset.
315        let mut offset = 0i64;
316        // Remember what the maximum index was, so we can assert that it's
317        // non-decreasing.
318        let mut max_i = 0;
319
320        for op in &self.ops {
321            match *op {
322                DateDividerOperation::Insert(i, ts) => {
323                    assert!(i >= max_i, "trying to insert at {i} < max_i={max_i}");
324
325                    let at = (i64::try_from(i).unwrap() + offset)
326                        .min(i64::try_from(items.len()).unwrap());
327                    assert!(at >= 0);
328                    let at = at as usize;
329
330                    let item = meta.new_timeline_item(VirtualTimelineItem::DateDivider(ts));
331
332                    // Keep push semantics, if we're inserting at the front or the back.
333                    if at == items.len() {
334                        items.push_back(item, None);
335                    } else if at == 0 {
336                        items.push_front(item, None);
337                    } else {
338                        items.insert(at, item, None);
339                    }
340
341                    offset += 1;
342                    max_i = i;
343                }
344
345                DateDividerOperation::Replace(i, ts) => {
346                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
347
348                    let at = i64::try_from(i).unwrap() + offset;
349                    assert!(at >= 0);
350                    let at = at as usize;
351
352                    let replaced = &items[at];
353                    if !replaced.is_date_divider() {
354                        error!("we replaced a non date-divider @ {i}: {:?}", replaced.kind());
355                    }
356
357                    let unique_id = replaced.unique_id();
358                    let item = TimelineItem::new(
359                        VirtualTimelineItem::DateDivider(ts),
360                        unique_id.to_owned(),
361                    );
362
363                    items.replace(at, item);
364                    max_i = i;
365                }
366
367                DateDividerOperation::Remove(i) => {
368                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
369
370                    let at = i64::try_from(i).unwrap() + offset;
371                    assert!(at >= 0);
372
373                    let removed = items.remove(at as usize);
374                    if !removed.is_date_divider() {
375                        error!("we removed a non date-divider @ {i}: {:?}", removed.kind());
376                    }
377
378                    offset -= 1;
379                    max_i = i;
380                }
381            }
382        }
383    }
384
385    /// Checks the invariants that must hold at any time after inserting date
386    /// dividers.
387    ///
388    /// Returns a report if and only if there was at least one error.
389    fn check_invariants<'a, 'o>(
390        &mut self,
391        items: &'a ObservableItemsTransaction<'o>,
392        initial_state: Option<Vec<Arc<TimelineItem>>>,
393    ) -> Option<DateDividerInvariantsReport<'a, 'o>> {
394        let mut report = DateDividerInvariantsReport {
395            initial_state,
396            errors: Vec::new(),
397            operations: std::mem::take(&mut self.ops),
398            final_state: items,
399        };
400
401        // Assert invariants.
402        // 1. The timeline starts with a date divider.
403        if let Some(item) = items.get(0) {
404            if item.is_read_marker() {
405                if let Some(next_item) = items.get(1) {
406                    if !next_item.is_date_divider() {
407                        report.errors.push(DateDividerInsertError::FirstItemNotDateDivider);
408                    }
409                }
410            } else if !item.is_date_divider() {
411                report.errors.push(DateDividerInsertError::FirstItemNotDateDivider);
412            }
413        }
414
415        // 2. There are no two date dividers following each other.
416        {
417            let mut prev_was_date_divider = false;
418            for (i, item) in items.iter().enumerate() {
419                if item.is_date_divider() {
420                    if prev_was_date_divider {
421                        report.errors.push(DateDividerInsertError::DuplicateDateDivider { at: i });
422                    }
423                    prev_was_date_divider = true;
424                } else {
425                    prev_was_date_divider = false;
426                }
427            }
428        };
429
430        // 3. There's no trailing date divider.
431        if let Some(last) = items.last() {
432            if last.is_date_divider() {
433                report.errors.push(DateDividerInsertError::TrailingDateDivider);
434            }
435        }
436
437        // 4. Items are properly separated with date dividers.
438        {
439            let mut prev_event_ts = None;
440            let mut prev_date_divider_ts = None;
441
442            for (i, item) in items.iter().enumerate() {
443                if let Some(ev) = item.as_event() {
444                    let ts = ev.timestamp();
445
446                    // We have the same date as the previous event we've seen.
447                    if let Some(prev_ts) = prev_event_ts {
448                        if !self.is_same_date_divider_group_as(prev_ts, ts) {
449                            report.errors.push(
450                                DateDividerInsertError::MissingDateDividerBetweenEvents { at: i },
451                            );
452                        }
453                    }
454
455                    // There is a date divider before us, and it's the same date as our timestamp.
456                    if let Some(prev_ts) = prev_date_divider_ts {
457                        if !self.is_same_date_divider_group_as(prev_ts, ts) {
458                            report.errors.push(
459                                DateDividerInsertError::InconsistentDateAfterPreviousDateDivider {
460                                    at: i,
461                                },
462                            );
463                        }
464                    } else {
465                        report
466                            .errors
467                            .push(DateDividerInsertError::MissingDateDividerBeforeEvent { at: i });
468                    }
469
470                    prev_event_ts = Some(ts);
471                } else if let TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) =
472                    item.kind()
473                {
474                    // The previous date divider is for a different date.
475                    if let Some(prev_ts) = prev_date_divider_ts {
476                        if self.is_same_date_divider_group_as(prev_ts, *ts) {
477                            report
478                                .errors
479                                .push(DateDividerInsertError::DuplicateDateDivider { at: i });
480                        }
481                    }
482
483                    prev_event_ts = None;
484                    prev_date_divider_ts = Some(*ts);
485                }
486            }
487        }
488
489        // 5. If there was a read marker at the beginning, there should be one at the
490        //    end.
491        if let Some(state) = &report.initial_state {
492            if state.iter().any(|item| item.is_read_marker())
493                && !report.final_state.iter().any(|item| item.is_read_marker())
494            {
495                report.errors.push(DateDividerInsertError::ReadMarkerDisappeared);
496            }
497        }
498
499        if report.errors.is_empty() {
500            None
501        } else {
502            Some(report)
503        }
504    }
505
506    /// Returns whether the two dates for the given timestamps are the same or
507    /// not.
508    fn is_same_date_divider_group_as(
509        &self,
510        lhs: MilliSecondsSinceUnixEpoch,
511        rhs: MilliSecondsSinceUnixEpoch,
512    ) -> bool {
513        match self.mode {
514            DateDividerMode::Daily => timestamp_to_date(lhs) == timestamp_to_date(rhs),
515            DateDividerMode::Monthly => {
516                timestamp_to_date(lhs).is_same_month_as(timestamp_to_date(rhs))
517            }
518        }
519    }
520}
521
522#[derive(Debug)]
523enum DateDividerOperation {
524    Insert(usize, MilliSecondsSinceUnixEpoch),
525    Replace(usize, MilliSecondsSinceUnixEpoch),
526    Remove(usize),
527}
528
529impl DateDividerOperation {
530    fn index(&self) -> usize {
531        match self {
532            DateDividerOperation::Insert(i, _)
533            | DateDividerOperation::Replace(i, _)
534            | DateDividerOperation::Remove(i) => *i,
535        }
536    }
537}
538
539/// A report returned by [`DateDividerAdjuster::check_invariants`].
540struct DateDividerInvariantsReport<'a, 'o> {
541    /// Initial state before inserting the items.
542    initial_state: Option<Vec<Arc<TimelineItem>>>,
543    /// The operations that have been applied on the list.
544    operations: Vec<DateDividerOperation>,
545    /// Final state after inserting the date dividers.
546    final_state: &'a ObservableItemsTransaction<'o>,
547    /// Errors encountered in the algorithm.
548    errors: Vec<DateDividerInsertError>,
549}
550
551impl Display for DateDividerInvariantsReport<'_, '_> {
552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553        // Write all the items of a slice of timeline items.
554        fn write_items(
555            f: &mut std::fmt::Formatter<'_>,
556            items: &[Arc<TimelineItem>],
557        ) -> std::fmt::Result {
558            for (i, item) in items.iter().enumerate() {
559                if let TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) = item.kind()
560                {
561                    writeln!(f, "#{i} --- {}", ts.0)?;
562                } else if let Some(event) = item.as_event() {
563                    // id: timestamp
564                    writeln!(
565                        f,
566                        "#{i} {}: {}",
567                        event
568                            .event_id()
569                            .map_or_else(|| "(no event id)".to_owned(), |id| id.to_string()),
570                        event.timestamp().0
571                    )?;
572                } else {
573                    writeln!(f, "#{i} (other virtual item)")?;
574                }
575            }
576
577            Ok(())
578        }
579
580        if let Some(initial_state) = &self.initial_state {
581            writeln!(f, "Initial state:")?;
582            write_items(f, initial_state)?;
583
584            writeln!(f, "\nOperations to apply:")?;
585            for op in &self.operations {
586                match *op {
587                    DateDividerOperation::Insert(i, ts) => writeln!(f, "insert @ {i}: {}", ts.0)?,
588                    DateDividerOperation::Replace(i, ts) => writeln!(f, "replace @ {i}: {}", ts.0)?,
589                    DateDividerOperation::Remove(i) => writeln!(f, "remove @ {i}")?,
590                }
591            }
592
593            writeln!(f, "\nFinal state:")?;
594            write_items(f, self.final_state.iter().cloned().collect::<Vec<_>>().as_slice())?;
595
596            writeln!(f)?;
597        }
598
599        for err in &self.errors {
600            writeln!(f, "{err}")?;
601        }
602
603        Ok(())
604    }
605}
606
607#[derive(Debug, thiserror::Error)]
608enum DateDividerInsertError {
609    /// The first item isn't a date divider.
610    #[error("The first item isn't a date divider")]
611    FirstItemNotDateDivider,
612
613    /// There are two date dividers for the same date.
614    #[error("Duplicate date divider @ {at}.")]
615    DuplicateDateDivider { at: usize },
616
617    /// The last item is a date divider.
618    #[error("The last item is a date divider.")]
619    TrailingDateDivider,
620
621    /// Two events are following each other but they have different dates
622    /// without a date divider between them.
623    #[error("Missing date divider between events @ {at}")]
624    MissingDateDividerBetweenEvents { at: usize },
625
626    /// Some event is missing a date divider before it.
627    #[error("Missing date divider before event @ {at}")]
628    MissingDateDividerBeforeEvent { at: usize },
629
630    /// An event and the previous date divider aren't focused on the same date.
631    #[error("Event @ {at} and the previous date divider aren't targeting the same date")]
632    InconsistentDateAfterPreviousDateDivider { at: usize },
633
634    /// The read marker has been removed.
635    #[error("The read marker has been removed")]
636    ReadMarkerDisappeared,
637}
638
639#[cfg(test)]
640mod tests {
641    use assert_matches2::assert_let;
642    use ruma::{owned_event_id, owned_user_id, uint, MilliSecondsSinceUnixEpoch};
643
644    use super::{super::controller::ObservableItems, DateDividerAdjuster};
645    use crate::timeline::{
646        controller::TimelineMetadata,
647        date_dividers::timestamp_to_date,
648        event_item::{EventTimelineItemKind, RemoteEventTimelineItem},
649        DateDividerMode, EventTimelineItem, TimelineItemContent, VirtualTimelineItem,
650    };
651
652    fn event_with_ts(timestamp: MilliSecondsSinceUnixEpoch) -> EventTimelineItem {
653        let event_kind = EventTimelineItemKind::Remote(RemoteEventTimelineItem {
654            event_id: owned_event_id!("$1"),
655            transaction_id: None,
656            read_receipts: Default::default(),
657            is_own: false,
658            is_highlighted: false,
659            encryption_info: None,
660            original_json: None,
661            latest_edit_json: None,
662            origin: crate::timeline::event_item::RemoteEventOrigin::Sync,
663        });
664        EventTimelineItem::new(
665            owned_user_id!("@alice:example.org"),
666            crate::timeline::TimelineDetails::Pending,
667            timestamp,
668            TimelineItemContent::RedactedMessage,
669            event_kind,
670            false,
671        )
672    }
673
674    fn test_metadata() -> TimelineMetadata {
675        TimelineMetadata::new(owned_user_id!("@a:b.c"), ruma::RoomVersionId::V11, None, None, false)
676    }
677
678    #[test]
679    fn test_no_trailing_date_divider() {
680        let mut items = ObservableItems::new();
681        let mut txn = items.transaction();
682
683        let mut meta = test_metadata();
684
685        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
686        let timestamp_next_day =
687            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
688
689        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
690        txn.push_back(
691            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
692            None,
693        );
694        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
695
696        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
697        adjuster.run(&mut txn, &mut meta);
698
699        txn.commit();
700
701        let mut iter = items.iter();
702
703        assert_let!(Some(item) = iter.next());
704        assert!(item.is_date_divider());
705
706        assert_let!(Some(item) = iter.next());
707        assert!(item.is_remote_event());
708
709        assert_let!(Some(item) = iter.next());
710        assert!(item.is_read_marker());
711
712        assert!(iter.next().is_none());
713    }
714
715    #[test]
716    fn test_read_marker_in_between_event_and_date_divider() {
717        let mut items = ObservableItems::new();
718        let mut txn = items.transaction();
719
720        let mut meta = test_metadata();
721
722        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
723        let timestamp_next_day =
724            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
725        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
726
727        let event = event_with_ts(timestamp);
728        txn.push_back(meta.new_timeline_item(event.clone()), None);
729        txn.push_back(
730            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
731            None,
732        );
733        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
734        txn.push_back(meta.new_timeline_item(event), None);
735
736        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
737        adjuster.run(&mut txn, &mut meta);
738
739        txn.commit();
740
741        let mut iter = items.iter();
742
743        assert!(iter.next().unwrap().is_date_divider());
744        assert!(iter.next().unwrap().is_remote_event());
745        assert!(iter.next().unwrap().is_read_marker());
746        assert!(iter.next().unwrap().is_remote_event());
747        assert!(iter.next().is_none());
748    }
749
750    #[test]
751    fn test_read_marker_in_between_date_dividers() {
752        let mut items = ObservableItems::new();
753        let mut txn = items.transaction();
754
755        let mut meta = test_metadata();
756
757        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
758        let timestamp_next_day =
759            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
760        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
761
762        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
763        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
764        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
765        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
766        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
767        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
768
769        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
770        adjuster.run(&mut txn, &mut meta);
771
772        txn.commit();
773
774        let mut iter = items.iter();
775
776        assert!(iter.next().unwrap().is_date_divider());
777        assert!(iter.next().unwrap().is_remote_event());
778        assert!(iter.next().unwrap().is_read_marker());
779        assert!(iter.next().unwrap().is_date_divider());
780        assert!(iter.next().unwrap().is_remote_event());
781        assert!(iter.next().is_none());
782    }
783
784    #[test]
785    fn test_remove_all_date_dividers() {
786        let mut items = ObservableItems::new();
787        let mut txn = items.transaction();
788
789        let mut meta = test_metadata();
790
791        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
792        let timestamp_next_day =
793            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
794        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
795
796        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
797        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
798        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
799        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
800
801        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
802        adjuster.run(&mut txn, &mut meta);
803
804        txn.commit();
805
806        let mut iter = items.iter();
807
808        assert!(iter.next().unwrap().is_date_divider());
809        assert!(iter.next().unwrap().is_remote_event());
810        assert!(iter.next().unwrap().is_remote_event());
811        assert!(iter.next().is_none());
812    }
813
814    #[test]
815    fn test_event_read_marker_spurious_date_divider() {
816        let mut items = ObservableItems::new();
817        let mut txn = items.transaction();
818
819        let mut meta = test_metadata();
820
821        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
822
823        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
824        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
825        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
826
827        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
828        adjuster.run(&mut txn, &mut meta);
829
830        txn.commit();
831
832        let mut iter = items.iter();
833
834        assert!(iter.next().unwrap().is_date_divider());
835        assert!(iter.next().unwrap().is_remote_event());
836        assert!(iter.next().unwrap().is_read_marker());
837        assert!(iter.next().is_none());
838    }
839
840    #[test]
841    fn test_multiple_trailing_date_dividers() {
842        let mut items = ObservableItems::new();
843        let mut txn = items.transaction();
844
845        let mut meta = test_metadata();
846
847        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
848
849        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
850        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
851        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
852
853        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
854        adjuster.run(&mut txn, &mut meta);
855
856        txn.commit();
857
858        let mut iter = items.iter();
859
860        assert!(iter.next().unwrap().is_read_marker());
861        assert!(iter.next().is_none());
862    }
863
864    #[test]
865    fn test_start_with_read_marker() {
866        let mut items = ObservableItems::new();
867        let mut txn = items.transaction();
868
869        let mut meta = test_metadata();
870        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
871
872        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
873        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
874
875        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
876        adjuster.run(&mut txn, &mut meta);
877
878        txn.commit();
879
880        let mut iter = items.iter();
881
882        assert!(iter.next().unwrap().is_read_marker());
883        assert!(iter.next().unwrap().is_date_divider());
884        assert!(iter.next().unwrap().is_remote_event());
885        assert!(iter.next().is_none());
886    }
887
888    #[test]
889    fn test_daily_divider_mode() {
890        let mut items = ObservableItems::new();
891        let mut txn = items.transaction();
892
893        let mut meta = test_metadata();
894
895        txn.push_back(
896            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(0)))),
897            None,
898        );
899        txn.push_back(
900            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))), // One day later
901            None,
902        );
903        txn.push_back(
904            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), // One month later
905            None,
906        );
907
908        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
909        adjuster.run(&mut txn, &mut meta);
910
911        txn.commit();
912
913        let mut iter = items.iter();
914
915        assert!(iter.next().unwrap().is_date_divider());
916        assert!(iter.next().unwrap().is_remote_event());
917        assert!(iter.next().unwrap().is_date_divider());
918        assert!(iter.next().unwrap().is_remote_event());
919        assert!(iter.next().unwrap().is_date_divider());
920        assert!(iter.next().unwrap().is_remote_event());
921        assert!(iter.next().is_none());
922    }
923
924    #[test]
925    fn test_monthly_divider_mode() {
926        let mut items = ObservableItems::new();
927        let mut txn = items.transaction();
928
929        let mut meta = test_metadata();
930
931        txn.push_back(
932            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(0)))),
933            None,
934        );
935        txn.push_back(
936            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))), // One day later
937            None,
938        );
939        txn.push_back(
940            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), // One month later
941            None,
942        );
943
944        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Monthly);
945        adjuster.run(&mut txn, &mut meta);
946
947        txn.commit();
948
949        let mut iter = items.iter();
950
951        assert!(iter.next().unwrap().is_date_divider());
952        assert!(iter.next().unwrap().is_remote_event());
953        assert!(iter.next().unwrap().is_remote_event());
954        assert!(iter.next().unwrap().is_date_divider());
955        assert!(iter.next().unwrap().is_remote_event());
956        assert!(iter.next().is_none());
957    }
958}