From cf11527fef26c6da9aa0117e438b18488d908fd2 Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Tue, 8 Aug 2023 10:50:07 -0600 Subject: [PATCH] Refactored the UI module and the handlers module to do a more chain-of-responsibility method to manage the UI's and handlers for different key events. Also, initial work for indexer settings as well --- src/app/key_binding.rs | 5 + src/app/radarr.rs | 320 ++-- src/app/radarr_tests.rs | 52 +- src/handlers/handler_test_utils.rs | 4 +- src/handlers/mod.rs | 1 + .../collection_details_handler.rs | 7 +- .../collection_details_handler_tests.rs | 17 +- .../collections/collections_handler_tests.rs | 673 ++++++++ .../edit_collection_handler.rs | 6 +- .../edit_collection_handler_tests.rs | 16 +- .../radarr_handlers/collections/mod.rs | 311 ++++ .../downloads/downloads_handler_tests.rs | 282 ++++ src/handlers/radarr_handlers/downloads/mod.rs | 132 ++ .../indexers/edit_indexer_settings_handler.rs | 74 + .../edit_indexer_settings_handler_tests.rs | 98 ++ .../indexers/indexers_handler_tests.rs | 342 ++++ src/handlers/radarr_handlers/indexers/mod.rs | 148 ++ .../{ => library}/add_movie_handler.rs | 6 +- .../{ => library}/add_movie_handler_tests.rs | 16 +- .../{ => library}/delete_movie_handler.rs | 6 +- .../delete_movie_handler_tests.rs | 17 +- .../{ => library}/edit_movie_handler.rs | 6 +- .../{ => library}/edit_movie_handler_tests.rs | 16 +- .../library/library_handler_tests.rs | 699 +++++++++ src/handlers/radarr_handlers/library/mod.rs | 298 ++++ .../{ => library}/movie_details_handler.rs | 8 +- .../movie_details_handler_tests.rs | 17 +- src/handlers/radarr_handlers/mod.rs | 722 ++------- .../radarr_handler_test_utils.rs | 37 +- .../radarr_handlers/radarr_handler_tests.rs | 1391 ++--------------- .../radarr_handlers/root_folders/mod.rs | 148 ++ .../root_folders_handler_tests.rs | 401 +++++ src/handlers/radarr_handlers/system/mod.rs | 116 ++ .../{ => system}/system_details_handler.rs | 4 + .../system_details_handler_tests.rs | 16 +- .../system/system_handler_tests.rs | 211 +++ src/ui/mod.rs | 3 +- .../collection_details_ui.rs | 18 +- .../collection_details_ui_tests.rs | 19 + .../collections/collections_ui_tests.rs | 26 + .../{ => collections}/edit_collection_ui.rs | 18 +- .../collections/edit_collection_ui_tests.rs | 19 + .../{collections_ui.rs => collections/mod.rs} | 90 +- .../radarr_ui/downloads/downloads_ui_tests.rs | 19 + .../{downloads_ui.rs => downloads/mod.rs} | 14 +- .../radarr_ui/indexers/indexer_settings_ui.rs | 26 + .../indexers/indexer_settings_ui_tests.rs | 19 + .../radarr_ui/indexers/indexers_ui_tests.rs | 23 + .../{indexers_ui.rs => indexers/mod.rs} | 46 +- .../radarr_ui/{ => library}/add_movie_ui.rs | 21 +- .../radarr_ui/library/add_movie_ui_tests.rs | 19 + .../{ => library}/delete_movie_ui.rs | 16 +- .../library/delete_movie_ui_tests.rs | 19 + .../radarr_ui/{ => library}/edit_movie_ui.rs | 18 +- .../radarr_ui/library/edit_movie_ui_tests.rs | 19 + src/ui/radarr_ui/library/library_ui_tests.rs | 29 + .../{library_ui.rs => library/mod.rs} | 72 +- .../{ => library}/movie_details_ui.rs | 16 +- .../library/movie_details_ui_tests.rs | 19 + src/ui/radarr_ui/mod.rs | 102 +- src/ui/radarr_ui/radarr_ui_tests.rs | 15 + .../mod.rs} | 14 +- .../root_folders/root_folders_ui_tests.rs | 19 + .../radarr_ui/{system_ui.rs => system/mod.rs} | 52 +- .../{ => system}/system_details_ui.rs | 16 +- .../system/system_details_ui_tests.rs | 19 + src/ui/radarr_ui/system/system_ui_tests.rs | 23 + 67 files changed, 5255 insertions(+), 2216 deletions(-) rename src/handlers/radarr_handlers/{ => collections}/collection_details_handler.rs (94%) rename src/handlers/radarr_handlers/{ => collections}/collection_details_handler_tests.rs (90%) create mode 100644 src/handlers/radarr_handlers/collections/collections_handler_tests.rs rename src/handlers/radarr_handlers/{ => collections}/edit_collection_handler.rs (97%) rename src/handlers/radarr_handlers/{ => collections}/edit_collection_handler_tests.rs (96%) create mode 100644 src/handlers/radarr_handlers/collections/mod.rs create mode 100644 src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/downloads/mod.rs create mode 100644 src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs create mode 100644 src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/indexers/mod.rs rename src/handlers/radarr_handlers/{ => library}/add_movie_handler.rs (98%) rename src/handlers/radarr_handlers/{ => library}/add_movie_handler_tests.rs (97%) rename src/handlers/radarr_handlers/{ => library}/delete_movie_handler.rs (93%) rename src/handlers/radarr_handlers/{ => library}/delete_movie_handler_tests.rs (91%) rename src/handlers/radarr_handlers/{ => library}/edit_movie_handler.rs (97%) rename src/handlers/radarr_handlers/{ => library}/edit_movie_handler_tests.rs (96%) create mode 100644 src/handlers/radarr_handlers/library/library_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/library/mod.rs rename src/handlers/radarr_handlers/{ => library}/movie_details_handler.rs (98%) rename src/handlers/radarr_handlers/{ => library}/movie_details_handler_tests.rs (97%) create mode 100644 src/handlers/radarr_handlers/root_folders/mod.rs create mode 100644 src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/system/mod.rs rename src/handlers/radarr_handlers/{ => system}/system_details_handler.rs (97%) rename src/handlers/radarr_handlers/{ => system}/system_details_handler_tests.rs (95%) create mode 100644 src/handlers/radarr_handlers/system/system_handler_tests.rs rename src/ui/radarr_ui/{ => collections}/collection_details_ui.rs (94%) create mode 100644 src/ui/radarr_ui/collections/collection_details_ui_tests.rs create mode 100644 src/ui/radarr_ui/collections/collections_ui_tests.rs rename src/ui/radarr_ui/{ => collections}/edit_collection_ui.rs (93%) create mode 100644 src/ui/radarr_ui/collections/edit_collection_ui_tests.rs rename src/ui/radarr_ui/{collections_ui.rs => collections/mod.rs} (63%) create mode 100644 src/ui/radarr_ui/downloads/downloads_ui_tests.rs rename src/ui/radarr_ui/{downloads_ui.rs => downloads/mod.rs} (92%) create mode 100644 src/ui/radarr_ui/indexers/indexer_settings_ui.rs create mode 100644 src/ui/radarr_ui/indexers/indexer_settings_ui_tests.rs create mode 100644 src/ui/radarr_ui/indexers/indexers_ui_tests.rs rename src/ui/radarr_ui/{indexers_ui.rs => indexers/mod.rs} (72%) rename src/ui/radarr_ui/{ => library}/add_movie_ui.rs (96%) create mode 100644 src/ui/radarr_ui/library/add_movie_ui_tests.rs rename src/ui/radarr_ui/{ => library}/delete_movie_ui.rs (81%) create mode 100644 src/ui/radarr_ui/library/delete_movie_ui_tests.rs rename src/ui/radarr_ui/{ => library}/edit_movie_ui.rs (92%) create mode 100644 src/ui/radarr_ui/library/edit_movie_ui_tests.rs create mode 100644 src/ui/radarr_ui/library/library_ui_tests.rs rename src/ui/radarr_ui/{library_ui.rs => library/mod.rs} (67%) rename src/ui/radarr_ui/{ => library}/movie_details_ui.rs (97%) create mode 100644 src/ui/radarr_ui/library/movie_details_ui_tests.rs create mode 100644 src/ui/radarr_ui/radarr_ui_tests.rs rename src/ui/radarr_ui/{root_folders_ui.rs => root_folders/mod.rs} (91%) create mode 100644 src/ui/radarr_ui/root_folders/root_folders_ui_tests.rs rename src/ui/radarr_ui/{system_ui.rs => system/mod.rs} (89%) rename src/ui/radarr_ui/{ => system}/system_details_ui.rs (92%) create mode 100644 src/ui/radarr_ui/system/system_details_ui_tests.rs create mode 100644 src/ui/radarr_ui/system/system_ui_tests.rs diff --git a/src/app/key_binding.rs b/src/app/key_binding.rs index e195677..475ec56 100644 --- a/src/app/key_binding.rs +++ b/src/app/key_binding.rs @@ -22,6 +22,7 @@ generate_keybindings! { edit, logs, tasks, + restrictions, refresh, update, events, @@ -95,6 +96,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { key: Key::Char('t'), desc: "Tasks", }, + restrictions: KeyBinding { + key: Key::Char('t'), + desc: "Restrictions", + }, refresh: KeyBinding { key: Key::Char('r'), desc: "Refresh", diff --git a/src/app/radarr.rs b/src/app/radarr.rs index dd0ba1c..e871c1f 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -1,6 +1,7 @@ use bimap::BiMap; use chrono::{DateTime, Utc}; use strum::IntoEnumIterator; +use strum_macros::EnumIter; use crate::app::{App, Route}; use crate::models::radarr_models::{ @@ -250,134 +251,134 @@ impl<'a> RadarrData<'a> { impl<'a> Default for RadarrData<'a> { fn default() -> RadarrData<'a> { RadarrData { - root_folders: StatefulTable::default(), - disk_space_vec: Vec::new(), - version: String::default(), - start_time: DateTime::default(), - movies: StatefulTable::default(), - add_searched_movies: StatefulTable::default(), - monitor_list: StatefulList::default(), - minimum_availability_list: StatefulList::default(), - quality_profile_list: StatefulList::default(), - root_folder_list: StatefulList::default(), - selected_block: BlockSelectionState::default(), - filtered_movies: StatefulTable::default(), - downloads: StatefulTable::default(), - indexers: StatefulTable::default(), - indexer_settings: None, - quality_profile_map: BiMap::default(), - tags_map: BiMap::default(), - file_details: String::default(), - audio_details: String::default(), - video_details: String::default(), - movie_details: ScrollableText::default(), - movie_history: StatefulTable::default(), - movie_cast: StatefulTable::default(), - movie_crew: StatefulTable::default(), - movie_releases: StatefulTable::default(), - movie_releases_sort: StatefulList::default(), - collections: StatefulTable::default(), - filtered_collections: StatefulTable::default(), - collection_movies: StatefulTable::default(), - logs: StatefulList::default(), - log_details: StatefulList::default(), - tasks: StatefulTable::default(), - queued_events: StatefulTable::default(), - updates: ScrollableText::default(), - prompt_confirm_action: None, - search: HorizontallyScrollableText::default(), - filter: HorizontallyScrollableText::default(), - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, - edit_search_on_add: None, - sort_ascending: None, - is_searching: false, - is_filtering: false, - prompt_confirm: false, - delete_movie_files: false, - add_list_exclusion: false, - main_tabs: TabState::new(vec![ - TabRoute { - title: "Library", - route: ActiveRadarrBlock::Movies.into(), - help: "", - contextual_help: Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter"), - }, - TabRoute { - title: "Downloads", - route: ActiveRadarrBlock::Downloads.into(), - help: "", - contextual_help: Some(" refresh | delete"), - }, - TabRoute { - title: "Collections", - route: ActiveRadarrBlock::Collections.into(), - help: "", - contextual_help: Some(" search | edit | filter | refresh | update all | details | cancel filter"), - }, - TabRoute { - title: "Root Folders", - route: ActiveRadarrBlock::RootFolders.into(), - help: "", - contextual_help: Some(" add | delete | refresh"), - }, - TabRoute { - title: "Indexers", - route: ActiveRadarrBlock::Indexers.into(), - help: "", - contextual_help: Some(" edit | settings | delete | refresh"), - }, - TabRoute { - title: "System", - route: ActiveRadarrBlock::System.into(), - help: "", - contextual_help: Some(" open tasks | open events | open logs | open updates | refresh") - } - ]), - movie_info_tabs: TabState::new(vec![ - TabRoute { - title: "Details", - route: ActiveRadarrBlock::MovieDetails.into(), - help: " refresh | update | edit | auto search | close", - contextual_help: None - }, - TabRoute { - title: "History", - route: ActiveRadarrBlock::MovieHistory.into(), - help: " refresh | update | edit | auto search | close", - contextual_help: None - }, - TabRoute { - title: "File", - route: ActiveRadarrBlock::FileInfo.into(), - help: " refresh | update | edit | auto search | close", - contextual_help: None, - }, - TabRoute { - title: "Cast", - route: ActiveRadarrBlock::Cast.into(), - help: " refresh | update | edit | auto search | close", - contextual_help: None, - }, - TabRoute { - title: "Crew", - route: ActiveRadarrBlock::Crew.into(), - help: " refresh | update | edit | auto search | close", - contextual_help: None, - }, - TabRoute { - title: "Manual Search", - route: ActiveRadarrBlock::ManualSearch.into(), - help: " refresh | update | edit | sort | auto search | close", - contextual_help: Some(" details") - } - ]), - } + root_folders: StatefulTable::default(), + disk_space_vec: Vec::new(), + version: String::default(), + start_time: DateTime::default(), + movies: StatefulTable::default(), + add_searched_movies: StatefulTable::default(), + monitor_list: StatefulList::default(), + minimum_availability_list: StatefulList::default(), + quality_profile_list: StatefulList::default(), + root_folder_list: StatefulList::default(), + selected_block: BlockSelectionState::default(), + filtered_movies: StatefulTable::default(), + downloads: StatefulTable::default(), + indexers: StatefulTable::default(), + indexer_settings: None, + quality_profile_map: BiMap::default(), + tags_map: BiMap::default(), + file_details: String::default(), + audio_details: String::default(), + video_details: String::default(), + movie_details: ScrollableText::default(), + movie_history: StatefulTable::default(), + movie_cast: StatefulTable::default(), + movie_crew: StatefulTable::default(), + movie_releases: StatefulTable::default(), + movie_releases_sort: StatefulList::default(), + collections: StatefulTable::default(), + filtered_collections: StatefulTable::default(), + collection_movies: StatefulTable::default(), + logs: StatefulList::default(), + log_details: StatefulList::default(), + tasks: StatefulTable::default(), + queued_events: StatefulTable::default(), + updates: ScrollableText::default(), + prompt_confirm_action: None, + search: HorizontallyScrollableText::default(), + filter: HorizontallyScrollableText::default(), + edit_path: HorizontallyScrollableText::default(), + edit_tags: HorizontallyScrollableText::default(), + edit_monitored: None, + edit_search_on_add: None, + sort_ascending: None, + is_searching: false, + is_filtering: false, + prompt_confirm: false, + delete_movie_files: false, + add_list_exclusion: false, + main_tabs: TabState::new(vec![ + TabRoute { + title: "Library", + route: ActiveRadarrBlock::Movies.into(), + help: "", + contextual_help: Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter"), + }, + TabRoute { + title: "Downloads", + route: ActiveRadarrBlock::Downloads.into(), + help: "", + contextual_help: Some(" refresh | delete"), + }, + TabRoute { + title: "Collections", + route: ActiveRadarrBlock::Collections.into(), + help: "", + contextual_help: Some(" search | edit | filter | refresh | update all | details | cancel filter"), + }, + TabRoute { + title: "Root Folders", + route: ActiveRadarrBlock::RootFolders.into(), + help: "", + contextual_help: Some(" add | delete | refresh"), + }, + TabRoute { + title: "Indexers", + route: ActiveRadarrBlock::Indexers.into(), + help: "", + contextual_help: Some(" add | edit | settings | restrictions | delete | refresh"), + }, + TabRoute { + title: "System", + route: ActiveRadarrBlock::System.into(), + help: "", + contextual_help: Some(" open tasks | open events | open logs | open updates | refresh"), + }, + ]), + movie_info_tabs: TabState::new(vec![ + TabRoute { + title: "Details", + route: ActiveRadarrBlock::MovieDetails.into(), + help: " refresh | update | edit | auto search | close", + contextual_help: None, + }, + TabRoute { + title: "History", + route: ActiveRadarrBlock::MovieHistory.into(), + help: " refresh | update | edit | auto search | close", + contextual_help: None, + }, + TabRoute { + title: "File", + route: ActiveRadarrBlock::FileInfo.into(), + help: " refresh | update | edit | auto search | close", + contextual_help: None, + }, + TabRoute { + title: "Cast", + route: ActiveRadarrBlock::Cast.into(), + help: " refresh | update | edit | auto search | close", + contextual_help: None, + }, + TabRoute { + title: "Crew", + route: ActiveRadarrBlock::Crew.into(), + help: " refresh | update | edit | auto search | close", + contextual_help: None, + }, + TabRoute { + title: "Manual Search", + route: ActiveRadarrBlock::ManualSearch.into(), + help: " refresh | update | edit | sort | auto search | close", + contextual_help: Some(" details"), + }, + ]), + } } } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)] pub enum ActiveRadarrBlock { AddIndexer, AddMovieAlreadyInLibrary, @@ -424,7 +425,16 @@ pub enum ActiveRadarrBlock { FilterCollections, FilterMovies, Indexers, - IndexerSettings, + IndexerSettingsPrompt, + IndexerSettingsAvailabilityDelayInput, + IndexerSettingsConfirmPrompt, + IndexerSettingsMaximumSizeInput, + IndexerSettingsMinimumAgeInput, + IndexerSettingsRetentionInput, + IndexerSettingsRssSyncIntervalInput, + IndexerSettingsToggleAllowHardcodedSubs, + IndexerSettingsTogglePreferIndexerFlags, + IndexerSettingsWhitelistedSubtitleTagsInput, ManualSearch, ManualSearchSortPrompt, ManualSearchConfirmPrompt, @@ -448,6 +458,29 @@ pub enum ActiveRadarrBlock { ViewMovieOverview, } +pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 4] = [ + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::SearchMovie, + ActiveRadarrBlock::FilterMovies, + ActiveRadarrBlock::UpdateAllMoviesPrompt, +]; +pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 4] = [ + ActiveRadarrBlock::Collections, + ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::FilterCollections, + ActiveRadarrBlock::UpdateAllCollectionsPrompt, +]; +pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [ + ActiveRadarrBlock::AddIndexer, + ActiveRadarrBlock::EditIndexer, + ActiveRadarrBlock::DeleteIndexerPrompt, + ActiveRadarrBlock::Indexers, +]; +pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [ + ActiveRadarrBlock::RootFolders, + ActiveRadarrBlock::AddRootFolderPrompt, + ActiveRadarrBlock::DeleteRootFolderPrompt, +]; pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchResults, @@ -502,6 +535,11 @@ pub static EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieConfirmPrompt, ]; +pub static DOWNLOADS_BLOCKS: [ActiveRadarrBlock; 3] = [ + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::DeleteDownloadPrompt, + ActiveRadarrBlock::UpdateDownloadsPrompt, +]; pub static MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory, @@ -537,12 +575,28 @@ pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [ ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ActiveRadarrBlock::DeleteMovieConfirmPrompt, ]; -pub static INDEXER_BLOCKS: [ActiveRadarrBlock; 5] = [ - ActiveRadarrBlock::Indexers, - ActiveRadarrBlock::IndexerSettings, - ActiveRadarrBlock::AddIndexer, - ActiveRadarrBlock::EditIndexer, - ActiveRadarrBlock::DeleteIndexerPrompt, +pub static INDEXER_SETTINGS_BLOCKS: [ActiveRadarrBlock; 10] = [ + ActiveRadarrBlock::IndexerSettingsPrompt, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsConfirmPrompt, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, +]; +pub static INDEXER_SETTINGS_SELECTION_BLOCKS: [ActiveRadarrBlock; 9] = [ + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, + ActiveRadarrBlock::IndexerSettingsConfirmPrompt, ]; pub static SYSTEM_DETAILS_BLOCKS: [ActiveRadarrBlock; 5] = [ ActiveRadarrBlock::SystemLogs, @@ -600,7 +654,7 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetIndexers.into()) .await; } - ActiveRadarrBlock::IndexerSettings => { + ActiveRadarrBlock::IndexerSettingsPrompt => { self .dispatch_network_event(RadarrEvent::GetIndexerSettings.into()) .await; diff --git a/src/app/radarr_tests.rs b/src/app/radarr_tests.rs index 1312640..1b433dc 100644 --- a/src/app/radarr_tests.rs +++ b/src/app/radarr_tests.rs @@ -30,7 +30,7 @@ mod tests { )), Route::Radarr( ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::AddMovieSearchResults) + Some(ActiveRadarrBlock::AddMovieSearchResults), ) ); } @@ -358,7 +358,9 @@ mod tests { assert!(radarr_data.main_tabs.tabs[4].help.is_empty()); assert_eq!( radarr_data.main_tabs.tabs[4].contextual_help, - Some(" edit | settings | delete | refresh") + Some( + " add | edit | settings | restrictions | delete | refresh" + ) ); assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "System"); @@ -461,6 +463,7 @@ mod tests { use crate::app::radarr::{ ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, + INDEXER_SETTINGS_SELECTION_BLOCKS, }; #[test] @@ -556,6 +559,7 @@ mod tests { #[test] fn test_delete_movie_prompt_block_order() { let mut delete_movie_block_iter = DELETE_MOVIE_SELECTION_BLOCKS.iter(); + assert_eq!( delete_movie_block_iter.next().unwrap(), &ActiveRadarrBlock::DeleteMovieToggleDeleteFile @@ -569,6 +573,48 @@ mod tests { &ActiveRadarrBlock::DeleteMovieConfirmPrompt ); } + + #[test] + fn test_indexer_settings_prompt_block_order() { + let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter(); + + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsRetentionInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsConfirmPrompt + ); + } } mod radarr_tests { @@ -719,7 +765,7 @@ mod tests { let (mut app, mut sync_network_rx) = construct_app_unit(); app - .dispatch_by_radarr_block(&ActiveRadarrBlock::IndexerSettings) + .dispatch_by_radarr_block(&ActiveRadarrBlock::IndexerSettingsPrompt) .await; assert!(app.is_loading); diff --git a/src/handlers/handler_test_utils.rs b/src/handlers/handler_test_utils.rs index 440ece2..c241b32 100644 --- a/src/handlers/handler_test_utils.rs +++ b/src/handlers/handler_test_utils.rs @@ -445,12 +445,12 @@ mod test_utils { #[macro_export] macro_rules! test_handler_delegation { - ($base:expr, $active_block:expr) => { + ($handler:ident, $base:expr, $active_block:expr) => { let mut app = App::default(); app.push_navigation_stack($base.clone().into()); app.push_navigation_stack($active_block.clone().into()); - RadarrHandler::with( + $handler::with( &DEFAULT_KEYBINDINGS.esc.key, &mut app, &$active_block, diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index a674dea..4edce7d 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -37,6 +37,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into> { self.handle_key_event(); } + fn accepts(active_block: &'a T) -> bool; fn with(key: &'a Key, app: &'a mut App<'b>, active_block: &'a T, context: &'a Option) -> Self; fn get_key(&self) -> &Key; fn handle_scroll_up(&mut self); diff --git a/src/handlers/radarr_handlers/collection_details_handler.rs b/src/handlers/radarr_handlers/collections/collection_details_handler.rs similarity index 94% rename from src/handlers/radarr_handlers/collection_details_handler.rs rename to src/handlers/radarr_handlers/collections/collection_details_handler.rs index fe04ed3..c882649 100644 --- a/src/handlers/radarr_handlers/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collections/collection_details_handler.rs @@ -1,6 +1,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::radarr::{ - ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, + ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, COLLECTION_DETAILS_BLOCKS, + EDIT_COLLECTION_SELECTION_BLOCKS, }; use crate::app::App; use crate::event::Key; @@ -19,6 +20,10 @@ pub(super) struct CollectionDetailsHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + COLLECTION_DETAILS_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/collection_details_handler_tests.rs b/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs similarity index 90% rename from src/handlers/radarr_handlers/collection_details_handler_tests.rs rename to src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs index 7d1a9b4..1fc0833 100644 --- a/src/handlers/radarr_handlers/collection_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler; + use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::CollectionMovie; use crate::models::HorizontallyScrollableText; @@ -207,7 +208,6 @@ mod tests { use crate::app::radarr::RadarrData; use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; use crate::models::radarr_models::{Collection, MinimumAvailability}; - use crate::models::BlockSelectionState; use crate::models::HorizontallyScrollableText; use crate::models::StatefulTable; use crate::test_edit_collection_key; @@ -223,4 +223,15 @@ mod tests { ); } } + + #[test] + fn test_collection_details_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(CollectionDetailsHandler::accepts(&active_radarr_block)); + } else { + assert!(!CollectionDetailsHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs new file mode 100644 index 0000000..65c7cb5 --- /dev/null +++ b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs @@ -0,0 +1,673 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ + ActiveRadarrBlock, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS, + }; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::collections::CollectionsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::Collection; + use crate::models::HorizontallyScrollableText; + use crate::{extended_stateful_iterable_vec, test_handler_delegation}; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_collections_scroll, + CollectionsHandler, + collections, + simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText), + ActiveRadarrBlock::Collections, + None, + title, + to_string + ); + + test_iterable_scroll!( + test_filtered_collections_scroll, + CollectionsHandler, + filtered_collections, + simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText), + ActiveRadarrBlock::Collections, + None, + title, + to_string + ); + } + + mod test_handle_home_end { + use pretty_assertions::assert_eq; + + use crate::{ + extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, + }; + + use super::*; + + test_iterable_home_and_end!( + test_collections_home_end, + CollectionsHandler, + collections, + extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText), + ActiveRadarrBlock::Collections, + None, + title, + to_string + ); + + test_iterable_home_and_end!( + test_filtered_collections_home_end, + CollectionsHandler, + filtered_collections, + extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText), + ActiveRadarrBlock::Collections, + None, + title, + to_string + ); + + #[test] + fn test_collection_search_box_home_end_keys() { + test_text_box_home_end_keys!( + CollectionsHandler, + ActiveRadarrBlock::SearchCollection, + search + ); + } + + #[test] + fn test_collection_filter_box_home_end_keys() { + test_text_box_home_end_keys!( + CollectionsHandler, + ActiveRadarrBlock::FilterCollections, + filter + ); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[test] + fn test_collections_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(2); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Downloads.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Downloads.into() + ); + } + + #[test] + fn test_collections_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(2); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + + #[rstest] + fn test_left_right_update_all_collections_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + + CollectionsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::UpdateAllCollectionsPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + CollectionsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::UpdateAllCollectionsPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_collection_search_box_left_right_keys() { + test_text_box_left_right_keys!( + CollectionsHandler, + ActiveRadarrBlock::SearchCollection, + search + ); + } + + #[test] + fn test_collection_filter_box_left_right_keys() { + test_text_box_left_right_keys!( + CollectionsHandler, + ActiveRadarrBlock::FilterCollections, + filter + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_collections_submit() { + let mut app = App::default(); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::CollectionDetails.into() + ); + } + + #[test] + fn test_search_collections_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 2".to_owned().into(); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .collections + .current_selection() + .title + .text, + "Test 2" + ); + } + + #[test] + fn test_search_filtered_collections_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .filtered_collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 2".to_owned().into(); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .filtered_collections + .current_selection() + .title + .text, + "Test 2" + ); + } + + #[test] + fn test_filter_collections_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = "Test".to_owned().into(); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.filtered_collections.items.len(), 3); + assert_str_eq!( + app + .data + .radarr_data + .filtered_collections + .current_selection() + .title + .text, + "Test 1" + ); + } + + #[test] + fn test_update_all_collections_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllCollectionsPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::UpdateCollections) + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + + #[test] + fn test_update_all_collections_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllCollectionsPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; + use crate::{assert_filter_reset, assert_search_reset}; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_search_collection_block_esc() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); + app.data.radarr_data = create_test_radarr_data(); + + CollectionsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(!app.should_ignore_quit_key); + assert_search_reset!(app.data.radarr_data); + } + + #[test] + fn test_filter_collections_block_esc() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + app.data.radarr_data = create_test_radarr_data(); + + CollectionsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(!app.should_ignore_quit_key); + assert_filter_reset!(app.data.radarr_data); + } + + #[test] + fn test_update_all_collections_prompt_block_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); + app.data.radarr_data.prompt_confirm = true; + + CollectionsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllCollectionsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.data.radarr_data = create_test_radarr_data(); + + CollectionsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Collections, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(app.error.text.is_empty()); + assert_search_reset!(app.data.radarr_data); + assert_filter_reset!(app.data.radarr_data); + } + } + + mod test_handle_key_char { + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use serde_json::Number; + use strum::IntoEnumIterator; + + use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; + use crate::app::radarr::RadarrData; + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::models::radarr_models::MinimumAvailability; + use crate::models::HorizontallyScrollableText; + use crate::models::StatefulTable; + use crate::{assert_refresh_key, test_edit_collection_key}; + + use super::*; + + #[test] + fn test_search_collections_key() { + let mut app = App::default(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.search.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SearchCollection.into() + ); + assert!(app.data.radarr_data.is_searching); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_filter_collections_key() { + let mut app = App::default(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.filter.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterCollections.into() + ); + assert!(app.data.radarr_data.is_filtering); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_collection_edit_key() { + test_edit_collection_key!( + CollectionsHandler, + ActiveRadarrBlock::Collections, + ActiveRadarrBlock::Collections + ); + } + + #[test] + fn test_update_key() { + let mut app = App::default(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.update.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::UpdateAllCollectionsPrompt.into() + ); + } + + #[test] + fn test_refresh_collections_key() { + assert_refresh_key!(CollectionsHandler, ActiveRadarrBlock::Collections); + } + + #[test] + fn test_search_collections_box_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.search = "Test".to_owned().into(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + } + + #[test] + fn test_filter_collections_box_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.filter = "Test".to_owned().into(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); + } + + #[test] + fn test_search_collections_box_char_key() { + let mut app = App::default(); + + CollectionsHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "h"); + } + + #[test] + fn test_filter_collections_box_char_key() { + let mut app = App::default(); + + CollectionsHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.filter.text, "h"); + } + } + + #[rstest] + fn test_delegate_collection_details_blocks_to_collection_details_handler( + #[values( + ActiveRadarrBlock::CollectionDetails, + ActiveRadarrBlock::ViewMovieOverview + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + CollectionsHandler, + ActiveRadarrBlock::Collections, + active_radarr_block + ); + } + + #[rstest] + fn test_delegate_edit_collection_blocks_to_edit_collection_handler( + #[values( + ActiveRadarrBlock::EditCollectionPrompt, + ActiveRadarrBlock::EditCollectionRootFolderPathInput, + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + ActiveRadarrBlock::EditCollectionSelectQualityProfile + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + CollectionsHandler, + ActiveRadarrBlock::Collections, + active_radarr_block + ); + } + + #[test] + fn test_collections_handler_accepts() { + let mut collections_handler_blocks = Vec::new(); + collections_handler_blocks.extend(COLLECTIONS_BLOCKS); + collections_handler_blocks.extend(COLLECTION_DETAILS_BLOCKS); + collections_handler_blocks.extend(EDIT_COLLECTION_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if collections_handler_blocks.contains(&active_radarr_block) { + assert!(CollectionsHandler::accepts(&active_radarr_block)); + } else { + assert!(!CollectionsHandler::accepts(&active_radarr_block)); + } + }); + } +} diff --git a/src/handlers/radarr_handlers/edit_collection_handler.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs similarity index 97% rename from src/handlers/radarr_handlers/edit_collection_handler.rs rename to src/handlers/radarr_handlers/collections/edit_collection_handler.rs index 6c08281..9813268 100644 --- a/src/handlers/radarr_handlers/edit_collection_handler.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs @@ -1,5 +1,5 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; @@ -19,6 +19,10 @@ pub(super) struct EditCollectionHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + EDIT_COLLECTION_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/edit_collection_handler_tests.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs similarity index 96% rename from src/handlers/radarr_handlers/edit_collection_handler_tests.rs rename to src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs index 3d1f3d7..aa3031f 100644 --- a/src/handlers/radarr_handlers/edit_collection_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHandler; + use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::MinimumAvailability; @@ -500,4 +501,15 @@ mod tests { assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); } } + + #[test] + fn test_edit_collection_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) { + assert!(EditCollectionHandler::accepts(&active_radarr_block)); + } else { + assert!(!EditCollectionHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/collections/mod.rs b/src/handlers/radarr_handlers/collections/mod.rs new file mode 100644 index 0000000..b6e2397 --- /dev/null +++ b/src/handlers/radarr_handlers/collections/mod.rs @@ -0,0 +1,311 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::{ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler; +use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; +use crate::handlers::radarr_handlers::{ + filter_table, handle_change_tab_left_right_keys, search_table, +}; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::{BlockSelectionState, Scrollable}; +use crate::network::radarr_network::RadarrEvent; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; + +mod collection_details_handler; +mod edit_collection_handler; + +#[cfg(test)] +#[path = "collections_handler_tests.rs"] +mod collections_handler_tests; + +pub(super) struct CollectionsHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_radarr_block { + _ if CollectionDetailsHandler::accepts(self.active_radarr_block) => { + CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle(); + } + _ if EditCollectionHandler::accepts(self.active_radarr_block) => { + EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle(); + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + CollectionDetailsHandler::accepts(active_block) + || EditCollectionHandler::accepts(active_block) + || COLLECTIONS_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + context: &'a Option, + ) -> CollectionsHandler<'a, 'b> { + CollectionsHandler { + key, + app, + active_radarr_block: active_block, + context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Collections { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self.app.data.radarr_data.filtered_collections.scroll_up(); + } else { + self.app.data.radarr_data.collections.scroll_up() + } + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Collections { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self.app.data.radarr_data.filtered_collections.scroll_down(); + } else { + self.app.data.radarr_data.collections.scroll_down() + } + } + } + + fn handle_home(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self + .app + .data + .radarr_data + .filtered_collections + .scroll_to_top(); + } else { + self.app.data.radarr_data.collections.scroll_to_top() + } + } + ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.scroll_home(), + ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.scroll_home(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self + .app + .data + .radarr_data + .filtered_collections + .scroll_to_bottom(); + } else { + self.app.data.radarr_data.collections.scroll_to_bottom() + } + } + ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.reset_offset(), + ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.reset_offset(), + _ => (), + } + } + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key), + ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key), + ActiveRadarrBlock::SearchCollection => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) + } + ActiveRadarrBlock::FilterCollections => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => self + .app + .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), + ActiveRadarrBlock::SearchCollection => { + if self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + let selected_index = search_table( + self.app, + &self.app.data.radarr_data.collections.items.clone(), + |collection| &collection.title.text, + ); + self + .app + .data + .radarr_data + .collections + .select_index(selected_index); + } else { + let selected_index = search_table( + self.app, + &self.app.data.radarr_data.filtered_collections.items.clone(), + |collection| &collection.title.text, + ); + self + .app + .data + .radarr_data + .filtered_collections + .select_index(selected_index); + } + } + ActiveRadarrBlock::FilterCollections => { + let filtered_collections = filter_table( + self.app, + &self.app.data.radarr_data.collections.items.clone(), + |collection| &collection.title.text, + ); + + if !filtered_collections.is_empty() { + self + .app + .data + .radarr_data + .filtered_collections + .set_items(filtered_collections); + } + } + ActiveRadarrBlock::UpdateAllCollectionsPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::FilterCollections => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.reset_filter(); + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::SearchCollection => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.reset_search(); + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::UpdateAllCollectionsPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } + _ => { + self.app.data.radarr_data.reset_search(); + self.app.data.radarr_data.reset_filter(); + handle_clear_errors(self.app); + } + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_radarr_block { + ActiveRadarrBlock::Collections => match self.key { + _ if *key == DEFAULT_KEYBINDINGS.search.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); + self.app.data.radarr_data.is_searching = true; + self.app.should_ignore_quit_key = true; + } + _ if *key == DEFAULT_KEYBINDINGS.filter.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + self.app.data.radarr_data.is_filtering = true; + self.app.should_ignore_quit_key = true; + } + _ if *key == DEFAULT_KEYBINDINGS.edit.key => { + self.app.push_navigation_stack( + ( + ActiveRadarrBlock::EditCollectionPrompt, + Some(ActiveRadarrBlock::Collections), + ) + .into(), + ); + self.app.data.radarr_data.populate_edit_collection_fields(); + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + } + _ if *key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ => (), + }, + ActiveRadarrBlock::SearchCollection => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.search) + } + ActiveRadarrBlock::FilterCollections => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.filter) + } + _ => (), + } + } +} diff --git a/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs new file mode 100644 index 0000000..2bbe5ab --- /dev/null +++ b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs @@ -0,0 +1,282 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::downloads::DownloadsHandler; + use crate::handlers::KeyEventHandler; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::models::radarr_models::DownloadRecord; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_downloads_scroll, + DownloadsHandler, + downloads, + DownloadRecord, + ActiveRadarrBlock::Downloads, + None, + title + ); + } + + mod test_handle_home_end { + use crate::models::radarr_models::DownloadRecord; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_downloads_home_end, + DownloadsHandler, + downloads, + DownloadRecord, + ActiveRadarrBlock::Downloads, + None, + title + ); + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use crate::assert_delete_prompt; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_delete_download_prompt() { + assert_delete_prompt!( + DownloadsHandler, + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::DeleteDownloadPrompt + ); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[test] + fn test_downloads_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(1); + + DownloadsHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::Downloads, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Movies.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + + #[test] + fn test_downloads_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(1); + + DownloadsHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::Downloads, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + + #[rstest] + fn test_downloads_left_right_prompt_toggle( + #[values( + ActiveRadarrBlock::DeleteDownloadPrompt, + ActiveRadarrBlock::UpdateDownloadsPrompt + )] + active_radarr_block: ActiveRadarrBlock, + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + + DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[rstest] + #[case( + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::DeleteDownloadPrompt, + RadarrEvent::DeleteDownload + )] + #[case( + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::UpdateDownloadsPrompt, + RadarrEvent::UpdateDownloads + )] + fn test_downloads_prompt_confirm_submit( + #[case] base_route: ActiveRadarrBlock, + #[case] prompt_block: ActiveRadarrBlock, + #[case] expected_action: RadarrEvent, + ) { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(base_route.into()); + app.push_navigation_stack(prompt_block.into()); + + DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(expected_action) + ); + assert_eq!(app.get_current_route(), &base_route.into()); + } + + #[rstest] + #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] + #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] + fn test_downloads_prompt_decline_submit( + #[case] base_route: ActiveRadarrBlock, + #[case] prompt_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(base_route.into()); + app.push_navigation_stack(prompt_block.into()); + + DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert_eq!(app.get_current_route(), &base_route.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] + #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] + fn test_downloads_prompt_blocks_esc( + #[case] base_block: ActiveRadarrBlock, + #[case] prompt_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(base_block.into()); + app.push_navigation_stack(prompt_block.into()); + app.data.radarr_data.prompt_confirm = true; + + DownloadsHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); + + assert_eq!(app.get_current_route(), &base_block.into()); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); + app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); + + DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Downloads.into() + ); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::assert_eq; + + use crate::assert_refresh_key; + + use super::*; + + #[test] + fn test_update_downloads_key() { + let mut app = App::default(); + + DownloadsHandler::with( + &DEFAULT_KEYBINDINGS.update.key, + &mut app, + &ActiveRadarrBlock::Downloads, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::UpdateDownloadsPrompt.into() + ); + } + + #[test] + fn test_refresh_downloads_key() { + assert_refresh_key!(DownloadsHandler, ActiveRadarrBlock::Downloads); + } + } + + #[test] + fn test_downloads_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if DOWNLOADS_BLOCKS.contains(&active_radarr_block) { + assert!(DownloadsHandler::accepts(&active_radarr_block)); + } else { + assert!(!DownloadsHandler::accepts(&active_radarr_block)); + } + }) + } +} diff --git a/src/handlers/radarr_handlers/downloads/mod.rs b/src/handlers/radarr_handlers/downloads/mod.rs new file mode 100644 index 0000000..65ac49c --- /dev/null +++ b/src/handlers/radarr_handlers/downloads/mod.rs @@ -0,0 +1,132 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::Scrollable; +use crate::network::radarr_network::RadarrEvent; + +#[cfg(test)] +#[path = "downloads_handler_tests.rs"] +mod downloads_handler_tests; + +pub(super) struct DownloadsHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + _context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + DOWNLOADS_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + _context: &'a Option, + ) -> DownloadsHandler<'a, 'b> { + DownloadsHandler { + key, + app, + active_radarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + self.app.data.radarr_data.downloads.scroll_up() + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + self.app.data.radarr_data.downloads.scroll_down() + } + } + + fn handle_home(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + self.app.data.radarr_data.downloads.scroll_to_top() + } + } + + fn handle_end(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + self.app.data.radarr_data.downloads.scroll_to_bottom() + } + } + + fn handle_delete(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into()) + } + } + + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Downloads => handle_change_tab_left_right_keys(self.app, self.key), + ActiveRadarrBlock::DeleteDownloadPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => { + handle_prompt_toggle(self.app, self.key) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::DeleteDownloadPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload); + } + + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::UpdateDownloadsPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::DeleteDownloadPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } + _ => handle_clear_errors(self.app), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + if self.active_radarr_block == &ActiveRadarrBlock::Downloads { + match self.key { + _ if *key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ => (), + } + } + } +} diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs new file mode 100644 index 0000000..11aedcf --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -0,0 +1,74 @@ +use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::KeyEventHandler; + +#[cfg(test)] +#[path = "./edit_indexer_settings_handler_tests.rs"] +mod edit_indexer_settings_handler_tests; + +pub(super) struct IndexerSettingsHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + _context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + INDEXER_SETTINGS_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + _context: &'a Option, + ) -> IndexerSettingsHandler<'a, 'b> { + IndexerSettingsHandler { + key, + app, + active_radarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt { + self.app.data.radarr_data.selected_block.previous() + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt { + self.app.data.radarr_data.selected_block.next() + } + } + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) {} + + fn handle_submit(&mut self) {} + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::IndexerSettingsPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + self.app.data.radarr_data.indexer_settings = None; + } + _ => self.app.pop_navigation_stack(), // Need to tweak this still and add unit tests + } + } + + fn handle_char_key_event(&mut self) {} +} diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs new file mode 100644 index 0000000..8af2c9f --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs @@ -0,0 +1,98 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS}; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; + use crate::handlers::KeyEventHandler; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::app::radarr::INDEXER_SETTINGS_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + + use super::*; + + #[rstest] + fn test_edit_indexer_settings_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); + + IndexerSettingsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + if key == Key::Up { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput + ); + } else { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput + ); + } + } + } + + mod test_handle_home_end {} + + mod test_handle_left_right_action {} + + mod test_handle_submit {} + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_edit_indexer_settings_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.data.radarr_data = create_test_radarr_data(); + + IndexerSettingsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.indexer_settings, None); + } + } + + mod test_handle_key_char {} + + #[test] + fn test_indexer_settings_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) { + assert!(IndexerSettingsHandler::accepts(&active_radarr_block)); + } else { + assert!(!IndexerSettingsHandler::accepts(&active_radarr_block)); + } + }) + } +} diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs new file mode 100644 index 0000000..75c13d1 --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -0,0 +1,342 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS}; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::indexers::IndexersHandler; + use crate::handlers::KeyEventHandler; + use crate::test_handler_delegation; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::models::radarr_models::Indexer; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_indexers_scroll, + IndexersHandler, + indexers, + simple_stateful_iterable_vec!(Indexer, String, protocol), + ActiveRadarrBlock::Indexers, + None, + protocol + ); + } + + mod test_handle_home_end { + use crate::models::radarr_models::Indexer; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_indexers_home_end, + IndexersHandler, + indexers, + extended_stateful_iterable_vec!(Indexer, String, protocol), + ActiveRadarrBlock::Indexers, + None, + protocol + ); + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use crate::assert_delete_prompt; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_delete_indexer_prompt() { + assert_delete_prompt!( + IndexersHandler, + ActiveRadarrBlock::Indexers, + ActiveRadarrBlock::DeleteIndexerPrompt + ); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[test] + fn test_indexers_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(4); + + IndexersHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::Indexers, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + + #[test] + fn test_indexers_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(4); + + IndexersHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::Indexers, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::System.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); + } + + #[rstest] + fn test_left_right_delete_indexer_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + + IndexersHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::DeleteIndexerPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + IndexersHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::DeleteIndexerPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_indexer_submit_aka_edit() { + let mut app = App::default(); + + IndexersHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditIndexer.into() + ); + } + + #[test] + fn test_delete_indexer_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); + + IndexersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteIndexerPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::DeleteIndexer) + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + } + + #[test] + fn test_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); + + IndexersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteIndexerPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_delete_indexer_prompt_block_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); + app.data.radarr_data.prompt_confirm = true; + + IndexersHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::DeleteIndexerPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + + IndexersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::assert_eq; + + use crate::app::radarr::INDEXER_SETTINGS_SELECTION_BLOCKS; + use crate::assert_refresh_key; + + use super::*; + + #[test] + fn test_indexer_add() { + let mut app = App::default(); + + IndexersHandler::with( + &DEFAULT_KEYBINDINGS.add.key, + &mut app, + &ActiveRadarrBlock::Indexers, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddIndexer.into() + ); + } + + #[test] + fn test_refresh_indexers_key() { + assert_refresh_key!(IndexersHandler, ActiveRadarrBlock::Indexers); + } + + #[test] + fn test_indexer_settings_key() { + let mut app = App::default(); + + IndexersHandler::with( + &DEFAULT_KEYBINDINGS.settings.key, + &mut app, + &ActiveRadarrBlock::Indexers, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + assert_eq!( + app.data.radarr_data.selected_block.blocks, + &INDEXER_SETTINGS_SELECTION_BLOCKS + ); + } + } + + #[rstest] + fn test_delegates_indexer_settings_blocks_to_indexer_settings_handler( + #[values( + ActiveRadarrBlock::IndexerSettingsPrompt, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsConfirmPrompt, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + IndexersHandler, + ActiveRadarrBlock::Indexers, + active_radarr_block + ); + } + + #[test] + fn test_indexers_handler_accepts() { + let mut indexers_blocks = Vec::new(); + indexers_blocks.extend(INDEXERS_BLOCKS); + indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if indexers_blocks.contains(&active_radarr_block) { + assert!(IndexersHandler::accepts(&active_radarr_block)); + } else { + assert!(!IndexersHandler::accepts(&active_radarr_block)); + } + }) + } +} diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs new file mode 100644 index 0000000..5590196 --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -0,0 +1,148 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::{BlockSelectionState, Scrollable}; +use crate::network::radarr_network::RadarrEvent; + +mod edit_indexer_settings_handler; + +#[cfg(test)] +#[path = "indexers_handler_tests.rs"] +mod indexers_handler_tests; + +pub(super) struct IndexersHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_radarr_block { + _ if IndexerSettingsHandler::accepts(self.active_radarr_block) => { + IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle() + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + IndexerSettingsHandler::accepts(active_block) || INDEXERS_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + context: &'a Option, + ) -> IndexersHandler<'a, 'b> { + IndexersHandler { + key, + app, + active_radarr_block: active_block, + context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + self.app.data.radarr_data.indexers.scroll_up(); + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + self.app.data.radarr_data.indexers.scroll_down(); + } + } + + fn handle_home(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + self.app.data.radarr_data.indexers.scroll_to_top(); + } + } + + fn handle_end(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + self.app.data.radarr_data.indexers.scroll_to_bottom(); + } + } + + fn handle_delete(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); + } + } + + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Indexers => handle_change_tab_left_right_keys(self.app, self.key), + ActiveRadarrBlock::DeleteIndexerPrompt => handle_prompt_toggle(self.app, self.key), + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::DeleteIndexerPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer); + } + + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::Indexers => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::EditIndexer.into()); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::DeleteIndexerPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } + _ => handle_clear_errors(self.app), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + if self.active_radarr_block == &ActiveRadarrBlock::Indexers { + match self.key { + _ if *key == DEFAULT_KEYBINDINGS.add.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::AddIndexer.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ if *key == DEFAULT_KEYBINDINGS.settings.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + } + _ => (), + } + } + } +} diff --git a/src/handlers/radarr_handlers/add_movie_handler.rs b/src/handlers/radarr_handlers/library/add_movie_handler.rs similarity index 98% rename from src/handlers/radarr_handlers/add_movie_handler.rs rename to src/handlers/radarr_handlers/library/add_movie_handler.rs index 2770db2..e74a30c 100644 --- a/src/handlers/radarr_handlers/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler.rs @@ -1,5 +1,5 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS}; +use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::models::{BlockSelectionState, Scrollable, StatefulTable}; use crate::network::radarr_network::RadarrEvent; @@ -17,6 +17,10 @@ pub(super) struct AddMovieHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + ADD_MOVIE_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/add_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs similarity index 97% rename from src/handlers/radarr_handlers/add_movie_handler_tests.rs rename to src/handlers/radarr_handlers/library/add_movie_handler_tests.rs index 0ce22e8..7ef27d6 100644 --- a/src/handlers/radarr_handlers/add_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler; + use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::{ AddMovieSearchResult, MinimumAvailability, Monitor, RootFolder, @@ -724,4 +725,15 @@ mod tests { assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); } } + + #[test] + fn test_add_movie_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(AddMovieHandler::accepts(&active_radarr_block)); + } else { + assert!(!AddMovieHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/delete_movie_handler.rs b/src/handlers/radarr_handlers/library/delete_movie_handler.rs similarity index 93% rename from src/handlers/radarr_handlers/delete_movie_handler.rs rename to src/handlers/radarr_handlers/library/delete_movie_handler.rs index c38b2b2..33d1de5 100644 --- a/src/handlers/radarr_handlers/delete_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/delete_movie_handler.rs @@ -1,4 +1,4 @@ -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; @@ -16,6 +16,10 @@ pub(super) struct DeleteMovieHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + DELETE_MOVIE_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/delete_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs similarity index 91% rename from src/handlers/radarr_handlers/delete_movie_handler_tests.rs rename to src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs index 132447c..16c2988 100644 --- a/src/handlers/radarr_handlers/delete_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs @@ -1,10 +1,12 @@ #[cfg(test)] mod tests { + use strum::IntoEnumIterator; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::delete_movie_handler::DeleteMovieHandler; + use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; use crate::handlers::KeyEventHandler; mod test_handle_scroll_up_and_down { @@ -197,4 +199,15 @@ mod tests { assert!(!app.data.radarr_data.add_list_exclusion); } } + + #[test] + fn test_delete_movie_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(DeleteMovieHandler::accepts(&active_radarr_block)); + } else { + assert!(!DeleteMovieHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/edit_movie_handler.rs b/src/handlers/radarr_handlers/library/edit_movie_handler.rs similarity index 97% rename from src/handlers/radarr_handlers/edit_movie_handler.rs rename to src/handlers/radarr_handlers/library/edit_movie_handler.rs index d9f169f..40eba00 100644 --- a/src/handlers/radarr_handlers/edit_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/edit_movie_handler.rs @@ -1,5 +1,5 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; @@ -19,6 +19,10 @@ pub(super) struct EditMovieHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + EDIT_MOVIE_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/edit_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs similarity index 96% rename from src/handlers/radarr_handlers/edit_movie_handler_tests.rs rename to src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs index fa09b10..3d30337 100644 --- a/src/handlers/radarr_handlers/edit_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler; + use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::MinimumAvailability; @@ -503,4 +504,15 @@ mod tests { assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); } } + + #[test] + fn test_edit_movie_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(EditMovieHandler::accepts(&active_radarr_block)); + } else { + assert!(!EditMovieHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/library/library_handler_tests.rs b/src/handlers/radarr_handlers/library/library_handler_tests.rs new file mode 100644 index 0000000..810c482 --- /dev/null +++ b/src/handlers/radarr_handlers/library/library_handler_tests.rs @@ -0,0 +1,699 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ + ActiveRadarrBlock, ADD_MOVIE_BLOCKS, DELETE_MOVIE_BLOCKS, EDIT_MOVIE_BLOCKS, LIBRARY_BLOCKS, + MOVIE_DETAILS_BLOCKS, + }; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::library::LibraryHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::Movie; + use crate::models::HorizontallyScrollableText; + use crate::test_handler_delegation; + + mod test_handle_scroll_up_and_down { + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_movies_scroll, + LibraryHandler, + movies, + simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText), + ActiveRadarrBlock::Movies, + None, + title, + to_string + ); + + test_iterable_scroll!( + test_filtered_movies_scroll, + LibraryHandler, + filtered_movies, + simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText), + ActiveRadarrBlock::Movies, + None, + title, + to_string + ); + } + + mod test_handle_home_end { + use pretty_assertions::assert_eq; + + use crate::{ + extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, + }; + + use super::*; + + test_iterable_home_and_end!( + test_movies_home_end, + LibraryHandler, + movies, + extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText), + ActiveRadarrBlock::Movies, + None, + title, + to_string + ); + + test_iterable_home_and_end!( + test_filtered_movies_home_end, + LibraryHandler, + filtered_movies, + extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText), + ActiveRadarrBlock::Movies, + None, + title, + to_string + ); + + #[test] + fn test_movie_search_box_home_end_keys() { + test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search); + } + + #[test] + fn test_movie_filter_box_home_end_keys() { + test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter); + } + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; + use crate::assert_delete_prompt; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_movies_delete() { + let mut app = App::default(); + + assert_delete_prompt!( + LibraryHandler, + app, + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::DeleteMoviePrompt + ); + assert_eq!( + app.data.radarr_data.selected_block.blocks, + &DELETE_MOVIE_SELECTION_BLOCKS + ); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[test] + fn test_movie_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(0); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::System.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); + } + + #[test] + fn test_movie_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(0); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Downloads.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Downloads.into() + ); + } + + #[rstest] + fn test_left_right_update_all_movies_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + + LibraryHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::UpdateAllMoviesPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + LibraryHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::UpdateAllMoviesPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_movie_search_box_left_right_keys() { + test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search); + } + + #[test] + fn test_movie_filter_box_left_right_keys() { + test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::extended_stateful_iterable_vec; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_movie_details_submit() { + let mut app = App::default(); + + LibraryHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::MovieDetails.into() + ); + } + + #[test] + fn test_search_movie_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 2".to_owned().into(); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_str_eq!( + app.data.radarr_data.movies.current_selection().title.text, + "Test 2" + ); + } + + #[test] + fn test_search_filtered_movies_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .filtered_movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 2".to_owned().into(); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .filtered_movies + .current_selection() + .title + .text, + "Test 2" + ); + } + + #[test] + fn test_filter_movies_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = "Test".to_owned().into(); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.filtered_movies.items.len(), 3); + assert_str_eq!( + app + .data + .radarr_data + .filtered_movies + .current_selection() + .title + .text, + "Test 1" + ); + } + + #[test] + fn test_update_all_movies_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllMoviesPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::UpdateAllMovies) + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + + #[test] + fn test_update_all_movies_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllMoviesPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; + use crate::{assert_filter_reset, assert_search_reset}; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_search_movie_block_esc() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + app.data.radarr_data = create_test_radarr_data(); + + LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SearchMovie, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.should_ignore_quit_key); + assert_search_reset!(app.data.radarr_data); + } + + #[test] + fn test_filter_movies_block_esc() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + app.data.radarr_data = create_test_radarr_data(); + + LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::FilterMovies, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.should_ignore_quit_key); + assert_filter_reset!(app.data.radarr_data); + } + + #[test] + fn test_update_all_movies_prompt_blocks_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); + app.data.radarr_data.prompt_confirm = true; + + LibraryHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::UpdateAllMoviesPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.data.radarr_data = create_test_radarr_data(); + + LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(app.error.text.is_empty()); + assert_search_reset!(app.data.radarr_data); + assert_filter_reset!(app.data.radarr_data); + } + } + + mod test_handle_key_char { + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use serde_json::Number; + use strum::IntoEnumIterator; + + use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; + use crate::app::radarr::RadarrData; + use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; + use crate::models::radarr_models::MinimumAvailability; + use crate::models::HorizontallyScrollableText; + use crate::models::StatefulTable; + use crate::{assert_refresh_key, test_edit_movie_key}; + + use super::*; + + #[test] + fn test_search_movies_key() { + let mut app = App::default(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.search.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SearchMovie.into() + ); + assert!(app.data.radarr_data.is_searching); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_filter_movies_key() { + let mut app = App::default(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.filter.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMovies.into() + ); + assert!(app.data.radarr_data.is_filtering); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_movie_add() { + let mut app = App::default(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.add.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchInput.into() + ); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_movie_edit_key() { + test_edit_movie_key!( + LibraryHandler, + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::Movies + ); + } + + #[test] + fn test_update_all_movies_key() { + let mut app = App::default(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.update.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::UpdateAllMoviesPrompt.into() + ); + } + + #[test] + fn test_refresh_movies_key() { + assert_refresh_key!(LibraryHandler, ActiveRadarrBlock::Movies); + } + + #[test] + fn test_search_movies_box_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.search = "Test".to_owned().into(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + } + + #[test] + fn test_filter_movies_box_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.filter = "Test".to_owned().into(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); + } + + #[test] + fn test_search_movies_box_char_key() { + let mut app = App::default(); + + LibraryHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "h"); + } + + #[test] + fn test_filter_movies_box_char_key() { + let mut app = App::default(); + + LibraryHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.filter.text, "h"); + } + } + + #[rstest] + fn test_delegates_add_movie_blocks_to_add_movie_handler( + #[values( + ActiveRadarrBlock::AddMovieSearchInput, + ActiveRadarrBlock::AddMovieSearchResults, + ActiveRadarrBlock::AddMoviePrompt, + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieAlreadyInLibrary, + ActiveRadarrBlock::AddMovieTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + LibraryHandler, + ActiveRadarrBlock::Movies, + active_radarr_block + ); + } + + #[rstest] + fn test_delegates_movie_details_blocks_to_movie_details_handler( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + ActiveRadarrBlock::UpdateAndScanPrompt, + ActiveRadarrBlock::ManualSearch, + ActiveRadarrBlock::ManualSearchConfirmPrompt + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + LibraryHandler, + ActiveRadarrBlock::Movies, + active_radarr_block + ); + } + + #[rstest] + fn test_delegates_edit_movie_blocks_to_edit_movie_handler( + #[values( + ActiveRadarrBlock::EditMoviePrompt, + ActiveRadarrBlock::EditMoviePathInput, + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + ActiveRadarrBlock::EditMovieTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + LibraryHandler, + ActiveRadarrBlock::Movies, + active_radarr_block + ); + } + + #[test] + fn test_delegates_delete_movie_blocks_to_delete_movie_handler() { + test_handler_delegation!( + LibraryHandler, + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::DeleteMoviePrompt + ); + } + + #[test] + fn test_library_handler_accepts() { + let mut library_handler_blocks = Vec::new(); + library_handler_blocks.extend(LIBRARY_BLOCKS); + library_handler_blocks.extend(ADD_MOVIE_BLOCKS); + library_handler_blocks.extend(DELETE_MOVIE_BLOCKS); + library_handler_blocks.extend(EDIT_MOVIE_BLOCKS); + library_handler_blocks.extend(MOVIE_DETAILS_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if library_handler_blocks.contains(&active_radarr_block) { + assert!(LibraryHandler::accepts(&active_radarr_block)); + } else { + assert!(!LibraryHandler::accepts(&active_radarr_block)); + } + }); + } +} diff --git a/src/handlers/radarr_handlers/library/mod.rs b/src/handlers/radarr_handlers/library/mod.rs new file mode 100644 index 0000000..f383cf0 --- /dev/null +++ b/src/handlers/radarr_handlers/library/mod.rs @@ -0,0 +1,298 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::{ + ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS, +}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler; +use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; +use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler; +use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler; +use crate::handlers::radarr_handlers::{ + filter_table, handle_change_tab_left_right_keys, search_table, +}; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::{BlockSelectionState, Scrollable}; +use crate::network::radarr_network::RadarrEvent; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; + +mod add_movie_handler; +mod delete_movie_handler; +mod edit_movie_handler; +mod movie_details_handler; + +#[cfg(test)] +#[path = "library_handler_tests.rs"] +mod library_handler_tests; + +pub(super) struct LibraryHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_radarr_block { + _ if AddMovieHandler::accepts(self.active_radarr_block) => { + AddMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle(); + } + _ if DeleteMovieHandler::accepts(self.active_radarr_block) => { + DeleteMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle(); + } + _ if EditMovieHandler::accepts(self.active_radarr_block) => { + EditMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle(); + } + _ if MovieDetailsHandler::accepts(self.active_radarr_block) => { + MovieDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle(); + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + AddMovieHandler::accepts(active_block) + || DeleteMovieHandler::accepts(active_block) + || EditMovieHandler::accepts(active_block) + || MovieDetailsHandler::accepts(active_block) + || LIBRARY_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + context: &'a Option, + ) -> LibraryHandler<'a, 'b> { + LibraryHandler { + key, + app, + active_radarr_block: active_block, + context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Movies { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_up(); + } else { + self.app.data.radarr_data.movies.scroll_up() + } + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Movies { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_down(); + } else { + self.app.data.radarr_data.movies.scroll_down() + } + } + } + + fn handle_home(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Movies => { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_to_top(); + } else { + self.app.data.radarr_data.movies.scroll_to_top() + } + } + ActiveRadarrBlock::SearchMovie => { + self.app.data.radarr_data.search.scroll_home(); + } + ActiveRadarrBlock::FilterMovies => { + self.app.data.radarr_data.filter.scroll_home(); + } + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Movies => { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_to_bottom(); + } else { + self.app.data.radarr_data.movies.scroll_to_bottom() + } + } + ActiveRadarrBlock::SearchMovie => self.app.data.radarr_data.search.reset_offset(), + ActiveRadarrBlock::FilterMovies => self.app.data.radarr_data.filter.reset_offset(), + _ => (), + } + } + + fn handle_delete(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::Movies { + self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + } + } + + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Movies => handle_change_tab_left_right_keys(self.app, self.key), + ActiveRadarrBlock::UpdateAllMoviesPrompt => handle_prompt_toggle(self.app, self.key), + ActiveRadarrBlock::SearchMovie => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) + } + ActiveRadarrBlock::FilterMovies => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Movies => self + .app + .push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()), + ActiveRadarrBlock::SearchMovie => { + if self.app.data.radarr_data.filtered_movies.items.is_empty() { + let selected_index = search_table( + self.app, + &self.app.data.radarr_data.movies.items.clone(), + |movie| &movie.title.text, + ); + self + .app + .data + .radarr_data + .movies + .select_index(selected_index); + } else { + let selected_index = search_table( + self.app, + &self.app.data.radarr_data.filtered_movies.items.clone(), + |movie| &movie.title.text, + ); + self + .app + .data + .radarr_data + .filtered_movies + .select_index(selected_index); + }; + } + ActiveRadarrBlock::FilterMovies => { + let filtered_movies = filter_table( + self.app, + &self.app.data.radarr_data.movies.items.clone(), + |movie| &movie.title.text, + ); + + if !filtered_movies.is_empty() { + self + .app + .data + .radarr_data + .filtered_movies + .set_items(filtered_movies); + } + } + ActiveRadarrBlock::UpdateAllMoviesPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::FilterMovies => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.reset_filter(); + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::SearchMovie => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.reset_search(); + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::UpdateAllMoviesPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } + _ => { + self.app.data.radarr_data.reset_search(); + self.app.data.radarr_data.reset_filter(); + handle_clear_errors(self.app); + } + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_radarr_block { + ActiveRadarrBlock::Movies => match self.key { + _ if *key == DEFAULT_KEYBINDINGS.search.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + self.app.data.radarr_data.is_searching = true; + self.app.should_ignore_quit_key = true; + } + _ if *key == DEFAULT_KEYBINDINGS.filter.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + self.app.data.radarr_data.is_filtering = true; + self.app.should_ignore_quit_key = true; + } + _ if *key == DEFAULT_KEYBINDINGS.edit.key => { + self.app.push_navigation_stack( + ( + ActiveRadarrBlock::EditMoviePrompt, + Some(ActiveRadarrBlock::Movies), + ) + .into(), + ); + self.app.data.radarr_data.populate_edit_movie_fields(); + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + } + _ if *key == DEFAULT_KEYBINDINGS.add.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); + self.app.should_ignore_quit_key = true; + } + _ if *key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ => (), + }, + ActiveRadarrBlock::SearchMovie => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.search) + } + ActiveRadarrBlock::FilterMovies => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.filter) + } + _ => (), + } + } +} diff --git a/src/handlers/radarr_handlers/movie_details_handler.rs b/src/handlers/radarr_handlers/library/movie_details_handler.rs similarity index 98% rename from src/handlers/radarr_handlers/movie_details_handler.rs rename to src/handlers/radarr_handlers/library/movie_details_handler.rs index b4f127f..2358da6 100644 --- a/src/handlers/radarr_handlers/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/library/movie_details_handler.rs @@ -4,7 +4,7 @@ use serde_json::Number; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS}; +use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS}; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; @@ -24,6 +24,10 @@ pub(super) struct MovieDetailsHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + MOVIE_DETAILS_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, @@ -292,7 +296,7 @@ fn sort_releases_by_selected_field( .cmp(release_b.size.as_u64().as_ref().unwrap()) }, ReleaseField::Peers => |release_a, release_b| { - let default_number = Number::from(i64::MAX); + let default_number = Number::from(i64::max_value()); let seeder_a = release_a .seeders .as_ref() diff --git a/src/handlers/radarr_handlers/movie_details_handler_tests.rs b/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs similarity index 97% rename from src/handlers/radarr_handlers/movie_details_handler_tests.rs rename to src/handlers/radarr_handlers/library/movie_details_handler_tests.rs index 4743d9f..a4d539d 100644 --- a/src/handlers/radarr_handlers/movie_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs @@ -3,12 +3,13 @@ mod tests { use pretty_assertions::assert_str_eq; use rstest::rstest; use serde_json::Number; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::movie_details_handler::{ + use crate::handlers::radarr_handlers::library::movie_details_handler::{ sort_releases_by_selected_field, MovieDetailsHandler, }; use crate::handlers::KeyEventHandler; @@ -415,7 +416,6 @@ mod tests { use crate::app::radarr::RadarrData; use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; use crate::models::radarr_models::{MinimumAvailability, Movie}; - use crate::models::BlockSelectionState; use crate::models::HorizontallyScrollableText; use crate::models::StatefulTable; use crate::test_edit_movie_key; @@ -643,4 +643,15 @@ mod tests { vec![release_a, release_b, release_c] } + + #[test] + fn test_movie_details_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(MovieDetailsHandler::accepts(&active_radarr_block)); + } else { + assert!(!MovieDetailsHandler::accepts(&active_radarr_block)); + } + }); + } } diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 9a92c19..328eb34 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -1,30 +1,21 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::{ - ActiveRadarrBlock, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, - DELETE_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, - EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, FILTER_BLOCKS, MOVIE_DETAILS_BLOCKS, - SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS, -}; -use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler; -use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler; -use crate::handlers::radarr_handlers::delete_movie_handler::DeleteMovieHandler; -use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHandler; -use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler; -use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler; -use crate::handlers::radarr_handlers::system_details_handler::SystemDetailsHandler; -use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; -use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; -use crate::network::radarr_network::RadarrEvent; +use crate::app::radarr::ActiveRadarrBlock; +use crate::handlers::radarr_handlers::collections::CollectionsHandler; +use crate::handlers::radarr_handlers::downloads::DownloadsHandler; +use crate::handlers::radarr_handlers::indexers::IndexersHandler; +use crate::handlers::radarr_handlers::library::LibraryHandler; +use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler; +use crate::handlers::radarr_handlers::system::SystemHandler; +use crate::handlers::KeyEventHandler; use crate::utils::strip_non_search_characters; -use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; +use crate::{App, Key}; -mod add_movie_handler; -mod collection_details_handler; -mod delete_movie_handler; -mod edit_collection_handler; -mod edit_movie_handler; -mod movie_details_handler; -mod system_details_handler; +mod collections; +mod downloads; +mod indexers; +mod library; +mod root_folders; +mod system; #[cfg(test)] #[path = "radarr_handler_tests.rs"] @@ -44,36 +35,34 @@ pub(super) struct RadarrHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b> { fn handle(&mut self) { match self.active_radarr_block { - _ if MOVIE_DETAILS_BLOCKS.contains(self.active_radarr_block) => { - MovieDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + _ if LibraryHandler::accepts(self.active_radarr_block) => { + LibraryHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle(); + } + _ if CollectionsHandler::accepts(self.active_radarr_block) => { + CollectionsHandler::with(self.key, self.app, self.active_radarr_block, self.context) .handle() } - _ if COLLECTION_DETAILS_BLOCKS.contains(self.active_radarr_block) => { - CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + _ if IndexersHandler::accepts(self.active_radarr_block) => { + IndexersHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle() + } + _ if SystemHandler::accepts(self.active_radarr_block) => { + SystemHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle() + } + _ if DownloadsHandler::accepts(self.active_radarr_block) => { + DownloadsHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle() + } + _ if RootFoldersHandler::accepts(self.active_radarr_block) => { + RootFoldersHandler::with(self.key, self.app, self.active_radarr_block, self.context) .handle() } - _ if ADD_MOVIE_BLOCKS.contains(self.active_radarr_block) => { - AddMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle() - } - _ if EDIT_MOVIE_BLOCKS.contains(self.active_radarr_block) => { - EditMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle() - } - _ if DELETE_MOVIE_BLOCKS.contains(self.active_radarr_block) => { - DeleteMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context) - .handle() - } - _ if EDIT_COLLECTION_BLOCKS.contains(self.active_radarr_block) => { - EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context) - .handle() - } - _ if SYSTEM_DETAILS_BLOCKS.contains(self.active_radarr_block) => { - SystemDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) - .handle(); - } _ => self.handle_key_event(), } } + fn accepts(_active_block: &'a ActiveRadarrBlock) -> bool { + true + } + fn with( key: &'a Key, app: &'a mut App<'b>, @@ -92,608 +81,77 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b self.key } - fn handle_scroll_up(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if !self - .app - .data - .radarr_data - .filtered_collections - .items - .is_empty() - { - self.app.data.radarr_data.filtered_collections.scroll_up(); - } else { - self.app.data.radarr_data.collections.scroll_up() - } - } - ActiveRadarrBlock::Movies => { - if !self.app.data.radarr_data.filtered_movies.items.is_empty() { - self.app.data.radarr_data.filtered_movies.scroll_up(); - } else { - self.app.data.radarr_data.movies.scroll_up() - } - } - ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_up(), - ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_up(), - ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_up(), - _ => (), - } - } + fn handle_scroll_up(&mut self) {} - fn handle_scroll_down(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if !self - .app - .data - .radarr_data - .filtered_collections - .items - .is_empty() - { - self.app.data.radarr_data.filtered_collections.scroll_down(); - } else { - self.app.data.radarr_data.collections.scroll_down() - } - } - ActiveRadarrBlock::Movies => { - if !self.app.data.radarr_data.filtered_movies.items.is_empty() { - self.app.data.radarr_data.filtered_movies.scroll_down(); - } else { - self.app.data.radarr_data.movies.scroll_down() - } - } - ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_down(), - ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_down(), - ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_down(), - _ => (), - } - } + fn handle_scroll_down(&mut self) {} - fn handle_home(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if !self - .app - .data - .radarr_data - .filtered_collections - .items - .is_empty() - { - self - .app - .data - .radarr_data - .filtered_collections - .scroll_to_top(); - } else { - self.app.data.radarr_data.collections.scroll_to_top() - } - } - ActiveRadarrBlock::Movies => { - if !self.app.data.radarr_data.filtered_movies.items.is_empty() { - self.app.data.radarr_data.filtered_movies.scroll_to_top(); - } else { - self.app.data.radarr_data.movies.scroll_to_top() - } - } - ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_top(), - ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_to_top(), - ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_top(), - ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => { - self.app.data.radarr_data.search.scroll_home() - } - ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => { - self.app.data.radarr_data.filter.scroll_home() - } - ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(), - _ => (), - } - } + fn handle_home(&mut self) {} - fn handle_end(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if !self - .app - .data - .radarr_data - .filtered_collections - .items - .is_empty() - { - self - .app - .data - .radarr_data - .filtered_collections - .scroll_to_bottom(); - } else { - self.app.data.radarr_data.collections.scroll_to_bottom() - } - } - ActiveRadarrBlock::Movies => { - if !self.app.data.radarr_data.filtered_movies.items.is_empty() { - self.app.data.radarr_data.filtered_movies.scroll_to_bottom(); - } else { - self.app.data.radarr_data.movies.scroll_to_bottom() - } - } - ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_bottom(), - ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_to_bottom(), - ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_bottom(), - ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => { - self.app.data.radarr_data.search.reset_offset() - } - ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => { - self.app.data.radarr_data.filter.reset_offset() - } - ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(), - _ => (), - } - } + fn handle_end(&mut self) {} - fn handle_delete(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Movies => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - self.app.data.radarr_data.selected_block = - BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); - } - ActiveRadarrBlock::Downloads => self - .app - .push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into()), - ActiveRadarrBlock::RootFolders => self - .app - .push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()), - ActiveRadarrBlock::Indexers => self - .app - .push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()), - _ => (), - } - } + fn handle_delete(&mut self) {} - fn handle_left_right_action(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Movies - | ActiveRadarrBlock::Downloads - | ActiveRadarrBlock::Collections - | ActiveRadarrBlock::RootFolders - | ActiveRadarrBlock::Indexers - | ActiveRadarrBlock::System => match self.key { - _ if *self.key == DEFAULT_KEYBINDINGS.left.key => { - self.app.data.radarr_data.main_tabs.previous(); - self - .app - .pop_and_push_navigation_stack(*self.app.data.radarr_data.main_tabs.get_active_route()); - } - _ if *self.key == DEFAULT_KEYBINDINGS.right.key => { - self.app.data.radarr_data.main_tabs.next(); - self - .app - .pop_and_push_navigation_stack(*self.app.data.radarr_data.main_tabs.get_active_route()); - } - _ => (), - }, - ActiveRadarrBlock::DeleteDownloadPrompt - | ActiveRadarrBlock::DeleteIndexerPrompt - | ActiveRadarrBlock::DeleteRootFolderPrompt - | ActiveRadarrBlock::UpdateAllMoviesPrompt - | ActiveRadarrBlock::UpdateAllCollectionsPrompt - | ActiveRadarrBlock::UpdateDownloadsPrompt => handle_prompt_toggle(self.app, self.key), - ActiveRadarrBlock::AddRootFolderPrompt => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path) - } - ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) - } - ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter) - } - _ => (), - } - } + fn handle_left_right_action(&mut self) {} - fn handle_submit(&mut self) { - match self.active_radarr_block { - ActiveRadarrBlock::Movies => self - .app - .push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()), - ActiveRadarrBlock::Collections => self - .app - .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), - ActiveRadarrBlock::SearchMovie => { - if self.app.data.radarr_data.filtered_movies.items.is_empty() { - let selected_index = self - .search_table(&self.app.data.radarr_data.movies.items.clone(), |movie| { - &movie.title.text - }); - self - .app - .data - .radarr_data - .movies - .select_index(selected_index); - } else { - let selected_index = self.search_table( - &self.app.data.radarr_data.filtered_movies.items.clone(), - |movie| &movie.title.text, - ); - self - .app - .data - .radarr_data - .filtered_movies - .select_index(selected_index); - }; - } - ActiveRadarrBlock::SearchCollection => { - if self - .app - .data - .radarr_data - .filtered_collections - .items - .is_empty() - { - let selected_index = self.search_table( - &self.app.data.radarr_data.collections.items.clone(), - |collection| &collection.title.text, - ); - self - .app - .data - .radarr_data - .collections - .select_index(selected_index); - } else { - let selected_index = self.search_table( - &self.app.data.radarr_data.filtered_collections.items.clone(), - |collection| &collection.title.text, - ); - self - .app - .data - .radarr_data - .filtered_collections - .select_index(selected_index); - } - } - ActiveRadarrBlock::FilterMovies => { - let filtered_movies = self - .filter_table(&self.app.data.radarr_data.movies.items.clone(), |movie| { - &movie.title.text - }); + fn handle_submit(&mut self) {} - if !filtered_movies.is_empty() { - self - .app - .data - .radarr_data - .filtered_movies - .set_items(filtered_movies); - } - } - ActiveRadarrBlock::FilterCollections => { - let filtered_collections = self.filter_table( - &self.app.data.radarr_data.collections.items.clone(), - |collection| &collection.title.text, - ); + fn handle_esc(&mut self) {} - if !filtered_collections.is_empty() { - self - .app - .data - .radarr_data - .filtered_collections - .set_items(filtered_collections); - } - } - ActiveRadarrBlock::DeleteDownloadPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::DeleteRootFolderPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteRootFolder); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::DeleteIndexerPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::UpdateAllMoviesPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::UpdateDownloadsPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::UpdateAllCollectionsPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); - } - - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::AddRootFolderPrompt => { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder); - self.app.data.radarr_data.prompt_confirm = true; - self.app.should_ignore_quit_key = false; - self.app.pop_navigation_stack(); - } - ActiveRadarrBlock::Indexers => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::EditIndexer.into()); - } - _ => (), - } - } - - fn handle_esc(&mut self) { - match self.active_radarr_block { - _ if FILTER_BLOCKS.contains(self.active_radarr_block) => { - self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_filter(); - self.app.should_ignore_quit_key = false; - } - _ if SEARCH_BLOCKS.contains(self.active_radarr_block) => { - self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_search(); - self.app.should_ignore_quit_key = false; - } - ActiveRadarrBlock::AddRootFolderPrompt => { - self.app.pop_navigation_stack(); - self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default(); - self.app.data.radarr_data.prompt_confirm = false; - self.app.should_ignore_quit_key = false; - } - ActiveRadarrBlock::DeleteDownloadPrompt - | ActiveRadarrBlock::DeleteIndexerPrompt - | ActiveRadarrBlock::DeleteRootFolderPrompt - | ActiveRadarrBlock::UpdateAllMoviesPrompt - | ActiveRadarrBlock::UpdateAllCollectionsPrompt - | ActiveRadarrBlock::UpdateDownloadsPrompt => { - self.app.pop_navigation_stack(); - self.app.data.radarr_data.prompt_confirm = false; - } - _ => { - self.app.data.radarr_data.reset_search(); - self.app.data.radarr_data.reset_filter(); - handle_clear_errors(self.app); - } - } - } - - fn handle_char_key_event(&mut self) { - let key = self.key; - match *self.active_radarr_block { - ActiveRadarrBlock::Movies => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.search.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - self.app.data.radarr_data.is_searching = true; - self.app.should_ignore_quit_key = true; - } - _ if *key == DEFAULT_KEYBINDINGS.filter.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - self.app.data.radarr_data.is_filtering = true; - self.app.should_ignore_quit_key = true; - } - _ if *key == DEFAULT_KEYBINDINGS.edit.key => { - self.app.push_navigation_stack( - ( - ActiveRadarrBlock::EditMoviePrompt, - Some(ActiveRadarrBlock::Movies), - ) - .into(), - ); - self.app.data.radarr_data.populate_edit_movie_fields(); - self.app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - } - _ if *key == DEFAULT_KEYBINDINGS.add.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); - self.app.should_ignore_quit_key = true; - } - _ if *key == DEFAULT_KEYBINDINGS.update.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ => (), - }, - ActiveRadarrBlock::Downloads => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.update.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ => (), - }, - ActiveRadarrBlock::Indexers => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.add.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::AddIndexer.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ if *key == DEFAULT_KEYBINDINGS.settings.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::IndexerSettings.into()); - } - _ => (), - }, - ActiveRadarrBlock::Collections => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.search.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); - self.app.data.radarr_data.is_searching = true; - self.app.should_ignore_quit_key = true; - } - _ if *key == DEFAULT_KEYBINDINGS.filter.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); - self.app.data.radarr_data.is_filtering = true; - self.app.should_ignore_quit_key = true; - } - _ if *key == DEFAULT_KEYBINDINGS.edit.key => { - self.app.push_navigation_stack( - ( - ActiveRadarrBlock::EditCollectionPrompt, - Some(ActiveRadarrBlock::Collections), - ) - .into(), - ); - self.app.data.radarr_data.populate_edit_collection_fields(); - self.app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - } - _ if *key == DEFAULT_KEYBINDINGS.update.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ => (), - }, - ActiveRadarrBlock::RootFolders => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ if *key == DEFAULT_KEYBINDINGS.add.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); - self.app.should_ignore_quit_key = true; - } - _ => (), - }, - ActiveRadarrBlock::System => match self.key { - _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { - self.app.should_refresh = true; - } - _ if *key == DEFAULT_KEYBINDINGS.events.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.logs.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SystemLogs.into()); - self - .app - .data - .radarr_data - .log_details - .set_items(self.app.data.radarr_data.logs.items.to_vec()); - self.app.data.radarr_data.log_details.scroll_to_bottom(); - } - _ if *key == DEFAULT_KEYBINDINGS.tasks.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); - } - _ if *key == DEFAULT_KEYBINDINGS.update.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into()); - } - _ => (), - }, - ActiveRadarrBlock::AddRootFolderPrompt => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path) - } - _ if SEARCH_BLOCKS.contains(self.active_radarr_block) => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.search) - } - _ if FILTER_BLOCKS.contains(self.active_radarr_block) => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.filter) - } - _ => (), - } - } + fn handle_char_key_event(&mut self) {} } -impl<'a, 'b> RadarrHandler<'a, 'b> { - fn search_table(&mut self, rows: &[T], field_selection_fn: F) -> Option - where - F: Fn(&T) -> &str, - { - let search_string = self.app.data.radarr_data.search.drain().to_lowercase(); - let search_index = rows.iter().position(|item| { - strip_non_search_characters(field_selection_fn(item)).contains(&search_string) - }); +pub fn search_table(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Option +where + F: Fn(&T) -> &str, +{ + let search_string = app.data.radarr_data.search.drain().to_lowercase(); + let search_index = rows.iter().position(|item| { + strip_non_search_characters(field_selection_fn(item)).contains(&search_string) + }); - self.app.data.radarr_data.is_searching = false; - self.app.should_ignore_quit_key = false; + app.data.radarr_data.is_searching = false; + app.should_ignore_quit_key = false; - if search_index.is_some() { - self.app.pop_navigation_stack(); - } - - search_index + if search_index.is_some() { + app.pop_navigation_stack(); } - fn filter_table(&mut self, rows: &[T], field_selection_fn: F) -> Vec - where - F: Fn(&T) -> &str, - T: Clone, - { - let filter = strip_non_search_characters(&self.app.data.radarr_data.filter.drain()); - let filter_matches: Vec = rows - .iter() - .filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter)) - .cloned() - .collect(); + search_index +} - self.app.data.radarr_data.is_filtering = false; - self.app.should_ignore_quit_key = false; +pub fn filter_table(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Vec +where + F: Fn(&T) -> &str, + T: Clone, +{ + let filter = strip_non_search_characters(&app.data.radarr_data.filter.drain()); + let filter_matches: Vec = rows + .iter() + .filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter)) + .cloned() + .collect(); - if !filter_matches.is_empty() { - self.app.pop_navigation_stack(); + app.data.radarr_data.is_filtering = false; + app.should_ignore_quit_key = false; + + if !filter_matches.is_empty() { + app.pop_navigation_stack(); + } + + filter_matches +} + +pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: &Key) { + let key_ref = key; + match key_ref { + _ if *key == DEFAULT_KEYBINDINGS.left.key => { + app.data.radarr_data.main_tabs.previous(); + app.pop_and_push_navigation_stack(*app.data.radarr_data.main_tabs.get_active_route()); } - - filter_matches + _ if *key == DEFAULT_KEYBINDINGS.right.key => { + app.data.radarr_data.main_tabs.next(); + app.pop_and_push_navigation_stack(*app.data.radarr_data.main_tabs.get_active_route()); + } + _ => (), } } diff --git a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs index 43d890e..a8dae82 100644 --- a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs +++ b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs @@ -26,7 +26,6 @@ mod utils { tags: vec![Number::from(1)], ..Movie::default() }]); - radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); app.data.radarr_data = radarr_data; $handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle(); @@ -66,6 +65,10 @@ mod utils { assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/movies/Test"); assert_str_eq!(app.data.radarr_data.edit_tags.text, "test"); assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); + assert_eq!( + app.data.radarr_data.selected_block.blocks, + &EDIT_MOVIE_SELECTION_BLOCKS + ); }; } @@ -93,7 +96,6 @@ mod utils { minimum_availability: MinimumAvailability::Released, ..Collection::default() }]); - radarr_data.selected_block = BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); app.data.radarr_data = radarr_data; $handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle(); @@ -133,6 +135,10 @@ mod utils { assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/movies/Test"); assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); assert_eq!(app.data.radarr_data.edit_search_on_add, Some(true)); + assert_eq!( + app.data.radarr_data.selected_block.blocks, + &EDIT_COLLECTION_SELECTION_BLOCKS + ); }; } @@ -146,10 +152,37 @@ mod utils { assert_eq!(app.get_current_route(), &$expected_block.into()); }; + ($handler:ident, $block:expr, $expected_block:expr) => { + let mut app = App::default(); + + $handler::with(&DELETE_KEY, &mut app, &$block, &None).handle(); + + assert_eq!(app.get_current_route(), &$expected_block.into()); + }; + ($app:expr, $block:expr, $expected_block:expr) => { RadarrHandler::with(&DELETE_KEY, &mut $app, &$block, &None).handle(); assert_eq!($app.get_current_route(), &$expected_block.into()); }; + + ($handler:ident, $app:expr, $block:expr, $expected_block:expr) => { + $handler::with(&DELETE_KEY, &mut $app, &$block, &None).handle(); + + assert_eq!($app.get_current_route(), &$expected_block.into()); + }; + } + + #[macro_export] + macro_rules! assert_refresh_key { + ($handler:ident, $block:expr) => { + let mut app = App::default(); + app.push_navigation_stack($block.into()); + + $handler::with(&DEFAULT_KEYBINDINGS.refresh.key, &mut app, &$block, &None).handle(); + + assert_eq!(app.get_current_route(), &$block.into()); + assert!(app.should_refresh); + }; } } diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs index 5a39255..2a8eebe 100644 --- a/src/handlers/radarr_handlers/radarr_handler_tests.rs +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -2,1192 +2,19 @@ mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::radarr::ActiveRadarrBlock; use crate::app::App; - use crate::event::Key; - use crate::handlers::radarr_handlers::RadarrHandler; + use crate::handlers::radarr_handlers::{ + filter_table, handle_change_tab_left_right_keys, search_table, RadarrHandler, + }; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::{Collection, Movie}; + use crate::models::radarr_models::Movie; use crate::models::HorizontallyScrollableText; use crate::{extended_stateful_iterable_vec, test_handler_delegation}; - mod test_handle_scroll_up_and_down { - use rstest::rstest; - - use crate::models::radarr_models::{DownloadRecord, Indexer, RootFolder}; - use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; - - use super::*; - - test_iterable_scroll!( - test_collections_scroll, - RadarrHandler, - collections, - simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText), - ActiveRadarrBlock::Collections, - None, - title, - to_string - ); - - test_iterable_scroll!( - test_filtered_collections_scroll, - RadarrHandler, - filtered_collections, - simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText), - ActiveRadarrBlock::Collections, - None, - title, - to_string - ); - - test_iterable_scroll!( - test_movies_scroll, - RadarrHandler, - movies, - simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText), - ActiveRadarrBlock::Movies, - None, - title, - to_string - ); - - test_iterable_scroll!( - test_filtered_movies_scroll, - RadarrHandler, - filtered_movies, - simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText), - ActiveRadarrBlock::Movies, - None, - title, - to_string - ); - - test_iterable_scroll!( - test_downloads_scroll, - RadarrHandler, - downloads, - DownloadRecord, - ActiveRadarrBlock::Downloads, - None, - title - ); - - test_iterable_scroll!( - test_indexers_scroll, - RadarrHandler, - indexers, - simple_stateful_iterable_vec!(Indexer, String, protocol), - ActiveRadarrBlock::Indexers, - None, - protocol - ); - - test_iterable_scroll!( - test_root_folders_scroll, - RadarrHandler, - root_folders, - simple_stateful_iterable_vec!(RootFolder, String, path), - ActiveRadarrBlock::RootFolders, - None, - path - ); - } - - mod test_handle_home_end { - use pretty_assertions::assert_eq; - - use crate::models::radarr_models::{DownloadRecord, Indexer, RootFolder}; - use crate::{ - extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, - }; - - use super::*; - - test_iterable_home_and_end!( - test_collections_home_end, - RadarrHandler, - collections, - extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText), - ActiveRadarrBlock::Collections, - None, - title, - to_string - ); - - test_iterable_home_and_end!( - test_filtered_collections_home_end, - RadarrHandler, - filtered_collections, - extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText), - ActiveRadarrBlock::Collections, - None, - title, - to_string - ); - - test_iterable_home_and_end!( - test_movies_home_end, - RadarrHandler, - movies, - extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText), - ActiveRadarrBlock::Movies, - None, - title, - to_string - ); - - test_iterable_home_and_end!( - test_filtered_movies_home_end, - RadarrHandler, - filtered_movies, - extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText), - ActiveRadarrBlock::Movies, - None, - title, - to_string - ); - - test_iterable_home_and_end!( - test_downloads_home_end, - RadarrHandler, - downloads, - DownloadRecord, - ActiveRadarrBlock::Downloads, - None, - title - ); - - test_iterable_home_and_end!( - test_indexers_home_end, - RadarrHandler, - indexers, - extended_stateful_iterable_vec!(Indexer, String, protocol), - ActiveRadarrBlock::Indexers, - None, - protocol - ); - - test_iterable_home_and_end!( - test_root_folders_home_end, - RadarrHandler, - root_folders, - extended_stateful_iterable_vec!(RootFolder, String, path), - ActiveRadarrBlock::RootFolders, - None, - path - ); - - #[test] - fn test_add_root_folder_prompt_home_end_keys() { - test_text_box_home_end_keys!( - RadarrHandler, - ActiveRadarrBlock::AddRootFolderPrompt, - edit_path - ); - } - - #[rstest] - fn test_search_boxes_home_end_keys( - #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] - active_radarr_block: ActiveRadarrBlock, - ) { - test_text_box_home_end_keys!(RadarrHandler, active_radarr_block, search); - } - - #[rstest] - fn test_filter_boxes_home_end_keys( - #[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)] - active_radarr_block: ActiveRadarrBlock, - ) { - test_text_box_home_end_keys!(RadarrHandler, active_radarr_block, filter); - } - } - - mod test_handle_delete { - use pretty_assertions::assert_eq; - - use crate::assert_delete_prompt; - - use super::*; - - const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; - - #[test] - fn test_movies_delete() { - let mut app = App::default(); - - assert_delete_prompt!( - app, - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::DeleteMoviePrompt - ); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::DeleteMovieToggleDeleteFile - ); - } - - #[rstest] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] - #[case( - ActiveRadarrBlock::RootFolders, - ActiveRadarrBlock::DeleteRootFolderPrompt - )] - #[case(ActiveRadarrBlock::Indexers, ActiveRadarrBlock::DeleteIndexerPrompt)] - fn test_delete_prompt( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - assert_delete_prompt!(active_radarr_block, expected_radarr_block); - } - } - - mod test_handle_left_right_action { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::test_text_box_left_right_keys; - - use super::*; - - #[rstest] - #[case(ActiveRadarrBlock::Movies, 0, ActiveRadarrBlock::System)] - #[case(ActiveRadarrBlock::System, 5, ActiveRadarrBlock::Indexers)] - #[case(ActiveRadarrBlock::Indexers, 4, ActiveRadarrBlock::RootFolders)] - #[case(ActiveRadarrBlock::RootFolders, 3, ActiveRadarrBlock::Collections)] - #[case(ActiveRadarrBlock::Collections, 2, ActiveRadarrBlock::Downloads)] - #[case(ActiveRadarrBlock::Downloads, 1, ActiveRadarrBlock::Movies)] - fn test_radarr_tab_left( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] index: usize, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data.main_tabs.set_index(index); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.left.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!( - app.data.radarr_data.main_tabs.get_active_route(), - &expected_radarr_block.into() - ); - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, 0, ActiveRadarrBlock::Downloads)] - #[case(ActiveRadarrBlock::Downloads, 1, ActiveRadarrBlock::Collections)] - #[case(ActiveRadarrBlock::Collections, 2, ActiveRadarrBlock::RootFolders)] - #[case(ActiveRadarrBlock::RootFolders, 3, ActiveRadarrBlock::Indexers)] - #[case(ActiveRadarrBlock::Indexers, 4, ActiveRadarrBlock::System)] - #[case(ActiveRadarrBlock::System, 5, ActiveRadarrBlock::Movies)] - fn test_radarr_tab_right( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] index: usize, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data.main_tabs.set_index(index); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.right.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!( - app.data.radarr_data.main_tabs.get_active_route(), - &expected_radarr_block.into() - ); - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - } - - #[rstest] - fn test_left_right_prompt_toggle( - #[values( - ActiveRadarrBlock::DeleteDownloadPrompt, - ActiveRadarrBlock::DeleteIndexerPrompt, - ActiveRadarrBlock::DeleteRootFolderPrompt, - ActiveRadarrBlock::UpdateAllMoviesPrompt, - ActiveRadarrBlock::UpdateAllCollectionsPrompt, - ActiveRadarrBlock::UpdateDownloadsPrompt - )] - active_radarr_block: ActiveRadarrBlock, - #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, - ) { - let mut app = App::default(); - - RadarrHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - RadarrHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[test] - fn test_add_root_folder_prompt_left_right_keys() { - test_text_box_left_right_keys!( - RadarrHandler, - ActiveRadarrBlock::AddRootFolderPrompt, - edit_path - ); - } - - #[rstest] - fn test_search_boxes_left_right_keys( - #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] - active_radarr_block: ActiveRadarrBlock, - ) { - test_text_box_left_right_keys!(RadarrHandler, active_radarr_block, search); - } - - #[rstest] - fn test_filter_boxes_left_right_keys( - #[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)] - active_radarr_block: ActiveRadarrBlock, - ) { - test_text_box_left_right_keys!(RadarrHandler, active_radarr_block, filter); - } - } - - mod test_handle_submit { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_indexer_submit_aka_edit() { - let mut app = App::default(); - - RadarrHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditIndexer.into() - ); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::MovieDetails)] - #[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::CollectionDetails)] - fn test_movies_collections_details_submit( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with(&SUBMIT_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - } - - #[test] - fn test_search_movie_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 2".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::SearchMovie, - &None, - ) - .handle(); - - assert_str_eq!( - app.data.radarr_data.movies.current_selection().title.text, - "Test 2" - ); - } - - #[test] - fn test_search_filtered_movies_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .filtered_movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 2".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::SearchMovie, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .current_selection() - .title - .text, - "Test 2" - ); - } - - #[test] - fn test_search_collections_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .collections - .set_items(extended_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 2".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::SearchCollection, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .collections - .current_selection() - .title - .text, - "Test 2" - ); - } - - #[test] - fn test_search_filtered_collections_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .filtered_collections - .set_items(extended_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 2".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::SearchCollection, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .current_selection() - .title - .text, - "Test 2" - ); - } - - #[test] - fn test_filter_movies_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = "Test".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::FilterMovies, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.filtered_movies.items.len(), 3); - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .current_selection() - .title - .text, - "Test 1" - ); - } - - #[test] - fn test_filter_collections_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .collections - .set_items(extended_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = "Test".to_owned().into(); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::FilterCollections, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.filtered_collections.items.len(), 3); - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .current_selection() - .title - .text, - "Test 1" - ); - } - - #[test] - fn test_add_root_folder_prompt_confirm_submit() { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = true; - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); - - RadarrHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddRootFolderPrompt, - &None, - ) - .handle(); - - assert!(app.data.radarr_data.prompt_confirm); - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::AddRootFolder) - ); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::RootFolders.into() - ); - } - - #[rstest] - #[case( - ActiveRadarrBlock::Downloads, - ActiveRadarrBlock::DeleteDownloadPrompt, - RadarrEvent::DeleteDownload - )] - #[case( - ActiveRadarrBlock::Indexers, - ActiveRadarrBlock::DeleteIndexerPrompt, - RadarrEvent::DeleteIndexer - )] - #[case( - ActiveRadarrBlock::RootFolders, - ActiveRadarrBlock::DeleteRootFolderPrompt, - RadarrEvent::DeleteRootFolder - )] - #[case( - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::UpdateAllMoviesPrompt, - RadarrEvent::UpdateAllMovies - )] - #[case( - ActiveRadarrBlock::Downloads, - ActiveRadarrBlock::UpdateDownloadsPrompt, - RadarrEvent::UpdateDownloads - )] - #[case( - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::UpdateAllCollectionsPrompt, - RadarrEvent::UpdateCollections - )] - fn test_prompt_confirm_submit( - #[case] base_route: ActiveRadarrBlock, - #[case] prompt_block: ActiveRadarrBlock, - #[case] expected_action: RadarrEvent, - ) { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = true; - app.push_navigation_stack(base_route.into()); - app.push_navigation_stack(prompt_block.into()); - - RadarrHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(expected_action) - ); - assert_eq!(app.get_current_route(), &base_route.into()); - } - - #[rstest] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::UpdateAllMoviesPrompt)] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] - #[case( - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::UpdateAllCollectionsPrompt - )] - fn test_prompt_decline_submit( - #[case] base_route: ActiveRadarrBlock, - #[case] prompt_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(base_route.into()); - app.push_navigation_stack(prompt_block.into()); - - RadarrHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - assert_eq!(app.get_current_route(), &base_route.into()); - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; - use crate::{assert_filter_reset, assert_search_reset}; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::SearchMovie)] - #[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::SearchCollection)] - fn test_search_blocks_esc( - #[case] base_block: ActiveRadarrBlock, - #[case] search_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(base_block.into()); - app.push_navigation_stack(search_block.into()); - app.data.radarr_data = create_test_radarr_data(); - - RadarrHandler::with(&ESC_KEY, &mut app, &search_block, &None).handle(); - - assert_eq!(app.get_current_route(), &base_block.into()); - assert!(!app.should_ignore_quit_key); - assert_search_reset!(app.data.radarr_data); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::FilterMovies)] - #[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::FilterCollections)] - fn test_filter_blocks_esc( - #[case] base_block: ActiveRadarrBlock, - #[case] filter_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(base_block.into()); - app.push_navigation_stack(filter_block.into()); - app.data.radarr_data = create_test_radarr_data(); - - RadarrHandler::with(&ESC_KEY, &mut app, &filter_block, &None).handle(); - - assert_eq!(app.get_current_route(), &base_block.into()); - assert!(!app.should_ignore_quit_key); - assert_filter_reset!(app.data.radarr_data); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::UpdateAllMoviesPrompt)] - #[case( - ActiveRadarrBlock::RootFolders, - ActiveRadarrBlock::DeleteRootFolderPrompt - )] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] - #[case(ActiveRadarrBlock::Indexers, ActiveRadarrBlock::DeleteIndexerPrompt)] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] - #[case( - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::UpdateAllCollectionsPrompt - )] - fn test_prompt_blocks_esc( - #[case] base_block: ActiveRadarrBlock, - #[case] prompt_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(base_block.into()); - app.push_navigation_stack(prompt_block.into()); - app.data.radarr_data.prompt_confirm = true; - - RadarrHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); - - assert_eq!(app.get_current_route(), &base_block.into()); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[test] - fn test_add_root_folder_prompt_esc() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); - app.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test"); - app.should_ignore_quit_key = true; - - RadarrHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddRootFolderPrompt, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::RootFolders.into() - ); - - assert!(app.data.radarr_data.edit_path.text.is_empty()); - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.should_ignore_quit_key); - } - - #[test] - fn test_default_esc() { - let mut app = App::default(); - app.error = "test error".to_owned().into(); - app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); - app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); - app.data.radarr_data = create_test_radarr_data(); - - RadarrHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Downloads.into() - ); - assert!(app.error.text.is_empty()); - assert_search_reset!(app.data.radarr_data); - assert_filter_reset!(app.data.radarr_data); - } - } - - mod test_handle_key_char { - use bimap::BiMap; - use pretty_assertions::{assert_eq, assert_str_eq}; - use rstest::rstest; - use serde_json::Number; - use strum::IntoEnumIterator; - - use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data; - use crate::app::radarr::RadarrData; - use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; - use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; - use crate::models::radarr_models::MinimumAvailability; - use crate::models::BlockSelectionState; - use crate::models::HorizontallyScrollableText; - use crate::models::StatefulTable; - use crate::{test_edit_collection_key, test_edit_movie_key}; - - use super::*; - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::SearchMovie)] - #[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::SearchCollection)] - fn test_search_key( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.search.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - assert!(app.data.radarr_data.is_searching); - assert!(app.should_ignore_quit_key); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::FilterMovies)] - #[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::FilterCollections)] - fn test_filter_key( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.filter.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - assert!(app.data.radarr_data.is_filtering); - assert!(app.should_ignore_quit_key); - } - - #[test] - fn test_movie_add() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.add.key, - &mut app, - &ActiveRadarrBlock::Movies, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchInput.into() - ); - assert!(app.should_ignore_quit_key); - } - - #[test] - fn test_indexer_add() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.add.key, - &mut app, - &ActiveRadarrBlock::Indexers, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddIndexer.into() - ); - } - - #[test] - fn test_root_folder_add() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.add.key, - &mut app, - &ActiveRadarrBlock::RootFolders, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddRootFolderPrompt.into() - ); - assert!(app.should_ignore_quit_key); - } - - #[test] - fn test_movie_edit_key() { - test_edit_movie_key!( - RadarrHandler, - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::Movies - ); - } - - #[test] - fn test_collection_edit_key() { - test_edit_collection_key!( - RadarrHandler, - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::Collections - ); - } - - #[rstest] - #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::UpdateAllMoviesPrompt)] - #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] - #[case( - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::UpdateAllCollectionsPrompt - )] - #[case(ActiveRadarrBlock::System, ActiveRadarrBlock::SystemUpdates)] - fn test_update_key( - #[case] active_radarr_block: ActiveRadarrBlock, - #[case] expected_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.update.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &expected_radarr_block.into()); - } - - #[test] - fn test_queued_events_key() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.events.key, - &mut app, - &ActiveRadarrBlock::System, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::SystemQueuedEvents.into() - ); - } - - #[rstest] - fn test_refresh_key( - #[values( - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::Downloads, - ActiveRadarrBlock::Indexers, - ActiveRadarrBlock::RootFolders, - ActiveRadarrBlock::System - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(active_radarr_block.into()); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.refresh.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &active_radarr_block.into()); - assert!(app.should_refresh); - } - - #[test] - fn test_logs_key() { - let mut app = App::default(); - app.data.radarr_data.logs.set_items(vec![ - HorizontallyScrollableText::from("test 1"), - HorizontallyScrollableText::from("test 2"), - ]); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.logs.key, - &mut app, - &ActiveRadarrBlock::System, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::SystemLogs.into() - ); - assert_eq!( - app.data.radarr_data.log_details.items, - app.data.radarr_data.logs.items - ); - assert_str_eq!( - app.data.radarr_data.log_details.current_selection().text, - "test 2" - ); - } - - #[test] - fn test_tasks_key() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.tasks.key, - &mut app, - &ActiveRadarrBlock::System, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::SystemTasks.into() - ); - } - - #[test] - fn test_indexer_settings_key() { - let mut app = App::default(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.settings.key, - &mut app, - &ActiveRadarrBlock::Indexers, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::IndexerSettings.into() - ); - } - - #[test] - fn test_add_root_folder_prompt_backspace_key() { - let mut app = App::default(); - app.data.radarr_data.edit_path = "/nfs/test".to_owned().into(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::AddRootFolderPrompt, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/tes"); - } - - #[rstest] - fn test_search_boxes_backspace_key( - #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data.search = "Test".to_owned().into(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.search.text, "Tes"); - } - - #[rstest] - fn test_filter_boxes_backspace_key( - #[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data.filter = "Test".to_owned().into(); - - RadarrHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); - } - - #[test] - fn test_add_root_folder_prompt_char_key() { - let mut app = App::default(); - - RadarrHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::AddRootFolderPrompt, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); - } - - #[rstest] - fn test_search_boxes_char_key( - #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle(); - - assert_str_eq!(app.data.radarr_data.search.text, "h"); - } - - #[rstest] - fn test_filter_boxes_char_key( - #[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle(); - - assert_str_eq!(app.data.radarr_data.filter.text, "h"); - } - } - #[test] fn test_search_table() { let mut app = App::default(); @@ -1206,13 +33,7 @@ mod tests { let movies = &app.data.radarr_data.movies.items.clone(); - let index = RadarrHandler::with( - &DEFAULT_KEYBINDINGS.submit.key, - &mut app, - &ActiveRadarrBlock::SearchMovie, - &None, - ) - .search_table(movies, |movie| &movie.title.text); + let index = search_table(&mut app, movies, |movie| &movie.title.text); assert_eq!(index, Some(1)); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); @@ -1239,13 +60,7 @@ mod tests { let movies = &app.data.radarr_data.movies.items.clone(); - let index = RadarrHandler::with( - &DEFAULT_KEYBINDINGS.submit.key, - &mut app, - &ActiveRadarrBlock::SearchMovie, - &None, - ) - .search_table(movies, |movie| &movie.title.text); + let index = search_table(&mut app, movies, |movie| &movie.title.text); assert_eq!(index, None); assert_eq!( @@ -1275,13 +90,7 @@ mod tests { let movies = &app.data.radarr_data.movies.items.clone(); - let filter_matches = RadarrHandler::with( - &DEFAULT_KEYBINDINGS.submit.key, - &mut app, - &ActiveRadarrBlock::FilterMovies, - &None, - ) - .filter_table(movies, |movie| &movie.title.text); + let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); assert_eq!(filter_matches.len(), 1); assert_str_eq!(filter_matches[0].title.text, "Test 2"); @@ -1309,13 +118,7 @@ mod tests { let movies = &app.data.radarr_data.movies.items.clone(); - let filter_matches = RadarrHandler::with( - &DEFAULT_KEYBINDINGS.submit.key, - &mut app, - &ActiveRadarrBlock::FilterMovies, - &None, - ) - .filter_table(movies, |movie| &movie.title.text); + let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); assert!(filter_matches.is_empty()); assert_eq!( @@ -1328,22 +131,65 @@ mod tests { } #[rstest] - fn test_delegates_system_details_blocks_to_system_details_handler( + #[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)] + #[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Collections)] + #[case(2, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)] + #[case(3, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Indexers)] + #[case(4, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)] + #[case(5, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)] + fn test_radarr_handler_change_tab_left_right_keys( + #[case] index: usize, + #[case] left_block: ActiveRadarrBlock, + #[case] right_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(index); + + handle_change_tab_left_right_keys(&mut app, &DEFAULT_KEYBINDINGS.left.key); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &left_block.into() + ); + assert_eq!(app.get_current_route(), &left_block.into()); + + app.data.radarr_data.main_tabs.set_index(index); + + handle_change_tab_left_right_keys(&mut app, &DEFAULT_KEYBINDINGS.right.key); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &right_block.into() + ); + assert_eq!(app.get_current_route(), &right_block.into()); + } + + #[rstest] + fn test_delegates_system_blocks_to_system_handler( #[values( ActiveRadarrBlock::System, ActiveRadarrBlock::SystemLogs, - ActiveRadarrBlock::SystemTasks, ActiveRadarrBlock::SystemQueuedEvents, + ActiveRadarrBlock::SystemTasks, + ActiveRadarrBlock::SystemTaskStartConfirmPrompt, ActiveRadarrBlock::SystemUpdates )] active_radarr_block: ActiveRadarrBlock, ) { - test_handler_delegation!(ActiveRadarrBlock::System, active_radarr_block); + test_handler_delegation!( + RadarrHandler, + ActiveRadarrBlock::System, + active_radarr_block + ); } #[rstest] - fn test_delegates_add_movie_blocks_to_add_movie_handler( + fn test_delegates_library_blocks_to_library_handler( #[values( + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::SearchMovie, + ActiveRadarrBlock::FilterMovies, + ActiveRadarrBlock::UpdateAllMoviesPrompt, ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchResults, ActiveRadarrBlock::AddMoviePrompt, @@ -1352,27 +198,7 @@ mod tests { ActiveRadarrBlock::AddMovieSelectQualityProfile, ActiveRadarrBlock::AddMovieSelectRootFolder, ActiveRadarrBlock::AddMovieAlreadyInLibrary, - ActiveRadarrBlock::AddMovieTagsInput - )] - active_radarr_block: ActiveRadarrBlock, - ) { - test_handler_delegation!(ActiveRadarrBlock::Movies, active_radarr_block); - } - - #[rstest] - fn test_delegate_collection_details_blocks_to_collection_details_handler( - #[values( - ActiveRadarrBlock::CollectionDetails, - ActiveRadarrBlock::ViewMovieOverview - )] - active_radarr_block: ActiveRadarrBlock, - ) { - test_handler_delegation!(ActiveRadarrBlock::Collections, active_radarr_block); - } - - #[rstest] - fn test_delegate_movie_details_blocks_to_movie_details_handler( - #[values( + ActiveRadarrBlock::AddMovieTagsInput, ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::FileInfo, @@ -1381,38 +207,32 @@ mod tests { ActiveRadarrBlock::AutomaticallySearchMoviePrompt, ActiveRadarrBlock::UpdateAndScanPrompt, ActiveRadarrBlock::ManualSearch, - ActiveRadarrBlock::ManualSearchConfirmPrompt - )] - active_radarr_block: ActiveRadarrBlock, - ) { - test_handler_delegation!(ActiveRadarrBlock::Movies, active_radarr_block); - } - - #[rstest] - fn test_delegate_edit_movie_blocks_to_edit_movie_handler( - #[values( + ActiveRadarrBlock::ManualSearchConfirmPrompt, ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMoviePathInput, ActiveRadarrBlock::EditMovieSelectMinimumAvailability, ActiveRadarrBlock::EditMovieSelectQualityProfile, - ActiveRadarrBlock::EditMovieTagsInput + ActiveRadarrBlock::EditMovieTagsInput, + ActiveRadarrBlock::DeleteMoviePrompt )] active_radarr_block: ActiveRadarrBlock, ) { - test_handler_delegation!(ActiveRadarrBlock::Movies, active_radarr_block); - } - - #[test] - fn test_delegate_delete_movie_blocks_to_delete_movie_handler() { test_handler_delegation!( + RadarrHandler, ActiveRadarrBlock::Movies, - ActiveRadarrBlock::DeleteMoviePrompt + active_radarr_block ); } #[rstest] - fn test_delegate_edit_collection_blocks_to_edit_collection_handler( + fn test_delegates_collections_blocks_to_collections_handler( #[values( + ActiveRadarrBlock::Collections, + ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::FilterCollections, + ActiveRadarrBlock::UpdateAllCollectionsPrompt, + ActiveRadarrBlock::CollectionDetails, + ActiveRadarrBlock::ViewMovieOverview, ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, @@ -1420,6 +240,77 @@ mod tests { )] active_radarr_block: ActiveRadarrBlock, ) { - test_handler_delegation!(ActiveRadarrBlock::Collections, active_radarr_block); + test_handler_delegation!( + RadarrHandler, + ActiveRadarrBlock::Collections, + active_radarr_block + ); + } + + #[rstest] + fn test_delegates_indexers_blocks_to_indexers_handler( + // Add these once implemented: + // ActiveRadarrBlock::AddIndexer, + // ActiveRadarrBlock::EditIndexer, + #[values( + ActiveRadarrBlock::DeleteIndexerPrompt, + ActiveRadarrBlock::Indexers, + ActiveRadarrBlock::IndexerSettingsPrompt, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsConfirmPrompt, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + RadarrHandler, + ActiveRadarrBlock::Indexers, + active_radarr_block + ); + } + + #[rstest] + fn test_delegates_downloads_blocks_to_downloads_handler( + #[values( + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::DeleteDownloadPrompt, + ActiveRadarrBlock::UpdateDownloadsPrompt + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + RadarrHandler, + ActiveRadarrBlock::Downloads, + active_radarr_block + ); + } + + #[rstest] + fn test_delegates_root_folders_blocks_to_root_folders_handler( + #[values( + ActiveRadarrBlock::RootFolders, + ActiveRadarrBlock::AddRootFolderPrompt, + ActiveRadarrBlock::DeleteRootFolderPrompt + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + RadarrHandler, + ActiveRadarrBlock::RootFolders, + active_radarr_block + ); + } + + #[test] + fn test_radarr_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + assert!(RadarrHandler::accepts(&active_radarr_block)); + }) } } diff --git a/src/handlers/radarr_handlers/root_folders/mod.rs b/src/handlers/radarr_handlers/root_folders/mod.rs new file mode 100644 index 0000000..dd6fe69 --- /dev/null +++ b/src/handlers/radarr_handlers/root_folders/mod.rs @@ -0,0 +1,148 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::{HorizontallyScrollableText, Scrollable}; +use crate::network::radarr_network::RadarrEvent; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; + +#[cfg(test)] +#[path = "root_folders_handler_tests.rs"] +mod root_folders_handler_tests; + +pub(super) struct RootFoldersHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + _context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + ROOT_FOLDERS_BLOCKS.contains(active_block) + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + _context: &'a Option, + ) -> RootFoldersHandler<'a, 'b> { + RootFoldersHandler { + key, + app, + active_radarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::RootFolders { + self.app.data.radarr_data.root_folders.scroll_up() + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::RootFolders { + self.app.data.radarr_data.root_folders.scroll_down() + } + } + + fn handle_home(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_top(), + ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_bottom(), + ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(), + _ => (), + } + } + + fn handle_delete(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::RootFolders { + self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()) + } + } + + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::RootFolders => handle_change_tab_left_right_keys(self.app, self.key), + ActiveRadarrBlock::DeleteRootFolderPrompt => handle_prompt_toggle(self.app, self.key), + ActiveRadarrBlock::AddRootFolderPrompt => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::DeleteRootFolderPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteRootFolder); + } + + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::AddRootFolderPrompt => { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder); + self.app.data.radarr_data.prompt_confirm = true; + self.app.should_ignore_quit_key = false; + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::AddRootFolderPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default(); + self.app.data.radarr_data.prompt_confirm = false; + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::DeleteRootFolderPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } + _ => handle_clear_errors(self.app), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_radarr_block { + ActiveRadarrBlock::RootFolders => match self.key { + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ if *key == DEFAULT_KEYBINDINGS.add.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + self.app.should_ignore_quit_key = true; + } + _ => (), + }, + ActiveRadarrBlock::AddRootFolderPrompt => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path) + } + _ => (), + } + } +} diff --git a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs new file mode 100644 index 0000000..cf4a393 --- /dev/null +++ b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs @@ -0,0 +1,401 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler; + use crate::handlers::KeyEventHandler; + use crate::models::HorizontallyScrollableText; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::models::radarr_models::RootFolder; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_root_folders_scroll, + RootFoldersHandler, + root_folders, + simple_stateful_iterable_vec!(RootFolder, String, path), + ActiveRadarrBlock::RootFolders, + None, + path + ); + } + + mod test_handle_home_end { + use pretty_assertions::assert_eq; + + use crate::models::radarr_models::RootFolder; + use crate::{ + extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, + }; + + use super::*; + + test_iterable_home_and_end!( + test_root_folders_home_end, + RootFoldersHandler, + root_folders, + extended_stateful_iterable_vec!(RootFolder, String, path), + ActiveRadarrBlock::RootFolders, + None, + path + ); + + #[test] + fn test_add_root_folder_prompt_home_end_keys() { + test_text_box_home_end_keys!( + RootFoldersHandler, + ActiveRadarrBlock::AddRootFolderPrompt, + edit_path + ); + } + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use crate::assert_delete_prompt; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_delete_root_folder_prompt() { + assert_delete_prompt!( + RootFoldersHandler, + ActiveRadarrBlock::RootFolders, + ActiveRadarrBlock::DeleteRootFolderPrompt + ); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[test] + fn test_root_folders_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(3); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + + #[test] + fn test_root_folders_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(3); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Indexers.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + } + + #[rstest] + fn test_left_right_delete_root_folder_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + + RootFoldersHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::DeleteRootFolderPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + RootFoldersHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::DeleteRootFolderPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_add_root_folder_prompt_left_right_keys() { + test_text_box_left_right_keys!( + RootFoldersHandler, + ActiveRadarrBlock::AddRootFolderPrompt, + edit_path + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_add_root_folder_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + + RootFoldersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::AddRootFolder) + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + + #[test] + fn test_delete_root_folder_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); + + RootFoldersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteRootFolderPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::DeleteRootFolder) + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + + #[test] + fn test_delete_root_folder_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); + + RootFoldersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteRootFolderPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_delete_root_folder_prompt_block_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); + app.data.radarr_data.prompt_confirm = true; + + RootFoldersHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::DeleteRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_add_root_folder_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + app.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test"); + app.should_ignore_quit_key = true; + + RootFoldersHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + + assert!(app.data.radarr_data.edit_path.text.is_empty()); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.should_ignore_quit_key); + } + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + + RootFoldersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::RootFolders, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::{assert_eq, assert_str_eq}; + + use crate::assert_refresh_key; + + use super::*; + + #[test] + fn test_root_folder_add() { + let mut app = App::default(); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.add.key, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddRootFolderPrompt.into() + ); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_refresh_root_folders_key() { + assert_refresh_key!(RootFoldersHandler, ActiveRadarrBlock::RootFolders); + } + + #[test] + fn test_add_root_folder_prompt_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.edit_path = "/nfs/test".to_owned().into(); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/tes"); + } + + #[test] + fn test_add_root_folder_prompt_char_key() { + let mut app = App::default(); + + RootFoldersHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); + } + } + + #[test] + fn test_root_folders_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block) { + assert!(RootFoldersHandler::accepts(&active_radarr_block)); + } else { + assert!(!RootFoldersHandler::accepts(&active_radarr_block)); + } + }) + } +} diff --git a/src/handlers/radarr_handlers/system/mod.rs b/src/handlers/radarr_handlers/system/mod.rs new file mode 100644 index 0000000..dfb0d0e --- /dev/null +++ b/src/handlers/radarr_handlers/system/mod.rs @@ -0,0 +1,116 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::radarr::ActiveRadarrBlock; +use crate::app::App; +use crate::event::Key; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; +use crate::handlers::{handle_clear_errors, KeyEventHandler}; +use crate::models::Scrollable; + +mod system_details_handler; + +#[cfg(test)] +#[path = "system_handler_tests.rs"] +mod system_handler_tests; + +pub(super) struct SystemHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_radarr_block { + _ if SystemDetailsHandler::accepts(self.active_radarr_block) => { + SystemDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle() + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + SystemDetailsHandler::accepts(active_block) || active_block == &ActiveRadarrBlock::System + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + context: &'a Option, + ) -> SystemHandler<'a, 'b> { + SystemHandler { + key, + app, + active_radarr_block: active_block, + context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) {} + + fn handle_scroll_down(&mut self) {} + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::System { + handle_change_tab_left_right_keys(self.app, self.key); + } + } + + fn handle_submit(&mut self) {} + + fn handle_esc(&mut self) { + handle_clear_errors(self.app) + } + + fn handle_char_key_event(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::System { + let key = self.key; + match self.key { + _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ if *key == DEFAULT_KEYBINDINGS.events.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.logs.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SystemLogs.into()); + self + .app + .data + .radarr_data + .log_details + .set_items(self.app.data.radarr_data.logs.items.to_vec()); + self.app.data.radarr_data.log_details.scroll_to_bottom(); + } + _ if *key == DEFAULT_KEYBINDINGS.tasks.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); + } + _ if *key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into()); + } + _ => (), + } + } + } +} diff --git a/src/handlers/radarr_handlers/system_details_handler.rs b/src/handlers/radarr_handlers/system/system_details_handler.rs similarity index 97% rename from src/handlers/radarr_handlers/system_details_handler.rs rename to src/handlers/radarr_handlers/system/system_details_handler.rs index f002ff0..b66ae80 100644 --- a/src/handlers/radarr_handlers/system_details_handler.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler.rs @@ -18,6 +18,10 @@ pub(super) struct SystemDetailsHandler<'a, 'b> { } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + SYSTEM_DETAILS_BLOCKS.contains(active_block) + } + fn with( key: &'a Key, app: &'a mut App<'b>, diff --git a/src/handlers/radarr_handlers/system_details_handler_tests.rs b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs similarity index 95% rename from src/handlers/radarr_handlers/system_details_handler_tests.rs rename to src/handlers/radarr_handlers/system/system_details_handler_tests.rs index 5114e6f..0c60fde 100644 --- a/src/handlers/radarr_handlers/system_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::system_details_handler::SystemDetailsHandler; + use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::{QueueEvent, Task}; @@ -448,4 +449,15 @@ mod tests { assert!(app.should_refresh); } } + + #[test] + fn test_system_details_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(SystemDetailsHandler::accepts(&active_radarr_block)); + } else { + assert!(!SystemDetailsHandler::accepts(&active_radarr_block)); + } + }) + } } diff --git a/src/handlers/radarr_handlers/system/system_handler_tests.rs b/src/handlers/radarr_handlers/system/system_handler_tests.rs new file mode 100644 index 0000000..c6a511e --- /dev/null +++ b/src/handlers/radarr_handlers/system/system_handler_tests.rs @@ -0,0 +1,211 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::system::SystemHandler; + use crate::handlers::KeyEventHandler; + use crate::test_handler_delegation; + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_system_tab_left() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(5); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Indexers.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + } + + #[test] + fn test_system_tab_right() { + let mut app = App::default(); + app.data.radarr_data.main_tabs.set_index(5); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.main_tabs.get_active_route(), + &ActiveRadarrBlock::Movies.into() + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_default_esc() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::System.into()); + app.push_navigation_stack(ActiveRadarrBlock::System.into()); + + SystemHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::System, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into()); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::{assert_eq, assert_str_eq}; + + use crate::assert_refresh_key; + use crate::models::HorizontallyScrollableText; + + use super::*; + + #[test] + fn test_update_system_key() { + let mut app = App::default(); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.update.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SystemUpdates.into() + ); + } + + #[test] + fn test_queued_events_key() { + let mut app = App::default(); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.events.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SystemQueuedEvents.into() + ); + } + + #[test] + fn test_refresh_system_key() { + assert_refresh_key!(SystemHandler, ActiveRadarrBlock::System); + } + + #[test] + fn test_logs_key() { + let mut app = App::default(); + app.data.radarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.logs.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SystemLogs.into() + ); + assert_eq!( + app.data.radarr_data.log_details.items, + app.data.radarr_data.logs.items + ); + assert_str_eq!( + app.data.radarr_data.log_details.current_selection().text, + "test 2" + ); + } + + #[test] + fn test_tasks_key() { + let mut app = App::default(); + + SystemHandler::with( + &DEFAULT_KEYBINDINGS.tasks.key, + &mut app, + &ActiveRadarrBlock::System, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SystemTasks.into() + ); + } + } + + #[rstest] + fn test_delegates_system_details_blocks_to_system_details_handler( + #[values( + ActiveRadarrBlock::SystemLogs, + ActiveRadarrBlock::SystemQueuedEvents, + ActiveRadarrBlock::SystemTasks, + ActiveRadarrBlock::SystemTaskStartConfirmPrompt, + ActiveRadarrBlock::SystemUpdates + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_handler_delegation!( + SystemHandler, + ActiveRadarrBlock::System, + active_radarr_block + ); + } + + #[test] + fn test_system_handler_accepts() { + let mut system_blocks = vec![ActiveRadarrBlock::System]; + system_blocks.extend(SYSTEM_DETAILS_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if system_blocks.contains(&active_radarr_block) { + assert!(SystemHandler::accepts(&active_radarr_block)); + } else { + assert!(!SystemHandler::accepts(&active_radarr_block)); + } + }) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index a7bc4c8..f5a257d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -31,6 +31,7 @@ mod utils; static HIGHLIGHT_SYMBOL: &str = "=> "; pub trait DrawUi { + fn accepts(route: Route) -> bool; fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect); fn draw_context_row(_f: &mut Frame<'_, B>, _app: &App<'_>, _area: Rect) {} } @@ -66,7 +67,7 @@ pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { draw_header_row(f, app, main_chunks[0]); - if let Route::Radarr(_, _) = *app.get_current_route() { + if RadarrUi::accepts(*app.get_current_route()) { RadarrUi::draw_context_row(f, app, main_chunks[1]); RadarrUi::draw(f, app, main_chunks[2]); } diff --git a/src/ui/radarr_ui/collection_details_ui.rs b/src/ui/radarr_ui/collections/collection_details_ui.rs similarity index 94% rename from src/ui/radarr_ui/collection_details_ui.rs rename to src/ui/radarr_ui/collections/collection_details_ui.rs index 0c35629..0e69298 100644 --- a/src/ui/radarr_ui/collection_details_ui.rs +++ b/src/ui/radarr_ui/collections/collection_details_ui.rs @@ -4,11 +4,11 @@ use tui::text::Text; use tui::widgets::{Cell, Paragraph, Row, Wrap}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::CollectionMovie; use crate::models::Route; -use crate::ui::radarr_ui::collections_ui::draw_collections; +use crate::ui::radarr_ui::collections::draw_collections; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_top_border_with_title, line_info_primary, style_default, style_help, style_primary, title_block, title_style, @@ -17,9 +17,21 @@ use crate::ui::utils::{ use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::utils::convert_runtime; +#[cfg(test)] +#[path = "collection_details_ui_tests.rs"] +mod collection_details_ui_tests; + pub(super) struct CollectionDetailsUi {} impl DrawUi for CollectionDetailsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_collection_details_popup = @@ -50,7 +62,7 @@ impl DrawUi for CollectionDetailsUi { } } -pub(super) fn draw_collection_details( +pub fn draw_collection_details( f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect, diff --git a/src/ui/radarr_ui/collections/collection_details_ui_tests.rs b/src/ui/radarr_ui/collections/collection_details_ui_tests.rs new file mode 100644 index 0000000..0d70de7 --- /dev/null +++ b/src/ui/radarr_ui/collections/collection_details_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS}; + use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; + use crate::ui::DrawUi; + + #[test] + fn test_collection_details_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(CollectionDetailsUi::accepts(active_radarr_block.into())); + } else { + assert!(!CollectionDetailsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/collections/collections_ui_tests.rs b/src/ui/radarr_ui/collections/collections_ui_tests.rs new file mode 100644 index 0000000..cd0c3ca --- /dev/null +++ b/src/ui/radarr_ui/collections/collections_ui_tests.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ + ActiveRadarrBlock, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS, + }; + use crate::ui::radarr_ui::collections::CollectionsUi; + use crate::ui::DrawUi; + + #[test] + fn test_collections_ui_accepts() { + let mut collections_ui_blocks = Vec::new(); + collections_ui_blocks.extend(COLLECTIONS_BLOCKS); + collections_ui_blocks.extend(COLLECTION_DETAILS_BLOCKS); + collections_ui_blocks.extend(EDIT_COLLECTION_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if collections_ui_blocks.contains(&active_radarr_block) { + assert!(CollectionsUi::accepts(active_radarr_block.into())); + } else { + assert!(!CollectionsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/edit_collection_ui.rs b/src/ui/radarr_ui/collections/edit_collection_ui.rs similarity index 93% rename from src/ui/radarr_ui/edit_collection_ui.rs rename to src/ui/radarr_ui/collections/edit_collection_ui.rs index b9e95f7..97d5f1b 100644 --- a/src/ui/radarr_ui/edit_collection_ui.rs +++ b/src/ui/radarr_ui/collections/edit_collection_ui.rs @@ -2,11 +2,11 @@ use tui::backend::Backend; use tui::layout::{Constraint, Rect}; use tui::Frame; -use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS}; +use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS}; use crate::app::App; use crate::models::Route; -use crate::ui::radarr_ui::collection_details_ui::CollectionDetailsUi; -use crate::ui::radarr_ui::collections_ui::draw_collections; +use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; +use crate::ui::radarr_ui::collections::draw_collections; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, }; @@ -19,9 +19,21 @@ use crate::ui::{ draw_text_box_with_label, DrawUi, }; +#[cfg(test)] +#[path = "edit_collection_ui_tests.rs"] +mod edit_collection_ui_tests; + pub(super) struct EditCollectionUi {} impl DrawUi for EditCollectionUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_collection_prompt = diff --git a/src/ui/radarr_ui/collections/edit_collection_ui_tests.rs b/src/ui/radarr_ui/collections/edit_collection_ui_tests.rs new file mode 100644 index 0000000..760e71d --- /dev/null +++ b/src/ui/radarr_ui/collections/edit_collection_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; + use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; + use crate::ui::DrawUi; + + #[test] + fn test_edit_collection_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) { + assert!(EditCollectionUi::accepts(active_radarr_block.into())); + } else { + assert!(!EditCollectionUi::accepts(active_radarr_block.into())); + } + }) + } +} diff --git a/src/ui/radarr_ui/collections_ui.rs b/src/ui/radarr_ui/collections/mod.rs similarity index 63% rename from src/ui/radarr_ui/collections_ui.rs rename to src/ui/radarr_ui/collections/mod.rs index a427c59..30dca14 100644 --- a/src/ui/radarr_ui/collections_ui.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -3,50 +3,80 @@ use tui::layout::{Constraint, Rect}; use tui::widgets::{Cell, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +pub(super) use collection_details_ui::draw_collection_details; + +use crate::app::radarr::{ActiveRadarrBlock, COLLECTIONS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::Collection; use crate::models::Route; +use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; +use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; use crate::ui::radarr_ui::{draw_filter_box, draw_search_box}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; use crate::ui::{ draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; +mod collection_details_ui; +#[cfg(test)] +#[path = "collections_ui_tests.rs"] +mod collections_ui_tests; +mod edit_collection_ui; + pub(super) struct CollectionsUi {} impl DrawUi for CollectionsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return CollectionDetailsUi::accepts(route) + || EditCollectionUi::accepts(route) + || COLLECTIONS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { - if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), - ActiveRadarrBlock::SearchCollection => draw_popup_over( - f, - app, - content_rect, - draw_collections, - draw_search_box, - 30, - 11, - ), - ActiveRadarrBlock::FilterCollections => draw_popup_over( - f, - app, - content_rect, - draw_collections, - draw_filter_box, - 30, - 11, - ), - ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_collections, - draw_update_all_collections_prompt, - ), - _ => (), + let route = *app.get_current_route(); + let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block { + ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), + ActiveRadarrBlock::SearchCollection => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_search_box, + 30, + 11, + ), + ActiveRadarrBlock::FilterCollections => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_filter_box, + 30, + 11, + ), + ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_collections, + draw_update_all_collections_prompt, + ), + _ => (), + }; + + match route { + _ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, content_rect), + _ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, content_rect), + Route::Radarr(active_radarr_block, _) + if COLLECTIONS_BLOCKS.contains(&active_radarr_block) => + { + collections_ui_matcher(active_radarr_block) } + _ => (), } } } diff --git a/src/ui/radarr_ui/downloads/downloads_ui_tests.rs b/src/ui/radarr_ui/downloads/downloads_ui_tests.rs new file mode 100644 index 0000000..ab08b40 --- /dev/null +++ b/src/ui/radarr_ui/downloads/downloads_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; + use crate::ui::radarr_ui::downloads::DownloadsUi; + use crate::ui::DrawUi; + + #[test] + fn test_downloads_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if DOWNLOADS_BLOCKS.contains(&active_radarr_block) { + assert!(DownloadsUi::accepts(active_radarr_block.into())); + } else { + assert!(!DownloadsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/downloads_ui.rs b/src/ui/radarr_ui/downloads/mod.rs similarity index 92% rename from src/ui/radarr_ui/downloads_ui.rs rename to src/ui/radarr_ui/downloads/mod.rs index 2b6266c..e7a44ef 100644 --- a/src/ui/radarr_ui/downloads_ui.rs +++ b/src/ui/radarr_ui/downloads/mod.rs @@ -3,7 +3,7 @@ use tui::layout::{Constraint, Rect}; use tui::widgets::{Cell, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::DownloadRecord; use crate::models::{HorizontallyScrollableText, Route}; @@ -11,9 +11,21 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; use crate::utils::convert_to_gb; +#[cfg(test)] +#[path = "downloads_ui_tests.rs"] +mod downloads_ui_tests; + pub(super) struct DownloadsUi {} impl DrawUi for DownloadsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return DOWNLOADS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs new file mode 100644 index 0000000..a02ffc2 --- /dev/null +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs @@ -0,0 +1,26 @@ +use tui::backend::Backend; +use tui::layout::Rect; +use tui::Frame; + +use crate::app::radarr::INDEXER_SETTINGS_BLOCKS; +use crate::app::App; +use crate::models::Route; +use crate::ui::DrawUi; + +#[cfg(test)] +#[path = "indexer_settings_ui_tests.rs"] +mod indexer_settings_ui_tests; + +pub(super) struct IndexerSettingsUi {} + +impl DrawUi for IndexerSettingsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block); + } + + false + } + + fn draw(_f: &mut Frame<'_, B>, _app: &mut App<'_>, _content_rect: Rect) {} +} diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui_tests.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui_tests.rs new file mode 100644 index 0000000..7072738 --- /dev/null +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS}; + use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi; + use crate::ui::DrawUi; + + #[test] + fn test_indexer_settings_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) { + assert!(IndexerSettingsUi::accepts(active_radarr_block.into())); + } else { + assert!(!IndexerSettingsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/indexers/indexers_ui_tests.rs b/src/ui/radarr_ui/indexers/indexers_ui_tests.rs new file mode 100644 index 0000000..e327cc6 --- /dev/null +++ b/src/ui/radarr_ui/indexers/indexers_ui_tests.rs @@ -0,0 +1,23 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS}; + use crate::ui::radarr_ui::indexers::IndexersUi; + use crate::ui::DrawUi; + + #[test] + fn test_indexers_ui_accepts() { + let mut indexers_blocks = Vec::new(); + indexers_blocks.extend(INDEXERS_BLOCKS); + indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if indexers_blocks.contains(&active_radarr_block) { + assert!(IndexersUi::accepts(active_radarr_block.into())); + } else { + assert!(!IndexersUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/indexers_ui.rs b/src/ui/radarr_ui/indexers/mod.rs similarity index 72% rename from src/ui/radarr_ui/indexers_ui.rs rename to src/ui/radarr_ui/indexers/mod.rs index 665fd84..3b14e25 100644 --- a/src/ui/radarr_ui/indexers_ui.rs +++ b/src/ui/radarr_ui/indexers/mod.rs @@ -4,29 +4,51 @@ use tui::text::Text; use tui::widgets::{Cell, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::Indexer; use crate::models::Route; +use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi; use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success}; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; +mod indexer_settings_ui; + +#[cfg(test)] +#[path = "indexers_ui_tests.rs"] +mod indexers_ui_tests; + pub(super) struct IndexersUi {} impl DrawUi for IndexersUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return IndexerSettingsUi::accepts(route) || INDEXERS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { - if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect), - ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_indexers, - draw_delete_indexer_prompt, - ), - _ => (), + let route = *app.get_current_route(); + let mut indexers_matchers = |active_radarr_block| match active_radarr_block { + ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect), + ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_indexers, + draw_delete_indexer_prompt, + ), + _ => (), + }; + + match route { + _ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect), + Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => { + indexers_matchers(active_radarr_block) } + _ => (), } } } diff --git a/src/ui/radarr_ui/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs similarity index 96% rename from src/ui/radarr_ui/add_movie_ui.rs rename to src/ui/radarr_ui/library/add_movie_ui.rs index 4460d88..a364ed8 100644 --- a/src/ui/radarr_ui/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -7,9 +7,8 @@ use tui::Frame; use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS}; use crate::models::radarr_models::AddMovieSearchResult; use crate::models::Route; -use crate::ui::radarr_ui::collection_details_ui::draw_collection_details; -use crate::ui::radarr_ui::collections_ui::draw_collections; -use crate::ui::radarr_ui::library_ui::draw_library; +use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collections}; +use crate::ui::radarr_ui::library::draw_library; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, draw_select_root_folder_popup, @@ -27,9 +26,21 @@ use crate::ui::{ use crate::utils::convert_runtime; use crate::App; -pub(super) struct AddMoviesUi {} +#[cfg(test)] +#[path = "add_movie_ui_tests.rs"] +mod add_movie_ui_tests; + +pub(super) struct AddMovieUi {} + +impl DrawUi for AddMovieUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return ADD_MOVIE_BLOCKS.contains(&active_radarr_block); + } + + false + } -impl DrawUi for AddMoviesUi { fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_add_movie_search_popup = diff --git a/src/ui/radarr_ui/library/add_movie_ui_tests.rs b/src/ui/radarr_ui/library/add_movie_ui_tests.rs new file mode 100644 index 0000000..2ca8f6e --- /dev/null +++ b/src/ui/radarr_ui/library/add_movie_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS}; + use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi; + use crate::ui::DrawUi; + + #[test] + fn test_add_movie_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(AddMovieUi::accepts(active_radarr_block.into())); + } else { + assert!(!AddMovieUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/delete_movie_ui.rs b/src/ui/radarr_ui/library/delete_movie_ui.rs similarity index 81% rename from src/ui/radarr_ui/delete_movie_ui.rs rename to src/ui/radarr_ui/library/delete_movie_ui.rs index 5f57eb2..d9d745e 100644 --- a/src/ui/radarr_ui/delete_movie_ui.rs +++ b/src/ui/radarr_ui/library/delete_movie_ui.rs @@ -2,15 +2,27 @@ use tui::backend::Backend; use tui::layout::Rect; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::app::App; use crate::models::Route; -use crate::ui::radarr_ui::library_ui::draw_library; +use crate::ui::radarr_ui::library::draw_library; use crate::ui::{draw_prompt_box_with_checkboxes, draw_prompt_popup_over, DrawUi}; +#[cfg(test)] +#[path = "delete_movie_ui_tests.rs"] +mod delete_movie_ui_tests; + pub(super) struct DeleteMovieUi {} impl DrawUi for DeleteMovieUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return DELETE_MOVIE_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if matches!( *app.get_current_route(), diff --git a/src/ui/radarr_ui/library/delete_movie_ui_tests.rs b/src/ui/radarr_ui/library/delete_movie_ui_tests.rs new file mode 100644 index 0000000..8713a9f --- /dev/null +++ b/src/ui/radarr_ui/library/delete_movie_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; + use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi; + use crate::ui::DrawUi; + + #[test] + fn test_delete_movie_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(DeleteMovieUi::accepts(active_radarr_block.into())); + } else { + assert!(!DeleteMovieUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/edit_movie_ui.rs b/src/ui/radarr_ui/library/edit_movie_ui.rs similarity index 92% rename from src/ui/radarr_ui/edit_movie_ui.rs rename to src/ui/radarr_ui/library/edit_movie_ui.rs index ca9a31a..4e1cc39 100644 --- a/src/ui/radarr_ui/edit_movie_ui.rs +++ b/src/ui/radarr_ui/library/edit_movie_ui.rs @@ -2,11 +2,11 @@ use tui::backend::Backend; use tui::layout::{Constraint, Rect}; use tui::Frame; -use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; +use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS, MOVIE_DETAILS_BLOCKS}; use crate::app::App; use crate::models::Route; -use crate::ui::radarr_ui::library_ui::draw_library; -use crate::ui::radarr_ui::movie_details_ui::MovieDetailsUi; +use crate::ui::radarr_ui::library::draw_library; +use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, }; @@ -19,9 +19,21 @@ use crate::ui::{ draw_text_box_with_label, DrawUi, }; +#[cfg(test)] +#[path = "edit_movie_ui_tests.rs"] +mod edit_movie_ui_tests; + pub(super) struct EditMovieUi {} impl DrawUi for EditMovieUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return EDIT_MOVIE_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_movie_prompt = diff --git a/src/ui/radarr_ui/library/edit_movie_ui_tests.rs b/src/ui/radarr_ui/library/edit_movie_ui_tests.rs new file mode 100644 index 0000000..2c17fc0 --- /dev/null +++ b/src/ui/radarr_ui/library/edit_movie_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; + use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; + use crate::ui::DrawUi; + + #[test] + fn test_edit_movie_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) { + assert!(EditMovieUi::accepts(active_radarr_block.into())); + } else { + assert!(!EditMovieUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/library/library_ui_tests.rs b/src/ui/radarr_ui/library/library_ui_tests.rs new file mode 100644 index 0000000..b8fdc15 --- /dev/null +++ b/src/ui/radarr_ui/library/library_ui_tests.rs @@ -0,0 +1,29 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ + ActiveRadarrBlock, ADD_MOVIE_BLOCKS, DELETE_MOVIE_BLOCKS, EDIT_MOVIE_BLOCKS, LIBRARY_BLOCKS, + MOVIE_DETAILS_BLOCKS, + }; + use crate::ui::radarr_ui::library::LibraryUi; + use crate::ui::DrawUi; + + #[test] + fn test_library_ui_accepts() { + let mut library_ui_blocks = Vec::new(); + library_ui_blocks.extend(LIBRARY_BLOCKS); + library_ui_blocks.extend(MOVIE_DETAILS_BLOCKS); + library_ui_blocks.extend(ADD_MOVIE_BLOCKS); + library_ui_blocks.extend(EDIT_MOVIE_BLOCKS); + library_ui_blocks.extend(DELETE_MOVIE_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if library_ui_blocks.contains(&active_radarr_block) { + assert!(LibraryUi::accepts(active_radarr_block.into())); + } else { + assert!(!LibraryUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/library_ui.rs b/src/ui/radarr_ui/library/mod.rs similarity index 67% rename from src/ui/radarr_ui/library_ui.rs rename to src/ui/radarr_ui/library/mod.rs index c2f71b7..394a80b 100644 --- a/src/ui/radarr_ui/library_ui.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -3,10 +3,14 @@ use tui::layout::{Constraint, Rect}; use tui::widgets::{Cell, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, LIBRARY_BLOCKS}; use crate::app::App; use crate::models::radarr_models::Movie; use crate::models::Route; +use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi; +use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi; +use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; +use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::{determine_row_style, draw_filter_box, draw_search_box}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{ @@ -14,28 +18,60 @@ use crate::ui::{ }; use crate::utils::{convert_runtime, convert_to_gb}; +mod add_movie_ui; +mod delete_movie_ui; +mod edit_movie_ui; +mod movie_details_ui; + +#[cfg(test)] +#[path = "library_ui_tests.rs"] +mod library_ui_tests; + pub(super) struct LibraryUi {} impl DrawUi for LibraryUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return MovieDetailsUi::accepts(route) + || AddMovieUi::accepts(route) + || EditMovieUi::accepts(route) + || DeleteMovieUi::accepts(route) + || LIBRARY_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { - if let Route::Radarr(active_radarr_block, _) = app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), - ActiveRadarrBlock::SearchMovie => { - draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11) - } - ActiveRadarrBlock::FilterMovies => { - draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11) - } - ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_library, - draw_update_all_movies_prompt, - ), - _ => (), + let route = *app.get_current_route(); + let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block + { + ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), + ActiveRadarrBlock::SearchMovie => { + draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11) } + ActiveRadarrBlock::FilterMovies => { + draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11) + } + ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_library, + draw_update_all_movies_prompt, + ), + _ => (), + }; + + match route { + _ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, content_rect), + _ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, content_rect), + _ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, content_rect), + _ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, content_rect), + Route::Radarr(active_radarr_block, _) if LIBRARY_BLOCKS.contains(&active_radarr_block) => { + library_ui_matchers(active_radarr_block) + } + _ => (), } } } diff --git a/src/ui/radarr_ui/movie_details_ui.rs b/src/ui/radarr_ui/library/movie_details_ui.rs similarity index 97% rename from src/ui/radarr_ui/movie_details_ui.rs rename to src/ui/radarr_ui/library/movie_details_ui.rs index 691ce03..d5a7339 100644 --- a/src/ui/radarr_ui/movie_details_ui.rs +++ b/src/ui/radarr_ui/library/movie_details_ui.rs @@ -7,11 +7,11 @@ use tui::text::{Line, Span, Text}; use tui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseField}; use crate::models::Route; -use crate::ui::radarr_ui::library_ui::draw_library; +use crate::ui::radarr_ui::library::draw_library; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, line_info_default, style_awaiting_import, style_bold, style_default, style_failure, @@ -24,9 +24,21 @@ use crate::ui::{ }; use crate::utils::convert_to_gb; +#[cfg(test)] +#[path = "movie_details_ui_tests.rs"] +mod movie_details_ui_tests; + pub(super) struct MovieDetailsUi {} impl DrawUi for MovieDetailsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_movie_info_popup = |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| { diff --git a/src/ui/radarr_ui/library/movie_details_ui_tests.rs b/src/ui/radarr_ui/library/movie_details_ui_tests.rs new file mode 100644 index 0000000..65b8140 --- /dev/null +++ b/src/ui/radarr_ui/library/movie_details_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; + use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; + use crate::ui::DrawUi; + + #[test] + fn test_movie_details_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(MovieDetailsUi::accepts(active_radarr_block.into())); + } else { + assert!(!MovieDetailsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index 1cb71c8..9a62bb1 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -9,11 +9,7 @@ use tui::widgets::ListItem; use tui::widgets::Paragraph; use tui::Frame; -use crate::app::radarr::{ - ActiveRadarrBlock, RadarrData, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, - EDIT_COLLECTION_BLOCKS, EDIT_MOVIE_BLOCKS, FILTER_BLOCKS, INDEXER_BLOCKS, MOVIE_DETAILS_BLOCKS, - SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS, -}; +use crate::app::radarr::{ActiveRadarrBlock, RadarrData, FILTER_BLOCKS, SEARCH_BLOCKS}; use crate::app::App; use crate::logos::RADARR_LOGO; use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}; @@ -21,15 +17,12 @@ use crate::models::Route; use crate::ui::draw_selectable_list; use crate::ui::draw_tabs; use crate::ui::loading; -use crate::ui::radarr_ui::indexers_ui::IndexersUi; -use crate::ui::radarr_ui::system_details_ui::SystemDetailsUi; -use crate::ui::radarr_ui::system_ui::SystemUi; -use crate::ui::radarr_ui::{ - add_movie_ui::AddMoviesUi, collection_details_ui::CollectionDetailsUi, - collections_ui::CollectionsUi, delete_movie_ui::DeleteMovieUi, downloads_ui::DownloadsUi, - edit_collection_ui::EditCollectionUi, edit_movie_ui::EditMovieUi, library_ui::LibraryUi, - movie_details_ui::MovieDetailsUi, root_folders_ui::RootFoldersUi, -}; +use crate::ui::radarr_ui::collections::CollectionsUi; +use crate::ui::radarr_ui::downloads::DownloadsUi; +use crate::ui::radarr_ui::indexers::IndexersUi; +use crate::ui::radarr_ui::library::LibraryUi; +use crate::ui::radarr_ui::root_folders::RootFoldersUi; +use crate::ui::radarr_ui::system::SystemUi; use crate::ui::utils::{ borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title, show_cursor, style_awaiting_import, style_bold, style_default, style_failure, style_success, @@ -38,72 +31,37 @@ use crate::ui::utils::{ use crate::ui::DrawUi; use crate::utils::convert_to_gb; -mod add_movie_ui; -mod collection_details_ui; -mod collections_ui; -mod delete_movie_ui; -mod downloads_ui; -mod edit_collection_ui; -mod edit_movie_ui; -mod indexers_ui; -mod library_ui; -mod movie_details_ui; +mod collections; +mod downloads; +mod indexers; +mod library; mod radarr_ui_utils; -mod root_folders_ui; -mod system_details_ui; -mod system_ui; +mod root_folders; +mod system; + +#[cfg(test)] +#[path = "radarr_ui_tests.rs"] +mod radarr_ui_tests; pub(super) struct RadarrUi {} impl DrawUi for RadarrUi { + fn accepts(route: Route) -> bool { + matches!(route, Route::Radarr(_, _)) + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); + let route = *app.get_current_route(); - if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::Movies - | ActiveRadarrBlock::SearchMovie - | ActiveRadarrBlock::FilterMovies - | ActiveRadarrBlock::UpdateAllMoviesPrompt => LibraryUi::draw(f, app, content_rect), - ActiveRadarrBlock::Collections - | ActiveRadarrBlock::SearchCollection - | ActiveRadarrBlock::FilterCollections - | ActiveRadarrBlock::UpdateAllCollectionsPrompt => { - CollectionsUi::draw(f, app, content_rect) - } - ActiveRadarrBlock::Downloads - | ActiveRadarrBlock::DeleteDownloadPrompt - | ActiveRadarrBlock::UpdateDownloadsPrompt => DownloadsUi::draw(f, app, content_rect), - _ if INDEXER_BLOCKS.contains(&active_radarr_block) => { - IndexersUi::draw(f, app, content_rect) - } - ActiveRadarrBlock::RootFolders - | ActiveRadarrBlock::AddRootFolderPrompt - | ActiveRadarrBlock::DeleteRootFolderPrompt => RootFoldersUi::draw(f, app, content_rect), - ActiveRadarrBlock::System => SystemUi::draw(f, app, content_rect), - _ if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) => { - SystemDetailsUi::draw(f, app, content_rect) - } - _ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => { - MovieDetailsUi::draw(f, app, content_rect) - } - _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { - AddMoviesUi::draw(f, app, content_rect) - } - _ if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) => { - CollectionDetailsUi::draw(f, app, content_rect) - } - _ if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) => { - EditMovieUi::draw(f, app, content_rect) - } - _ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) => { - EditCollectionUi::draw(f, app, content_rect) - } - _ if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) => { - DeleteMovieUi::draw(f, app, content_rect) - } - _ => (), - } + match route { + _ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_rect), + _ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_rect), + _ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_rect), + _ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_rect), + _ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_rect), + _ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_rect), + _ => (), } } diff --git a/src/ui/radarr_ui/radarr_ui_tests.rs b/src/ui/radarr_ui/radarr_ui_tests.rs new file mode 100644 index 0000000..b1dc966 --- /dev/null +++ b/src/ui/radarr_ui/radarr_ui_tests.rs @@ -0,0 +1,15 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::ActiveRadarrBlock; + use crate::ui::radarr_ui::RadarrUi; + use crate::ui::DrawUi; + + #[test] + fn test_radarr_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + assert!(RadarrUi::accepts(active_radarr_block.into())); + }); + } +} diff --git a/src/ui/radarr_ui/root_folders_ui.rs b/src/ui/radarr_ui/root_folders/mod.rs similarity index 91% rename from src/ui/radarr_ui/root_folders_ui.rs rename to src/ui/radarr_ui/root_folders/mod.rs index ba00188..70e7610 100644 --- a/src/ui/radarr_ui/root_folders_ui.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -3,7 +3,7 @@ use tui::layout::{Alignment, Constraint, Rect}; use tui::widgets::{Cell, Paragraph, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::app::App; use crate::models::radarr_models::RootFolder; use crate::models::Route; @@ -16,9 +16,21 @@ use crate::ui::{ }; use crate::utils::convert_to_gb; +#[cfg(test)] +#[path = "root_folders_ui_tests.rs"] +mod root_folders_ui_tests; + pub(super) struct RootFoldersUi {} impl DrawUi for RootFoldersUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { diff --git a/src/ui/radarr_ui/root_folders/root_folders_ui_tests.rs b/src/ui/radarr_ui/root_folders/root_folders_ui_tests.rs new file mode 100644 index 0000000..09c3d29 --- /dev/null +++ b/src/ui/radarr_ui/root_folders/root_folders_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; + use crate::ui::radarr_ui::root_folders::RootFoldersUi; + use crate::ui::DrawUi; + + #[test] + fn test_root_folders_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block) { + assert!(RootFoldersUi::accepts(active_radarr_block.into())); + } else { + assert!(!RootFoldersUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/system_ui.rs b/src/ui/radarr_ui/system/mod.rs similarity index 89% rename from src/ui/radarr_ui/system_ui.rs rename to src/ui/radarr_ui/system/mod.rs index 64eccab..bf1345b 100644 --- a/src/ui/radarr_ui/system_ui.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -1,7 +1,21 @@ +use std::ops::Sub; + +use chrono::Utc; +use tui::layout::Alignment; +use tui::text::{Span, Text}; +use tui::widgets::{Cell, Paragraph, Row}; +use tui::{ + backend::Backend, + layout::{Constraint, Rect}, + widgets::ListItem, + Frame, +}; + use crate::models::radarr_models::Task; use crate::ui::radarr_ui::radarr_ui_utils::{ convert_to_minutes_hours_days, determine_log_style_by_level, }; +use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; use crate::ui::utils::{layout_block_top_border, style_help, style_primary}; use crate::ui::{draw_table, ListProps, TableProps}; use crate::{ @@ -13,17 +27,12 @@ use crate::{ DrawUi, }, }; -use chrono::Utc; -use std::ops::Sub; -use tui::layout::Alignment; -use tui::text::{Span, Text}; -use tui::widgets::{Cell, Paragraph, Row}; -use tui::{ - backend::Backend, - layout::{Constraint, Rect}, - widgets::ListItem, - Frame, -}; + +mod system_details_ui; + +#[cfg(test)] +#[path = "system_ui_tests.rs"] +mod system_ui_tests; pub(super) const TASK_TABLE_HEADERS: [&str; 5] = [ "Name", @@ -44,12 +53,23 @@ pub(super) const TASK_TABLE_CONSTRAINTS: [Constraint; 5] = [ pub(super) struct SystemUi {} impl DrawUi for SystemUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return SystemDetailsUi::accepts(route) || active_radarr_block == ActiveRadarrBlock::System; + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { - if matches!( - *app.get_current_route(), - Route::Radarr(ActiveRadarrBlock::System, _) - ) { - draw_system_ui_layout(f, app, content_rect) + let route = *app.get_current_route(); + + match route { + _ if SystemDetailsUi::accepts(route) => SystemDetailsUi::draw(f, app, content_rect), + _ if matches!(route, Route::Radarr(ActiveRadarrBlock::System, _)) => { + draw_system_ui_layout(f, app, content_rect) + } + _ => (), } } } diff --git a/src/ui/radarr_ui/system_details_ui.rs b/src/ui/radarr_ui/system/system_details_ui.rs similarity index 92% rename from src/ui/radarr_ui/system_details_ui.rs rename to src/ui/radarr_ui/system/system_details_ui.rs index f866f7e..8de9c60 100644 --- a/src/ui/radarr_ui/system_details_ui.rs +++ b/src/ui/radarr_ui/system/system_details_ui.rs @@ -4,11 +4,11 @@ use tui::text::{Span, Text}; use tui::widgets::{Cell, ListItem, Paragraph, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::app::App; use crate::models::Route; use crate::ui::radarr_ui::radarr_ui_utils::determine_log_style_by_level; -use crate::ui::radarr_ui::system_ui::{ +use crate::ui::radarr_ui::system::{ draw_queued_events, draw_system_ui_layout, extract_task_props, TASK_TABLE_CONSTRAINTS, TASK_TABLE_HEADERS, }; @@ -18,9 +18,21 @@ use crate::ui::{ draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps, }; +#[cfg(test)] +#[path = "system_details_ui_tests.rs"] +mod system_details_ui_tests; + pub(super) struct SystemDetailsUi {} impl DrawUi for SystemDetailsUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block); + } + + false + } + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { diff --git a/src/ui/radarr_ui/system/system_details_ui_tests.rs b/src/ui/radarr_ui/system/system_details_ui_tests.rs new file mode 100644 index 0000000..85f184d --- /dev/null +++ b/src/ui/radarr_ui/system/system_details_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; + use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; + use crate::ui::DrawUi; + + #[test] + fn test_system_details_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) { + assert!(SystemDetailsUi::accepts(active_radarr_block.into())); + } else { + assert!(!SystemDetailsUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/system/system_ui_tests.rs b/src/ui/radarr_ui/system/system_ui_tests.rs new file mode 100644 index 0000000..2663c4f --- /dev/null +++ b/src/ui/radarr_ui/system/system_ui_tests.rs @@ -0,0 +1,23 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; + use crate::ui::radarr_ui::system::SystemUi; + use crate::ui::DrawUi; + + #[test] + fn test_system_ui_accepts() { + let mut system_ui_blocks = Vec::new(); + system_ui_blocks.push(ActiveRadarrBlock::System); + system_ui_blocks.extend(SYSTEM_DETAILS_BLOCKS); + + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if system_ui_blocks.contains(&active_radarr_block) { + assert!(SystemUi::accepts(active_radarr_block.into())); + } else { + assert!(!SystemUi::accepts(active_radarr_block.into())); + } + }); + } +}