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 }
159 }
160 }
161
162 for (i, item) in items.iter().enumerate().rev() {
166 if item.is_date_divider() {
167 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 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 break;
189 }
190 }
191
192 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 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 #[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 return false;
224 };
225
226 match prev_item.kind() {
227 TimelineItemKind::Event(event) => {
228 if self.is_same_date_divider_group_as(event.timestamp(), ts) {
230 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 self.ops.push(DateDividerOperation::Remove(i));
242 return true;
243 }
244
245 TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
246 }
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 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 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 if timestamp_to_date(*prev_ts) != event_date {
289 if let Some(last_event_ts) = latest_event_ts {
292 if timestamp_to_date(last_event_ts) == event_date {
293 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 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 }
310 }
311 }
312
313 fn process_ops(&self, items: &mut ObservableItemsTransaction<'_>, meta: &mut TimelineMetadata) {
314 let mut offset = 0i64;
316 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 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 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 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 {
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 if let Some(last) = items.last() {
432 if last.is_date_divider() {
433 report.errors.push(DateDividerInsertError::TrailingDateDivider);
434 }
435 }
436
437 {
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 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 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 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 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 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
539struct DateDividerInvariantsReport<'a, 'o> {
541 initial_state: Option<Vec<Arc<TimelineItem>>>,
543 operations: Vec<DateDividerOperation>,
545 final_state: &'a ObservableItemsTransaction<'o>,
547 errors: Vec<DateDividerInsertError>,
549}
550
551impl Display for DateDividerInvariantsReport<'_, '_> {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 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 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 #[error("The first item isn't a date divider")]
611 FirstItemNotDateDivider,
612
613 #[error("Duplicate date divider @ {at}.")]
615 DuplicateDateDivider { at: usize },
616
617 #[error("The last item is a date divider.")]
619 TrailingDateDivider,
620
621 #[error("Missing date divider between events @ {at}")]
624 MissingDateDividerBetweenEvents { at: usize },
625
626 #[error("Missing date divider before event @ {at}")]
628 MissingDateDividerBeforeEvent { at: usize },
629
630 #[error("Event @ {at} and the previous date divider aren't targeting the same date")]
632 InconsistentDateAfterPreviousDateDivider { at: usize },
633
634 #[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)))), None,
902 );
903 txn.push_back(
904 meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), 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)))), None,
938 );
939 txn.push_back(
940 meta.new_timeline_item(event_with_ts(MilliSecondsSinceUnixEpoch(uint!(2_678_400_000)))), 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}