From 681ccedc3265c9e4b2e9857dd42f4ceea27dd283 Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Sat, 10 Aug 2024 20:41:01 -0500 Subject: [PATCH 1/7] test(filters): test all filters individually and combined --- src/backend/filter.rs | 159 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 146 insertions(+), 13 deletions(-) diff --git a/src/backend/filter.rs b/src/backend/filter.rs index b796dd0..d2b8ab8 100644 --- a/src/backend/filter.rs +++ b/src/backend/filter.rs @@ -1,14 +1,14 @@ -use std::fmt::Write; +use std::fmt::{Debug, Write}; use strum::{Display, EnumIter, IntoEnumIterator}; use crate::global::PREFERRED_LANGUAGE; use crate::view::widgets::filter_widget::state::{FilterListItem, TagListItem, TagListItemState}; -pub trait IntoParam { +pub trait IntoParam: Debug { fn into_param(self) -> String; } -#[derive(Display, Clone)] +#[derive(Display, Clone, Debug)] pub enum ContentRating { #[strum(to_string = "safe")] Safe, @@ -32,7 +32,7 @@ impl From<&str> for ContentRating { } } -#[derive(Display, Clone, EnumIter, PartialEq, Eq, Default)] +#[derive(Display, Clone, EnumIter, PartialEq, Eq, Default, Debug)] pub enum SortBy { #[strum(to_string = "Best match")] BestMatch, @@ -167,7 +167,7 @@ impl IntoParam for SortBy { } } -#[derive(Display, Clone, EnumIter, PartialEq, Eq)] +#[derive(Display, Clone, EnumIter, PartialEq, Eq, Debug)] pub enum MagazineDemographic { Shounen, Shoujo, @@ -205,7 +205,7 @@ impl IntoParam for Vec { } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct Author(String); impl Author { @@ -214,7 +214,7 @@ impl Author { } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct Artist(String); impl Artist { @@ -223,8 +223,8 @@ impl Artist { } } -#[derive(Default, Clone)] -pub struct User(pub Vec); +#[derive(Default, Clone, Debug)] +pub struct User(pub Vec); impl IntoParam for User { fn into_param(self) -> String { @@ -254,6 +254,10 @@ impl User where T: Clone + Default + Sized, { + pub fn new(users: Vec) -> Self { + Self(users) + } + pub fn set_one_user(&mut self, user: T) { self.0.push(user); } @@ -422,7 +426,7 @@ impl IntoParam for Vec { } } -#[derive(Clone, Display, EnumIter)] +#[derive(Clone, Display, EnumIter, Debug)] pub enum PublicationStatus { #[strum(to_string = "ongoing")] Ongoing, @@ -456,7 +460,7 @@ impl IntoParam for Vec { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Filters { pub content_rating: Vec, pub publication_status: Vec, @@ -540,13 +544,14 @@ impl Filters { } } +/// This test may be changed depending on the Mangadex Api #[cfg(test)] mod test { use super::*; #[test] - fn language_conversion_works() { + fn language_from_filter_list_item() { let language_formatted = FilterListItem { name: format!( "{} {}", @@ -561,6 +566,135 @@ mod test { assert_eq!(conversion, Languages::Spanish); } + #[test] + fn filter_by_content_rating_works() { + let content_rating = vec![ + ContentRating::Safe, + ContentRating::Erotic, + ContentRating::Pornographic, + ContentRating::Suggestive, + ]; + + assert_eq!( + "&contentRating[]=safe&contentRating[]=erotica&contentRating[]=pornographic&contentRating[]=suggestive", + content_rating.into_param() + ); + } + + #[test] + fn sort_by_works() { + assert_eq!("&order[relevance]=desc", SortBy::BestMatch.into_param()); + + assert_eq!("&order[createdAt]=asc", SortBy::OldestAdded.into_param()); + + assert_eq!( + "&order[followedCount]=desc", + SortBy::MostFollows.into_param() + ); + + assert_eq!( + "&order[followedCount]=asc", + SortBy::FewestFollows.into_param() + ); + + assert_eq!( + "&order[latestUploadedChapter]=desc", + SortBy::LatestUpload.into_param() + ); + + assert_eq!( + "&order[latestUploadedChapter]=asc", + SortBy::OldestUpload.into_param() + ); + + assert_eq!("&order[rating]=desc", SortBy::HighestRating.into_param()); + + assert_eq!("&order[rating]=asc", SortBy::LowestRating.into_param()); + + assert_eq!("&order[createdAt]=desc", SortBy::RecentlyAdded.into_param()); + + assert_eq!("&order[year]=asc", SortBy::YearAscending.into_param()); + + assert_eq!("&order[year]=desc", SortBy::YearDescending.into_param()); + + assert_eq!("&order[title]=asc", SortBy::TitleAscending.into_param()); + + assert_eq!("&order[title]=desc", SortBy::TitleDescending.into_param()); + } + + #[test] + fn filter_by_magazine_demographic_works() { + let magazine_demographic = vec![ + MagazineDemographic::Shounen, + MagazineDemographic::Shoujo, + MagazineDemographic::Josei, + MagazineDemographic::Seinen, + ]; + + assert_eq!("&publicationDemographic[]=shounen&publicationDemographic[]=shoujo&publicationDemographic[]=josei&publicationDemographic[]=seinen", magazine_demographic.into_param()); + } + + #[test] + fn filter_by_artist_works() { + let sample_artists: Vec = vec![ + Artist::new("id_artist1".to_string()), + Artist::new("id_artist2".to_string()), + ]; + let filter_artist = User::::new(sample_artists); + assert_eq!( + "&artists[]=id_artist1&artists[]=id_artist2", + filter_artist.into_param() + ); + } + + #[test] + fn filter_by_author_works() { + let sample_authors: Vec = vec![ + Author::new("id_author1".to_string()), + Author::new("id_author2".to_string()), + ]; + let filter_artist = User::::new(sample_authors); + assert_eq!( + "&authors[]=id_author1&authors[]=id_author2", + filter_artist.into_param() + ); + } + + #[test] + fn filter_by_language_works() { + let _ = PREFERRED_LANGUAGE.set(Languages::default()); + + let default_language: Vec = vec![]; + assert_eq!( + "&availableTranslatedLanguage[]=en", + default_language.into_param() + ); + + let languages: Vec = vec![ + Languages::English, + Languages::Spanish, + Languages::SpanishLa, + Languages::BrazilianPortuguese, + ]; + + assert_eq!("&availableTranslatedLanguage[]=en&availableTranslatedLanguage[]=es&availableTranslatedLanguage[]=es-la&availableTranslatedLanguage[]=pt-br", languages.into_param()); + } + + #[test] + fn filter_by_publication_status_works() { + let publication_status: Vec = vec![ + PublicationStatus::Ongoing, + PublicationStatus::Hiatus, + PublicationStatus::Completed, + PublicationStatus::Cancelled, + ]; + + assert_eq!( + "&status[]=ongoing&status[]=hiatus&status[]=completed&status[]=cancelled", + publication_status.into_param() + ); + } + #[test] fn filter_by_tags_works() { let tags = Tags::new(vec![ @@ -582,7 +716,6 @@ mod test { #[test] fn filters_combined_work() { - PREFERRED_LANGUAGE.set(Languages::default()).unwrap(); let filters = Filters::default(); assert_eq!("&availableTranslatedLanguage[]=en&contentRating[]=safe&contentRating[]=suggestive&order[latestUploadedChapter]=desc", filters.into_param()); From e04d325178d515eed2bbb226da58ba428064dde7 Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Sat, 10 Aug 2024 22:36:23 -0500 Subject: [PATCH 2/7] ci(test-ci): add check and build steps --- .github/workflows/test.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5cfba9..23fa129 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,11 +23,17 @@ jobs: with: rust-version: stable - - name: cargo test - run: cargo test + - name: check + run: cargo check + + - name: build + run: cargo build --release --verbose + + - name: test + run: cargo test -- --test-threads=1 - name: clippy - run: cargo clippy --all --all-features --tests -- -D warnings + run: cargo clippy -- -D warnings env: RUST_BACKTRACE: full From ad0e4867148f5b552b4e83aea49a45d97e6529e3 Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Sun, 11 Aug 2024 15:20:22 -0500 Subject: [PATCH 3/7] test(filter_state): added tests for filter_state --- src/view/widgets/filter_widget/state.rs | 154 ++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 7 deletions(-) diff --git a/src/view/widgets/filter_widget/state.rs b/src/view/widgets/filter_widget/state.rs index 5a6037a..2ad125d 100644 --- a/src/view/widgets/filter_widget/state.rs +++ b/src/view/widgets/filter_widget/state.rs @@ -21,7 +21,7 @@ pub enum FilterEvents { LoadTags(TagsResponse), } -#[derive(Display, PartialEq, Eq)] +#[derive(Display, PartialEq, Eq, Debug)] pub enum MangaFilters { #[strum(to_string = "Content rating")] ContentRating, @@ -48,7 +48,7 @@ pub const FILTERS: [MangaFilters; 8] = [ MangaFilters::Artists, ]; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FilterListItem { pub is_selected: bool, pub name: String, @@ -60,12 +60,18 @@ impl FilterListItem { } } +#[derive(Debug)] pub struct ContentRatingState; +#[derive(Debug)] pub struct PublicationStatusState; +#[derive(Debug)] pub struct SortByState; +#[derive(Debug)] pub struct MagazineDemographicState; +#[derive(Debug)] pub struct LanguageState; +#[derive(Debug)] pub struct FilterList { pub items: Vec, pub state: ListState, @@ -207,20 +213,21 @@ impl Default for FilterList { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ListItemId { pub id: String, pub name: String, pub is_selected: bool, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct AuthorState; -#[derive(Default)] + +#[derive(Default, Debug)] pub struct ArtistState; // It's called dynamic because the items must be fetched -#[derive(Default)] +#[derive(Default, Debug)] pub struct FilterListDynamic { pub items: Option>, pub state: ListState, @@ -272,7 +279,6 @@ impl FilterListDynamic { self.items = None; } - fn toggle(&mut self) { if let Some(items) = self.items.as_mut() { if let Some(index) = self.state.selected() { @@ -422,6 +428,8 @@ impl TagsState { } } } + +#[derive(Debug)] pub struct FilterState { pub is_open: bool, pub id_filter: usize, @@ -892,3 +900,135 @@ impl FilterState { self.filters.artists.set_one_user(Artist::new(artist.id)) } } + +#[cfg(test)] +mod test { + + use crate::backend::authors::Data; + + use super::*; + + #[test] + fn filter_list_works() { + let mut filter_list: FilterList = FilterList::default(); + + filter_list.scroll_down(); + + filter_list.toggle(); + + assert_eq!(Some(0), filter_list.state.selected()); + + assert!(filter_list.items.iter().any(|item| item.is_selected)); + + assert_eq!(1, filter_list.num_filters_active()); + + filter_list.scroll_down(); + + filter_list.toggle(); + + assert_eq!(Some(1), filter_list.state.selected()); + + assert_eq!(2, filter_list.num_filters_active()); + + filter_list.scroll_up(); + + assert_eq!(Some(0), filter_list.state.selected()); + } + + #[test] + fn language_filter_list_works() { + let filter_list: FilterList = FilterList::default(); + + assert_eq!( + Languages::default(), + filter_list + .items + .iter() + .find(|filter_list_item| filter_list_item.is_selected) + .cloned() + .unwrap() + .into() + ); + + let language_items: Vec = filter_list + .items + .into_iter() + .map(|filter_list_item| filter_list_item.into()) + .collect(); + + assert!(!language_items.iter().any(|lang| *lang == Languages::Unkown)); + } + + #[test] + fn sort_by_state_works() { + let mut filter_list: FilterList = FilterList::default(); + + filter_list.scroll_down(); + filter_list.toggle_sort_by(); + filter_list.scroll_down(); + filter_list.toggle_sort_by(); + + // for the sort_by filter only one can be selected at a time + assert_eq!(1, filter_list.num_filters_active()); + } + + #[test] + fn filter_list_dynamic_works() { + let mut filter_list: FilterListDynamic = FilterListDynamic::default(); + + let mock_response = AuthorsResponse { + data: vec![Data::default()], + ..Default::default() + }; + + assert_eq!(0, filter_list.num_filters_active()); + + filter_list.toggle(); + + filter_list.load_users(Some(mock_response)); + + assert!(filter_list.items.is_some()); + + filter_list.state.select_next(); + + filter_list.toggle(); + + assert!(filter_list + .items + .as_ref() + .is_some_and(|items| items.iter().any(|item| item.is_selected))); + + filter_list.load_users(Some(AuthorsResponse::default())); + + assert!(filter_list.items.is_none()); + } + + #[test] + fn tag_state_works() { + let mut tag_state = TagsState { + tags: Some(vec![TagListItem::default(), TagListItem::default()]), + ..Default::default() + }; + + tag_state.state.select_next(); + + tag_state.include_tag(); + + assert!(tag_state.tags.as_ref().is_some_and(|tags| tags + .iter() + .any(|tag| tag.state == TagListItemState::Included))); + + tag_state.exclude_tag(); + + assert!(tag_state.tags.as_ref().is_some_and(|tags| tags + .iter() + .any(|tag| tag.state == TagListItemState::Excluded))); + } + + #[test] + fn filter_state() { + let mut filter_state = FilterState::new(); + + filter_state.scroll_down_filter_list(); + } +} From 9a161818e97954edb76239ce8310b4b38d4b376d Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Mon, 12 Aug 2024 10:41:49 -0500 Subject: [PATCH 4/7] test(filterState-/-searchPage): finished test filterState, add tests searchPage --- src/backend/filter.rs | 3 + src/view/pages/search.rs | 40 +++++++++++ src/view/widgets.rs | 7 ++ src/view/widgets/filter_widget/state.rs | 91 ++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/backend/filter.rs b/src/backend/filter.rs index d2b8ab8..f0949e7 100644 --- a/src/backend/filter.rs +++ b/src/backend/filter.rs @@ -101,6 +101,9 @@ impl Tags { pub fn new(tags: Vec) -> Self { Self(tags) } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl IntoParam for Tags { diff --git a/src/view/pages/search.rs b/src/view/pages/search.rs index 3f63da1..5c03ad9 100644 --- a/src/view/pages/search.rs +++ b/src/view/pages/search.rs @@ -629,3 +629,43 @@ impl SearchPage { } } } + +#[cfg(test)] +mod test { + use crate::view::widgets::press_key; + + use super::*; + + #[tokio::test] + async fn search_page_key_events() { + let (tx, _) = mpsc::unbounded_channel::(); + let mut search_page = SearchPage::init(tx); + + assert!(search_page.state == PageState::Normal); + + // focus search_bar + press_key(&mut search_page, KeyCode::Char('s')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert!(search_page.input_mode == InputMode::Typing); + + // user is typing in the search_bar + press_key(&mut search_page, KeyCode::Char('t')); + press_key(&mut search_page, KeyCode::Char('e')); + + assert_eq!("te", search_page.search_bar.value()); + + // unfocus search_bar + press_key(&mut search_page, KeyCode::Esc); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert!(search_page.input_mode == InputMode::Idle); + + } +} diff --git a/src/view/widgets.rs b/src/view/widgets.rs index 166f407..53c4f98 100644 --- a/src/view/widgets.rs +++ b/src/view/widgets.rs @@ -1,4 +1,5 @@ use crate::backend::tui::Events; +use crossterm::event::KeyCode; use ratatui::Frame; use ratatui_image::protocol::StatefulProtocol; @@ -34,3 +35,9 @@ pub trait ImageHandler: Send + 'static { fn load(image: Box, id: String) -> Self; fn not_found(id: String) -> Self; } + + +// Use in testing +pub fn press_key(page: &mut dyn Component, key: KeyCode) { + page.handle_events(Events::Key(key.into())); +} diff --git a/src/view/widgets/filter_widget/state.rs b/src/view/widgets/filter_widget/state.rs index 2ad125d..6b34a3b 100644 --- a/src/view/widgets/filter_widget/state.rs +++ b/src/view/widgets/filter_widget/state.rs @@ -905,6 +905,7 @@ impl FilterState { mod test { use crate::backend::authors::Data; + use crate::backend::tags::TagsData; use super::*; @@ -1025,10 +1026,98 @@ mod test { .any(|tag| tag.state == TagListItemState::Excluded))); } + // simulate what the user can do + fn next_tab(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::Tab.into())); + } + + fn previous_tab(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::BackTab.into())); + } + + fn scroll_down(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::Char('j').into())); + } + + fn press_s(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::Char('s').into())); + } + + fn start_typing(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::Char('l').into())); + } + + fn type_a_letter(filter_state: &mut FilterState, character: char) { + filter_state.handle_events(Events::Key(KeyCode::Char(character).into())); + } + #[test] fn filter_state() { let mut filter_state = FilterState::new(); - filter_state.scroll_down_filter_list(); + let mock_response = TagsResponse { + data: vec![TagsData::default(), TagsData::default()], + ..Default::default() + }; + + filter_state.set_tags_from_response(mock_response); + + assert!(filter_state.tags_state.tags.is_some()); + + // Go to magazine demographic + previous_tab(&mut filter_state); + previous_tab(&mut filter_state); + previous_tab(&mut filter_state); + + scroll_down(&mut filter_state); + press_s(&mut filter_state); + + assert!(filter_state.magazine_demographic.state.selected().is_some()); + + assert!(filter_state + .magazine_demographic + .items + .iter() + .any(|item| item.is_selected)); + + // Go to tags + previous_tab(&mut filter_state); + + scroll_down(&mut filter_state); + press_s(&mut filter_state); + + assert!(filter_state + .tags_state + .tags + .as_ref() + .is_some_and(|tags| tags + .iter() + .any(|tag| tag.state == TagListItemState::Included))); + + assert!(!filter_state.filters.tags.is_empty()); + + // Go to Publication status + previous_tab(&mut filter_state); + scroll_down(&mut filter_state); + press_s(&mut filter_state); + + assert!(filter_state.publication_status.state.selected().is_some()); + assert!(filter_state + .publication_status + .items + .iter() + .any(|item| item.is_selected)); + + // Go to tags + next_tab(&mut filter_state); + start_typing(&mut filter_state); + + assert!(filter_state.is_typing); + + type_a_letter(&mut filter_state, 't'); + type_a_letter(&mut filter_state, 'e'); + type_a_letter(&mut filter_state, 's'); + type_a_letter(&mut filter_state, 't'); + assert_eq!("test", filter_state.tags_state.filter_input.value()); } } From bf2079b5c00798075b05948542404eaa912d016a Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Mon, 12 Aug 2024 13:41:26 -0500 Subject: [PATCH 5/7] test(SearchPage): Add test for searchPage key-events --- src/backend/filter.rs | 1 + src/view/pages/search.rs | 76 +++++++++++++++++++++++++ src/view/widgets.rs | 4 +- src/view/widgets/filter_widget/state.rs | 15 +++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/backend/filter.rs b/src/backend/filter.rs index f0949e7..7cca855 100644 --- a/src/backend/filter.rs +++ b/src/backend/filter.rs @@ -1,5 +1,6 @@ use std::fmt::{Debug, Write}; use strum::{Display, EnumIter, IntoEnumIterator}; +#[allow(dead_code)] use crate::global::PREFERRED_LANGUAGE; use crate::view::widgets::filter_widget::state::{FilterListItem, TagListItem, TagListItemState}; diff --git a/src/view/pages/search.rs b/src/view/pages/search.rs index 5c03ad9..794bb02 100644 --- a/src/view/pages/search.rs +++ b/src/view/pages/search.rs @@ -64,6 +64,7 @@ impl ImageHandler for SearchPageEvents { } /// These are actions that the user actively does +#[derive(Debug, PartialEq, Eq)] pub enum SearchPageActions { StartTyping, StopTyping, @@ -642,6 +643,7 @@ mod test { let mut search_page = SearchPage::init(tx); assert!(search_page.state == PageState::Normal); + assert!(!search_page.filter_state.is_open); // focus search_bar press_key(&mut search_page, KeyCode::Char('s')); @@ -667,5 +669,79 @@ mod test { assert!(search_page.input_mode == InputMode::Idle); + // Assuming a search was made and some mangas were found + search_page.state = PageState::DisplayingMangasFound; + search_page.mangas_found_list.widget.mangas = + vec![MangaItem::default(), MangaItem::default()]; + search_page.mangas_found_list.total_result = 20; + search_page.mangas_found_list.page = 1; + + let area = Rect::new(0, 0, 50, 50); + let mut buf = Buffer::empty(area); + + // Render the list of mangas found + StatefulWidgetRef::render_ref( + &search_page.mangas_found_list.widget, + area, + &mut buf, + &mut search_page.mangas_found_list.state, + ); + + // scroll down the list + press_key(&mut search_page, KeyCode::Char('j')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert!(search_page.mangas_found_list.state.selected.is_some()); + + // open filters + press_key(&mut search_page, KeyCode::Char('f')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert!(search_page.filter_state.is_open); + + search_page.filter_state.is_open = false; + + // Add a manga to plan to read + press_key(&mut search_page, KeyCode::Char('p')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert!(search_page.manga_added_to_plan_to_read.is_some()); + + // Go next page + press_key(&mut search_page, KeyCode::Char('w')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + + assert_eq!(2, search_page.mangas_found_list.page); + + search_page.state = PageState::DisplayingMangasFound; + + // Go previous page + press_key(&mut search_page, KeyCode::Char('b')); + + if let Some(action) = search_page.local_action_rx.recv().await { + search_page.update(action) + } + assert_eq!(1, search_page.mangas_found_list.page); + + // Go to manga page + press_key(&mut search_page, KeyCode::Char('r')); + + if let Some(action) = search_page.local_action_rx.recv().await { + assert_eq!(SearchPageActions::GoToMangaPage, action); + } else { + panic!("The action `go to manga page` is not working"); + } } } diff --git a/src/view/widgets.rs b/src/view/widgets.rs index 53c4f98..93d65a0 100644 --- a/src/view/widgets.rs +++ b/src/view/widgets.rs @@ -36,8 +36,8 @@ pub trait ImageHandler: Send + 'static { fn not_found(id: String) -> Self; } - -// Use in testing +#[allow(dead_code)] +// Use in testing pub fn press_key(page: &mut dyn Component, key: KeyCode) { page.handle_events(Events::Key(key.into())); } diff --git a/src/view/widgets/filter_widget/state.rs b/src/view/widgets/filter_widget/state.rs index 6b34a3b..ddae226 100644 --- a/src/view/widgets/filter_widget/state.rs +++ b/src/view/widgets/filter_widget/state.rs @@ -1051,10 +1051,17 @@ mod test { filter_state.handle_events(Events::Key(KeyCode::Char(character).into())); } + // this action both sets fillter_state.is_open = false and unfocus search_bar input + fn close_filter(filter_state: &mut FilterState) { + filter_state.handle_events(Events::Key(KeyCode::Esc.into())); + } + #[test] fn filter_state() { let mut filter_state = FilterState::new(); + filter_state.is_open = true; + let mock_response = TagsResponse { data: vec![TagsData::default(), TagsData::default()], ..Default::default() @@ -1119,5 +1126,13 @@ mod test { type_a_letter(&mut filter_state, 's'); type_a_letter(&mut filter_state, 't'); assert_eq!("test", filter_state.tags_state.filter_input.value()); + + // First unfocus the filter bar + close_filter(&mut filter_state); + + // then "close" the filter + close_filter(&mut filter_state); + + assert!(!filter_state.is_open); } } From 68529adc9ed186a2d33232e323779b7eb4ffeae2 Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Mon, 12 Aug 2024 13:52:59 -0500 Subject: [PATCH 6/7] test(searchPage): change test for add manga to plan to read --- src/view/pages/search.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/view/pages/search.rs b/src/view/pages/search.rs index 794bb02..61b0d1a 100644 --- a/src/view/pages/search.rs +++ b/src/view/pages/search.rs @@ -707,15 +707,26 @@ mod test { search_page.filter_state.is_open = false; + // // Add a manga to plan to read + // To test the actual funcionality it's necessary the database, so let's assert the right + // event is called in the meantime + // press_key(&mut search_page, KeyCode::Char('p')); + // + // if let Some(action) = search_page.local_action_rx.recv().await { + // search_page.update(action) + // } + // + // assert!(search_page.manga_added_to_plan_to_read.is_some()); + // Add a manga to plan to read press_key(&mut search_page, KeyCode::Char('p')); if let Some(action) = search_page.local_action_rx.recv().await { - search_page.update(action) + assert_eq!(SearchPageActions::PlanToRead, action); + } else { + panic!("Add plan to read functionality is not being called"); } - assert!(search_page.manga_added_to_plan_to_read.is_some()); - // Go next page press_key(&mut search_page, KeyCode::Char('w')); From 3f9d020b0400f09d22f45e3e1c0702243bb06e86 Mon Sep 17 00:00:00 2001 From: josuebarretogit Date: Mon, 12 Aug 2024 14:04:00 -0500 Subject: [PATCH 7/7] chore: allow dead_code and unused at crate-level --- src/backend/filter.rs | 1 - src/main.rs | 13 +++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend/filter.rs b/src/backend/filter.rs index 7cca855..f0949e7 100644 --- a/src/backend/filter.rs +++ b/src/backend/filter.rs @@ -1,6 +1,5 @@ use std::fmt::{Debug, Write}; use strum::{Display, EnumIter, IntoEnumIterator}; -#[allow(dead_code)] use crate::global::PREFERRED_LANGUAGE; use crate::view::widgets::filter_widget::state::{FilterListItem, TagListItem, TagListItemState}; diff --git a/src/main.rs b/src/main.rs index 1a1f5bb..2de4f77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,6 @@ #![forbid(unsafe_code)] -use clap::Parser; -use once_cell::sync::Lazy; -use ratatui::backend::CrosstermBackend; -use ratatui_image::picker::{Picker, ProtocolType}; -use reqwest::{Client, StatusCode}; +#![allow(dead_code)] +#![allow(unused)] use self::backend::error_log::init_error_hooks; use self::backend::fetch::{MangadexClient, MANGADEX_CLIENT_INSTANCE}; use self::backend::filter::Languages; @@ -11,6 +8,11 @@ use self::backend::tui::{init, restore, run_app}; use self::backend::{build_data_dir, APP_DATA_DIR}; use self::cli::CliArgs; use self::global::PREFERRED_LANGUAGE; +use clap::Parser; +use once_cell::sync::Lazy; +use ratatui::backend::CrosstermBackend; +use ratatui_image::picker::{Picker, ProtocolType}; +use reqwest::{Client, StatusCode}; mod backend; mod cli; @@ -81,7 +83,6 @@ async fn main() -> Result<(), Box> { None => PREFERRED_LANGUAGE.set(Languages::default()).unwrap(), } - let user_agent = format!( "manga-tui/{} ({}/{}/{})", env!("CARGO_PKG_VERSION"),