From 0c6ad19600df1238a36e89aafbd9e4963ba4ad0b Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Fri, 6 Dec 2024 20:45:48 +0100 Subject: [PATCH] test(tasks): extract tests from tasks --- src/tasks.rs | 459 +------------------------------------------- src/tasks/tests.rs | 468 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 474 insertions(+), 453 deletions(-) create mode 100644 src/tasks/tests.rs diff --git a/src/tasks.rs b/src/tasks.rs index 4fb1cb6..66b2b05 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,9 +1,11 @@ pub(crate) mod nostr_users; +#[cfg(test)] +mod tests; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; use std::fmt::{Display, Formatter}; use std::iter::{empty, once, FusedIterator}; -use std::ops::{Div, Rem}; +use std::ops::{Deref, Div, Rem}; use std::str::FromStr; use std::time::Duration; @@ -397,13 +399,11 @@ impl TasksRelay { // Parents + /// Move up `count` parent tasks from current position pub(crate) fn up_by(&self, count: usize) -> Option { let pos = self.get_position(); - let mut result = pos.as_ref(); - for _ in 0..count { - result = self.get_parent(result); - } - result.cloned() + if count < 1 { return pos; } + self.traverse_up_from(pos).nth(count - 1)?.parent_id().cloned() } pub(crate) fn get_parent(&self, id: Option<&EventId>) -> Option<&EventId> { @@ -1806,450 +1806,3 @@ impl<'a> Iterator for ParentIterator<'a> { }) } } - -#[cfg(test)] -mod tasks_test { - use super::*; - use std::collections::HashSet; - - fn stub_tasks() -> TasksRelay { - use nostr_sdk::Keys; - use tokio::sync::mpsc; - - let (tx, _rx) = mpsc::channel(16); - TasksRelay::with_sender(EventSender { - url: None, - tx, - keys: Keys::generate(), - queue: Default::default(), - }) - } - - macro_rules! assert_position { - ($tasks:expr, $id:expr $(,)?) => { - assert_eq!($tasks.get_position(), Some($id)) - }; - } - - macro_rules! assert_tasks_visible { - ($tasks:expr, $expected:expr $(,)?) => { - assert_tasks!($tasks, $tasks.visible_tasks(), $expected, - "\nQuick Access: {:?}", - $tasks.quick_access_raw().map(|id| $tasks.get_relative_path(id)).collect_vec()); - }; - } - - macro_rules! assert_tasks_view { - ($tasks:expr, $expected:expr $(,)?) => { - assert_tasks!($tasks, $tasks.viewed_tasks(), $expected, ""); - }; - } - - macro_rules! assert_tasks { - ($tasks:expr, $tasklist:expr, $expected:expr $(, $($arg:tt)*)?) => { - assert_eq!( - $tasklist - .iter() - .map(|t| t.get_id()) - .collect::>(), - HashSet::from_iter($expected.clone()), - "Tasks Visible: {:?}\nExpected: {:?}{}", - $tasklist.iter().map(|t| t.get_id()).map(|id| $tasks.get_relative_path(id)).collect_vec(), - $expected.into_iter().map(|id| $tasks.get_relative_path(id)).collect_vec(), - format!($($($arg)*)?) - ); - }; - } - - #[test] - fn test_recursive_closing() { - let mut tasks = stub_tasks(); - - tasks.custom_time = Some(Timestamp::zero()); - let parent = tasks.make_task_unwrapped("parent #tag1"); - tasks.move_to(Some(parent)); - let sub = tasks.make_task_unwrapped("sub #oi # tag2"); - assert_eq!( - tasks.all_hashtags(), - ["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect() - ); - tasks.make_note("note with #tag3 # yeah"); - let all_tags = ["oi", "tag1", "tag2", "tag3", "yeah"].into_iter().map(Hashtag::from).collect(); - assert_eq!(tasks.all_hashtags(), all_tags); - - tasks.custom_time = Some(Timestamp::now()); - tasks.update_state("Finished #YeaH # oi", State::Done); - assert_eq!( - tasks.get_by_id(&parent).unwrap().list_hashtags().collect_vec(), - ["YeaH", "oi", "tag3", "yeah", "tag1"].map(Hashtag::from) - ); - assert_eq!(tasks.all_hashtags(), all_tags); - - tasks.custom_time = Some(now()); - tasks.update_state("Closing Down", State::Closed); - assert_eq!(tasks.get_by_id(&sub).unwrap().pure_state(), State::Closed); - assert_eq!(tasks.get_by_id(&parent).unwrap().pure_state(), State::Closed); - assert_eq!(tasks.nonclosed_tasks().next(), None); - assert_eq!(tasks.all_hashtags(), Default::default()); - } - - #[test] - fn test_context() { - let mut tasks = stub_tasks(); - tasks.update_tags(["dp", "yeah"].into_iter().map(Hashtag::from)); - assert_eq!(tasks.get_prompt_suffix(), " #dp #yeah"); - tasks.remove_tag("Y"); - assert_eq!(tasks.tags, ["dp"].into_iter().map(Hashtag::from).collect()); - - tasks.set_priority(Some(HIGH_PRIO)); - assert_eq!(tasks.get_prompt_suffix(), " #dp *85"); - let id_hp = tasks.make_task_unwrapped("high prio tagged # tag"); - let hp = tasks.get_by_id(&id_hp).unwrap(); - assert_eq!(hp.priority(), Some(HIGH_PRIO)); - assert_eq!( - hp.list_hashtags().collect_vec(), - vec!["DP", "tag"].into_iter().map(Hashtag::from).collect_vec() - ); - - tasks.state = StateFilter::from("WIP"); - tasks.set_priority(Some(QUICK_PRIO)); - - tasks.make_task_and_enter("another *4", State::Pending); - let task2 = tasks.get_current_task().unwrap(); - assert_eq!(task2.priority(), Some(40)); - assert_eq!(task2.pure_state(), State::Pending); - assert_eq!(task2.state().unwrap().get_label(), "Pending"); - tasks.make_note("*3"); - let task2 = tasks.get_current_task().unwrap(); - assert_eq!(task2.descriptions().next(), None); - assert_eq!(task2.priority(), Some(30)); - let anid = task2.get_id(); - - tasks.custom_time = Some(Timestamp::now() + 1); - let s1 = tasks.make_task_unwrapped("sub1"); - tasks.custom_time = Some(Timestamp::now() + 2); - tasks.set_priority(Some(QUICK_PRIO + 1)); - let s2 = tasks.make_task_unwrapped("sub2"); - let s3 = tasks.make_task_unwrapped("sub3"); - tasks.set_priority(Some(QUICK_PRIO)); - - assert_tasks_visible!(tasks, [s1, s2, s3]); - tasks.state = StateFilter::Default; - assert_tasks_view!(tasks, [s1, s2, s3]); - assert_tasks_visible!(tasks, [id_hp, s1, s2, s3]); - tasks.move_up(); - tasks.set_search_depth(1); - assert_tasks_view!(tasks, [id_hp]); - assert_tasks_visible!(tasks, [s1, s2, s3, id_hp]); - - tasks.set_priority(None); - let s4 = tasks.make_task_with("sub4", [tasks.make_event_tag_from_id(anid, MARKER_PARENT)], true).unwrap(); - assert_eq!(tasks.get_parent(Some(&s4)), Some(&anid)); - assert_tasks_view!(tasks, [anid, id_hp]); - // s2-4 are newest while s2,s3,hp are highest prio - assert_tasks_visible!(tasks, [s4, s2, s3, anid, id_hp]); - - tasks.pubkey = Some(Keys::generate().public_key); - } - - #[test] - fn test_sibling_dependency() { - let mut tasks = stub_tasks(); - let parent = tasks.make_task_unwrapped("parent"); - let sub = tasks.submit( - EventBuilder::new(TASK_KIND, "sub") - .tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]), - ); - assert_tasks_view!(tasks, [parent]); - tasks.track_at(Timestamp::now(), Some(sub)); - assert_eq!(tasks.get_own_events_history().count(), 1); - assert_tasks_view!(tasks, []); - - tasks.make_dependent_sibling("sibling"); - assert_eq!(tasks.len(), 3); - assert_eq!(tasks.viewed_tasks().len(), 2); - } - - #[test] - fn test_bookmarks() { - let mut tasks = stub_tasks(); - let zero = EventId::all_zeros(); - let test = tasks.make_task_unwrapped("test # tag"); - let parent = tasks.make_task_unwrapped("parent"); - assert_eq!(tasks.viewed_tasks().len(), 2); - tasks.move_to(Some(parent)); - let pin = tasks.make_task_unwrapped("pin"); - - tasks.search_depth = 1; - assert_eq!(tasks.filtered_tasks(None, true).len(), 2); - assert_eq!(tasks.filtered_tasks(None, false).len(), 2); - assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0); - assert_eq!(tasks.filtered_tasks(Some(parent), false).len(), 1); - assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0); - assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0); - - tasks.submit( - EventBuilder::new(Kind::Bookmarks, "") - .tags([Tag::event(pin), Tag::event(zero)]) - ); - assert_eq!(tasks.viewed_tasks().len(), 1); - assert_eq!(tasks.filtered_tasks(Some(pin), true).len(), 0); - assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0); - assert_eq!(tasks.filtered_tasks(Some(zero), true).len(), 0); - assert_eq!( - tasks.filtered_tasks(Some(zero), false), - vec![tasks.get_by_id(&pin).unwrap()] - ); - - tasks.move_to(None); - assert_eq!(tasks.view_depth, 0); - assert_tasks_visible!(tasks, [pin, test, parent]); - tasks.set_view_depth(1); - assert_tasks_visible!(tasks, [pin, test]); - tasks.add_tag("tag"); - assert_tasks_visible!(tasks, [test]); - assert_eq!( - tasks.filtered_tasks(None, true), - vec![tasks.get_by_id(&test).unwrap()] - ); - - tasks.submit(EventBuilder::new(Kind::Bookmarks, "")); - assert!(tasks.bookmarks.is_empty()); - tasks.clear_filters(); - assert_tasks_visible!(tasks, [pin, test]); - tasks.set_view_depth(0); - tasks.custom_time = Some(now()); - let mut new = (0..3).map(|t| tasks.make_task_unwrapped(t.to_string().as_str())).collect_vec(); - // Show the newest tasks in quick access and remove old pin - new.extend([test, parent]); - assert_tasks_visible!(tasks, new); - } - - #[test] - fn test_procedures() { - let mut tasks = stub_tasks(); - tasks.make_task_and_enter("proc # tags", State::Procedure); - assert_eq!(tasks.get_own_events_history().count(), 1); - let side = tasks.submit( - EventBuilder::new(TASK_KIND, "side") - .tags([tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)]) - ); - assert_eq!(tasks.viewed_tasks(), Vec::<&Task>::new()); - let sub_id = tasks.make_task_unwrapped("sub"); - assert_tasks_view!(tasks, [sub_id]); - assert_eq!(tasks.len(), 3); - let sub = tasks.get_by_id(&sub_id).unwrap(); - assert_eq!(sub.find_dependents(), Vec::<&EventId>::new()); - } - - #[test] - fn test_filter_or_create() { - let mut tasks = stub_tasks(); - let zeros = EventId::all_zeros(); - let zero = Some(zeros); - - let id1 = tasks.filter_or_create(zero, "newer"); - assert_eq!(tasks.len(), 1); - assert_eq!(tasks.viewed_tasks().len(), 0); - assert_eq!(tasks.get_by_id(&id1.unwrap()).unwrap().parent_id(), zero.as_ref()); - - tasks.move_to(zero); - assert_eq!(tasks.viewed_tasks().len(), 1); - let sub = tasks.make_task_unwrapped("test"); - assert_eq!(tasks.len(), 2); - assert_eq!(tasks.viewed_tasks().len(), 2); - assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero.as_ref()); - - // Do not substring match invisible subtask - let id2 = tasks.filter_or_create(None, "#new-is gold wrapped").unwrap(); - assert_eq!(tasks.len(), 3); - assert_eq!(tasks.viewed_tasks().len(), 2); - let new2 = tasks.get_by_id(&id2).unwrap(); - assert_eq!(new2.props, Default::default()); - - tasks.move_up(); - assert_eq!(tasks.get_matching(tasks.get_position(), "wrapped").len(), 1); - assert_eq!(tasks.get_matching(tasks.get_position(), "new-i").len(), 1); - tasks.filter_or_create(None, "is gold"); - assert_position!(tasks, id2); - - assert_eq!(tasks.get_own_events_history().count(), 3); - // Global match - let idagain = tasks.filter_or_create(None, "newer"); - assert_eq!(idagain, None); - assert_position!(tasks, id1.unwrap()); - assert_eq!(tasks.get_own_events_history().count(), 4); - assert_eq!(tasks.len(), 3); - } - - #[test] - fn test_tracking() { - let mut tasks = stub_tasks(); - let zero = EventId::all_zeros(); - - tasks.track_at(Timestamp::from(0), None); - assert_eq!(tasks.history.len(), 0); - - let almost_now: Timestamp = Timestamp::now() - 12u64; - tasks.track_at(Timestamp::from(11), Some(zero)); - tasks.track_at(Timestamp::from(13), Some(zero)); - assert_position!(tasks, zero); - assert!(tasks.time_tracked(zero) > almost_now.as_u64()); - - // Because None is backtracked by one to avoid conflicts - tasks.track_at(Timestamp::from(22 + 1), None); - assert_eq!(tasks.get_own_events_history().count(), 2); - assert_eq!(tasks.time_tracked(zero), 11); - tasks.track_at(Timestamp::from(22 + 1), Some(zero)); - assert_eq!(tasks.get_own_events_history().count(), 3); - assert!(tasks.time_tracked(zero) > 999); - - let some = tasks.make_task_unwrapped("some"); - tasks.track_at(Timestamp::from(22 + 1), Some(some)); - assert_eq!(tasks.get_own_events_history().count(), 4); - assert_eq!(tasks.time_tracked(zero), 12); - assert!(tasks.time_tracked(some) > 999); - - // TODO test received events - } - - #[test] - #[ignore] - fn test_timestamps() { - let mut tasks = stub_tasks(); - let zero = EventId::all_zeros(); - - tasks.track_at(Timestamp::now() + 100, Some(zero)); - assert_eq!( - timestamps(tasks.get_own_events_history(), &[zero]) - .collect_vec() - .len(), - 2 - ) - // TODO Does not show both future and current tracking properly, need to split by current time - } - - #[test] - fn test_depth() { - let mut tasks = stub_tasks(); - - let t1 = tasks.make_note("t1"); - let activity_t1 = tasks.get_by_id(&t1).unwrap(); - assert!(!activity_t1.is_task()); - assert_eq!(tasks.view_depth, 0); - assert_eq!(activity_t1.pure_state(), State::Open); - debug!("{:?}", tasks); - assert_eq!(tasks.viewed_tasks().len(), 1); - tasks.search_depth = 0; - assert_eq!(tasks.viewed_tasks().len(), 0); - tasks.recurse_activities = false; - assert_eq!(tasks.filtered_tasks(None, false).len(), 1); - - tasks.move_to(Some(t1)); - assert_position!(tasks, t1); - tasks.search_depth = 2; - assert_eq!(tasks.viewed_tasks().len(), 0); - let t11 = tasks.make_task_unwrapped("t11 # tag"); - assert_eq!(tasks.viewed_tasks().len(), 1); - assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11"); - assert_eq!(tasks.get_relative_path(t11), "t11"); - let t12 = tasks.make_task_unwrapped("t12"); - assert_eq!(tasks.viewed_tasks().len(), 2); - - tasks.move_to(Some(t11)); - assert_position!(tasks, t11); - assert_eq!(tasks.viewed_tasks().len(), 0); - let t111 = tasks.make_task_unwrapped("t111"); - assert_tasks_view!(tasks, [t111]); - assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111"); - assert_eq!(tasks.get_relative_path(t111), "t111"); - tasks.view_depth = 2; - assert_tasks_view!(tasks, [t111]); - - assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_all().len(), 1); - assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(0).len(), 1); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(1).len(), 3); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(2).len(), 4); - assert_eq!(ChildIterator::from(&tasks, t1).get_depth(9).len(), 4); - assert_eq!(ChildIterator::from(&tasks, t1).get_all().len(), 4); - - tasks.move_to(Some(t1)); - assert_position!(tasks, t1); - assert_eq!(tasks.get_own_events_history().count(), 3); - assert_eq!(tasks.get_relative_path(t111), "t11>t111"); - assert_eq!(tasks.view_depth, 2); - tasks.set_search_depth(1); - assert_tasks_view!(tasks, [t111, t12]); - tasks.set_view_depth(0); - assert_tasks_view!(tasks, [t11, t12]); - tasks.set_view(vec![t11]); - assert_tasks_view!(tasks, [t11]); - tasks.set_view_depth(1); - assert_tasks_view!(tasks, [t111]); - tasks.set_search_depth(2); // resets view - assert_tasks_view!(tasks, [t111, t12]); - tasks.set_view_depth(0); - assert_tasks_view!(tasks, [t11, t12]); - - tasks.move_to(None); - tasks.recurse_activities = true; - assert_tasks_view!(tasks, [t11, t12]); - tasks.recurse_activities = false; - assert_tasks_view!(tasks, [t1]); - tasks.view_depth = 1; - assert_tasks_view!(tasks, [t11, t12]); - tasks.view_depth = 2; - assert_tasks_view!(tasks, [t111, t12]); - tasks.view_depth = 9; - assert_tasks_view!(tasks, [t111, t12]); - - tasks.add_tag("tag"); - tasks.view_depth = 0; - assert_tasks_view!(tasks, [t11]); - tasks.search_depth = 0; - assert_eq!(tasks.view, []); - assert_tasks_view!(tasks, []); - } - - #[test] - fn test_empty_task_title_fallback_to_id() { - let mut tasks = stub_tasks(); - - let empty = tasks.make_task_unchecked("", vec![]); - let empty_task = tasks.get_by_id(&empty).unwrap(); - let empty_id = empty_task.get_id().to_string(); - assert_eq!(empty_task.get_title(), empty_id); - assert_eq!(tasks.get_task_path(Some(empty)), empty_id); - } - - #[test] - fn test_short_task() { - let mut tasks = stub_tasks(); - let str = " # one"; - assert_eq!(extract_tags(str, &tasks.users), ("".to_string(), vec![to_hashtag_tag("one")])); - assert_eq!(tasks.make_task(str), None); - } - - #[test] - fn test_unknown_task() { - let mut tasks = stub_tasks(); - - let zero = EventId::all_zeros(); - assert_eq!(tasks.get_task_path(Some(zero)), zero.to_string()); - tasks.move_to(Some(zero)); - let dangling = tasks.make_task_unwrapped("test"); - assert_eq!( - tasks.get_task_path(Some(dangling)), - "0000000000000000000000000000000000000000000000000000000000000000>test" - ); - assert_eq!(tasks.get_relative_path(dangling), "test"); - } - - #[allow(dead_code)] // #[test] - fn test_itertools() { - use itertools::Itertools; - assert_eq!("test toast".split(' ').collect_vec().len(), 3); - assert_eq!("test toast".split_ascii_whitespace().collect_vec().len(), 2); - } -} diff --git a/src/tasks/tests.rs b/src/tasks/tests.rs new file mode 100644 index 0000000..28fb29d --- /dev/null +++ b/src/tasks/tests.rs @@ -0,0 +1,468 @@ +use super::*; +use crate::event_sender::EventSender; +use crate::hashtag::Hashtag; +use crate::kinds::{extract_tags, to_hashtag_tag, TASK_KIND}; +use crate::task::{State, Task, MARKER_DEPENDS, MARKER_PARENT}; +use itertools::Itertools; +use nostr_sdk::{EventBuilder, EventId, Keys, Kind, Tag, Timestamp}; +use std::collections::HashSet; + +fn stub_tasks() -> TasksRelay { + use nostr_sdk::Keys; + use tokio::sync::mpsc; + + let (tx, _rx) = mpsc::channel(16); + TasksRelay::with_sender(EventSender { + url: None, + tx, + keys: Keys::generate(), + queue: Default::default(), + }) +} + +macro_rules! assert_position { + ($tasks:expr, $id:expr $(,)?) => { + let pos = $tasks.get_position(); + assert_eq!(pos, Some($id), + "Current: {:?}\nExpected: {:?}", + $tasks.get_task_path(pos), + $tasks.get_task_path(Some($id)), + ) + }; + } + +macro_rules! assert_tasks_visible { + ($tasks:expr, $expected:expr $(,)?) => { + assert_tasks!($tasks, $tasks.visible_tasks(), $expected, + "\nQuick Access: {:?}", + $tasks.quick_access_raw().map(|id| $tasks.get_task_path(Some(id))).collect_vec()); + }; + } + +macro_rules! assert_tasks_view { + ($tasks:expr, $expected:expr $(,)?) => { + assert_tasks!($tasks, $tasks.viewed_tasks(), $expected, ""); + }; + } + +macro_rules! assert_tasks { + ($tasks:expr, $tasklist:expr, $expected:expr $(, $($arg:tt)*)?) => { + assert_eq!( + $tasklist + .iter() + .map(|t| t.get_id()) + .collect::>(), + HashSet::from_iter($expected.clone()), + "Tasks Visible: {:?}\nExpected: {:?}{}", + $tasklist.iter().map(|t| t.get_id()).map(|id| $tasks.get_task_path(Some(id))).collect_vec(), + $expected.into_iter().map(|id| $tasks.get_task_path(Some(id))).collect_vec(), + format!($($($arg)*)?) + ); + }; + } + +#[test] +fn test_recursive_closing() { + let mut tasks = stub_tasks(); + + tasks.custom_time = Some(Timestamp::zero()); + let parent = tasks.make_task_unwrapped("parent #tag1"); + tasks.move_to(Some(parent)); + let sub = tasks.make_task_unwrapped("sub #oi # tag2"); + assert_eq!( + tasks.all_hashtags(), + ["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect() + ); + tasks.make_note("note with #tag3 # yeah"); + let all_tags = ["oi", "tag1", "tag2", "tag3", "yeah"].into_iter().map(Hashtag::from).collect(); + assert_eq!(tasks.all_hashtags(), all_tags); + + tasks.custom_time = Some(Timestamp::now()); + tasks.update_state("Finished #YeaH # oi", State::Done); + assert_eq!( + tasks.get_by_id(&parent).unwrap().list_hashtags().collect_vec(), + ["YeaH", "oi", "tag3", "yeah", "tag1"].map(Hashtag::from) + ); + assert_eq!(tasks.all_hashtags(), all_tags); + + tasks.custom_time = Some(now()); + tasks.update_state("Closing Down", State::Closed); + assert_eq!(tasks.get_by_id(&sub).unwrap().pure_state(), State::Closed); + assert_eq!(tasks.get_by_id(&parent).unwrap().pure_state(), State::Closed); + assert_eq!(tasks.nonclosed_tasks().next(), None); + assert_eq!(tasks.all_hashtags(), Default::default()); +} + +#[test] +fn test_context() { + let mut tasks = stub_tasks(); + tasks.update_tags(["dp", "yeah"].into_iter().map(Hashtag::from)); + assert_eq!(tasks.get_prompt_suffix(), " #dp #yeah"); + tasks.remove_tag("Y"); + assert_eq!(tasks.tags, ["dp"].into_iter().map(Hashtag::from).collect()); + + tasks.set_priority(Some(HIGH_PRIO)); + assert_eq!(tasks.get_prompt_suffix(), " #dp *85"); + let id_hp = tasks.make_task_unwrapped("high prio tagged # tag"); + let hp = tasks.get_by_id(&id_hp).unwrap(); + assert_eq!(hp.priority(), Some(HIGH_PRIO)); + assert_eq!( + hp.list_hashtags().collect_vec(), + vec!["DP", "tag"].into_iter().map(Hashtag::from).collect_vec() + ); + + tasks.state = StateFilter::from("WIP"); + tasks.set_priority(Some(QUICK_PRIO)); + + tasks.make_task_and_enter("another *4", State::Pending); + let task2 = tasks.get_current_task().unwrap(); + assert_eq!(task2.priority(), Some(40)); + assert_eq!(task2.pure_state(), State::Pending); + assert_eq!(task2.state().unwrap().get_label(), "Pending"); + tasks.make_note("*3"); + let task2 = tasks.get_current_task().unwrap(); + assert_eq!(task2.descriptions().next(), None); + assert_eq!(task2.priority(), Some(30)); + let anid = task2.get_id(); + + tasks.custom_time = Some(Timestamp::now() + 1); + let s1 = tasks.make_task_unwrapped("sub1"); + tasks.custom_time = Some(Timestamp::now() + 2); + tasks.set_priority(Some(QUICK_PRIO + 1)); + let s2 = tasks.make_task_unwrapped("sub2"); + let s3 = tasks.make_task_unwrapped("sub3"); + tasks.set_priority(Some(QUICK_PRIO)); + + assert_tasks_visible!(tasks, [s1, s2, s3]); + tasks.state = StateFilter::Default; + assert_tasks_view!(tasks, [s1, s2, s3]); + assert_tasks_visible!(tasks, [id_hp, s1, s2, s3]); + tasks.move_up(); + tasks.set_search_depth(1); + assert_tasks_view!(tasks, [id_hp]); + assert_tasks_visible!(tasks, [s1, s2, s3, id_hp]); + + tasks.set_priority(None); + let s4 = tasks.make_task_with("sub4", [tasks.make_event_tag_from_id(anid, MARKER_PARENT)], true).unwrap(); + assert_eq!(tasks.get_parent(Some(&s4)), Some(&anid)); + assert_tasks_view!(tasks, [anid, id_hp]); + // s2-4 are newest while s2,s3,hp are highest prio + assert_tasks_visible!(tasks, [s4, s2, s3, anid, id_hp]); + + tasks.pubkey = Some(Keys::generate().public_key); +} + +#[test] +fn test_sibling_dependency() { + let mut tasks = stub_tasks(); + let parent = tasks.make_task_unwrapped("parent"); + let sub = tasks.submit( + EventBuilder::new(TASK_KIND, "sub") + .tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]), + ); + assert_tasks_view!(tasks, [parent]); + tasks.track_at(Timestamp::now(), Some(sub)); + assert_eq!(tasks.get_own_events_history().count(), 1); + assert_tasks_view!(tasks, []); + + tasks.make_dependent_sibling("sibling"); + assert_eq!(tasks.len(), 3); + assert_eq!(tasks.viewed_tasks().len(), 2); +} + +#[test] +fn test_bookmarks() { + let mut tasks = stub_tasks(); + let zero = EventId::all_zeros(); + let test = tasks.make_task_unwrapped("test # tag"); + let parent = tasks.make_task_unwrapped("parent"); + assert_eq!(tasks.viewed_tasks().len(), 2); + tasks.move_to(Some(parent)); + let pin = tasks.make_task_unwrapped("pin"); + + tasks.search_depth = 1; + assert_eq!(tasks.filtered_tasks(None, true).len(), 2); + assert_eq!(tasks.filtered_tasks(None, false).len(), 2); + assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0); + assert_eq!(tasks.filtered_tasks(Some(parent), false).len(), 1); + assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0); + assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0); + + tasks.submit( + EventBuilder::new(Kind::Bookmarks, "") + .tags([Tag::event(pin), Tag::event(zero)]) + ); + assert_eq!(tasks.viewed_tasks().len(), 1); + assert_eq!(tasks.filtered_tasks(Some(pin), true).len(), 0); + assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0); + assert_eq!(tasks.filtered_tasks(Some(zero), true).len(), 0); + assert_eq!( + tasks.filtered_tasks(Some(zero), false), + vec![tasks.get_by_id(&pin).unwrap()] + ); + + tasks.move_to(None); + assert_eq!(tasks.view_depth, 0); + assert_tasks_visible!(tasks, [pin, test, parent]); + tasks.set_view_depth(1); + assert_tasks_visible!(tasks, [pin, test]); + tasks.add_tag("tag"); + assert_tasks_visible!(tasks, [test]); + assert_eq!( + tasks.filtered_tasks(None, true), + vec![tasks.get_by_id(&test).unwrap()] + ); + + tasks.submit(EventBuilder::new(Kind::Bookmarks, "")); + assert!(tasks.bookmarks.is_empty()); + tasks.clear_filters(); + assert_tasks_visible!(tasks, [pin, test]); + tasks.set_view_depth(0); + tasks.custom_time = Some(now()); + let mut new = (0..3).map(|t| tasks.make_task_unwrapped(t.to_string().as_str())).collect_vec(); + // Show the newest tasks in quick access and remove old pin + new.extend([test, parent]); + assert_tasks_visible!(tasks, new); +} + +#[test] +fn test_procedures() { + let mut tasks = stub_tasks(); + tasks.make_task_and_enter("proc # tags", State::Procedure); + assert_eq!(tasks.get_own_events_history().count(), 1); + let side = tasks.submit( + EventBuilder::new(TASK_KIND, "side") + .tags([tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)]) + ); + assert_eq!(tasks.viewed_tasks(), Vec::<&Task>::new()); + let sub_id = tasks.make_task_unwrapped("sub"); + assert_tasks_view!(tasks, [sub_id]); + assert_eq!(tasks.len(), 3); + let sub = tasks.get_by_id(&sub_id).unwrap(); + assert_eq!(sub.find_dependents(), Vec::<&EventId>::new()); +} + +#[test] +fn test_filter_or_create() { + let mut tasks = stub_tasks(); + let zeros = EventId::all_zeros(); + let zero = Some(zeros); + + let id1 = tasks.filter_or_create(zero, "newer"); + assert_eq!(tasks.len(), 1); + assert_eq!(tasks.viewed_tasks().len(), 0); + assert_eq!(tasks.get_by_id(&id1.unwrap()).unwrap().parent_id(), zero.as_ref()); + + tasks.move_to(zero); + assert_eq!(tasks.viewed_tasks().len(), 1); + let sub = tasks.make_task_unwrapped("test"); + assert_eq!(tasks.len(), 2); + assert_eq!(tasks.viewed_tasks().len(), 2); + assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero.as_ref()); + + // Do not substring match invisible subtask + let id2 = tasks.filter_or_create(None, "#new-is gold wrapped").unwrap(); + assert_eq!(tasks.len(), 3); + assert_eq!(tasks.viewed_tasks().len(), 2); + let new2 = tasks.get_by_id(&id2).unwrap(); + assert_eq!(new2.props, Default::default()); + + tasks.move_up(); + assert_eq!(tasks.get_matching(tasks.get_position(), "wrapped").len(), 1); + assert_eq!(tasks.get_matching(tasks.get_position(), "new-i").len(), 1); + tasks.filter_or_create(None, "is gold"); + assert_position!(tasks, id2); + + assert_eq!(tasks.get_own_events_history().count(), 3); + // Global match + assert_eq!(tasks.filter_or_create(None, "newer"), None); + assert_position!(tasks, id1.unwrap()); + assert_eq!(tasks.get_own_events_history().count(), 4); + assert_eq!(tasks.len(), 3); +} + +#[test] +fn test_tracking() { + let mut tasks = stub_tasks(); + let zero = EventId::all_zeros(); + + tasks.track_at(Timestamp::from(0), None); + assert_eq!(tasks.history.len(), 0); + + let almost_now: Timestamp = Timestamp::now() - 12u64; + tasks.track_at(Timestamp::from(11), Some(zero)); + tasks.track_at(Timestamp::from(13), Some(zero)); + assert_position!(tasks, zero); + assert!(tasks.time_tracked(zero) > almost_now.as_u64()); + + // Because None is backtracked by one to avoid conflicts + tasks.track_at(Timestamp::from(22 + 1), None); + assert_eq!(tasks.get_own_events_history().count(), 2); + assert_eq!(tasks.time_tracked(zero), 11); + tasks.track_at(Timestamp::from(22 + 1), Some(zero)); + assert_eq!(tasks.get_own_events_history().count(), 3); + assert!(tasks.time_tracked(zero) > 999); + + let some = tasks.make_task_unwrapped("some"); + tasks.track_at(Timestamp::from(22 + 1), Some(some)); + assert_eq!(tasks.get_own_events_history().count(), 4); + assert_eq!(tasks.time_tracked(zero), 12); + assert!(tasks.time_tracked(some) > 999); + + // TODO test received events +} + +#[test] +#[ignore] +fn test_timestamps() { + let mut tasks = stub_tasks(); + let zero = EventId::all_zeros(); + + tasks.track_at(Timestamp::now() + 100, Some(zero)); + assert_eq!( + timestamps(tasks.get_own_events_history(), &[zero]) + .collect_vec() + .len(), + 2 + ) + // TODO Does not show both future and current tracking properly, need to split by current time +} + +#[test] +fn test_depth() { + let mut tasks = stub_tasks(); + + let t1 = tasks.make_note("t1"); + let activity_t1 = tasks.get_by_id(&t1).unwrap(); + assert!(!activity_t1.is_task()); + assert_eq!(tasks.view_depth, 0); + assert_eq!(activity_t1.pure_state(), State::Open); + assert_eq!(tasks.viewed_tasks().len(), 1); + tasks.search_depth = 0; + assert_eq!(tasks.viewed_tasks().len(), 0); + tasks.recurse_activities = false; + assert_eq!(tasks.filtered_tasks(None, false).len(), 1); + + tasks.move_to(Some(t1)); + assert_position!(tasks, t1); + tasks.search_depth = 2; + assert_eq!(tasks.viewed_tasks().len(), 0); + let t11 = tasks.make_task_unwrapped("t11 # tag"); + assert_eq!(tasks.viewed_tasks().len(), 1); + assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11"); + assert_eq!(tasks.get_relative_path(t11), "t11"); + let t12 = tasks.make_task_unwrapped("t12"); + assert_eq!(tasks.viewed_tasks().len(), 2); + + tasks.move_to(Some(t11)); + assert_position!(tasks, t11); + assert_eq!(tasks.viewed_tasks().len(), 0); + let t111 = tasks.make_task_unwrapped("t111"); + assert_tasks_view!(tasks, [t111]); + assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111"); + assert_eq!(tasks.get_relative_path(t111), "t111"); + tasks.view_depth = 2; + assert_tasks_view!(tasks, [t111]); + + assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_all().len(), 1); + assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1); + assert_eq!(ChildIterator::from(&tasks, t1).get_depth(0).len(), 1); + assert_eq!(ChildIterator::from(&tasks, t1).get_depth(1).len(), 3); + assert_eq!(ChildIterator::from(&tasks, t1).get_depth(2).len(), 4); + assert_eq!(ChildIterator::from(&tasks, t1).get_depth(9).len(), 4); + assert_eq!(ChildIterator::from(&tasks, t1).get_all().len(), 4); + + tasks.move_up(); + assert_position!(tasks, t1); + assert_eq!(tasks.get_own_events_history().count(), 3); + assert_eq!(tasks.get_relative_path(t111), "t11>t111"); + assert_eq!(tasks.view_depth, 2); + tasks.set_search_depth(1); + assert_tasks_view!(tasks, [t111, t12]); + tasks.set_view_depth(0); + assert_tasks_view!(tasks, [t11, t12]); + tasks.set_view(vec![t11]); + assert_tasks_view!(tasks, [t11]); + tasks.set_view_depth(1); + assert_tasks_view!(tasks, [t111]); + tasks.set_search_depth(2); // resets view + assert_tasks_view!(tasks, [t111, t12]); + tasks.set_view_depth(0); + assert_tasks_view!(tasks, [t11, t12]); + + tasks.move_to(None); + tasks.recurse_activities = true; + assert_tasks_view!(tasks, [t11, t12]); + tasks.recurse_activities = false; + assert_tasks_view!(tasks, [t1]); + tasks.view_depth = 1; + assert_tasks_view!(tasks, [t11, t12]); + tasks.view_depth = 2; + assert_tasks_view!(tasks, [t111, t12]); + tasks.view_depth = 9; + assert_tasks_view!(tasks, [t111, t12]); + + tasks.add_tag("tag"); + assert_eq!(tasks.get_prompt_suffix(), " #tag"); + tasks.view_depth = 0; + assert_tasks_view!(tasks, [t11]); + tasks.search_depth = 0; + assert_eq!(tasks.view, []); + assert_tasks_view!(tasks, []); + + // Upwards + tasks.move_to(Some(t111)); + assert_eq!(tasks.get_task_path(tasks.get_position()), "t1>t11>t111"); + assert_eq!(tasks.up_by(1), Some(t11)); + assert_eq!(tasks.up_by(2), Some(t1)); + assert_eq!(tasks.up_by(4), None); + tasks.move_to(Some(t12)); + assert_eq!(tasks.up_by(1), Some(t1)); + assert_eq!(tasks.up_by(2), None); +} + +#[test] +fn test_empty_task_title_fallback_to_id() { + let mut tasks = stub_tasks(); + + let empty = tasks.make_task_unchecked("", vec![]); + let empty_task = tasks.get_by_id(&empty).unwrap(); + let empty_id = empty_task.get_id().to_string(); + assert_eq!(empty_task.get_title(), empty_id); + assert_eq!(tasks.get_task_path(Some(empty)), empty_id); +} + +#[test] +fn test_short_task() { + let mut tasks = stub_tasks(); + let str = " # one"; + assert_eq!(extract_tags(str, &tasks.users), ("".to_string(), vec![to_hashtag_tag("one")])); + assert_eq!(tasks.make_task(str), None); +} + +#[test] +fn test_unknown_task() { + let mut tasks = stub_tasks(); + + let zero = EventId::all_zeros(); + assert_eq!(tasks.get_task_path(Some(zero)), zero.to_string()); + tasks.move_to(Some(zero)); + let dangling = tasks.make_task_unwrapped("test"); + assert_eq!( + tasks.get_task_path(Some(dangling)), + "0000000000000000000000000000000000000000000000000000000000000000>test" + ); + assert_eq!(tasks.get_relative_path(dangling), "test"); + + tasks.move_to(Some(dangling)); + assert_eq!(tasks.up_by(0), Some(dangling)); + assert_eq!(tasks.up_by(1), Some(zero)); + assert_eq!(tasks.up_by(2), None); +} + +#[allow(dead_code)] // #[test] +fn test_itertools() { + use itertools::Itertools; + assert_eq!("test toast".split(' ').collect_vec().len(), 3); + assert_eq!("test toast".split_ascii_whitespace().collect_vec().len(), 2); +}