1use 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
42fn timestamp_to_date(ts: MilliSecondsSinceUnixEpoch) -> Date {
44 let datetime = Local
45 .timestamp_millis_opt(ts.0.into())
46 .single()
48 .unwrap_or_else(Local::now);
51
52 Date { year: datetime.year(), month: datetime.month(), day: datetime.day() }
53}
54
55pub(super) struct DateDividerAdjuster {
58 ops: Vec<DateDividerOperation>,
61
62 consumed: bool,
65
66 mode: DateDividerMode,
67}
68
69impl Drop for DateDividerAdjuster {
70 fn drop(&mut self) {
71 if !std::thread::panicking() && !self.consumed {
73 error!("a DateDividerAdjuster has not been consumed with run()");
74 }
75 }
76}
77
78struct PrevItemDesc<'a> {
80 item_index: usize,
82
83 item: &'a Arc<TimelineItem>,
85
86 insert_op_at: usize,
88}
89
90impl DateDividerAdjuster {
91 pub fn new(mode: DateDividerMode) -> Self {
92 Self {
93 ops: Default::default(),
94 consumed: true,
97 mode,
98 }
99 }
100
101 pub fn mark_used(&mut self) {
104 self.consumed = false;
106 }
107
108 #[instrument(skip_all)]
111 pub fn run(&mut self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
112 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 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 }
160 }
161 }
162
163 for (i, item) in items.iter().enumerate().rev() {
167 if item.is_date_divider() {
168 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 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 break;
190 }
191 }
192
193 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 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 #[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 return false;
225 };
226
227 match prev_item.kind() {
228 TimelineItemKind::Event(event) => {
229 if self.is_same_date_divider_group_as(event.timestamp(), ts) {
231 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 self.ops.push(DateDividerOperation::Remove(i));
243 return true;
244 }
245
246 TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker)
247 | TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
248 }
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 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 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 if timestamp_to_date(*prev_ts) != event_date {
291 if let Some(last_event_ts) = latest_event_ts {
294 if timestamp_to_date(last_event_ts) == event_date {
295 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 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 }
313 }
314 }
315
316 fn process_ops(&self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
317 let mut offset = 0i64;
319 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 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 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 {
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 break;
413 }
414 } else {
415 report.errors.push(DateDividerInsertError::FirstItemNotDateDivider);
417 break;
418 }
419 i += 1;
420 }
421 }
422
423 {
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 if let Some(last) = items.last() {
440 if last.is_date_divider() {
441 report.errors.push(DateDividerInsertError::TrailingDateDivider);
442 }
443 }
444
445 {
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 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 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 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 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 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
547struct DateDividerInvariantsReport<'a, 'o> {
549 initial_state: Option<Vec<Arc<TimelineItem>>>,
551 operations: Vec<DateDividerOperation>,
553 final_state: &'a ObservableItemsTransaction<'o>,
555 errors: Vec<DateDividerInsertError>,
557}
558
559impl Display for DateDividerInvariantsReport<'_, '_> {
560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561 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 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 #[error("The first item isn't a date divider")]
619 FirstItemNotDateDivider,
620
621 #[error("Duplicate date divider @ {at}.")]
623 DuplicateDateDivider { at: usize },
624
625 #[error("The last item is a date divider.")]
627 TrailingDateDivider,
628
629 #[error("Missing date divider between events @ {at}")]
632 MissingDateDividerBetweenEvents { at: usize },
633
634 #[error("Missing date divider before event @ {at}")]
636 MissingDateDividerBeforeEvent { at: usize },
637
638 #[error("Event @ {at} and the previous date divider aren't targeting the same date")]
640 InconsistentDateAfterPreviousDateDivider { at: usize },
641
642 #[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)))), None,
911 );
912 txn.push_back(
913 meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), 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)))), None,
947 );
948 txn.push_back(
949 meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), 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}