use cfait::config::SortPreset;
use cfait::context::TestContext;
use cfait::model::{DateType, Task, TaskStatus};
use cfait::store::{FilterOptions, TaskStore};
use chrono::Utc;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[test]
fn test_blocked_tasks_skip_urgent_rank() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut urgent_task = Task::new("Urgent Task", &aliases, None);
urgent_task.priority = 1;
urgent_task.calendar_href = "cal1".to_string();
urgent_task.uid = "urgent".to_string();
let mut blocked_urgent = Task::new("Blocked Urgent #blocked", &aliases, None);
blocked_urgent.priority = 1;
blocked_urgent.calendar_href = "cal1".to_string();
blocked_urgent.uid = "blocked_urgent".to_string();
blocked_urgent.categories.push("blocked".to_string());
let mut normal_task = Task::new("Normal Task", &aliases, None);
normal_task.priority = 5;
normal_task.calendar_href = "cal1".to_string();
normal_task.uid = "normal".to_string();
store.add_task(urgent_task.clone());
store.add_task(blocked_urgent.clone());
store.add_task(normal_task.clone());
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "",
hide_completed_global: false,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
let urgent_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "urgent"
} else {
false
}
});
let blocked_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "blocked_urgent"
} else {
false
}
});
let normal_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "normal"
} else {
false
}
});
assert!(urgent_pos.unwrap() < normal_pos.unwrap());
assert!(blocked_pos.unwrap() > urgent_pos.unwrap());
}
#[test]
fn test_blocked_tasks_skip_due_soon_rank() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let now = Utc::now();
let mut due_soon = Task::new("Due Soon", &aliases, None);
due_soon.due = Some(DateType::Specific(now + chrono::Duration::days(3)));
due_soon.calendar_href = "cal1".to_string();
due_soon.uid = "due_soon".to_string();
let mut blocked_due_soon = Task::new("Blocked Due Soon #blocked", &aliases, None);
blocked_due_soon.due = Some(DateType::Specific(now + chrono::Duration::days(2)));
blocked_due_soon.calendar_href = "cal1".to_string();
blocked_due_soon.uid = "blocked_due_soon".to_string();
blocked_due_soon.categories.push("blocked".to_string());
let mut due_later = Task::new("Due Later", &aliases, None);
due_later.due = Some(DateType::Specific(now + chrono::Duration::days(30)));
due_later.calendar_href = "cal1".to_string();
due_later.uid = "due_later".to_string();
store.add_task(due_soon.clone());
store.add_task(blocked_due_soon.clone());
store.add_task(due_later.clone());
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "",
hide_completed_global: false,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
let due_soon_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "due_soon"
} else {
false
}
});
let blocked_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "blocked_due_soon"
} else {
false
}
});
let due_later_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "due_later"
} else {
false
}
});
assert!(due_soon_pos.unwrap() < due_later_pos.unwrap());
assert!(blocked_pos.unwrap() > due_soon_pos.unwrap());
}
#[test]
fn test_blocked_tasks_skip_started_rank() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut started = Task::new("Started Task", &aliases, None);
started.status = TaskStatus::InProcess;
started.calendar_href = "cal1".to_string();
started.uid = "started".to_string();
let mut blocked_started = Task::new("Blocked Started #blocked", &aliases, None);
blocked_started.status = TaskStatus::InProcess;
blocked_started.calendar_href = "cal1".to_string();
blocked_started.uid = "blocked_started".to_string();
blocked_started.categories.push("blocked".to_string());
let mut normal = Task::new("Normal Task", &aliases, None);
normal.calendar_href = "cal1".to_string();
normal.uid = "normal".to_string();
store.add_task(started.clone());
store.add_task(blocked_started.clone());
store.add_task(normal.clone());
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "",
hide_completed_global: false,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
let started_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "started"
} else {
false
}
});
let blocked_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "blocked_started"
} else {
false
}
});
let normal_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "normal"
} else {
false
}
});
assert!(started_pos.unwrap() < normal_pos.unwrap());
assert!(blocked_pos.unwrap() > started_pos.unwrap());
}
#[test]
fn test_dependency_blocked_tasks_also_skip_ranks() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut dep_task = Task::new("Dependency Task", &aliases, None);
dep_task.status = TaskStatus::NeedsAction;
dep_task.calendar_href = "cal1".to_string();
dep_task.uid = "dep".to_string();
let mut blocked_by_dep = Task::new("Blocked by Dependency", &aliases, None);
blocked_by_dep.priority = 1;
blocked_by_dep.dependencies.push(dep_task.uid.clone());
blocked_by_dep.calendar_href = "cal1".to_string();
blocked_by_dep.uid = "blocked_by_dep".to_string();
let mut urgent = Task::new("Normal Urgent", &aliases, None);
urgent.priority = 1;
urgent.calendar_href = "cal1".to_string();
urgent.uid = "urgent".to_string();
store.add_task(dep_task.clone());
store.add_task(blocked_by_dep.clone());
store.add_task(urgent.clone());
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "",
hide_completed_global: false,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
let urgent_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "urgent"
} else {
false
}
});
let blocked_pos = filtered.iter().position(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == "blocked_by_dep"
} else {
false
}
});
assert!(urgent_pos.unwrap() < blocked_pos.unwrap());
assert!(store.is_blocked(&blocked_by_dep));
}
#[test]
fn test_blocked_tag_is_recognized() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut task = Task::new("Task #blocked", &aliases, None);
task.categories.push("blocked".to_string());
task.calendar_href = "cal1".to_string();
store.add_task(task.clone());
assert!(store.is_blocked(&task));
let mut unblocked = task.clone();
unblocked.categories.clear();
store.add_task(unblocked.clone());
assert!(!store.is_blocked(&unblocked));
}
#[test]
fn test_is_ready_filters_manually_blocked_tasks() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut blocked_task = Task::new("Blocked Task #blocked", &aliases, None);
blocked_task.calendar_href = "cal1".to_string();
blocked_task.categories.push("blocked".to_string());
let mut unblocked_task = Task::new("Unblocked Task", &aliases, None);
unblocked_task.calendar_href = "cal1".to_string();
store.add_task(blocked_task);
store.add_task(unblocked_task);
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "is:ready",
hide_completed_global: true,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 5,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
assert!(!filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Blocked Task")
} else {
false
}
}));
assert!(filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Unblocked Task")
} else {
false
}
}));
}
#[test]
fn test_is_blocked_filter_shows_only_blocked() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx);
let aliases = HashMap::new();
let mut dep_task = Task::new("Dependency Task", &aliases, None);
dep_task.status = TaskStatus::NeedsAction;
dep_task.calendar_href = "cal1".to_string();
dep_task.uid = "dep".to_string();
let mut blocked_by_dep = Task::new("Blocked by Dependency", &aliases, None);
blocked_by_dep.dependencies.push(dep_task.uid.clone());
blocked_by_dep.calendar_href = "cal1".to_string();
let mut manually_blocked = Task::new("Manually Blocked #blocked", &aliases, None);
manually_blocked.calendar_href = "cal1".to_string();
manually_blocked.categories.push("blocked".to_string());
let mut unblocked = Task::new("Unblocked Task", &aliases, None);
unblocked.calendar_href = "cal1".to_string();
store.add_task(dep_task);
store.add_task(blocked_by_dep.clone());
store.add_task(manually_blocked.clone());
store.add_task(unblocked);
let options = FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "is:blocked",
hide_completed_global: false,
hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 7,
urgent_prio: 5,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
sort_preset: SortPreset::default(),
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
search_collapsed_tasks: &HashSet::new(),
};
let filtered = store.filter(options).items;
assert!(filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Blocked by Dependency")
} else {
false
}
}));
assert!(filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Manually Blocked")
} else {
false
}
}));
assert!(!filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Unblocked Task")
} else {
false
}
}));
assert!(!filtered.iter().any(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.summary.contains("Dependency Task")
} else {
false
}
}));
}