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::{Level, error, event_enabled, instrument, trace, warn};
23
24use super::{
25    DateDividerMode, TimelineItem, TimelineItemKind, VirtualTimelineItem,
26    controller::{ObservableItemsTransaction, TimelineMetadata},
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_remotes_and_locals_regions() {
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_remotes_and_locals_regions().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 = if event_enabled!(Level::TRACE) {
196            Some(
197                items
198                    .iter_remotes_and_locals_regions()
199                    .map(|(_i, timeline_item)| timeline_item.clone())
200                    .collect(),
201            )
202        } else {
203            None
204        };
205
206        self.process_ops(items, meta);
207
208        // Then check invariants.
209        if let Some(report) = self.check_invariants(items, initial_state) {
210            error!(sentry = true, %report, "day divider invariants violated");
211            #[cfg(any(debug_assertions, test))]
212            panic!("There was an error checking date separator invariants");
213        }
214
215        self.consumed = true;
216    }
217
218    /// Decides what to do with a date divider.
219    ///
220    /// Returns whether it's been removed or not.
221    #[inline]
222    fn handle_date_divider(
223        &mut self,
224        i: usize,
225        ts: MilliSecondsSinceUnixEpoch,
226        prev_item: Option<&Arc<TimelineItem>>,
227    ) -> bool {
228        let Some(prev_item) = prev_item else {
229            // No interesting item prior to the date divider: it must be the first one,
230            // nothing to do.
231            return false;
232        };
233
234        match prev_item.kind() {
235            TimelineItemKind::Event(event) => {
236                // This date divider is preceded by an event.
237                if self.is_same_date_divider_group_as(event.timestamp(), ts) {
238                    // The event has the same date as the date divider: remove the current date
239                    // divider.
240                    trace!("removing date divider following event with same timestamp @ {i}");
241                    self.ops.push(DateDividerOperation::Remove(i));
242                    return true;
243                }
244            }
245
246            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(_)) => {
247                trace!("removing duplicate date divider @ {i}");
248                // This date divider is preceded by another one: remove the current one.
249                self.ops.push(DateDividerOperation::Remove(i));
250                return true;
251            }
252
253            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
254            | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
255                // Nothing to do.
256            }
257        }
258
259        false
260    }
261
262    #[inline]
263    fn handle_event(
264        &mut self,
265        i: usize,
266        ts: MilliSecondsSinceUnixEpoch,
267        prev_item_desc: Option<PrevItemDesc<'_>>,
268        latest_event_ts: Option<MilliSecondsSinceUnixEpoch>,
269    ) {
270        let Some(PrevItemDesc { item_index, insert_op_at, item }) = prev_item_desc else {
271            // The event was the first item, so there wasn't any date divider before it:
272            // insert one.
273            trace!("inserting the first date divider @ {}", i);
274            self.ops.push(DateDividerOperation::Insert(i, ts));
275            return;
276        };
277
278        match item.kind() {
279            TimelineItemKind::Event(prev_event) => {
280                // The event is preceded by another event. If they're not the same date,
281                // insert a date divider.
282                let prev_ts = prev_event.timestamp();
283
284                if !self.is_same_date_divider_group_as(prev_ts, ts) {
285                    trace!(
286                        "inserting date divider @ {} between two events with different dates",
287                        i
288                    );
289                    self.ops.push(DateDividerOperation::Insert(i, ts));
290                }
291            }
292
293            TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(prev_ts)) => {
294                let event_date = timestamp_to_date(ts);
295
296                // The event is preceded by a date divider.
297                if timestamp_to_date(*prev_ts) != event_date {
298                    // The date divider is wrong. Should we replace it with the correct value, or
299                    // remove it entirely?
300                    if let Some(last_event_ts) = latest_event_ts
301                        && timestamp_to_date(last_event_ts) == event_date
302                    {
303                        // There's a previous event with the same date: remove the divider.
304                        trace!(
305                            "removed date divider @ {item_index} between two events \
306                                 that have the same date"
307                        );
308                        self.ops.insert(insert_op_at, DateDividerOperation::Remove(item_index));
309                        return;
310                    }
311
312                    // There's no previous event or there's one with a different date: replace
313                    // the current divider.
314                    trace!("replacing date divider @ {item_index} with new timestamp from event");
315                    self.ops.insert(insert_op_at, DateDividerOperation::Replace(item_index, ts));
316                }
317            }
318
319            TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
320            | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
321                // Nothing to do.
322            }
323        }
324    }
325
326    fn process_ops(&self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
327        // Record the deletion offset.
328        let mut offset = 0i64;
329        // Remember what the maximum index was, so we can assert that it's
330        // non-decreasing.
331        let mut max_i = 0;
332
333        for op in &self.ops {
334            match *op {
335                DateDividerOperation::Insert(i, ts) => {
336                    assert!(i >= max_i, "trying to insert at {i} < max_i={max_i}");
337
338                    let at = (i64::try_from(i).unwrap() + offset)
339                        .min(i64::try_from(items.len()).unwrap());
340                    assert!(at >= 0);
341                    let at = at as usize;
342
343                    items.push_date_divider(
344                        at,
345                        meta.new_timeline_item(VirtualTimelineItem::DateDivider(ts)),
346                    );
347
348                    offset += 1;
349                    max_i = i;
350                }
351
352                DateDividerOperation::Replace(i, ts) => {
353                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
354
355                    let at = i64::try_from(i).unwrap() + offset;
356                    assert!(at >= 0);
357                    let at = at as usize;
358
359                    let replaced = &items[at];
360                    if !replaced.is_date_divider() {
361                        error!("we replaced a non date-divider @ {i}: {:?}", replaced.kind());
362                    }
363
364                    let unique_id = replaced.unique_id();
365                    let item = TimelineItem::new(
366                        VirtualTimelineItem::DateDivider(ts),
367                        unique_id.to_owned(),
368                    );
369
370                    items.replace(at, item);
371                    max_i = i;
372                }
373
374                DateDividerOperation::Remove(i) => {
375                    assert!(i >= max_i, "trying to replace at {i} < max_i={max_i}");
376
377                    let at = i64::try_from(i).unwrap() + offset;
378                    assert!(at >= 0);
379
380                    let removed = items.remove(at as usize);
381                    if !removed.is_date_divider() {
382                        error!("we removed a non date-divider @ {i}: {:?}", removed.kind());
383                    }
384
385                    offset -= 1;
386                    max_i = i;
387                }
388            }
389        }
390    }
391
392    /// Checks the invariants that must hold at any time after inserting date
393    /// dividers.
394    ///
395    /// Returns a report if and only if there was at least one error.
396    fn check_invariants<'a, 'o>(
397        &mut self,
398        items: &'a ObservableItemsTransaction<'o>,
399        initial_state: Option<Vec<Arc<TimelineItem>>>,
400    ) -> Option<DateDividerInvariantsReport<'a, 'o>> {
401        let mut report = DateDividerInvariantsReport {
402            initial_state,
403            errors: Vec::new(),
404            operations: std::mem::take(&mut self.ops),
405            final_state: items,
406        };
407
408        // Assert invariants.
409        // 1. The timeline starts with a date divider, if it's not only virtual items.
410        {
411            let mut i = items.first_remotes_region_index();
412            while let Some(item) = items.get(i) {
413                if let Some(virt) = item.as_virtual() {
414                    if matches!(virt, VirtualTimelineItem::DateDivider(_)) {
415                        // We found a date divider among the first virtual items: stop here.
416                        break;
417                    }
418                } else {
419                    // We found an event, but we didn't have a date divider: report an error.
420                    report.errors.push(DateDividerInsertError::FirstItemNotDateDivider);
421                    break;
422                }
423                i += 1;
424            }
425        }
426
427        // 2. There are no two date dividers following each other.
428        {
429            let mut prev_was_date_divider = false;
430            for (i, item) in items.iter_remotes_and_locals_regions() {
431                if item.is_date_divider() {
432                    if prev_was_date_divider {
433                        report.errors.push(DateDividerInsertError::DuplicateDateDivider { at: i });
434                    }
435                    prev_was_date_divider = true;
436                } else {
437                    prev_was_date_divider = false;
438                }
439            }
440        };
441
442        // 3. There's no trailing date divider.
443        if let Some(last) = items.last()
444            && last.is_date_divider()
445        {
446            report.errors.push(DateDividerInsertError::TrailingDateDivider);
447        }
448
449        // 4. Items are properly separated with date dividers.
450        {
451            let mut prev_event_ts = None;
452            let mut prev_date_divider_ts = None;
453
454            for (i, item) in items.iter_remotes_and_locals_regions() {
455                if let Some(ev) = item.as_event() {
456                    let ts = ev.timestamp();
457
458                    // We have the same date as the previous event we've seen.
459                    if let Some(prev_ts) = prev_event_ts
460                        && !self.is_same_date_divider_group_as(prev_ts, ts)
461                    {
462                        report.errors.push(
463                            DateDividerInsertError::MissingDateDividerBetweenEvents { at: i },
464                        );
465                    }
466
467                    // There is a date divider before us, and it's the same date as our timestamp.
468                    if let Some(prev_ts) = prev_date_divider_ts {
469                        if !self.is_same_date_divider_group_as(prev_ts, ts) {
470                            report.errors.push(
471                                DateDividerInsertError::InconsistentDateAfterPreviousDateDivider {
472                                    at: i,
473                                },
474                            );
475                        }
476                    } else {
477                        report
478                            .errors
479                            .push(DateDividerInsertError::MissingDateDividerBeforeEvent { at: i });
480                    }
481
482                    prev_event_ts = Some(ts);
483                } else if let TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(ts)) =
484                    item.kind()
485                {
486                    // The previous date divider is for a different date.
487                    if let Some(prev_ts) = prev_date_divider_ts
488                        && self.is_same_date_divider_group_as(prev_ts, *ts)
489                    {
490                        report.errors.push(DateDividerInsertError::DuplicateDateDivider { at: i });
491                    }
492
493                    prev_event_ts = None;
494                    prev_date_divider_ts = Some(*ts);
495                }
496            }
497        }
498
499        // 5. If there was a read marker at the beginning, there should be one at the
500        //    end.
501        if let Some(state) = &report.initial_state
502            && state.iter().any(|item| item.is_read_marker())
503            && !report
504                .final_state
505                .iter_remotes_and_locals_regions()
506                .any(|(_i, item)| item.is_read_marker())
507        {
508            report.errors.push(DateDividerInsertError::ReadMarkerDisappeared);
509        }
510
511        if report.errors.is_empty() { None } else { Some(report) }
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(
603                f,
604                self.final_state
605                    .iter_remotes_and_locals_regions()
606                    .map(|(_i, item)| item.clone())
607                    .collect::<Vec<_>>()
608                    .as_slice(),
609            )?;
610
611            writeln!(f)?;
612        }
613
614        for err in &self.errors {
615            writeln!(f, "{err}")?;
616        }
617
618        Ok(())
619    }
620}
621
622#[derive(Debug, thiserror::Error)]
623enum DateDividerInsertError {
624    /// The first item isn't a date divider.
625    #[error("The first item isn't a date divider")]
626    FirstItemNotDateDivider,
627
628    /// There are two date dividers for the same date.
629    #[error("Duplicate date divider @ {at}.")]
630    DuplicateDateDivider { at: usize },
631
632    /// The last item is a date divider.
633    #[error("The last item is a date divider.")]
634    TrailingDateDivider,
635
636    /// Two events are following each other but they have different dates
637    /// without a date divider between them.
638    #[error("Missing date divider between events @ {at}")]
639    MissingDateDividerBetweenEvents { at: usize },
640
641    /// Some event is missing a date divider before it.
642    #[error("Missing date divider before event @ {at}")]
643    MissingDateDividerBeforeEvent { at: usize },
644
645    /// An event and the previous date divider aren't focused on the same date.
646    #[error("Event @ {at} and the previous date divider aren't targeting the same date")]
647    InconsistentDateAfterPreviousDateDivider { at: usize },
648
649    /// The read marker has been removed.
650    #[error("The read marker has been removed")]
651    ReadMarkerDisappeared,
652}
653
654#[cfg(test)]
655mod tests {
656    use assert_matches2::assert_let;
657    use ruma::{
658        MilliSecondsSinceUnixEpoch, owned_event_id, owned_user_id,
659        room_version_rules::RoomVersionRules, uint,
660    };
661
662    use super::{super::controller::ObservableItems, DateDividerAdjuster};
663    use crate::timeline::{
664        DateDividerMode, EventTimelineItem, MsgLikeContent, TimelineItemContent,
665        VirtualTimelineItem,
666        controller::TimelineMetadata,
667        date_dividers::timestamp_to_date,
668        event_item::{EventTimelineItemKind, RemoteEventTimelineItem},
669    };
670
671    fn event_with_ts(timestamp: MilliSecondsSinceUnixEpoch) -> EventTimelineItem {
672        let event_kind = EventTimelineItemKind::Remote(RemoteEventTimelineItem {
673            event_id: owned_event_id!("$1"),
674            transaction_id: None,
675            read_receipts: Default::default(),
676            is_own: false,
677            is_highlighted: false,
678            encryption_info: None,
679            original_json: None,
680            latest_edit_json: None,
681            origin: crate::timeline::event_item::RemoteEventOrigin::Sync,
682        });
683        EventTimelineItem::new(
684            owned_user_id!("@alice:example.org"),
685            crate::timeline::TimelineDetails::Pending,
686            timestamp,
687            TimelineItemContent::MsgLike(MsgLikeContent::redacted()),
688            event_kind,
689            false,
690        )
691    }
692
693    fn test_metadata() -> TimelineMetadata {
694        TimelineMetadata::new(owned_user_id!("@a:b.c"), RoomVersionRules::V11, None, None, false)
695    }
696
697    #[test]
698    fn test_no_trailing_date_divider() {
699        let mut items = ObservableItems::new();
700        let mut txn = items.transaction();
701
702        let mut meta = test_metadata();
703
704        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
705        let timestamp_next_day =
706            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
707
708        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
709        txn.push_back(
710            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
711            None,
712        );
713        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
714
715        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
716        adjuster.run(&mut txn, &mut meta);
717
718        txn.commit();
719
720        let mut iter = items.iter();
721
722        assert_let!(Some(item) = iter.next());
723        assert!(item.is_date_divider());
724
725        assert_let!(Some(item) = iter.next());
726        assert!(item.is_remote_event());
727
728        assert_let!(Some(item) = iter.next());
729        assert!(item.is_read_marker());
730
731        assert!(iter.next().is_none());
732    }
733
734    #[test]
735    fn test_read_marker_in_between_event_and_date_divider() {
736        let mut items = ObservableItems::new();
737        let mut txn = items.transaction();
738
739        let mut meta = test_metadata();
740
741        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
742        let timestamp_next_day =
743            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
744        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
745
746        let event = event_with_ts(timestamp);
747        txn.push_back(meta.new_timeline_item(event.clone()), None);
748        txn.push_back(
749            meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp_next_day)),
750            None,
751        );
752        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
753        txn.push_back(meta.new_timeline_item(event), None);
754
755        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
756        adjuster.run(&mut txn, &mut meta);
757
758        txn.commit();
759
760        let mut iter = items.iter();
761
762        assert!(iter.next().unwrap().is_date_divider());
763        assert!(iter.next().unwrap().is_remote_event());
764        assert!(iter.next().unwrap().is_read_marker());
765        assert!(iter.next().unwrap().is_remote_event());
766        assert!(iter.next().is_none());
767    }
768
769    #[test]
770    fn test_read_marker_in_between_date_dividers() {
771        let mut items = ObservableItems::new();
772        let mut txn = items.transaction();
773
774        let mut meta = test_metadata();
775
776        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
777        let timestamp_next_day =
778            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
779        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
780
781        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
782        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
783        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
784        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
785        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
786        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
787
788        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
789        adjuster.run(&mut txn, &mut meta);
790
791        txn.commit();
792
793        let mut iter = items.iter();
794
795        assert!(iter.next().unwrap().is_date_divider());
796        assert!(iter.next().unwrap().is_remote_event());
797        assert!(iter.next().unwrap().is_read_marker());
798        assert!(iter.next().unwrap().is_date_divider());
799        assert!(iter.next().unwrap().is_remote_event());
800        assert!(iter.next().is_none());
801    }
802
803    #[test]
804    fn test_remove_all_date_dividers() {
805        let mut items = ObservableItems::new();
806        let mut txn = items.transaction();
807
808        let mut meta = test_metadata();
809
810        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
811        let timestamp_next_day =
812            MilliSecondsSinceUnixEpoch((42 + 3600 * 24 * 1000).try_into().unwrap());
813        assert_ne!(timestamp_to_date(timestamp), timestamp_to_date(timestamp_next_day));
814
815        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
816        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
817        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
818        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp_next_day)), None);
819
820        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
821        adjuster.run(&mut txn, &mut meta);
822
823        txn.commit();
824
825        let mut iter = items.iter();
826
827        assert!(iter.next().unwrap().is_date_divider());
828        assert!(iter.next().unwrap().is_remote_event());
829        assert!(iter.next().unwrap().is_remote_event());
830        assert!(iter.next().is_none());
831    }
832
833    #[test]
834    fn test_event_read_marker_spurious_date_divider() {
835        let mut items = ObservableItems::new();
836        let mut txn = items.transaction();
837
838        let mut meta = test_metadata();
839
840        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
841
842        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
843        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
844        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
845
846        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
847        adjuster.run(&mut txn, &mut meta);
848
849        txn.commit();
850
851        let mut iter = items.iter();
852
853        assert!(iter.next().unwrap().is_date_divider());
854        assert!(iter.next().unwrap().is_remote_event());
855        assert!(iter.next().unwrap().is_read_marker());
856        assert!(iter.next().is_none());
857    }
858
859    #[test]
860    fn test_multiple_trailing_date_dividers() {
861        let mut items = ObservableItems::new();
862        let mut txn = items.transaction();
863
864        let mut meta = test_metadata();
865
866        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
867
868        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
869        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
870        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::DateDivider(timestamp)), None);
871
872        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
873        adjuster.run(&mut txn, &mut meta);
874
875        txn.commit();
876
877        let mut iter = items.iter();
878
879        assert!(iter.next().unwrap().is_read_marker());
880        assert!(iter.next().is_none());
881    }
882
883    #[test]
884    fn test_start_with_read_marker() {
885        let mut items = ObservableItems::new();
886        let mut txn = items.transaction();
887
888        let mut meta = test_metadata();
889        let timestamp = MilliSecondsSinceUnixEpoch(uint!(42));
890
891        txn.push_back(meta.new_timeline_item(VirtualTimelineItem::ReadMarker), None);
892        txn.push_back(meta.new_timeline_item(event_with_ts(timestamp)), None);
893
894        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
895        adjuster.run(&mut txn, &mut meta);
896
897        txn.commit();
898
899        let mut iter = items.iter();
900
901        assert!(iter.next().unwrap().is_read_marker());
902        assert!(iter.next().unwrap().is_date_divider());
903        assert!(iter.next().unwrap().is_remote_event());
904        assert!(iter.next().is_none());
905    }
906
907    #[test]
908    fn test_daily_divider_mode() {
909        let mut items = ObservableItems::new();
910        let mut txn = items.transaction();
911
912        let mut meta = test_metadata();
913
914        txn.push_back(
915            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(0)))),
916            None,
917        );
918        txn.push_back(
919            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))), // One day later
920            None,
921        );
922        txn.push_back(
923            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), // One month later
924            None,
925        );
926
927        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Daily);
928        adjuster.run(&mut txn, &mut meta);
929
930        txn.commit();
931
932        let mut iter = items.iter();
933
934        assert!(iter.next().unwrap().is_date_divider());
935        assert!(iter.next().unwrap().is_remote_event());
936        assert!(iter.next().unwrap().is_date_divider());
937        assert!(iter.next().unwrap().is_remote_event());
938        assert!(iter.next().unwrap().is_date_divider());
939        assert!(iter.next().unwrap().is_remote_event());
940        assert!(iter.next().is_none());
941    }
942
943    #[test]
944    fn test_monthly_divider_mode() {
945        let mut items = ObservableItems::new();
946        let mut txn = items.transaction();
947
948        let mut meta = test_metadata();
949
950        txn.push_back(
951            // Start one day later than the origin, to make this test pass on all timezones.
952            // Let's call this time T.
953            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(86_400_000)))),
954            None,
955        );
956        txn.push_back(
957            // One day later (T+1 day).
958            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(172_800_000)))),
959            None,
960        );
961        txn.push_back(
962            // One month later (T+31 days).
963            meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_764_800_000)))),
964            None,
965        );
966
967        let mut adjuster = DateDividerAdjuster::new(DateDividerMode::Monthly);
968        adjuster.run(&mut txn, &mut meta);
969
970        txn.commit();
971
972        let mut iter = items.iter();
973
974        assert!(iter.next().unwrap().is_date_divider());
975        assert!(iter.next().unwrap().is_remote_event());
976        assert!(iter.next().unwrap().is_remote_event());
977        assert!(iter.next().unwrap().is_date_divider());
978        assert!(iter.next().unwrap().is_remote_event());
979        assert!(iter.next().is_none());
980    }
981}