diff --git a/src/lib.rs b/src/lib.rs index fb13107..01ea6cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,12 +25,6 @@ pub enum Urgency { High, } -#[test] -fn urgency_ord_does_the_right_thing() { - assert!(Urgency::High > Urgency::Normal); - assert!(Urgency::Normal > Urgency::Low); -} - /// The entire to-do list. #[derive(Debug, Default)] pub struct TodoList { @@ -44,6 +38,10 @@ impl TodoList { TodoList::default() } + pub fn add_full_item(&mut self, item: &Item) { + self.items.push(item.clone()); + } + /// Adds a new item with default settings to the to-do list. pub fn add(&mut self, item_text: String) { todo!("Insert code here to add an item to the todo list") @@ -75,7 +73,17 @@ impl TodoList { urgency_at_least: Option, urgency_at_most: Option, ) -> Vec<(usize, Item)> { - todo!("Insert code here to search for items based on given criteria.") + self.items.iter().enumerate().filter(|(_, item)| { + (urgency_at_least.is_some() && // Some > None, add this check + Some(&item.urgency) >= urgency_at_least.as_ref()) || + // no need to worry about is_some check as Some > None + Some(&item.urgency) <= urgency_at_most.as_ref() || + Some(&item.text) == text_substr.as_ref() || + Some(&item.checked) == checked.as_ref() || + item.due_at.map_or(false, + |date| Some(date - Utc::now()) <= due_in_less_than + ) + }).map(|(ind, item)| (ind, (*item).clone())).collect() } /// Sets whether a to-do item is checked off. @@ -98,3 +106,247 @@ impl TodoList { todo!("Insert code here to set the urgency of an item.") } } + +#[cfg(test)] +mod tests { + use super::*; + + /* urgency tests */ + #[test] + fn urgency_ord_does_the_right_thing() { + assert!(Urgency::High > Urgency::Normal); + assert!(Urgency::Normal > Urgency::Low); + } + + /* search tests */ + fn setup_search_tests(add: bool) -> (Vec, TodoList) { + let mut list = TodoList::new(); + let mut item_vec: Vec = vec![]; + if add { + let item1 = Item { + text: String::from("I am item1!"), + checked: false, + due_at: None, + urgency: Urgency::Low + }; + + let item2 = Item { + text: String::from("I am item2!"), + checked: true, + due_at: Some(Utc::now() + Duration::seconds(100000)), // 27 hours roughly + urgency: Urgency::High + }; + + let item3 = Item { + text: String::from("I am item3!"), + checked: false, + due_at: None, + urgency: Urgency::Normal + }; + + item_vec.extend([item1, item2, item3]); + for item in item_vec.iter() { + list.add_full_item(&item); + } + }; + + (item_vec, list) + } + + fn compare_items(item1: &Item, item2: &Item) { + assert!(item1.text == item2.text); + assert!(item1.urgency == item2.urgency); + assert!(item1.checked == item2.checked); + assert!(item1.due_at == item2.due_at); + } + + fn compare_item_lists(list1: &Vec<(usize, Item)>, list2: &Vec) { + assert!(list1.len() == list2.len()); + for (ind, item) in list1.iter() { + assert!(ind < &list2.len()); + let act_item = &list2[*ind]; + compare_items(item, act_item); + } + } + + #[test] + fn none_search_on_empty_list_returns_nothing() { + let (_, list) = setup_search_tests(false); + + let empty_list = list.search(None, None, None, None, None); + + assert!(empty_list.len() == 0); + } + + #[test] + fn non_none_search_on_empty_list_returns_nothing() { + let (_, list) = setup_search_tests(false); + + let empty_list = list.search( + Some(String::from("nothing here!")), + None, None, None, None + ); + + assert!(empty_list.len() == 0); + } + + #[test] + fn none_search_on_full_list_returns_nothing() { + let (_, list) = setup_search_tests(true); + + let empty_list = list.search(None, None, None, None, None); + + assert!(empty_list.len() == 0); + } + + #[test] + fn search_on_full_list_with_no_matchable_items_returns_nothing() { + let (_, list) = setup_search_tests(true); + + let empty_list = list.search( + Some(String::from("nothing here!")), + None, None, None, None + ); + + assert!(empty_list.len() == 0); + } + + #[test] + fn search_on_list_with_one_matching_item() { + let (items, list) = setup_search_tests(true); + + let one_list = list.search( + Some(String::from("I am item1!")), + None, None, None, None + ); + + assert!(one_list.len() == 1); + + let (ind, item) = &one_list[0]; + let (act_ind, act_item) = (0, &items[0]); + assert!(*ind == act_ind); + compare_items(&item, act_item); + } + + #[test] + fn search_on_list_matching_all() { + let (items, list) = setup_search_tests(true); + + let all_list = list.search( + None, None, None, Some(Urgency::Low), Some(Urgency::High) + ); + + compare_item_lists(&all_list, &items); + } + + #[test] + fn search_returned_values_arent_modified() { + let (_, list) = setup_search_tests(true); + + let all_list = list.search( + None, None, None, Some(Urgency::Low), Some(Urgency::High) + ); + + compare_item_lists(&all_list, &list.items); + } + + #[test] + fn search_on_string() { + let (items, list) = setup_search_tests(true); + + let string_list = list.search( + Some(String::from("I am item2!")), None, None, None, None + ); + + assert!(string_list.len() == 1); + let (ind, item) = &string_list[0]; + assert!(*ind < items.len()); + assert!(*ind == 1); + compare_items(&item, &items[*ind]); + } + + #[test] + fn search_on_checked() { + let (items, list) = setup_search_tests(true); + + let checked_list = list.search( + None, Some(true), None, None, None + ); + + assert!(checked_list.len() == 1); + let (ind, item) = &checked_list[0]; + assert!(*ind < items.len()); + assert!(*ind == 1); + compare_items(&item, &items[*ind]); + } + + #[test] + fn search_on_due_before() { + let (items, list) = setup_search_tests(true); + + let due_list = list.search( + None, None, Some(Duration::seconds(200000)), None, None + ); + assert!(due_list.len() == 1); + let (ind, item) = &due_list[0]; + assert!(*ind < items.len()); + assert!(*ind == 1); + compare_items(&item, &items[*ind]); + } + + #[test] + fn search_on_least_urgency() { + let (items, list) = setup_search_tests(true); + + let least_list = list.search( + None, None, None, Some(Urgency::Normal), None + ); + + assert!(least_list.len() == 2); + let (ind1, item1) = &least_list[0]; + let (ind2, item2) = &least_list[1]; + assert!(*ind1 < items.len() && *ind2 < items.len()); + assert!(*ind1 == 1 && *ind2 == 2); + compare_items(&item1, &items[*ind1]); + compare_items(&item2, &items[*ind2]); + } + + #[test] + fn search_on_most_urgency() { + let (items, list) = setup_search_tests(true); + + let most_list = list.search( + None, None, None, None, Some(Urgency::Normal) + ); + + assert!(most_list.len() == 2); + let (ind1, item1) = &most_list[0]; + let (ind2, item2) = &most_list[1]; + assert!(*ind1 < items.len() && *ind2 < items.len()); + assert!(*ind1 == 0 && *ind2 == 2); + compare_items(&item1, &items[*ind1]); + compare_items(&item2, &items[*ind2]); + } + + // doesn't actually test anything, just times the search function + // should be run with the --nocapture argument to actually show stdout + #[test] + fn timing_test() { + let (_, mut list) = setup_search_tests(false); + for i in 1..1_000_000 { + list.add_full_item(&Item { + text: format!("I am item {i}!"), + checked: true, + due_at: None, + urgency: Urgency::Normal + }) + } + + let before = Utc::now(); + list.search(None, Some(true), None, None, None); + let after = Utc::now(); + + let diff = (after - before).num_milliseconds(); + println!("Duration: {diff}"); + } +}