use cfait::model::{DateType, Task, TaskStatus};
use chrono::{Duration, Utc};
use std::collections::HashMap;
fn create_task_due_in_days(days: i64, recurrence: &str) -> Task {
let mut t = Task::new("Task", &HashMap::new(), None);
let dt = Utc::now() + Duration::days(days);
t.due = Some(DateType::Specific(dt));
t.rrule = Some(recurrence.to_string());
t.status = TaskStatus::Completed;
t
}
#[test]
fn test_daily_recurrence() {
let mut t = create_task_due_in_days(-1, "FREQ=DAILY");
let original_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let advanced = t.advance_recurrence();
assert!(advanced);
assert_eq!(t.status, TaskStatus::NeedsAction);
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let original_local = original_due.with_timezone(&chrono::Local).naive_local();
let new_local = new_due.with_timezone(&chrono::Local).naive_local();
let expected_local = original_local + Duration::days(2);
let diff = new_local - expected_local;
assert!(diff.num_seconds().abs() < 5, "Expected around tomorrow");
}
#[test]
fn test_weekly_recurrence() {
let mut t = create_task_due_in_days(-8, "FREQ=WEEKLY");
let original_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let advanced = t.advance_recurrence();
assert!(advanced);
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let original_local = original_due.with_timezone(&chrono::Local).naive_local();
let new_local = new_due.with_timezone(&chrono::Local).naive_local();
let expected_local = original_local + Duration::days(14);
let diff = new_local - expected_local;
assert!(diff.num_seconds().abs() < 5);
}
#[test]
fn test_monthly_recurrence() {
let mut t = create_task_due_in_days(-35, "FREQ=MONTHLY");
let advanced = t.advance_recurrence();
assert!(advanced);
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
assert!(new_due > Utc::now());
}
#[test]
fn test_custom_interval() {
let mut t = create_task_due_in_days(-4, "FREQ=DAILY;INTERVAL=3");
let original_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let advanced = t.advance_recurrence();
assert!(advanced);
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let original_local = original_due.with_timezone(&chrono::Local).naive_local();
let new_local = new_due.with_timezone(&chrono::Local).naive_local();
let expected_local = original_local + Duration::days(6);
let diff = new_local - expected_local;
assert!(diff.num_seconds().abs() < 5);
}
#[test]
fn test_complex_weekday_recurrence() {
let mut t = create_task_due_in_days(-10, "FREQ=WEEKLY;BYDAY=MO");
t.advance_recurrence();
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
assert!(new_due > Utc::now());
}
#[test]
fn test_recurrence_preserves_time() {
let mut t = Task::new("Time Test", &HashMap::new(), None);
let dt = Utc::now();
t.due = Some(DateType::Specific(dt));
t.rrule = Some("FREQ=DAILY".to_string());
t.advance_recurrence();
let new_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
assert_eq!(
dt.with_timezone(&chrono::Local).format("%H:%M").to_string(),
new_due
.with_timezone(&chrono::Local)
.format("%H:%M")
.to_string()
);
}
#[test]
fn test_cancel_single_occurrence_daily() {
let t = create_task_due_in_days(-1, "FREQ=DAILY");
let original_due = match t.due.as_ref().unwrap() {
DateType::Specific(d) => *d,
_ => panic!(),
};
let (history, recycled) = t.recycle(TaskStatus::Cancelled, false);
let advanced_task = recycled.expect("Should advance");
assert_eq!(history.status, TaskStatus::Cancelled);
assert_eq!(
history.due.as_ref().unwrap().to_comparison_time(),
original_due
);
assert_eq!(advanced_task.status, TaskStatus::NeedsAction);
assert!(advanced_task.due.as_ref().unwrap().to_comparison_time() > Utc::now());
}