From 519778c0ca87e78f38b3537859af798b9235b38a Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Tue, 8 Aug 2023 10:50:06 -0600 Subject: [PATCH] Partial implementation of Tasks and Logs and test refactor --- src/app/app_tests.rs | 212 ++ src/app/mod.rs | 220 +- src/app/radarr.rs | 1257 +---------- src/app/radarr_test_utils.rs | 126 ++ src/app/radarr_tests.rs | 1130 ++++++++++ src/event/key.rs | 88 +- src/event/key_tests.rs | 83 + src/handlers/handler_test_utils.rs | 416 ++++ src/handlers/handlers_tests.rs | 33 + src/handlers/mod.rs | 459 +--- .../radarr_handlers/add_movie_handler.rs | 732 +----- .../add_movie_handler_tests.rs | 727 ++++++ .../collection_details_handler.rs | 231 +- .../collection_details_handler_tests.rs | 226 ++ .../radarr_handlers/delete_movie_handler.rs | 205 +- .../delete_movie_handler_tests.rs | 200 ++ .../edit_collection_handler.rs | 508 +---- .../edit_collection_handler_tests.rs | 503 +++++ .../radarr_handlers/edit_movie_handler.rs | 511 +---- .../edit_movie_handler_tests.rs | 506 +++++ src/handlers/radarr_handlers/mod.rs | 1427 +----------- .../radarr_handlers/movie_details_handler.rs | 685 +----- .../movie_details_handler_tests.rs | 680 ++++++ .../radarr_handler_test_utils.rs | 138 ++ .../radarr_handlers/radarr_handler_tests.rs | 1280 +++++++++++ src/models/mod.rs | 533 +---- src/models/model_tests.rs | 528 +++++ src/models/radarr_models.rs | 76 +- src/models/radarr_models_tests.rs | 45 + src/network/mod.rs | 308 +-- src/network/network_tests.rs | 301 +++ src/network/radarr_network.rs | 1912 +--------------- src/network/radarr_network_tests.rs | 1986 +++++++++++++++++ src/ui/mod.rs | 22 +- src/ui/radarr_ui/add_movie_ui.rs | 6 +- src/ui/radarr_ui/mod.rs | 11 +- src/ui/radarr_ui/movie_details_ui.rs | 8 +- src/ui/radarr_ui/system_ui.rs | 147 ++ src/ui/utils.rs | 414 +--- src/ui/utils_tests.rs | 409 ++++ src/utils.rs | 33 +- src/utils_tests.rs | 28 + 42 files changed, 9914 insertions(+), 9436 deletions(-) create mode 100644 src/app/app_tests.rs create mode 100644 src/app/radarr_test_utils.rs create mode 100644 src/app/radarr_tests.rs create mode 100644 src/event/key_tests.rs create mode 100644 src/handlers/handler_test_utils.rs create mode 100644 src/handlers/handlers_tests.rs create mode 100644 src/handlers/radarr_handlers/add_movie_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/collection_details_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/delete_movie_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/edit_collection_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/edit_movie_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/movie_details_handler_tests.rs create mode 100644 src/handlers/radarr_handlers/radarr_handler_test_utils.rs create mode 100644 src/handlers/radarr_handlers/radarr_handler_tests.rs create mode 100644 src/models/model_tests.rs create mode 100644 src/models/radarr_models_tests.rs create mode 100644 src/network/network_tests.rs create mode 100644 src/network/radarr_network_tests.rs create mode 100644 src/ui/radarr_ui/system_ui.rs create mode 100644 src/ui/utils_tests.rs create mode 100644 src/utils_tests.rs diff --git a/src/app/app_tests.rs b/src/app/app_tests.rs new file mode 100644 index 0000000..aa07add --- /dev/null +++ b/src/app/app_tests.rs @@ -0,0 +1,212 @@ +#[cfg(test)] +mod tests { + use anyhow::anyhow; + use pretty_assertions::{assert_eq, assert_str_eq}; + use tokio::sync::mpsc; + + use crate::app::radarr::{ActiveRadarrBlock, RadarrData}; + use crate::app::{App, Data, RadarrConfig, DEFAULT_ROUTE}; + use crate::models::{HorizontallyScrollableText, Route, TabRoute}; + use crate::network::radarr_network::RadarrEvent; + use crate::network::NetworkEvent; + + #[test] + fn test_app_default() { + let app = App::default(); + + assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]); + assert!(app.network_tx.is_none()); + assert_eq!(app.error, HorizontallyScrollableText::default()); + assert!(app.response.is_empty()); + assert_eq!(app.server_tabs.index, 0); + assert_eq!( + app.server_tabs.tabs, + vec![ + TabRoute { + title: "Radarr", + route: ActiveRadarrBlock::Movies.into(), + help: "<↑↓> scroll | ←→ change tab | change servarr | quit ", + contextual_help: None, + }, + TabRoute { + title: "Sonarr", + route: Route::Sonarr, + help: " change servarr | quit ", + contextual_help: None, + }, + ] + ); + assert_str_eq!(app.title, "Managarr"); + assert_eq!(app.tick_until_poll, 400); + assert_eq!(app.ticks_until_scroll, 4); + assert_eq!(app.tick_count, 0); + assert!(!app.is_loading); + assert!(!app.is_routing); + assert!(!app.should_refresh); + assert!(!app.should_ignore_quit_key); + } + + #[test] + fn test_navigation_stack_methods() { + let mut app = App::default(); + + assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); + + app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Downloads.into() + ); + assert!(app.is_routing); + + app.is_routing = false; + app.pop_and_push_navigation_stack(ActiveRadarrBlock::Collections.into()); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(app.is_routing); + + app.is_routing = false; + app.pop_navigation_stack(); + + assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); + assert!(app.is_routing); + + app.is_routing = false; + app.pop_navigation_stack(); + + assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); + assert!(app.is_routing); + } + + #[test] + fn test_reset_tick_count() { + let mut app = App { + tick_count: 2, + ..App::default() + }; + + app.reset_tick_count(); + + assert_eq!(app.tick_count, 0); + } + + #[test] + fn test_reset() { + let mut app = App { + tick_count: 2, + error: "Test error".to_owned().into(), + data: Data { + radarr_data: RadarrData { + version: "test".to_owned(), + ..RadarrData::default() + }, + }, + ..App::default() + }; + + app.reset(); + + assert_eq!(app.tick_count, 0); + assert_eq!(app.error, HorizontallyScrollableText::default()); + assert!(app.data.radarr_data.version.is_empty()); + } + + #[test] + fn test_handle_error() { + let mut app = App::default(); + let test_string = "Testing"; + + app.handle_error(anyhow!(test_string)); + + assert_eq!(app.error.text, test_string); + + app.handle_error(anyhow!("Testing a different error")); + + assert_eq!(app.error.text, test_string); + } + + #[tokio::test] + async fn test_on_tick_first_render() { + let (sync_network_tx, mut sync_network_rx) = mpsc::channel::(500); + + let mut app = App { + tick_until_poll: 2, + network_tx: Some(sync_network_tx), + ..App::default() + }; + + assert_eq!(app.tick_count, 0); + + app.on_tick(true).await; + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetOverview.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovies.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.is_routing); + assert!(!app.should_refresh); + assert_eq!(app.tick_count, 1); + } + + #[tokio::test] + async fn test_on_tick_routing() { + let mut app = App { + tick_until_poll: 2, + tick_count: 2, + is_routing: true, + ..App::default() + }; + + app.on_tick(false).await; + assert!(!app.is_routing); + } + + #[tokio::test] + async fn test_on_tick_should_refresh() { + let mut app = App { + tick_until_poll: 2, + tick_count: 2, + should_refresh: true, + ..App::default() + }; + + app.on_tick(false).await; + assert!(!app.should_refresh); + } + + #[test] + fn test_radarr_config_default() { + let radarr_config = RadarrConfig::default(); + + assert_str_eq!(radarr_config.host, "localhost"); + assert_eq!(radarr_config.port, Some(7878)); + assert!(radarr_config.api_token.is_empty()); + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 2386795..ee3b6d8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -9,8 +9,11 @@ use crate::app::radarr::{ActiveRadarrBlock, RadarrData}; use crate::models::{HorizontallyScrollableText, Route, TabRoute, TabState}; use crate::network::NetworkEvent; -pub(crate) mod key_binding; -pub(crate) mod radarr; +#[cfg(test)] +#[path = "app_tests.rs"] +mod app_tests; +pub mod key_binding; +pub mod radarr; const DEFAULT_ROUTE: Route = Route::Radarr(ActiveRadarrBlock::Movies, None); @@ -174,216 +177,3 @@ impl Default for RadarrConfig { } } } - -#[cfg(test)] -mod tests { - use anyhow::anyhow; - use pretty_assertions::{assert_eq, assert_str_eq}; - use tokio::sync::mpsc; - - use crate::app::radarr::{ActiveRadarrBlock, RadarrData}; - use crate::app::{App, Data, RadarrConfig, DEFAULT_ROUTE}; - use crate::models::{HorizontallyScrollableText, Route, TabRoute}; - use crate::network::radarr_network::RadarrEvent; - use crate::network::NetworkEvent; - - #[test] - fn test_app_default() { - let app = App::default(); - - assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]); - assert!(app.network_tx.is_none()); - assert_eq!(app.error, HorizontallyScrollableText::default()); - assert!(app.response.is_empty()); - assert_eq!(app.server_tabs.index, 0); - assert_eq!( - app.server_tabs.tabs, - vec![ - TabRoute { - title: "Radarr", - route: ActiveRadarrBlock::Movies.into(), - help: "<↑↓> scroll | ←→ change tab | change servarr | quit ", - contextual_help: None, - }, - TabRoute { - title: "Sonarr", - route: Route::Sonarr, - help: " change servarr | quit ", - contextual_help: None, - } - ] - ); - assert_str_eq!(app.title, "Managarr"); - assert_eq!(app.tick_until_poll, 400); - assert_eq!(app.ticks_until_scroll, 4); - assert_eq!(app.tick_count, 0); - assert!(!app.is_loading); - assert!(!app.is_routing); - assert!(!app.should_refresh); - assert!(!app.should_ignore_quit_key); - } - - #[test] - fn test_navigation_stack_methods() { - let mut app = App::default(); - - assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); - - app.push_navigation_stack(ActiveRadarrBlock::Downloads.into()); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Downloads.into() - ); - assert!(app.is_routing); - - app.is_routing = false; - app.pop_and_push_navigation_stack(ActiveRadarrBlock::Collections.into()); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - assert!(app.is_routing); - - app.is_routing = false; - app.pop_navigation_stack(); - - assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); - assert!(app.is_routing); - - app.is_routing = false; - app.pop_navigation_stack(); - - assert_eq!(app.get_current_route(), &DEFAULT_ROUTE); - assert!(app.is_routing); - } - - #[test] - fn test_reset_tick_count() { - let mut app = App { - tick_count: 2, - ..App::default() - }; - - app.reset_tick_count(); - - assert_eq!(app.tick_count, 0); - } - - #[test] - fn test_reset() { - let mut app = App { - tick_count: 2, - error: "Test error".to_owned().into(), - data: Data { - radarr_data: RadarrData { - version: "test".to_owned(), - ..RadarrData::default() - }, - }, - ..App::default() - }; - - app.reset(); - - assert_eq!(app.tick_count, 0); - assert_eq!(app.error, HorizontallyScrollableText::default()); - assert!(app.data.radarr_data.version.is_empty()); - } - - #[test] - fn test_handle_error() { - let mut app = App::default(); - let test_string = "Testing"; - - app.handle_error(anyhow!(test_string)); - - assert_eq!(app.error.text, test_string); - - app.handle_error(anyhow!("Testing a different error")); - - assert_eq!(app.error.text, test_string); - } - - #[tokio::test] - async fn test_on_tick_first_render() { - let (sync_network_tx, mut sync_network_rx) = mpsc::channel::(500); - - let mut app = App { - tick_until_poll: 2, - network_tx: Some(sync_network_tx), - ..App::default() - }; - - assert_eq!(app.tick_count, 0); - - app.on_tick(true).await; - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetOverview.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovies.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.is_routing); - assert!(!app.should_refresh); - assert_eq!(app.tick_count, 1); - } - - #[tokio::test] - async fn test_on_tick_routing() { - let mut app = App { - tick_until_poll: 2, - tick_count: 2, - is_routing: true, - ..App::default() - }; - - app.on_tick(false).await; - assert!(!app.is_routing); - } - - #[tokio::test] - async fn test_on_tick_should_refresh() { - let mut app = App { - tick_until_poll: 2, - tick_count: 2, - should_refresh: true, - ..App::default() - }; - - app.on_tick(false).await; - assert!(!app.should_refresh); - } - - #[test] - fn test_radarr_config_default() { - let radarr_config = RadarrConfig::default(); - - assert_str_eq!(radarr_config.host, "localhost"); - assert_eq!(radarr_config.port, Some(7878)); - assert!(radarr_config.api_token.is_empty()); - } -} diff --git a/src/app/radarr.rs b/src/app/radarr.rs index c0e0a43..3b98040 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -4,8 +4,8 @@ use strum::IntoEnumIterator; use crate::app::{App, Route}; use crate::models::radarr_models::{ - AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, - MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField, RootFolder, + AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, Log, + MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField, RootFolder, Task, }; use crate::models::{ BlockSelectionState, HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable, @@ -13,6 +13,14 @@ use crate::models::{ }; use crate::network::radarr_network::RadarrEvent; +#[cfg(test)] +#[path = "radarr_tests.rs"] +mod radarr_tests; + +#[cfg(test)] +#[path = "radarr_test_utils.rs"] +pub mod radarr_test_utils; + pub struct RadarrData<'a> { pub root_folders: StatefulTable, pub disk_space_vec: Vec, @@ -41,6 +49,8 @@ pub struct RadarrData<'a> { pub collections: StatefulTable, pub filtered_collections: StatefulTable, pub collection_movies: StatefulTable, + pub logs: StatefulList, + pub tasks: StatefulTable, pub prompt_confirm_action: Option, pub main_tabs: TabState, pub movie_info_tabs: TabState, @@ -257,6 +267,8 @@ impl<'a> Default for RadarrData<'a> { collections: StatefulTable::default(), filtered_collections: StatefulTable::default(), collection_movies: StatefulTable::default(), + logs: StatefulList::default(), + tasks: StatefulTable::default(), prompt_confirm_action: None, search: HorizontallyScrollableText::default(), filter: HorizontallyScrollableText::default(), @@ -299,7 +311,7 @@ impl<'a> Default for RadarrData<'a> { title: "System", route: ActiveRadarrBlock::System.into(), help: "", - contextual_help: Some(" select menu item | go back to menu selection") + contextual_help: Some(" select block | go back to block selection") } ]), movie_info_tabs: TabState::new(vec![ @@ -538,6 +550,14 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetDownloads.into()) .await; } + ActiveRadarrBlock::System => { + self + .dispatch_network_event(RadarrEvent::GetTasks.into()) + .await; + self + .dispatch_network_event(RadarrEvent::GetLogs.into()) + .await; + } ActiveRadarrBlock::AddMovieSearchResults => { self .dispatch_network_event(RadarrEvent::SearchNewMovie.into()) @@ -663,1234 +683,3 @@ impl<'a> App<'a> { .set_items(collection_movies); } } - -#[cfg(test)] -#[macro_use] -pub mod radarr_test_utils { - use crate::app::radarr::RadarrData; - use crate::models::radarr_models::{ - AddMovieSearchResult, Collection, CollectionMovie, Credit, MinimumAvailability, Monitor, Movie, - MovieHistoryItem, Release, ReleaseField, RootFolder, - }; - use crate::models::ScrollableText; - - pub fn create_test_radarr_data<'a>() -> RadarrData<'a> { - let mut radarr_data = RadarrData { - is_searching: true, - is_filtering: true, - delete_movie_files: true, - add_list_exclusion: true, - search: "test search".to_owned().into(), - filter: "test filter".to_owned().into(), - edit_path: "test path".to_owned().into(), - edit_tags: "usenet, test".to_owned().into(), - edit_monitored: Some(true), - edit_search_on_add: Some(true), - file_details: "test file details".to_owned(), - audio_details: "test audio details".to_owned(), - video_details: "test video details".to_owned(), - movie_details: ScrollableText::with_string("test movie details".to_owned()), - ..RadarrData::default() - }; - radarr_data - .movie_history - .set_items(vec![MovieHistoryItem::default()]); - radarr_data.movie_cast.set_items(vec![Credit::default()]); - radarr_data.movie_crew.set_items(vec![Credit::default()]); - radarr_data - .movie_releases - .set_items(vec![Release::default()]); - radarr_data.movie_info_tabs.index = 1; - radarr_data.monitor_list.set_items(vec![Monitor::default()]); - radarr_data - .minimum_availability_list - .set_items(vec![MinimumAvailability::default()]); - radarr_data - .quality_profile_list - .set_items(vec![String::default()]); - radarr_data - .root_folder_list - .set_items(vec![RootFolder::default()]); - radarr_data - .movie_releases_sort - .set_items(vec![ReleaseField::default()]); - radarr_data.sort_ascending = Some(true); - radarr_data - .filtered_movies - .set_items(vec![Movie::default()]); - radarr_data - .filtered_collections - .set_items(vec![Collection::default()]); - radarr_data - .add_searched_movies - .set_items(vec![AddMovieSearchResult::default()]); - radarr_data - .collection_movies - .set_items(vec![CollectionMovie::default()]); - - radarr_data - } - - #[macro_export] - macro_rules! assert_search_reset { - ($radarr_data:expr) => { - assert!(!$radarr_data.is_searching); - assert!($radarr_data.search.text.is_empty()); - assert!($radarr_data.filter.text.is_empty()); - assert!($radarr_data.filtered_movies.items.is_empty()); - assert!($radarr_data.filtered_collections.items.is_empty()); - assert!($radarr_data.add_searched_movies.items.is_empty()); - }; - } - - #[macro_export] - macro_rules! assert_edit_media_reset { - ($radarr_data:expr) => { - assert!($radarr_data.edit_monitored.is_none()); - assert!($radarr_data.edit_search_on_add.is_none()); - assert!($radarr_data.edit_path.text.is_empty()); - assert!($radarr_data.edit_tags.text.is_empty()); - }; - } - - #[macro_export] - macro_rules! assert_filter_reset { - ($radarr_data:expr) => { - assert!(!$radarr_data.is_filtering); - assert!($radarr_data.filter.text.is_empty()); - assert!($radarr_data.filtered_movies.items.is_empty()); - assert!($radarr_data.filtered_collections.items.is_empty()); - }; - } - - #[macro_export] - macro_rules! assert_movie_info_tabs_reset { - ($radarr_data:expr) => { - assert!($radarr_data.file_details.is_empty()); - assert!($radarr_data.audio_details.is_empty()); - assert!($radarr_data.video_details.is_empty()); - assert!($radarr_data.movie_details.get_text().is_empty()); - assert!($radarr_data.movie_history.items.is_empty()); - assert!($radarr_data.movie_cast.items.is_empty()); - assert!($radarr_data.movie_crew.items.is_empty()); - assert!($radarr_data.movie_releases.items.is_empty()); - assert!($radarr_data.movie_releases_sort.items.is_empty()); - assert!($radarr_data.sort_ascending.is_none()); - assert_eq!($radarr_data.movie_info_tabs.index, 0); - }; - } - - #[macro_export] - macro_rules! assert_preferences_selections_reset { - ($radarr_data:expr) => { - assert!($radarr_data.monitor_list.items.is_empty()); - assert!($radarr_data.minimum_availability_list.items.is_empty()); - assert!($radarr_data.quality_profile_list.items.is_empty()); - assert!($radarr_data.root_folder_list.items.is_empty()); - }; - } -} - -#[cfg(test)] -mod tests { - mod radarr_data_tests { - use bimap::BiMap; - use chrono::{DateTime, Utc}; - 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::create_test_radarr_data; - use crate::app::radarr::{ActiveRadarrBlock, RadarrData}; - use crate::models::radarr_models::{ - Collection, MinimumAvailability, Monitor, Movie, RootFolder, - }; - use crate::models::Route; - use crate::models::StatefulTable; - use crate::models::{BlockSelectionState, HorizontallyScrollableText}; - - #[test] - fn test_from_tuple_to_route_with_context() { - assert_eq!( - Route::from(( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::AddMovieSearchResults) - )), - Route::Radarr( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::AddMovieSearchResults) - ) - ); - } - - #[test] - fn test_reset_movie_collection_table() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_movie_collection_table(); - - assert!(radarr_data.collection_movies.items.is_empty()); - } - - #[test] - fn test_reset_delete_movie_preferences() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_delete_movie_preferences(); - - assert!(!radarr_data.delete_movie_files); - assert!(!radarr_data.add_list_exclusion); - } - - #[test] - fn test_reset_search() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_search(); - - assert_search_reset!(radarr_data); - } - - #[test] - fn test_reset_filter() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_filter(); - - assert_filter_reset!(radarr_data); - } - - #[test] - fn test_reset_movie_info_tabs() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_movie_info_tabs(); - - assert_movie_info_tabs_reset!(radarr_data); - } - - #[test] - fn test_reset_add_edit_media_fields() { - let mut radarr_data = RadarrData { - edit_monitored: Some(true), - edit_search_on_add: Some(true), - edit_path: "test path".to_owned().into(), - edit_tags: "test tag".to_owned().into(), - ..RadarrData::default() - }; - - radarr_data.reset_add_edit_media_fields(); - - assert_edit_media_reset!(radarr_data); - } - - #[test] - fn test_reset_preferences_selections() { - let mut radarr_data = create_test_radarr_data(); - - radarr_data.reset_preferences_selections(); - - assert_preferences_selections_reset!(radarr_data); - } - - #[test] - fn test_populate_preferences_lists() { - let root_folder = RootFolder { - id: Number::from(1), - path: "/nfs".to_owned(), - accessible: true, - free_space: Number::from(219902325555200u64), - unmapped_folders: None, - }; - let mut radarr_data = RadarrData { - quality_profile_map: BiMap::from_iter([ - (2222, "HD - 1080p".to_owned()), - (1111, "Any".to_owned()), - ]), - ..RadarrData::default() - }; - radarr_data - .root_folders - .set_items(vec![root_folder.clone()]); - - radarr_data.populate_preferences_lists(); - - assert_eq!( - radarr_data.monitor_list.items, - Vec::from_iter(Monitor::iter()) - ); - assert_eq!( - radarr_data.minimum_availability_list.items, - Vec::from_iter(MinimumAvailability::iter()) - ); - assert_eq!( - radarr_data.quality_profile_list.items, - vec!["Any".to_owned(), "HD - 1080p".to_owned()] - ); - assert_eq!(radarr_data.root_folder_list.items, vec![root_folder]); - } - - #[rstest] - fn test_populate_edit_movie_fields(#[values(true, false)] test_filtered_movies: bool) { - let mut radarr_data = RadarrData { - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, - quality_profile_map: BiMap::from_iter([ - (2222, "HD - 1080p".to_owned()), - (1111, "Any".to_owned()), - ]), - tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), - filtered_movies: StatefulTable::default(), - ..create_test_radarr_data() - }; - let movie = Movie { - path: "/nfs/movies/Test".to_owned(), - monitored: true, - quality_profile_id: Number::from(2222), - minimum_availability: MinimumAvailability::Released, - tags: vec![Number::from(1), Number::from(2)], - ..Movie::default() - }; - - if test_filtered_movies { - radarr_data.filtered_movies.set_items(vec![movie]); - } else { - radarr_data.movies.set_items(vec![movie]); - } - - radarr_data.populate_edit_movie_fields(); - - assert_eq!( - radarr_data.minimum_availability_list.items, - Vec::from_iter(MinimumAvailability::iter()) - ); - assert_eq!( - radarr_data.minimum_availability_list.current_selection(), - &MinimumAvailability::Released - ); - assert_eq!( - radarr_data.quality_profile_list.items, - vec!["Any".to_owned(), "HD - 1080p".to_owned()] - ); - assert_str_eq!( - radarr_data.quality_profile_list.current_selection(), - "HD - 1080p" - ); - assert_str_eq!(radarr_data.edit_path.text, "/nfs/movies/Test"); - assert_str_eq!(radarr_data.edit_tags.text, "usenet, test"); - assert_eq!(radarr_data.edit_monitored, Some(true)); - } - - #[rstest] - fn test_populate_edit_collection_fields( - #[values(true, false)] test_filtered_collections: bool, - ) { - let mut radarr_data = RadarrData { - edit_path: HorizontallyScrollableText::default(), - edit_monitored: None, - edit_search_on_add: None, - quality_profile_map: BiMap::from_iter([ - (2222, "HD - 1080p".to_owned()), - (1111, "Any".to_owned()), - ]), - filtered_collections: StatefulTable::default(), - ..create_test_radarr_data() - }; - let collection = Collection { - root_folder_path: Some("/nfs/movies/Test".to_owned()), - monitored: true, - search_on_add: true, - quality_profile_id: Number::from(2222), - minimum_availability: MinimumAvailability::Released, - ..Collection::default() - }; - - if test_filtered_collections { - radarr_data.filtered_collections.set_items(vec![collection]); - } else { - radarr_data.collections.set_items(vec![collection]); - } - - radarr_data.populate_edit_collection_fields(); - - assert_eq!( - radarr_data.minimum_availability_list.items, - Vec::from_iter(MinimumAvailability::iter()) - ); - assert_eq!( - radarr_data.minimum_availability_list.current_selection(), - &MinimumAvailability::Released - ); - assert_eq!( - radarr_data.quality_profile_list.items, - vec!["Any".to_owned(), "HD - 1080p".to_owned()] - ); - assert_str_eq!( - radarr_data.quality_profile_list.current_selection(), - "HD - 1080p" - ); - assert_str_eq!(radarr_data.edit_path.text, "/nfs/movies/Test"); - assert_eq!(radarr_data.edit_monitored, Some(true)); - assert_eq!(radarr_data.edit_search_on_add, Some(true)); - } - - #[test] - fn test_radarr_data_defaults() { - let radarr_data = RadarrData::default(); - - assert!(radarr_data.root_folders.items.is_empty()); - assert_eq!(radarr_data.disk_space_vec, Vec::new()); - assert!(radarr_data.version.is_empty()); - assert_eq!(radarr_data.start_time, >::default()); - assert!(radarr_data.movies.items.is_empty()); - assert!(radarr_data.add_searched_movies.items.is_empty()); - assert!(radarr_data.monitor_list.items.is_empty()); - assert!(radarr_data.minimum_availability_list.items.is_empty()); - assert!(radarr_data.quality_profile_list.items.is_empty()); - assert!(radarr_data.root_folder_list.items.is_empty()); - assert_eq!(radarr_data.selected_block, BlockSelectionState::default()); - assert!(radarr_data.filtered_movies.items.is_empty()); - assert!(radarr_data.downloads.items.is_empty()); - assert!(radarr_data.quality_profile_map.is_empty()); - assert!(radarr_data.tags_map.is_empty()); - assert!(radarr_data.file_details.is_empty()); - assert!(radarr_data.audio_details.is_empty()); - assert!(radarr_data.video_details.is_empty()); - assert!(radarr_data.movie_details.get_text().is_empty()); - assert!(radarr_data.movie_history.items.is_empty()); - assert!(radarr_data.movie_cast.items.is_empty()); - assert!(radarr_data.movie_crew.items.is_empty()); - assert!(radarr_data.movie_releases.items.is_empty()); - assert!(radarr_data.movie_releases_sort.items.is_empty()); - assert!(radarr_data.collections.items.is_empty()); - assert!(radarr_data.filtered_collections.items.is_empty()); - assert!(radarr_data.collection_movies.items.is_empty()); - assert!(radarr_data.prompt_confirm_action.is_none()); - assert!(radarr_data.search.text.is_empty()); - assert!(radarr_data.filter.text.is_empty()); - assert!(radarr_data.edit_path.text.is_empty()); - assert!(radarr_data.edit_tags.text.is_empty()); - assert!(radarr_data.edit_monitored.is_none()); - assert!(radarr_data.edit_search_on_add.is_none()); - assert!(radarr_data.sort_ascending.is_none()); - assert!(!radarr_data.is_searching); - assert!(!radarr_data.is_filtering); - assert!(!radarr_data.prompt_confirm); - assert!(!radarr_data.delete_movie_files); - assert!(!radarr_data.add_list_exclusion); - - assert_eq!(radarr_data.main_tabs.tabs.len(), 5); - - assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library"); - assert_eq!( - radarr_data.main_tabs.tabs[0].route, - ActiveRadarrBlock::Movies.into() - ); - assert!(radarr_data.main_tabs.tabs[0].help.is_empty()); - assert_eq!(radarr_data.main_tabs.tabs[0].contextual_help, - Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter")); - - assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Downloads"); - assert_eq!( - radarr_data.main_tabs.tabs[1].route, - ActiveRadarrBlock::Downloads.into() - ); - assert!(radarr_data.main_tabs.tabs[1].help.is_empty()); - assert_eq!( - radarr_data.main_tabs.tabs[1].contextual_help, - Some(" refresh | delete") - ); - - assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Collections"); - assert_eq!( - radarr_data.main_tabs.tabs[2].route, - ActiveRadarrBlock::Collections.into() - ); - assert!(radarr_data.main_tabs.tabs[2].help.is_empty()); - assert_eq!(radarr_data.main_tabs.tabs[2].contextual_help, - Some(" search | edit | filter | refresh | update all | details | cancel filter")); - - assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Root Folders"); - assert_eq!( - radarr_data.main_tabs.tabs[3].route, - ActiveRadarrBlock::RootFolders.into() - ); - assert!(radarr_data.main_tabs.tabs[3].help.is_empty()); - assert_eq!( - radarr_data.main_tabs.tabs[3].contextual_help, - Some(" add | delete | refresh") - ); - - assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "System"); - assert_eq!( - radarr_data.main_tabs.tabs[4].route, - ActiveRadarrBlock::System.into() - ); - assert!(radarr_data.main_tabs.tabs[4].help.is_empty()); - assert_eq!( - radarr_data.main_tabs.tabs[4].contextual_help, - Some(" select menu item | go back to menu selection") - ); - - assert_eq!(radarr_data.movie_info_tabs.tabs.len(), 6); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[0].title, "Details"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[0].route, - ActiveRadarrBlock::MovieDetails.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[0].help, - " refresh | update | edit | auto search | close" - ); - assert!(radarr_data.movie_info_tabs.tabs[0] - .contextual_help - .is_none()); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[1].title, "History"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[1].route, - ActiveRadarrBlock::MovieHistory.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[1].help, - " refresh | update | edit | auto search | close" - ); - assert!(radarr_data.movie_info_tabs.tabs[1] - .contextual_help - .is_none()); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[2].title, "File"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[2].route, - ActiveRadarrBlock::FileInfo.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[2].help, - " refresh | update | edit | auto search | close" - ); - assert!(radarr_data.movie_info_tabs.tabs[2] - .contextual_help - .is_none()); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[3].title, "Cast"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[3].route, - ActiveRadarrBlock::Cast.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[3].help, - " refresh | update | edit | auto search | close" - ); - assert!(radarr_data.movie_info_tabs.tabs[3] - .contextual_help - .is_none()); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[4].title, "Crew"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[4].route, - ActiveRadarrBlock::Crew.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[4].help, - " refresh | update | edit | auto search | close" - ); - assert!(radarr_data.movie_info_tabs.tabs[4] - .contextual_help - .is_none()); - - assert_str_eq!(radarr_data.movie_info_tabs.tabs[5].title, "Manual Search"); - assert_eq!( - radarr_data.movie_info_tabs.tabs[5].route, - ActiveRadarrBlock::ManualSearch.into() - ); - assert_str_eq!( - radarr_data.movie_info_tabs.tabs[5].help, - " refresh | update | edit | sort | auto search | close" - ); - assert_eq!( - radarr_data.movie_info_tabs.tabs[5].contextual_help, - Some(" details") - ); - } - } - - mod active_radarr_block_tests { - use pretty_assertions::assert_eq; - - use crate::app::radarr::{ - ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS, - EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, - }; - - #[test] - fn test_add_movie_prompt_block_order() { - let mut add_movie_block_iter = ADD_MOVIE_SELECTION_BLOCKS.iter(); - - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieSelectRootFolder - ); - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieSelectMonitor - ); - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieSelectMinimumAvailability - ); - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieSelectQualityProfile - ); - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieTagsInput - ); - assert_eq!( - add_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::AddMovieConfirmPrompt - ); - } - - #[test] - fn test_edit_movie_prompt_block_order() { - let mut edit_movie_block_iter = EDIT_MOVIE_SELECTION_BLOCKS.iter(); - - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMovieToggleMonitored - ); - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMovieSelectMinimumAvailability - ); - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMovieSelectQualityProfile - ); - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMoviePathInput - ); - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMovieTagsInput - ); - assert_eq!( - edit_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditMovieConfirmPrompt - ); - } - - #[test] - fn test_edit_collection_prompt_block_order() { - let mut edit_collection_block_iter = EDIT_COLLECTION_SELECTION_BLOCKS.iter(); - - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionToggleMonitored - ); - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability - ); - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionSelectQualityProfile - ); - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionRootFolderPathInput - ); - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd - ); - assert_eq!( - edit_collection_block_iter.next().unwrap(), - &ActiveRadarrBlock::EditCollectionConfirmPrompt - ); - } - - #[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 - ); - assert_eq!( - delete_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion - ); - assert_eq!( - delete_movie_block_iter.next().unwrap(), - &ActiveRadarrBlock::DeleteMovieConfirmPrompt - ); - } - } - - mod radarr_tests { - use pretty_assertions::assert_eq; - use tokio::sync::mpsc; - - use crate::app::radarr::ActiveRadarrBlock; - use crate::app::App; - use crate::models::radarr_models::{Collection, CollectionMovie, Credit, Release}; - use crate::models::StatefulTable; - use crate::network::radarr_network::RadarrEvent; - use crate::network::NetworkEvent; - - #[tokio::test] - async fn test_dispatch_by_collections_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Collections) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetCollections.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_collection_details_block() { - let (mut app, _) = construct_app_unit(); - - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) - .await; - - assert!(!app.is_loading); - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - assert_eq!(app.tick_count, 0); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_dispatch_by_collection_details_block_with_add_movie() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie); - - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::AddMovie.into() - ); - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - assert_eq!(app.tick_count, 0); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_dispatch_by_downloads_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Downloads) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_root_folders_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::RootFolders) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movies_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Movies) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovies.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_add_movie_search_results_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::AddMovieSearchResults) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::SearchNewMovie.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movie_details_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieDetails) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieDetails.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_file_info_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::FileInfo) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieDetails.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movie_history_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieHistory) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieHistory.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - app.data.radarr_data.movie_cast = StatefulTable::default(); - app.data.radarr_data.movie_crew = StatefulTable::default(); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_movie_cast_non_empty() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - app - .data - .radarr_data - .movie_cast - .set_items(vec![Credit::default()]); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_movie_crew_non_empty() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - app - .data - .radarr_data - .movie_crew - .set_items(vec![Credit::default()]); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_cast_and_crew_non_empty() { - let mut app = App::default(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - app - .data - .radarr_data - .movie_cast - .set_items(vec![Credit::default()]); - app - .data - .radarr_data - .movie_crew - .set_items(vec![Credit::default()]); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(!app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetReleases.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block_movie_releases_non_empty() { - let mut app = App::default(); - app - .data - .radarr_data - .movie_releases - .set_items(vec![Release::default()]); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; - - assert!(!app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block_is_loading() { - let mut app = App { - is_loading: true, - ..App::default() - }; - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; - - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_check_for_prompt_action_no_prompt_confirm() { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = false; - - app.check_for_prompt_action().await; - - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.should_refresh); - } - - #[tokio::test] - async fn test_check_for_prompt_action() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::GetStatus); - - app.check_for_prompt_action().await; - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert!(app.should_refresh); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[tokio::test] - async fn test_radarr_refresh_metadata() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_routing = true; - - app.refresh_metadata().await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - } - - #[tokio::test] - async fn test_radarr_on_tick_first_render() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app.radarr_on_tick(ActiveRadarrBlock::Downloads, true).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetOverview.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_radarr_on_tick_not_routing() { - let mut app = App::default(); - - app - .radarr_on_tick(ActiveRadarrBlock::Downloads, false) - .await; - - assert!(!app.is_routing); - } - - #[tokio::test] - async fn test_radarr_on_tick_routing() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_routing = true; - - app - .radarr_on_tick(ActiveRadarrBlock::Downloads, false) - .await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_radarr_on_tick_should_refresh() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.should_refresh = true; - - app - .radarr_on_tick(ActiveRadarrBlock::Downloads, false) - .await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - assert!(app.should_refresh); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_radarr_on_tick_network_tick_frequency() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.tick_count = 2; - app.tick_until_poll = 2; - - app - .radarr_on_tick(ActiveRadarrBlock::Downloads, false) - .await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_populate_movie_collection_table_unfiltered() { - let mut app = App::default(); - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app.populate_movie_collection_table().await; - - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - } - - #[tokio::test] - async fn test_populate_movie_collection_table_filtered() { - let mut app = App::default(); - app - .data - .radarr_data - .filtered_collections - .set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app.populate_movie_collection_table().await; - - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - } - - fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { - let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); - let mut app = App { - network_tx: Some(sync_network_tx), - tick_count: 1, - ..App::default() - }; - app.data.radarr_data.prompt_confirm = true; - - (app, sync_network_rx) - } - } -} diff --git a/src/app/radarr_test_utils.rs b/src/app/radarr_test_utils.rs new file mode 100644 index 0000000..5555ee5 --- /dev/null +++ b/src/app/radarr_test_utils.rs @@ -0,0 +1,126 @@ +#[cfg(test)] +pub mod utils { + use crate::app::radarr::RadarrData; + use crate::models::radarr_models::{ + AddMovieSearchResult, Collection, CollectionMovie, Credit, Log, MinimumAvailability, Monitor, + Movie, MovieHistoryItem, Release, ReleaseField, RootFolder, + }; + use crate::models::ScrollableText; + + pub fn create_test_radarr_data<'a>() -> RadarrData<'a> { + let mut radarr_data = RadarrData { + is_searching: true, + is_filtering: true, + delete_movie_files: true, + add_list_exclusion: true, + search: "test search".to_owned().into(), + filter: "test filter".to_owned().into(), + edit_path: "test path".to_owned().into(), + edit_tags: "usenet, test".to_owned().into(), + edit_monitored: Some(true), + edit_search_on_add: Some(true), + file_details: "test file details".to_owned(), + audio_details: "test audio details".to_owned(), + video_details: "test video details".to_owned(), + movie_details: ScrollableText::with_string("test movie details".to_owned()), + ..RadarrData::default() + }; + radarr_data + .movie_history + .set_items(vec![MovieHistoryItem::default()]); + radarr_data.movie_cast.set_items(vec![Credit::default()]); + radarr_data.movie_crew.set_items(vec![Credit::default()]); + radarr_data + .movie_releases + .set_items(vec![Release::default()]); + radarr_data.movie_info_tabs.index = 1; + radarr_data.monitor_list.set_items(vec![Monitor::default()]); + radarr_data + .minimum_availability_list + .set_items(vec![MinimumAvailability::default()]); + radarr_data + .quality_profile_list + .set_items(vec![String::default()]); + radarr_data + .root_folder_list + .set_items(vec![RootFolder::default()]); + radarr_data + .movie_releases_sort + .set_items(vec![ReleaseField::default()]); + radarr_data.sort_ascending = Some(true); + radarr_data + .filtered_movies + .set_items(vec![Movie::default()]); + radarr_data + .filtered_collections + .set_items(vec![Collection::default()]); + radarr_data + .add_searched_movies + .set_items(vec![AddMovieSearchResult::default()]); + radarr_data + .collection_movies + .set_items(vec![CollectionMovie::default()]); + radarr_data.logs.set_items(vec![Log::default()]); + + radarr_data + } + + #[macro_export] + macro_rules! assert_search_reset { + ($radarr_data:expr) => { + assert!(!$radarr_data.is_searching); + assert!($radarr_data.search.text.is_empty()); + assert!($radarr_data.filter.text.is_empty()); + assert!($radarr_data.filtered_movies.items.is_empty()); + assert!($radarr_data.filtered_collections.items.is_empty()); + assert!($radarr_data.add_searched_movies.items.is_empty()); + }; + } + + #[macro_export] + macro_rules! assert_edit_media_reset { + ($radarr_data:expr) => { + assert!($radarr_data.edit_monitored.is_none()); + assert!($radarr_data.edit_search_on_add.is_none()); + assert!($radarr_data.edit_path.text.is_empty()); + assert!($radarr_data.edit_tags.text.is_empty()); + }; + } + + #[macro_export] + macro_rules! assert_filter_reset { + ($radarr_data:expr) => { + assert!(!$radarr_data.is_filtering); + assert!($radarr_data.filter.text.is_empty()); + assert!($radarr_data.filtered_movies.items.is_empty()); + assert!($radarr_data.filtered_collections.items.is_empty()); + }; + } + + #[macro_export] + macro_rules! assert_movie_info_tabs_reset { + ($radarr_data:expr) => { + assert!($radarr_data.file_details.is_empty()); + assert!($radarr_data.audio_details.is_empty()); + assert!($radarr_data.video_details.is_empty()); + assert!($radarr_data.movie_details.get_text().is_empty()); + assert!($radarr_data.movie_history.items.is_empty()); + assert!($radarr_data.movie_cast.items.is_empty()); + assert!($radarr_data.movie_crew.items.is_empty()); + assert!($radarr_data.movie_releases.items.is_empty()); + assert!($radarr_data.movie_releases_sort.items.is_empty()); + assert!($radarr_data.sort_ascending.is_none()); + assert_eq!($radarr_data.movie_info_tabs.index, 0); + }; + } + + #[macro_export] + macro_rules! assert_preferences_selections_reset { + ($radarr_data:expr) => { + assert!($radarr_data.monitor_list.items.is_empty()); + assert!($radarr_data.minimum_availability_list.items.is_empty()); + assert!($radarr_data.quality_profile_list.items.is_empty()); + assert!($radarr_data.root_folder_list.items.is_empty()); + }; + } +} diff --git a/src/app/radarr_tests.rs b/src/app/radarr_tests.rs new file mode 100644 index 0000000..8f06bf0 --- /dev/null +++ b/src/app/radarr_tests.rs @@ -0,0 +1,1130 @@ +#[cfg(test)] +mod tests { + mod radarr_data_tests { + use bimap::BiMap; + use chrono::{DateTime, Utc}; + 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::{ActiveRadarrBlock, RadarrData}; + use crate::models::radarr_models::{ + Collection, MinimumAvailability, Monitor, Movie, RootFolder, + }; + use crate::models::Route; + use crate::models::StatefulTable; + use crate::models::{BlockSelectionState, HorizontallyScrollableText}; + use crate::{ + assert_edit_media_reset, assert_filter_reset, assert_movie_info_tabs_reset, + assert_preferences_selections_reset, assert_search_reset, + }; + + #[test] + fn test_from_tuple_to_route_with_context() { + assert_eq!( + Route::from(( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::AddMovieSearchResults) + )), + Route::Radarr( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::AddMovieSearchResults) + ) + ); + } + + #[test] + fn test_reset_movie_collection_table() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_movie_collection_table(); + + assert!(radarr_data.collection_movies.items.is_empty()); + } + + #[test] + fn test_reset_delete_movie_preferences() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_delete_movie_preferences(); + + assert!(!radarr_data.delete_movie_files); + assert!(!radarr_data.add_list_exclusion); + } + + #[test] + fn test_reset_search() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_search(); + + assert_search_reset!(radarr_data); + } + + #[test] + fn test_reset_filter() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_filter(); + + assert_filter_reset!(radarr_data); + } + + #[test] + fn test_reset_movie_info_tabs() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_movie_info_tabs(); + + assert_movie_info_tabs_reset!(radarr_data); + } + + #[test] + fn test_reset_add_edit_media_fields() { + let mut radarr_data = RadarrData { + edit_monitored: Some(true), + edit_search_on_add: Some(true), + edit_path: "test path".to_owned().into(), + edit_tags: "test tag".to_owned().into(), + ..RadarrData::default() + }; + + radarr_data.reset_add_edit_media_fields(); + + assert_edit_media_reset!(radarr_data); + } + + #[test] + fn test_reset_preferences_selections() { + let mut radarr_data = create_test_radarr_data(); + + radarr_data.reset_preferences_selections(); + + assert_preferences_selections_reset!(radarr_data); + } + + #[test] + fn test_populate_preferences_lists() { + let root_folder = RootFolder { + id: Number::from(1), + path: "/nfs".to_owned(), + accessible: true, + free_space: Number::from(219902325555200u64), + unmapped_folders: None, + }; + let mut radarr_data = RadarrData { + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + ..RadarrData::default() + }; + radarr_data + .root_folders + .set_items(vec![root_folder.clone()]); + + radarr_data.populate_preferences_lists(); + + assert_eq!( + radarr_data.monitor_list.items, + Vec::from_iter(Monitor::iter()) + ); + assert_eq!( + radarr_data.minimum_availability_list.items, + Vec::from_iter(MinimumAvailability::iter()) + ); + assert_eq!( + radarr_data.quality_profile_list.items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_eq!(radarr_data.root_folder_list.items, vec![root_folder]); + } + + #[rstest] + fn test_populate_edit_movie_fields(#[values(true, false)] test_filtered_movies: bool) { + let mut radarr_data = RadarrData { + edit_path: HorizontallyScrollableText::default(), + edit_tags: HorizontallyScrollableText::default(), + edit_monitored: None, + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), + filtered_movies: StatefulTable::default(), + ..create_test_radarr_data() + }; + let movie = Movie { + path: "/nfs/movies/Test".to_owned(), + monitored: true, + quality_profile_id: Number::from(2222), + minimum_availability: MinimumAvailability::Released, + tags: vec![Number::from(1), Number::from(2)], + ..Movie::default() + }; + + if test_filtered_movies { + radarr_data.filtered_movies.set_items(vec![movie]); + } else { + radarr_data.movies.set_items(vec![movie]); + } + + radarr_data.populate_edit_movie_fields(); + + assert_eq!( + radarr_data.minimum_availability_list.items, + Vec::from_iter(MinimumAvailability::iter()) + ); + assert_eq!( + radarr_data.minimum_availability_list.current_selection(), + &MinimumAvailability::Released + ); + assert_eq!( + radarr_data.quality_profile_list.items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_str_eq!( + radarr_data.quality_profile_list.current_selection(), + "HD - 1080p" + ); + assert_str_eq!(radarr_data.edit_path.text, "/nfs/movies/Test"); + assert_str_eq!(radarr_data.edit_tags.text, "usenet, test"); + assert_eq!(radarr_data.edit_monitored, Some(true)); + } + + #[rstest] + fn test_populate_edit_collection_fields( + #[values(true, false)] test_filtered_collections: bool, + ) { + let mut radarr_data = RadarrData { + edit_path: HorizontallyScrollableText::default(), + edit_monitored: None, + edit_search_on_add: None, + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + filtered_collections: StatefulTable::default(), + ..create_test_radarr_data() + }; + let collection = Collection { + root_folder_path: Some("/nfs/movies/Test".to_owned()), + monitored: true, + search_on_add: true, + quality_profile_id: Number::from(2222), + minimum_availability: MinimumAvailability::Released, + ..Collection::default() + }; + + if test_filtered_collections { + radarr_data.filtered_collections.set_items(vec![collection]); + } else { + radarr_data.collections.set_items(vec![collection]); + } + + radarr_data.populate_edit_collection_fields(); + + assert_eq!( + radarr_data.minimum_availability_list.items, + Vec::from_iter(MinimumAvailability::iter()) + ); + assert_eq!( + radarr_data.minimum_availability_list.current_selection(), + &MinimumAvailability::Released + ); + assert_eq!( + radarr_data.quality_profile_list.items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_str_eq!( + radarr_data.quality_profile_list.current_selection(), + "HD - 1080p" + ); + assert_str_eq!(radarr_data.edit_path.text, "/nfs/movies/Test"); + assert_eq!(radarr_data.edit_monitored, Some(true)); + assert_eq!(radarr_data.edit_search_on_add, Some(true)); + } + + #[test] + fn test_radarr_data_defaults() { + let radarr_data = RadarrData::default(); + + assert!(radarr_data.root_folders.items.is_empty()); + assert_eq!(radarr_data.disk_space_vec, Vec::new()); + assert!(radarr_data.version.is_empty()); + assert_eq!(radarr_data.start_time, >::default()); + assert!(radarr_data.movies.items.is_empty()); + assert!(radarr_data.add_searched_movies.items.is_empty()); + assert!(radarr_data.monitor_list.items.is_empty()); + assert!(radarr_data.minimum_availability_list.items.is_empty()); + assert!(radarr_data.quality_profile_list.items.is_empty()); + assert!(radarr_data.root_folder_list.items.is_empty()); + assert_eq!(radarr_data.selected_block, BlockSelectionState::default()); + assert!(radarr_data.filtered_movies.items.is_empty()); + assert!(radarr_data.downloads.items.is_empty()); + assert!(radarr_data.quality_profile_map.is_empty()); + assert!(radarr_data.tags_map.is_empty()); + assert!(radarr_data.file_details.is_empty()); + assert!(radarr_data.audio_details.is_empty()); + assert!(radarr_data.video_details.is_empty()); + assert!(radarr_data.movie_details.get_text().is_empty()); + assert!(radarr_data.movie_history.items.is_empty()); + assert!(radarr_data.movie_cast.items.is_empty()); + assert!(radarr_data.movie_crew.items.is_empty()); + assert!(radarr_data.movie_releases.items.is_empty()); + assert!(radarr_data.movie_releases_sort.items.is_empty()); + assert!(radarr_data.collections.items.is_empty()); + assert!(radarr_data.filtered_collections.items.is_empty()); + assert!(radarr_data.collection_movies.items.is_empty()); + assert!(radarr_data.logs.items.is_empty()); + assert!(radarr_data.tasks.items.is_empty()); + assert!(radarr_data.prompt_confirm_action.is_none()); + assert!(radarr_data.search.text.is_empty()); + assert!(radarr_data.filter.text.is_empty()); + assert!(radarr_data.edit_path.text.is_empty()); + assert!(radarr_data.edit_tags.text.is_empty()); + assert!(radarr_data.edit_monitored.is_none()); + assert!(radarr_data.edit_search_on_add.is_none()); + assert!(radarr_data.sort_ascending.is_none()); + assert!(!radarr_data.is_searching); + assert!(!radarr_data.is_filtering); + assert!(!radarr_data.prompt_confirm); + assert!(!radarr_data.delete_movie_files); + assert!(!radarr_data.add_list_exclusion); + + assert_eq!(radarr_data.main_tabs.tabs.len(), 5); + + assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library"); + assert_eq!( + radarr_data.main_tabs.tabs[0].route, + ActiveRadarrBlock::Movies.into() + ); + assert!(radarr_data.main_tabs.tabs[0].help.is_empty()); + assert_eq!(radarr_data.main_tabs.tabs[0].contextual_help, + Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter")); + + assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Downloads"); + assert_eq!( + radarr_data.main_tabs.tabs[1].route, + ActiveRadarrBlock::Downloads.into() + ); + assert!(radarr_data.main_tabs.tabs[1].help.is_empty()); + assert_eq!( + radarr_data.main_tabs.tabs[1].contextual_help, + Some(" refresh | delete") + ); + + assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Collections"); + assert_eq!( + radarr_data.main_tabs.tabs[2].route, + ActiveRadarrBlock::Collections.into() + ); + assert!(radarr_data.main_tabs.tabs[2].help.is_empty()); + assert_eq!(radarr_data.main_tabs.tabs[2].contextual_help, + Some(" search | edit | filter | refresh | update all | details | cancel filter")); + + assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Root Folders"); + assert_eq!( + radarr_data.main_tabs.tabs[3].route, + ActiveRadarrBlock::RootFolders.into() + ); + assert!(radarr_data.main_tabs.tabs[3].help.is_empty()); + assert_eq!( + radarr_data.main_tabs.tabs[3].contextual_help, + Some(" add | delete | refresh") + ); + + assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "System"); + assert_eq!( + radarr_data.main_tabs.tabs[4].route, + ActiveRadarrBlock::System.into() + ); + assert!(radarr_data.main_tabs.tabs[4].help.is_empty()); + assert_eq!( + radarr_data.main_tabs.tabs[4].contextual_help, + Some(" select menu item | go back to menu selection") + ); + + assert_eq!(radarr_data.movie_info_tabs.tabs.len(), 6); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[0].title, "Details"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[0].route, + ActiveRadarrBlock::MovieDetails.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[0].help, + " refresh | update | edit | auto search | close" + ); + assert!(radarr_data.movie_info_tabs.tabs[0] + .contextual_help + .is_none()); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[1].title, "History"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[1].route, + ActiveRadarrBlock::MovieHistory.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[1].help, + " refresh | update | edit | auto search | close" + ); + assert!(radarr_data.movie_info_tabs.tabs[1] + .contextual_help + .is_none()); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[2].title, "File"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[2].route, + ActiveRadarrBlock::FileInfo.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[2].help, + " refresh | update | edit | auto search | close" + ); + assert!(radarr_data.movie_info_tabs.tabs[2] + .contextual_help + .is_none()); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[3].title, "Cast"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[3].route, + ActiveRadarrBlock::Cast.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[3].help, + " refresh | update | edit | auto search | close" + ); + assert!(radarr_data.movie_info_tabs.tabs[3] + .contextual_help + .is_none()); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[4].title, "Crew"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[4].route, + ActiveRadarrBlock::Crew.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[4].help, + " refresh | update | edit | auto search | close" + ); + assert!(radarr_data.movie_info_tabs.tabs[4] + .contextual_help + .is_none()); + + assert_str_eq!(radarr_data.movie_info_tabs.tabs[5].title, "Manual Search"); + assert_eq!( + radarr_data.movie_info_tabs.tabs[5].route, + ActiveRadarrBlock::ManualSearch.into() + ); + assert_str_eq!( + radarr_data.movie_info_tabs.tabs[5].help, + " refresh | update | edit | sort | auto search | close" + ); + assert_eq!( + radarr_data.movie_info_tabs.tabs[5].contextual_help, + Some(" details") + ); + } + } + + mod active_radarr_block_tests { + use pretty_assertions::assert_eq; + + use crate::app::radarr::{ + ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS, + EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, + }; + + #[test] + fn test_add_movie_prompt_block_order() { + let mut add_movie_block_iter = ADD_MOVIE_SELECTION_BLOCKS.iter(); + + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectRootFolder + ); + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectMonitor + ); + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectMinimumAvailability + ); + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectQualityProfile + ); + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieTagsInput + ); + assert_eq!( + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieConfirmPrompt + ); + } + + #[test] + fn test_edit_movie_prompt_block_order() { + let mut edit_movie_block_iter = EDIT_MOVIE_SELECTION_BLOCKS.iter(); + + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieToggleMonitored + ); + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieSelectMinimumAvailability + ); + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieSelectQualityProfile + ); + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMoviePathInput + ); + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieTagsInput + ); + assert_eq!( + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieConfirmPrompt + ); + } + + #[test] + fn test_edit_collection_prompt_block_order() { + let mut edit_collection_block_iter = EDIT_COLLECTION_SELECTION_BLOCKS.iter(); + + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionToggleMonitored + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionSelectQualityProfile + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionRootFolderPathInput + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionConfirmPrompt + ); + } + + #[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 + ); + assert_eq!( + delete_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion + ); + assert_eq!( + delete_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::DeleteMovieConfirmPrompt + ); + } + } + + mod radarr_tests { + use pretty_assertions::assert_eq; + use tokio::sync::mpsc; + + use crate::app::radarr::ActiveRadarrBlock; + use crate::app::App; + use crate::models::radarr_models::{Collection, CollectionMovie, Credit, Release}; + use crate::models::StatefulTable; + use crate::network::radarr_network::RadarrEvent; + use crate::network::NetworkEvent; + + #[tokio::test] + async fn test_dispatch_by_collections_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Collections) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetCollections.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_collection_details_block() { + let (mut app, _) = construct_app_unit(); + + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) + .await; + + assert!(!app.is_loading); + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + assert_eq!(app.tick_count, 0); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_dispatch_by_collection_details_block_with_add_movie() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie); + + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::AddMovie.into() + ); + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + assert_eq!(app.tick_count, 0); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_dispatch_by_downloads_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Downloads) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_root_folders_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::RootFolders) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movies_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Movies) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovies.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_system_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::System) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTasks.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetLogs.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_add_movie_search_results_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::AddMovieSearchResults) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::SearchNewMovie.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movie_details_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieDetails) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieDetails.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_file_info_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::FileInfo) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieDetails.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movie_history_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieHistory) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieHistory.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + app.data.radarr_data.movie_cast = StatefulTable::default(); + app.data.radarr_data.movie_crew = StatefulTable::default(); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieCredits.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_movie_cast_non_empty() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + app + .data + .radarr_data + .movie_cast + .set_items(vec![Credit::default()]); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieCredits.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_movie_crew_non_empty() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + app + .data + .radarr_data + .movie_crew + .set_items(vec![Credit::default()]); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieCredits.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_cast_and_crew_non_empty() { + let mut app = App::default(); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + app + .data + .radarr_data + .movie_cast + .set_items(vec![Credit::default()]); + app + .data + .radarr_data + .movie_crew + .set_items(vec![Credit::default()]); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(!app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetReleases.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block_movie_releases_non_empty() { + let mut app = App::default(); + app + .data + .radarr_data + .movie_releases + .set_items(vec![Release::default()]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(!app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block_is_loading() { + let mut app = App { + is_loading: true, + ..App::default() + }; + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_check_for_prompt_action_no_prompt_confirm() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = false; + + app.check_for_prompt_action().await; + + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.should_refresh); + } + + #[tokio::test] + async fn test_check_for_prompt_action() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::GetStatus); + + app.check_for_prompt_action().await; + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert!(app.should_refresh); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[tokio::test] + async fn test_radarr_refresh_metadata() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_routing = true; + + app.refresh_metadata().await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + } + + #[tokio::test] + async fn test_radarr_on_tick_first_render() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app.radarr_on_tick(ActiveRadarrBlock::Downloads, true).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetOverview.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_radarr_on_tick_not_routing() { + let mut app = App::default(); + + app + .radarr_on_tick(ActiveRadarrBlock::Downloads, false) + .await; + + assert!(!app.is_routing); + } + + #[tokio::test] + async fn test_radarr_on_tick_routing() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_routing = true; + + app + .radarr_on_tick(ActiveRadarrBlock::Downloads, false) + .await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_radarr_on_tick_should_refresh() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.should_refresh = true; + + app + .radarr_on_tick(ActiveRadarrBlock::Downloads, false) + .await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + assert!(app.should_refresh); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_radarr_on_tick_network_tick_frequency() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.tick_count = 2; + app.tick_until_poll = 2; + + app + .radarr_on_tick(ActiveRadarrBlock::Downloads, false) + .await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_populate_movie_collection_table_unfiltered() { + let mut app = App::default(); + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app.populate_movie_collection_table().await; + + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + } + + #[tokio::test] + async fn test_populate_movie_collection_table_filtered() { + let mut app = App::default(); + app + .data + .radarr_data + .filtered_collections + .set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app.populate_movie_collection_table().await; + + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + } + + fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { + let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); + let mut app = App { + network_tx: Some(sync_network_tx), + tick_count: 1, + ..App::default() + }; + app.data.radarr_data.prompt_confirm = true; + + (app, sync_network_rx) + } + } +} diff --git a/src/event/key.rs b/src/event/key.rs index a063386..60b5f31 100644 --- a/src/event/key.rs +++ b/src/event/key.rs @@ -3,6 +3,10 @@ use std::fmt::{Display, Formatter}; use crossterm::event::{KeyCode, KeyEvent}; +#[cfg(test)] +#[path = "key_tests.rs"] +mod key_tests; + #[derive(Debug, PartialEq, Eq)] pub enum Key { Up, @@ -76,87 +80,3 @@ impl From for Key { } } } - -#[cfg(test)] -mod tests { - use crossterm::event::{KeyCode, KeyEvent}; - use pretty_assertions::{assert_eq, assert_str_eq}; - - use crate::event::key::Key; - - #[test] - fn test_key_formatter() { - assert_str_eq!(format!("{}", Key::Esc), ""); - } - - #[test] - fn test_key_formatter_char() { - assert_str_eq!(format!("{}", Key::Char('q')), ""); - } - - #[test] - fn test_key_from_up() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Up)), Key::Up); - } - - #[test] - fn test_key_from_down() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Down)), Key::Down); - } - - #[test] - fn test_key_from_left() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Left)), Key::Left); - } - - #[test] - fn test_key_from_right() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Right)), Key::Right); - } - - #[test] - fn test_key_from_backspace() { - assert_eq!( - Key::from(KeyEvent::from(KeyCode::Backspace)), - Key::Backspace - ); - } - - #[test] - fn test_key_from_home() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Home)), Key::Home); - } - - #[test] - fn test_key_from_end() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::End)), Key::End); - } - - #[test] - fn test_key_from_delete() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Delete)), Key::Delete); - } - - #[test] - fn test_key_from_enter() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Enter)), Key::Enter); - } - - #[test] - fn test_key_from_esc() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Esc)), Key::Esc); - } - - #[test] - fn test_key_from_char() { - assert_eq!( - Key::from(KeyEvent::from(KeyCode::Char('q'))), - Key::Char('q') - ) - } - - #[test] - fn test_key_from_unknown() { - assert_eq!(Key::from(KeyEvent::from(KeyCode::Pause)), Key::Unknown); - } -} diff --git a/src/event/key_tests.rs b/src/event/key_tests.rs new file mode 100644 index 0000000..6ec0f5a --- /dev/null +++ b/src/event/key_tests.rs @@ -0,0 +1,83 @@ +#[cfg(test)] +mod tests { + use crossterm::event::{KeyCode, KeyEvent}; + use pretty_assertions::{assert_eq, assert_str_eq}; + + use crate::event::key::Key; + + #[test] + fn test_key_formatter() { + assert_str_eq!(format!("{}", Key::Esc), ""); + } + + #[test] + fn test_key_formatter_char() { + assert_str_eq!(format!("{}", Key::Char('q')), ""); + } + + #[test] + fn test_key_from_up() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Up)), Key::Up); + } + + #[test] + fn test_key_from_down() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Down)), Key::Down); + } + + #[test] + fn test_key_from_left() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Left)), Key::Left); + } + + #[test] + fn test_key_from_right() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Right)), Key::Right); + } + + #[test] + fn test_key_from_backspace() { + assert_eq!( + Key::from(KeyEvent::from(KeyCode::Backspace)), + Key::Backspace + ); + } + + #[test] + fn test_key_from_home() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Home)), Key::Home); + } + + #[test] + fn test_key_from_end() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::End)), Key::End); + } + + #[test] + fn test_key_from_delete() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Delete)), Key::Delete); + } + + #[test] + fn test_key_from_enter() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Enter)), Key::Enter); + } + + #[test] + fn test_key_from_esc() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Esc)), Key::Esc); + } + + #[test] + fn test_key_from_char() { + assert_eq!( + Key::from(KeyEvent::from(KeyCode::Char('q'))), + Key::Char('q') + ) + } + + #[test] + fn test_key_from_unknown() { + assert_eq!(Key::from(KeyEvent::from(KeyCode::Pause)), Key::Unknown); + } +} diff --git a/src/handlers/handler_test_utils.rs b/src/handlers/handler_test_utils.rs new file mode 100644 index 0000000..2f20265 --- /dev/null +++ b/src/handlers/handler_test_utils.rs @@ -0,0 +1,416 @@ +#[cfg(test)] +#[macro_use] +mod test_utils { + #[macro_export] + macro_rules! simple_stateful_iterable_vec { + ($name:ident) => { + vec![ + $name { + title: "Test 1".to_owned(), + ..$name::default() + }, + $name { + title: "Test 2".to_owned(), + ..$name::default() + }, + ] + }; + ($name:ident, $title_ident:ident) => { + vec![ + $name { + title: $title_ident::from("Test 1".to_owned()), + ..$name::default() + }, + $name { + title: $title_ident::from("Test 2".to_owned()), + ..$name::default() + }, + ] + }; + ($name:ident, $title_ident:ident, $field:ident) => { + vec![ + $name { + $field: $title_ident::from("Test 1".to_owned()), + ..$name::default() + }, + $name { + $field: $title_ident::from("Test 2".to_owned()), + ..$name::default() + }, + ] + }; + } + + #[macro_export] + macro_rules! extended_stateful_iterable_vec { + ($name:ident) => { + vec![ + $name { + title: "Test 1".to_owned(), + ..$name::default() + }, + $name { + title: "Test 2".to_owned(), + ..$name::default() + }, + $name { + title: "Test 3".to_owned(), + ..$name::default() + }, + ] + }; + ($name:ident, $title_ident:ident) => { + vec![ + $name { + title: $title_ident::from("Test 1".to_owned()), + ..$name::default() + }, + $name { + title: $title_ident::from("Test 2".to_owned()), + ..$name::default() + }, + $name { + title: $title_ident::from("Test 3".to_owned()), + ..$name::default() + }, + ] + }; + ($name:ident, $title_ident:ident, $field:ident) => { + vec![ + $name { + $field: $title_ident::from("Test 1".to_owned()), + ..$name::default() + }, + $name { + $field: $title_ident::from("Test 2".to_owned()), + ..$name::default() + }, + $name { + $field: $title_ident::from("Test 3".to_owned()), + ..$name::default() + }, + ] + }; + } + + #[macro_export] + macro_rules! test_iterable_scroll { + ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { + #[rstest] + fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { + let mut app = App::default(); + app + .data + .radarr_data + .$data_ref + .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 2"); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { + #[rstest] + fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { + let mut app = App::default(); + app + .data + .radarr_data + .$data_ref + .set_items(simple_stateful_iterable_vec!($items)); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 2" + ); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 1" + ); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { + #[rstest] + fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.$data_ref.set_items($items); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 2" + ); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 1" + ); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { + #[rstest] + fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.$data_ref.set_items($items); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app + .data + .radarr_data + .$data_ref + .current_selection() + .$field + .$conversion_fn(), + "Test 2" + ); + + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app + .data + .radarr_data + .$data_ref + .current_selection() + .$field + .$conversion_fn(), + "Test 1" + ); + } + }; + } + + #[macro_export] + macro_rules! test_enum_scroll { + ($func:ident, $handler:ident, $name:ident, $data_ref:ident, $block:expr, $context:expr) => { + #[rstest] + fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { + let reference_vec = Vec::from_iter($name::iter()); + let mut app = App::default(); + app + .data + .radarr_data + .$data_ref + .set_items(reference_vec.clone()); + + if key == Key::Up { + for i in (0..reference_vec.len()).rev() { + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_eq!( + app.data.radarr_data.$data_ref.current_selection(), + &reference_vec[i] + ); + } + } else { + for i in 0..reference_vec.len() { + $handler::with(&key, &mut app, &$block, &$context).handle(); + + assert_eq!( + app.data.radarr_data.$data_ref.current_selection(), + &reference_vec[(i + 1) % reference_vec.len()] + ); + } + } + } + }; + } + + #[macro_export] + macro_rules! test_iterable_home_and_end { + ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { + #[test] + fn $func() { + let mut app = App::default(); + app.data.radarr_data.$data_ref.set_items(vec![ + "Test 1".to_owned(), + "Test 2".to_owned(), + "Test 3".to_owned(), + ]); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 3"); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { + #[test] + fn $func() { + let mut app = App::default(); + app + .data + .radarr_data + .$data_ref + .set_items(extended_stateful_iterable_vec!($items)); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 3" + ); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 1" + ); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { + #[test] + fn $func() { + let mut app = App::default(); + app.data.radarr_data.$data_ref.set_items($items); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 3" + ); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app.data.radarr_data.$data_ref.current_selection().$field, + "Test 1" + ); + } + }; + ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { + #[test] + fn $func() { + let mut app = App::default(); + app.data.radarr_data.$data_ref.set_items($items); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app + .data + .radarr_data + .$data_ref + .current_selection() + .$field + .$conversion_fn(), + "Test 3" + ); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); + + assert_str_eq!( + app + .data + .radarr_data + .$data_ref + .current_selection() + .$field + .$conversion_fn(), + "Test 1" + ); + } + }; + } + + #[macro_export] + macro_rules! test_enum_home_and_end { + ($func:ident, $handler:ident, $name:ident, $data_ref:ident, $block:expr, $context:expr) => { + #[test] + fn $func() { + let reference_vec = Vec::from_iter($name::iter()); + let mut app = App::default(); + app + .data + .radarr_data + .$data_ref + .set_items(reference_vec.clone()); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); + + assert_eq!( + app.data.radarr_data.$data_ref.current_selection(), + &reference_vec[reference_vec.len() - 1] + ); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); + + assert_eq!( + app.data.radarr_data.$data_ref.current_selection(), + &reference_vec[0] + ); + } + }; + } + + #[macro_export] + macro_rules! test_text_box_home_end_keys { + ($handler:ident, $block:expr, $field:ident) => { + let mut app = App::default(); + app.data.radarr_data.$field = "Test".to_owned().into(); + + $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &None).handle(); + + assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 4); + + $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &None).handle(); + + assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); + }; + } + + #[macro_export] + macro_rules! test_text_box_left_right_keys { + ($handler:ident, $block:expr, $field:ident) => { + let mut app = App::default(); + app.data.radarr_data.$field = "Test".to_owned().into(); + + $handler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &$block, &None).handle(); + + assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 1); + + $handler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &$block, &None).handle(); + + assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); + }; + } + + #[macro_export] + macro_rules! test_handler_delegation { + ($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( + &DEFAULT_KEYBINDINGS.esc.key, + &mut app, + &$active_block, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &$base.into()); + }; + } +} diff --git a/src/handlers/handlers_tests.rs b/src/handlers/handlers_tests.rs new file mode 100644 index 0000000..f203227 --- /dev/null +++ b/src/handlers/handlers_tests.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::app::App; + use crate::event::Key; + use crate::handlers::{handle_clear_errors, handle_prompt_toggle}; + + #[test] + fn test_handle_clear_errors() { + let mut app = App::default(); + app.error = "test error".to_owned().into(); + + handle_clear_errors(&mut app); + + assert!(app.error.text.is_empty()); + } + + #[rstest] + fn test_handle_prompt_toggle_left_right(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + + assert!(!app.data.radarr_data.prompt_confirm); + + handle_prompt_toggle(&mut app, &key); + + assert!(app.data.radarr_data.prompt_confirm); + + handle_prompt_toggle(&mut app, &key); + + assert!(!app.data.radarr_data.prompt_confirm); + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 631a480..a674dea 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -7,6 +7,14 @@ use crate::models::{HorizontallyScrollableText, Route}; mod radarr_handlers; +#[cfg(test)] +#[path = "handlers_tests.rs"] +mod handlers_tests; + +#[cfg(test)] +#[path = "handler_test_utils.rs"] +pub mod handler_test_utils; + pub trait KeyEventHandler<'a, 'b, T: Into> { fn handle_key_event(&mut self) { let key = self.get_key(); @@ -94,454 +102,3 @@ macro_rules! handle_text_box_keys { } }; } - -#[cfg(test)] -#[macro_use] -mod test_utils { - #[macro_export] - macro_rules! simple_stateful_iterable_vec { - ($name:ident) => { - vec![ - $name { - title: "Test 1".to_owned(), - ..$name::default() - }, - $name { - title: "Test 2".to_owned(), - ..$name::default() - }, - ] - }; - ($name:ident, $title_ident:ident) => { - vec![ - $name { - title: $title_ident::from("Test 1".to_owned()), - ..$name::default() - }, - $name { - title: $title_ident::from("Test 2".to_owned()), - ..$name::default() - }, - ] - }; - ($name:ident, $title_ident:ident, $field:ident) => { - vec![ - $name { - $field: $title_ident::from("Test 1".to_owned()), - ..$name::default() - }, - $name { - $field: $title_ident::from("Test 2".to_owned()), - ..$name::default() - }, - ] - }; - } - - #[macro_export] - macro_rules! extended_stateful_iterable_vec { - ($name:ident) => { - vec![ - $name { - title: "Test 1".to_owned(), - ..$name::default() - }, - $name { - title: "Test 2".to_owned(), - ..$name::default() - }, - $name { - title: "Test 3".to_owned(), - ..$name::default() - }, - ] - }; - ($name:ident, $title_ident:ident) => { - vec![ - $name { - title: $title_ident::from("Test 1".to_owned()), - ..$name::default() - }, - $name { - title: $title_ident::from("Test 2".to_owned()), - ..$name::default() - }, - $name { - title: $title_ident::from("Test 3".to_owned()), - ..$name::default() - }, - ] - }; - ($name:ident, $title_ident:ident, $field:ident) => { - vec![ - $name { - $field: $title_ident::from("Test 1".to_owned()), - ..$name::default() - }, - $name { - $field: $title_ident::from("Test 2".to_owned()), - ..$name::default() - }, - $name { - $field: $title_ident::from("Test 3".to_owned()), - ..$name::default() - }, - ] - }; - } - - #[macro_export] - macro_rules! test_iterable_scroll { - ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { - #[rstest] - fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { - let mut app = App::default(); - app - .data - .radarr_data - .$data_ref - .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 2"); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { - #[rstest] - fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { - let mut app = App::default(); - app - .data - .radarr_data - .$data_ref - .set_items(simple_stateful_iterable_vec!($items)); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 2" - ); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 1" - ); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { - #[rstest] - fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.$data_ref.set_items($items); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 2" - ); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 1" - ); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { - #[rstest] - fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.$data_ref.set_items($items); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .$data_ref - .current_selection() - .$field - .$conversion_fn(), - "Test 2" - ); - - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .$data_ref - .current_selection() - .$field - .$conversion_fn(), - "Test 1" - ); - } - }; - } - - #[macro_export] - macro_rules! test_enum_scroll { - ($func:ident, $handler:ident, $name:ident, $data_ref:ident, $block:expr, $context:expr) => { - #[rstest] - fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { - let reference_vec = Vec::from_iter($name::iter()); - let mut app = App::default(); - app - .data - .radarr_data - .$data_ref - .set_items(reference_vec.clone()); - - if key == Key::Up { - for i in (0..reference_vec.len()).rev() { - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_eq!( - app.data.radarr_data.$data_ref.current_selection(), - &reference_vec[i] - ); - } - } else { - for i in 0..reference_vec.len() { - $handler::with(&key, &mut app, &$block, &$context).handle(); - - assert_eq!( - app.data.radarr_data.$data_ref.current_selection(), - &reference_vec[(i + 1) % reference_vec.len()] - ); - } - } - } - }; - } - - #[macro_export] - macro_rules! test_iterable_home_and_end { - ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { - #[test] - fn $func() { - let mut app = App::default(); - app.data.radarr_data.$data_ref.set_items(vec![ - "Test 1".to_owned(), - "Test 2".to_owned(), - "Test 3".to_owned(), - ]); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 3"); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 1"); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { - #[test] - fn $func() { - let mut app = App::default(); - app - .data - .radarr_data - .$data_ref - .set_items(extended_stateful_iterable_vec!($items)); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 3" - ); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 1" - ); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { - #[test] - fn $func() { - let mut app = App::default(); - app.data.radarr_data.$data_ref.set_items($items); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 3" - ); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app.data.radarr_data.$data_ref.current_selection().$field, - "Test 1" - ); - } - }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { - #[test] - fn $func() { - let mut app = App::default(); - app.data.radarr_data.$data_ref.set_items($items); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .$data_ref - .current_selection() - .$field - .$conversion_fn(), - "Test 3" - ); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .$data_ref - .current_selection() - .$field - .$conversion_fn(), - "Test 1" - ); - } - }; - } - - #[macro_export] - macro_rules! test_enum_home_and_end { - ($func:ident, $handler:ident, $name:ident, $data_ref:ident, $block:expr, $context:expr) => { - #[test] - fn $func() { - let reference_vec = Vec::from_iter($name::iter()); - let mut app = App::default(); - app - .data - .radarr_data - .$data_ref - .set_items(reference_vec.clone()); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle(); - - assert_eq!( - app.data.radarr_data.$data_ref.current_selection(), - &reference_vec[reference_vec.len() - 1] - ); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle(); - - assert_eq!( - app.data.radarr_data.$data_ref.current_selection(), - &reference_vec[0] - ); - } - }; - } - - #[macro_export] - macro_rules! test_text_box_home_end_keys { - ($handler:ident, $block:expr, $field:ident) => { - let mut app = App::default(); - app.data.radarr_data.$field = "Test".to_owned().into(); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 4); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); - }; - } - - #[macro_export] - macro_rules! test_text_box_left_right_keys { - ($handler:ident, $block:expr, $field:ident) => { - let mut app = App::default(); - app.data.radarr_data.$field = "Test".to_owned().into(); - - $handler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 1); - - $handler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); - }; - } - - #[macro_export] - macro_rules! test_handler_delegation { - ($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( - &DEFAULT_KEYBINDINGS.esc.key, - &mut app, - &$active_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &$base.into()); - }; - } -} - -#[cfg(test)] -mod tests { - use rstest::rstest; - - use crate::app::App; - use crate::event::Key; - use crate::handlers::{handle_clear_errors, handle_prompt_toggle}; - - #[test] - fn test_handle_clear_errors() { - let mut app = App::default(); - app.error = "test error".to_owned().into(); - - handle_clear_errors(&mut app); - - assert!(app.error.text.is_empty()); - } - - #[rstest] - fn test_handle_prompt_toggle_left_right(#[values(Key::Left, Key::Right)] key: Key) { - let mut app = App::default(); - - assert!(!app.data.radarr_data.prompt_confirm); - - handle_prompt_toggle(&mut app, &key); - - assert!(app.data.radarr_data.prompt_confirm); - - handle_prompt_toggle(&mut app, &key); - - assert!(!app.data.radarr_data.prompt_confirm); - } -} diff --git a/src/handlers/radarr_handlers/add_movie_handler.rs b/src/handlers/radarr_handlers/add_movie_handler.rs index 10b0cec..2770db2 100644 --- a/src/handlers/radarr_handlers/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/add_movie_handler.rs @@ -5,6 +5,10 @@ use crate::models::{BlockSelectionState, Scrollable, StatefulTable}; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; +#[cfg(test)] +#[path = "add_movie_handler_tests.rs"] +mod add_movie_handler_tests; + pub(super) struct AddMovieHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -297,731 +301,3 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, } } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - 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::add_movie_handler::AddMovieHandler; - use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::{ - AddMovieSearchResult, MinimumAvailability, Monitor, RootFolder, - }; - use crate::models::HorizontallyScrollableText; - - mod test_handle_scroll_up_and_down { - use pretty_assertions::assert_eq; - use rstest::rstest; - use strum::IntoEnumIterator; - - use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; - use crate::models::BlockSelectionState; - use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; - - use super::*; - - test_iterable_scroll!( - test_add_movie_search_results_scroll, - AddMovieHandler, - add_searched_movies, - simple_stateful_iterable_vec!(AddMovieSearchResult, HorizontallyScrollableText), - ActiveRadarrBlock::AddMovieSearchResults, - None, - title, - to_string - ); - - test_enum_scroll!( - test_add_movie_select_monitor_scroll, - AddMovieHandler, - Monitor, - monitor_list, - ActiveRadarrBlock::AddMovieSelectMonitor, - None - ); - - test_enum_scroll!( - test_add_movie_select_minimum_availability_scroll, - AddMovieHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - None - ); - - test_iterable_scroll!( - test_add_movie_select_quality_profile_scroll, - AddMovieHandler, - quality_profile_list, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - None - ); - - test_iterable_scroll!( - test_add_movie_select_root_folder_scroll, - AddMovieHandler, - root_folder_list, - simple_stateful_iterable_vec!(RootFolder, String, path), - ActiveRadarrBlock::AddMovieSelectRootFolder, - None, - path - ); - - #[rstest] - fn test_add_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.next(); - - AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); - - if key == Key::Up { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::AddMovieSelectRootFolder - ); - } else { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::AddMovieSelectMinimumAvailability - ); - } - } - } - - mod test_handle_home_end { - use strum::IntoEnumIterator; - - use crate::{ - extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, - test_text_box_home_end_keys, - }; - - use super::*; - - test_iterable_home_and_end!( - test_add_movie_search_results_home_end, - AddMovieHandler, - add_searched_movies, - extended_stateful_iterable_vec!(AddMovieSearchResult, HorizontallyScrollableText), - ActiveRadarrBlock::AddMovieSearchResults, - None, - title, - to_string - ); - - test_enum_home_and_end!( - test_add_movie_select_monitor_home_end, - AddMovieHandler, - Monitor, - monitor_list, - ActiveRadarrBlock::AddMovieSelectMonitor, - None - ); - - test_enum_home_and_end!( - test_add_movie_select_minimum_availability_home_end, - AddMovieHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - None - ); - - test_iterable_home_and_end!( - test_add_movie_select_quality_profile_home_end, - AddMovieHandler, - quality_profile_list, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - None - ); - - test_iterable_home_and_end!( - test_add_movie_select_root_folder_home_end, - AddMovieHandler, - root_folder_list, - extended_stateful_iterable_vec!(RootFolder, String, path), - ActiveRadarrBlock::AddMovieSelectRootFolder, - None, - path - ); - - #[test] - fn test_add_movie_search_input_home_end_keys() { - test_text_box_home_end_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieSearchInput, - search - ); - } - - #[test] - fn test_add_movie_tags_input_home_end_keys() { - test_text_box_home_end_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieTagsInput, - edit_tags - ); - } - } - - mod test_handle_left_right_action { - use rstest::rstest; - - use crate::test_text_box_left_right_keys; - - use super::*; - - #[rstest] - fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { - let mut app = App::default(); - - AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[test] - fn test_add_movie_search_input_left_right_keys() { - test_text_box_left_right_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieSearchInput, - search - ); - } - - #[test] - fn test_add_movie_tags_input_left_right_keys() { - test_text_box_left_right_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieTagsInput, - edit_tags - ); - } - } - - mod test_handle_submit { - use bimap::BiMap; - use pretty_assertions::{assert_eq, assert_str_eq}; - use rstest::rstest; - - use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; - use crate::models::radarr_models::Movie; - use crate::models::BlockSelectionState; - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_add_movie_search_input_submit() { - let mut app = App::default(); - app.should_ignore_quit_key = true; - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieSearchInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchResults.into() - ); - } - - #[test] - fn test_add_movie_search_results_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .add_searched_movies - .set_items(vec![AddMovieSearchResult::default()]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieSearchResults, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMoviePrompt.into() - ); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::AddMovieSelectRootFolder - ); - assert!(!app.data.radarr_data.monitor_list.items.is_empty()); - assert!(!app - .data - .radarr_data - .minimum_availability_list - .items - .is_empty()); - assert!(!app.data.radarr_data.quality_profile_list.items.is_empty()); - assert_str_eq!( - app - .data - .radarr_data - .quality_profile_list - .current_selection(), - "A - Test 1" - ); - } - - #[test] - fn test_add_movie_search_results_submit_does_nothing_on_empty_table() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieSearchResults, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchResults.into() - ); - } - - #[test] - fn test_add_movie_search_results_submit_movie_already_in_library() { - let mut app = App::default(); - app - .data - .radarr_data - .add_searched_movies - .set_items(vec![AddMovieSearchResult::default()]); - app - .data - .radarr_data - .movies - .set_items(vec![Movie::default()]); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieSearchResults, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieAlreadyInLibrary.into() - ); - } - - #[test] - fn test_add_movie_prompt_prompt_decline_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[test] - fn test_add_movie_confirm_prompt_prompt_confirmation_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::AddMovie) - ); - } - - #[rstest] - #[case(ActiveRadarrBlock::AddMovieSelectRootFolder, 0)] - #[case(ActiveRadarrBlock::AddMovieSelectMonitor, 1)] - #[case(ActiveRadarrBlock::AddMovieSelectMinimumAvailability, 2)] - #[case(ActiveRadarrBlock::AddMovieSelectQualityProfile, 3)] - #[case(ActiveRadarrBlock::AddMovieTagsInput, 4)] - fn test_add_movie_prompt_selected_block_submit( - #[case] selected_block: ActiveRadarrBlock, - #[case] index: usize, - ) { - let mut app = App::default(); - app.push_navigation_stack( - ( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::CollectionDetails), - ) - .into(), - ); - app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.set_index(index); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::AddMoviePrompt, - &Some(ActiveRadarrBlock::CollectionDetails), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &(selected_block, Some(ActiveRadarrBlock::CollectionDetails)).into() - ); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - - if selected_block == ActiveRadarrBlock::AddMovieTagsInput { - assert!(app.should_ignore_quit_key); - } - } - - #[rstest] - fn test_add_movie_prompt_selecting_preferences_blocks_submit( - #[values( - ActiveRadarrBlock::AddMovieSelectMonitor, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieTagsInput - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.push_navigation_stack(active_radarr_block.into()); - - AddMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &active_radarr_block, - &Some(ActiveRadarrBlock::CollectionDetails), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMoviePrompt.into() - ); - - if active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput { - assert!(!app.should_ignore_quit_key); - } - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::radarr_test_utils::create_test_radarr_data; - use crate::{ - assert_edit_media_reset, assert_preferences_selections_reset, assert_search_reset, - simple_stateful_iterable_vec, - }; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[test] - fn test_add_movie_search_input_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieSearchInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_search_reset!(app.data.radarr_data); - } - - #[test] - fn test_add_movie_input_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieTagsInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMoviePrompt.into() - ); - } - - #[rstest] - fn test_add_movie_search_results_esc( - #[values( - ActiveRadarrBlock::AddMovieSearchResults, - ActiveRadarrBlock::AddMovieEmptySearchResults - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); - app.push_navigation_stack(active_radarr_block.into()); - app - .data - .radarr_data - .add_searched_movies - .set_items(simple_stateful_iterable_vec!( - AddMovieSearchResult, - HorizontallyScrollableText - )); - - AddMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchInput.into() - ); - assert!(app.data.radarr_data.add_searched_movies.items.is_empty()); - assert!(app.should_ignore_quit_key); - } - - #[test] - fn test_add_movie_already_in_library_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieAlreadyInLibrary.into()); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieAlreadyInLibrary, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchResults.into() - ); - } - - #[test] - fn test_add_movie_prompt_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddMoviePrompt, - &None, - ) - .handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMovieSearchResults.into() - ); - assert_preferences_selections_reset!(app.data.radarr_data); - assert_edit_media_reset!(app.data.radarr_data); - } - - #[test] - fn test_add_movie_tags_input_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::AddMovieTagsInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AddMoviePrompt.into() - ); - } - - #[rstest] - fn test_selecting_preferences_blocks_esc( - #[values( - ActiveRadarrBlock::AddMovieSelectMonitor, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - ActiveRadarrBlock::AddMovieSelectRootFolder - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack( - ( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::CollectionDetails), - ) - .into(), - ); - app.push_navigation_stack( - ( - active_radarr_block, - Some(ActiveRadarrBlock::CollectionDetails), - ) - .into(), - ); - - AddMovieHandler::with( - &ESC_KEY, - &mut app, - &active_radarr_block, - &Some(ActiveRadarrBlock::CollectionDetails), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::CollectionDetails), - ) - .into() - ); - } - } - - mod test_handle_key_char { - use super::*; - - #[test] - fn test_add_movie_search_input_backspace() { - let mut app = App::default(); - app.data.radarr_data.search = "Test".to_owned().into(); - - AddMovieHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::AddMovieSearchInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.search.text, "Tes"); - } - - #[test] - fn test_add_movie_tags_input_backspace() { - let mut app = App::default(); - app.data.radarr_data.edit_tags = "Test".to_owned().into(); - - AddMovieHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::AddMovieTagsInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_tags.text, "Tes"); - } - - #[test] - fn test_add_movie_search_input_char_key() { - let mut app = App::default(); - - AddMovieHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::AddMovieSearchInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.search.text, "h"); - } - - #[test] - fn test_add_movie_tags_input_char_key() { - let mut app = App::default(); - - AddMovieHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::AddMovieTagsInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); - } - } -} diff --git a/src/handlers/radarr_handlers/add_movie_handler_tests.rs b/src/handlers/radarr_handlers/add_movie_handler_tests.rs new file mode 100644 index 0000000..0ce22e8 --- /dev/null +++ b/src/handlers/radarr_handlers/add_movie_handler_tests.rs @@ -0,0 +1,727 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + 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::add_movie_handler::AddMovieHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::{ + AddMovieSearchResult, MinimumAvailability, Monitor, RootFolder, + }; + use crate::models::HorizontallyScrollableText; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_add_movie_search_results_scroll, + AddMovieHandler, + add_searched_movies, + simple_stateful_iterable_vec!(AddMovieSearchResult, HorizontallyScrollableText), + ActiveRadarrBlock::AddMovieSearchResults, + None, + title, + to_string + ); + + test_enum_scroll!( + test_add_movie_select_monitor_scroll, + AddMovieHandler, + Monitor, + monitor_list, + ActiveRadarrBlock::AddMovieSelectMonitor, + None + ); + + test_enum_scroll!( + test_add_movie_select_minimum_availability_scroll, + AddMovieHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + None + ); + + test_iterable_scroll!( + test_add_movie_select_quality_profile_scroll, + AddMovieHandler, + quality_profile_list, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + None + ); + + test_iterable_scroll!( + test_add_movie_select_root_folder_scroll, + AddMovieHandler, + root_folder_list, + simple_stateful_iterable_vec!(RootFolder, String, path), + ActiveRadarrBlock::AddMovieSelectRootFolder, + None, + path + ); + + #[rstest] + fn test_add_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); + + AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); + + if key == Key::Up { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder + ); + } else { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectMinimumAvailability + ); + } + } + } + + mod test_handle_home_end { + use strum::IntoEnumIterator; + + use crate::{ + extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, + test_text_box_home_end_keys, + }; + + use super::*; + + test_iterable_home_and_end!( + test_add_movie_search_results_home_end, + AddMovieHandler, + add_searched_movies, + extended_stateful_iterable_vec!(AddMovieSearchResult, HorizontallyScrollableText), + ActiveRadarrBlock::AddMovieSearchResults, + None, + title, + to_string + ); + + test_enum_home_and_end!( + test_add_movie_select_monitor_home_end, + AddMovieHandler, + Monitor, + monitor_list, + ActiveRadarrBlock::AddMovieSelectMonitor, + None + ); + + test_enum_home_and_end!( + test_add_movie_select_minimum_availability_home_end, + AddMovieHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + None + ); + + test_iterable_home_and_end!( + test_add_movie_select_quality_profile_home_end, + AddMovieHandler, + quality_profile_list, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + None + ); + + test_iterable_home_and_end!( + test_add_movie_select_root_folder_home_end, + AddMovieHandler, + root_folder_list, + extended_stateful_iterable_vec!(RootFolder, String, path), + ActiveRadarrBlock::AddMovieSelectRootFolder, + None, + path + ); + + #[test] + fn test_add_movie_search_input_home_end_keys() { + test_text_box_home_end_keys!( + AddMovieHandler, + ActiveRadarrBlock::AddMovieSearchInput, + search + ); + } + + #[test] + fn test_add_movie_tags_input_home_end_keys() { + test_text_box_home_end_keys!( + AddMovieHandler, + ActiveRadarrBlock::AddMovieTagsInput, + edit_tags + ); + } + } + + mod test_handle_left_right_action { + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + + AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_add_movie_search_input_left_right_keys() { + test_text_box_left_right_keys!( + AddMovieHandler, + ActiveRadarrBlock::AddMovieSearchInput, + search + ); + } + + #[test] + fn test_add_movie_tags_input_left_right_keys() { + test_text_box_left_right_keys!( + AddMovieHandler, + ActiveRadarrBlock::AddMovieTagsInput, + edit_tags + ); + } + } + + mod test_handle_submit { + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; + use crate::models::radarr_models::Movie; + use crate::models::BlockSelectionState; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_add_movie_search_input_submit() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchResults.into() + ); + } + + #[test] + fn test_add_movie_search_results_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .add_searched_movies + .set_items(vec![AddMovieSearchResult::default()]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieSearchResults, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMoviePrompt.into() + ); + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder + ); + assert!(!app.data.radarr_data.monitor_list.items.is_empty()); + assert!(!app + .data + .radarr_data + .minimum_availability_list + .items + .is_empty()); + assert!(!app.data.radarr_data.quality_profile_list.items.is_empty()); + assert_str_eq!( + app + .data + .radarr_data + .quality_profile_list + .current_selection(), + "A - Test 1" + ); + } + + #[test] + fn test_add_movie_search_results_submit_does_nothing_on_empty_table() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieSearchResults, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchResults.into() + ); + } + + #[test] + fn test_add_movie_search_results_submit_movie_already_in_library() { + let mut app = App::default(); + app + .data + .radarr_data + .add_searched_movies + .set_items(vec![AddMovieSearchResult::default()]); + app + .data + .radarr_data + .movies + .set_items(vec![Movie::default()]); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieSearchResults, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieAlreadyInLibrary.into() + ); + } + + #[test] + fn test_add_movie_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[test] + fn test_add_movie_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::AddMovie) + ); + } + + #[rstest] + #[case(ActiveRadarrBlock::AddMovieSelectRootFolder, 0)] + #[case(ActiveRadarrBlock::AddMovieSelectMonitor, 1)] + #[case(ActiveRadarrBlock::AddMovieSelectMinimumAvailability, 2)] + #[case(ActiveRadarrBlock::AddMovieSelectQualityProfile, 3)] + #[case(ActiveRadarrBlock::AddMovieTagsInput, 4)] + fn test_add_movie_prompt_selected_block_submit( + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack( + ( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::CollectionDetails), + ) + .into(), + ); + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddMoviePrompt, + &Some(ActiveRadarrBlock::CollectionDetails), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &(selected_block, Some(ActiveRadarrBlock::CollectionDetails)).into() + ); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + + if selected_block == ActiveRadarrBlock::AddMovieTagsInput { + assert!(app.should_ignore_quit_key); + } + } + + #[rstest] + fn test_add_movie_prompt_selecting_preferences_blocks_submit( + #[values( + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + app.push_navigation_stack(active_radarr_block.into()); + + AddMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &active_radarr_block, + &Some(ActiveRadarrBlock::CollectionDetails), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMoviePrompt.into() + ); + + if active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput { + assert!(!app.should_ignore_quit_key); + } + } + } + + 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_edit_media_reset, assert_preferences_selections_reset, assert_search_reset, + simple_stateful_iterable_vec, + }; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_add_movie_search_input_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_search_reset!(app.data.radarr_data); + } + + #[test] + fn test_add_movie_input_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieTagsInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMoviePrompt.into() + ); + } + + #[rstest] + fn test_add_movie_search_results_esc( + #[values( + ActiveRadarrBlock::AddMovieSearchResults, + ActiveRadarrBlock::AddMovieEmptySearchResults + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); + app.push_navigation_stack(active_radarr_block.into()); + app + .data + .radarr_data + .add_searched_movies + .set_items(simple_stateful_iterable_vec!( + AddMovieSearchResult, + HorizontallyScrollableText + )); + + AddMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchInput.into() + ); + assert!(app.data.radarr_data.add_searched_movies.items.is_empty()); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_add_movie_already_in_library_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieAlreadyInLibrary.into()); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieAlreadyInLibrary, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchResults.into() + ); + } + + #[test] + fn test_add_movie_prompt_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddMoviePrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMovieSearchResults.into() + ); + assert_preferences_selections_reset!(app.data.radarr_data); + assert_edit_media_reset!(app.data.radarr_data); + } + + #[test] + fn test_add_movie_tags_input_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddMovieTagsInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddMoviePrompt.into() + ); + } + + #[rstest] + fn test_selecting_preferences_blocks_esc( + #[values( + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieSelectRootFolder + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack( + ( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::CollectionDetails), + ) + .into(), + ); + app.push_navigation_stack( + ( + active_radarr_block, + Some(ActiveRadarrBlock::CollectionDetails), + ) + .into(), + ); + + AddMovieHandler::with( + &ESC_KEY, + &mut app, + &active_radarr_block, + &Some(ActiveRadarrBlock::CollectionDetails), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::CollectionDetails), + ) + .into() + ); + } + } + + mod test_handle_key_char { + use super::*; + + #[test] + fn test_add_movie_search_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.search = "Test".to_owned().into(); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + } + + #[test] + fn test_add_movie_tags_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.edit_tags = "Test".to_owned().into(); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::AddMovieTagsInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_tags.text, "Tes"); + } + + #[test] + fn test_add_movie_search_input_char_key() { + let mut app = App::default(); + + AddMovieHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.search.text, "h"); + } + + #[test] + fn test_add_movie_tags_input_char_key() { + let mut app = App::default(); + + AddMovieHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::AddMovieTagsInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); + } + } +} diff --git a/src/handlers/radarr_handlers/collection_details_handler.rs b/src/handlers/radarr_handlers/collection_details_handler.rs index b1c3aaf..fe04ed3 100644 --- a/src/handlers/radarr_handlers/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collection_details_handler.rs @@ -7,6 +7,10 @@ use crate::event::Key; use crate::handlers::KeyEventHandler; use crate::models::{BlockSelectionState, Scrollable}; +#[cfg(test)] +#[path = "collection_details_handler_tests.rs"] +mod collection_details_handler_tests; + pub(super) struct CollectionDetailsHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -132,230 +136,3 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan } } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - 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::collection_details_handler::CollectionDetailsHandler; - use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::CollectionMovie; - use crate::models::HorizontallyScrollableText; - - 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_collection_details_scroll, - CollectionDetailsHandler, - collection_movies, - simple_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), - ActiveRadarrBlock::CollectionDetails, - None, - title, - to_string - ); - } - - mod test_handle_home_end { - use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; - - use super::*; - - test_iterable_home_and_end!( - test_collection_details_home_end, - CollectionDetailsHandler, - collection_movies, - extended_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), - ActiveRadarrBlock::CollectionDetails, - None, - title, - to_string - ); - } - - mod test_handle_submit { - use bimap::BiMap; - use pretty_assertions::assert_eq; - - use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; - use crate::models::radarr_models::Movie; - use crate::models::BlockSelectionState; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_collection_details_submit() { - let mut app = App::default(); - app - .data - .radarr_data - .collection_movies - .set_items(vec![CollectionMovie::default()]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); - app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); - - CollectionDetailsHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::CollectionDetails, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &( - ActiveRadarrBlock::AddMoviePrompt, - Some(ActiveRadarrBlock::CollectionDetails) - ) - .into() - ); - assert!(!app.data.radarr_data.monitor_list.items.is_empty()); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::AddMovieSelectRootFolder - ); - assert!(!app - .data - .radarr_data - .minimum_availability_list - .items - .is_empty()); - assert!(!app.data.radarr_data.quality_profile_list.items.is_empty()); - assert_str_eq!( - app - .data - .radarr_data - .quality_profile_list - .current_selection(), - "A - Test 1" - ); - } - - #[test] - fn test_collection_details_submit_movie_already_in_library() { - let mut app = App::default(); - app - .data - .radarr_data - .collection_movies - .set_items(vec![CollectionMovie::default()]); - app - .data - .radarr_data - .movies - .set_items(vec![Movie::default()]); - - CollectionDetailsHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::CollectionDetails, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::ViewMovieOverview.into() - ); - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[test] - fn test_esc_collection_details() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); - app - .data - .radarr_data - .collection_movies - .set_items(vec![CollectionMovie::default()]); - - CollectionDetailsHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::CollectionDetails, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - assert!(app.data.radarr_data.collection_movies.items.is_empty()); - } - - #[test] - fn test_esc_view_movie_overview() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); - app.push_navigation_stack(ActiveRadarrBlock::ViewMovieOverview.into()); - - CollectionDetailsHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::ViewMovieOverview, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::CollectionDetails.into() - ); - } - } - - 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::create_test_radarr_data; - 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; - - use super::*; - - #[test] - fn test_edit_key() { - test_edit_collection_key!( - CollectionDetailsHandler, - ActiveRadarrBlock::CollectionDetails, - ActiveRadarrBlock::CollectionDetails - ); - } - } -} diff --git a/src/handlers/radarr_handlers/collection_details_handler_tests.rs b/src/handlers/radarr_handlers/collection_details_handler_tests.rs new file mode 100644 index 0000000..7d1a9b4 --- /dev/null +++ b/src/handlers/radarr_handlers/collection_details_handler_tests.rs @@ -0,0 +1,226 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + 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::collection_details_handler::CollectionDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::CollectionMovie; + use crate::models::HorizontallyScrollableText; + + 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_collection_details_scroll, + CollectionDetailsHandler, + collection_movies, + simple_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), + ActiveRadarrBlock::CollectionDetails, + None, + title, + to_string + ); + } + + mod test_handle_home_end { + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_collection_details_home_end, + CollectionDetailsHandler, + collection_movies, + extended_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), + ActiveRadarrBlock::CollectionDetails, + None, + title, + to_string + ); + } + + mod test_handle_submit { + use bimap::BiMap; + use pretty_assertions::assert_eq; + + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; + use crate::models::radarr_models::Movie; + use crate::models::BlockSelectionState; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_collection_details_submit() { + let mut app = App::default(); + app + .data + .radarr_data + .collection_movies + .set_items(vec![CollectionMovie::default()]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); + + CollectionDetailsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::CollectionDetails, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &( + ActiveRadarrBlock::AddMoviePrompt, + Some(ActiveRadarrBlock::CollectionDetails) + ) + .into() + ); + assert!(!app.data.radarr_data.monitor_list.items.is_empty()); + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder + ); + assert!(!app + .data + .radarr_data + .minimum_availability_list + .items + .is_empty()); + assert!(!app.data.radarr_data.quality_profile_list.items.is_empty()); + assert_str_eq!( + app + .data + .radarr_data + .quality_profile_list + .current_selection(), + "A - Test 1" + ); + } + + #[test] + fn test_collection_details_submit_movie_already_in_library() { + let mut app = App::default(); + app + .data + .radarr_data + .collection_movies + .set_items(vec![CollectionMovie::default()]); + app + .data + .radarr_data + .movies + .set_items(vec![Movie::default()]); + + CollectionDetailsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::CollectionDetails, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::ViewMovieOverview.into() + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_esc_collection_details() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); + app + .data + .radarr_data + .collection_movies + .set_items(vec![CollectionMovie::default()]); + + CollectionDetailsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::CollectionDetails, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert!(app.data.radarr_data.collection_movies.items.is_empty()); + } + + #[test] + fn test_esc_view_movie_overview() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); + app.push_navigation_stack(ActiveRadarrBlock::ViewMovieOverview.into()); + + CollectionDetailsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::ViewMovieOverview, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::CollectionDetails.into() + ); + } + } + + 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::{Collection, MinimumAvailability}; + use crate::models::BlockSelectionState; + use crate::models::HorizontallyScrollableText; + use crate::models::StatefulTable; + use crate::test_edit_collection_key; + + use super::*; + + #[test] + fn test_edit_key() { + test_edit_collection_key!( + CollectionDetailsHandler, + ActiveRadarrBlock::CollectionDetails, + ActiveRadarrBlock::CollectionDetails + ); + } + } +} diff --git a/src/handlers/radarr_handlers/delete_movie_handler.rs b/src/handlers/radarr_handlers/delete_movie_handler.rs index 1bf2bc0..c38b2b2 100644 --- a/src/handlers/radarr_handlers/delete_movie_handler.rs +++ b/src/handlers/radarr_handlers/delete_movie_handler.rs @@ -4,6 +4,10 @@ use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::network::radarr_network::RadarrEvent; +#[cfg(test)] +#[path = "delete_movie_handler_tests.rs"] +mod delete_movie_handler_tests; + pub(super) struct DeleteMovieHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -90,204 +94,3 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<' fn handle_char_key_event(&mut self) {} } - -#[cfg(test)] -mod tests { - 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::delete_movie_handler::DeleteMovieHandler; - use crate::handlers::KeyEventHandler; - - mod test_handle_scroll_up_and_down { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; - use crate::models::BlockSelectionState; - - use super::*; - - #[rstest] - fn test_delete_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.next(); - - DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) - .handle(); - - if key == Key::Up { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::DeleteMovieToggleDeleteFile - ); - } else { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::DeleteMovieConfirmPrompt - ); - } - } - } - - mod test_handle_left_right_action { - use rstest::rstest; - - use super::*; - - #[rstest] - fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { - let mut app = App::default(); - - DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) - .handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) - .handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - } - - mod test_handle_submit { - use pretty_assertions::assert_eq; - - use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; - use crate::models::BlockSelectionState; - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_delete_movie_prompt_prompt_decline_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); - app.data.radarr_data.delete_movie_files = true; - app.data.radarr_data.add_list_exclusion = true; - - DeleteMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::DeleteMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.data.radarr_data.delete_movie_files); - assert!(!app.data.radarr_data.add_list_exclusion); - } - - #[test] - fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.delete_movie_files = true; - app.data.radarr_data.add_list_exclusion = true; - app.data.radarr_data.selected_block = - BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); - - DeleteMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::DeleteMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteMovie) - ); - assert!(app.should_refresh); - assert!(app.data.radarr_data.prompt_confirm); - assert!(app.data.radarr_data.delete_movie_files); - assert!(app.data.radarr_data.add_list_exclusion); - } - - #[test] - fn test_delete_movie_toggle_delete_files_submit() { - let current_route = ActiveRadarrBlock::DeleteMoviePrompt.into(); - let mut app = App::default(); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); - app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - - DeleteMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::DeleteMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.delete_movie_files, true); - - DeleteMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::DeleteMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.delete_movie_files, false); - } - } - - mod test_handle_esc { - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[test] - fn test_delete_movie_prompt_esc() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.delete_movie_files = true; - app.data.radarr_data.add_list_exclusion = true; - - DeleteMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::DeleteMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.data.radarr_data.delete_movie_files); - assert!(!app.data.radarr_data.add_list_exclusion); - } - } -} diff --git a/src/handlers/radarr_handlers/delete_movie_handler_tests.rs b/src/handlers/radarr_handlers/delete_movie_handler_tests.rs new file mode 100644 index 0000000..132447c --- /dev/null +++ b/src/handlers/radarr_handlers/delete_movie_handler_tests.rs @@ -0,0 +1,200 @@ +#[cfg(test)] +mod tests { + 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::delete_movie_handler::DeleteMovieHandler; + use crate::handlers::KeyEventHandler; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + + use super::*; + + #[rstest] + fn test_delete_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); + + DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) + .handle(); + + if key == Key::Up { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieToggleDeleteFile + ); + } else { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieConfirmPrompt + ); + } + } + } + + mod test_handle_left_right_action { + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + + DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_delete_movie_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); + app.data.radarr_data.delete_movie_files = true; + app.data.radarr_data.add_list_exclusion = true; + + DeleteMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.data.radarr_data.delete_movie_files); + assert!(!app.data.radarr_data.add_list_exclusion); + } + + #[test] + fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.delete_movie_files = true; + app.data.radarr_data.add_list_exclusion = true; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); + + DeleteMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::DeleteMovie) + ); + assert!(app.should_refresh); + assert!(app.data.radarr_data.prompt_confirm); + assert!(app.data.radarr_data.delete_movie_files); + assert!(app.data.radarr_data.add_list_exclusion); + } + + #[test] + fn test_delete_movie_toggle_delete_files_submit() { + let current_route = ActiveRadarrBlock::DeleteMoviePrompt.into(); + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + + DeleteMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.delete_movie_files, true); + + DeleteMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::DeleteMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.delete_movie_files, false); + } + } + + mod test_handle_esc { + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_delete_movie_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.delete_movie_files = true; + app.data.radarr_data.add_list_exclusion = true; + + DeleteMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::DeleteMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.data.radarr_data.delete_movie_files); + assert!(!app.data.radarr_data.add_list_exclusion); + } + } +} diff --git a/src/handlers/radarr_handlers/edit_collection_handler.rs b/src/handlers/radarr_handlers/edit_collection_handler.rs index 1482e0e..6c08281 100644 --- a/src/handlers/radarr_handlers/edit_collection_handler.rs +++ b/src/handlers/radarr_handlers/edit_collection_handler.rs @@ -7,6 +7,10 @@ use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; +#[cfg(test)] +#[path = "edit_collection_handler_tests.rs"] +mod edit_collection_handler_tests; + pub(super) struct EditCollectionHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -204,507 +208,3 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle } } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - 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::edit_collection_handler::EditCollectionHandler; - use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::MinimumAvailability; - - mod test_handle_scroll_up_and_down { - use pretty_assertions::assert_eq; - use rstest::rstest; - use strum::IntoEnumIterator; - - use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; - use crate::models::BlockSelectionState; - use crate::{test_enum_scroll, test_iterable_scroll}; - - use super::*; - - test_enum_scroll!( - test_edit_collection_select_minimum_availability_scroll, - EditCollectionHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, - None - ); - - test_iterable_scroll!( - test_edit_collection_select_quality_profile_scroll, - EditCollectionHandler, - quality_profile_list, - ActiveRadarrBlock::EditCollectionSelectQualityProfile, - None - ); - - #[rstest] - fn test_edit_collection_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.next(); - - EditCollectionHandler::with( - &key, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - if key == Key::Up { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditCollectionToggleMonitored - ); - } else { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditCollectionSelectQualityProfile - ); - } - } - } - - mod test_handle_home_end { - use strum::IntoEnumIterator; - - use crate::{test_enum_home_and_end, test_iterable_home_and_end, test_text_box_home_end_keys}; - - use super::*; - - test_enum_home_and_end!( - test_edit_collection_select_minimum_availability_home_end, - EditCollectionHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, - None - ); - - test_iterable_home_and_end!( - test_edit_collection_select_quality_profile_scroll, - EditCollectionHandler, - quality_profile_list, - ActiveRadarrBlock::EditCollectionSelectQualityProfile, - None - ); - - #[test] - fn test_edit_collection_root_folder_path_input_home_end_keys() { - test_text_box_home_end_keys!( - EditCollectionHandler, - ActiveRadarrBlock::EditCollectionRootFolderPathInput, - edit_path - ); - } - } - - mod test_handle_left_right_action { - use rstest::rstest; - - use crate::test_text_box_left_right_keys; - - use super::*; - - #[rstest] - fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { - let mut app = App::default(); - - EditCollectionHandler::with( - &key, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - EditCollectionHandler::with( - &key, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[test] - fn test_edit_collection_root_folder_path_input_left_right_keys() { - test_text_box_left_right_keys!( - EditCollectionHandler, - ActiveRadarrBlock::EditCollectionRootFolderPathInput, - edit_path - ); - } - } - - mod test_handle_submit { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; - use crate::models::{BlockSelectionState, Route}; - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_edit_collection_root_folder_path_input_submit() { - let mut app = App::default(); - app.should_ignore_quit_key = true; - app.data.radarr_data.edit_path = "Test Path".to_owned().into(); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionRootFolderPathInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert!(!app.data.radarr_data.edit_path.text.is_empty()); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditCollectionPrompt.into() - ); - } - - #[test] - fn test_edit_collection_prompt_prompt_decline_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[test] - fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditCollection) - ); - assert!(app.should_refresh); - } - - #[test] - fn test_edit_collection_toggle_monitored_submit() { - let current_route = Route::from(( - ActiveRadarrBlock::EditCollectionPrompt, - Some(ActiveRadarrBlock::Collections), - )); - let mut app = App::default(); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app.push_navigation_stack(current_route); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_monitored, Some(false)); - } - - #[test] - fn test_edit_collection_toggle_search_on_add_submit() { - let current_route = Route::from(( - ActiveRadarrBlock::EditCollectionPrompt, - Some(ActiveRadarrBlock::Collections), - )); - let mut app = App::default(); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 2); - app.push_navigation_stack(current_route); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_search_on_add, Some(true)); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_search_on_add, Some(false)); - } - - #[rstest] - #[case(ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, 1)] - #[case(ActiveRadarrBlock::EditCollectionSelectQualityProfile, 2)] - #[case(ActiveRadarrBlock::EditCollectionRootFolderPathInput, 3)] - fn test_edit_collection_prompt_selected_block_submit( - #[case] selected_block: ActiveRadarrBlock, - #[case] index: usize, - ) { - let mut app = App::default(); - app.push_navigation_stack( - ( - ActiveRadarrBlock::EditCollectionPrompt, - Some(ActiveRadarrBlock::Collections), - ) - .into(), - ); - app.data.radarr_data.selected_block = - BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.set_index(index); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &(selected_block, Some(ActiveRadarrBlock::Collections)).into() - ); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - - if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { - assert!(app.should_ignore_quit_key); - } - } - - #[rstest] - fn test_edit_collection_prompt_selecting_preferences_blocks_submit( - #[values( - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, - ActiveRadarrBlock::EditCollectionSelectQualityProfile, - ActiveRadarrBlock::EditCollectionRootFolderPathInput - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.push_navigation_stack(active_radarr_block.into()); - - EditCollectionHandler::with( - &SUBMIT_KEY, - &mut app, - &active_radarr_block, - &Some(ActiveRadarrBlock::Collections), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditCollectionPrompt.into() - ); - - if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { - assert!(!app.should_ignore_quit_key); - } - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::radarr_test_utils::create_test_radarr_data; - use crate::{assert_edit_media_reset, assert_preferences_selections_reset}; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[test] - fn test_edit_collection_root_folder_path_input_esc() { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); - - EditCollectionHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionRootFolderPathInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditCollectionPrompt.into() - ); - } - - #[test] - fn test_edit_collection_prompt_esc() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.data.radarr_data = create_test_radarr_data(); - - EditCollectionHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::EditCollectionPrompt, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - let radarr_data = &app.data.radarr_data; - - assert_preferences_selections_reset!(radarr_data); - assert_edit_media_reset!(radarr_data); - assert!(!radarr_data.prompt_confirm); - } - - #[rstest] - fn test_edit_collection_esc( - #[values( - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, - ActiveRadarrBlock::EditCollectionSelectQualityProfile - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(active_radarr_block.into()); - - EditCollectionHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::Collections.into() - ); - } - } - - mod test_handle_key_char { - use super::*; - - #[test] - fn test_edit_collection_root_folder_path_input_backspace() { - let mut app = App::default(); - app.data.radarr_data.edit_path = "Test".to_owned().into(); - - EditCollectionHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::EditCollectionRootFolderPathInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "Tes"); - } - - #[test] - fn test_edit_collection_root_folder_path_input_char_key() { - let mut app = App::default(); - - EditCollectionHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::EditCollectionRootFolderPathInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); - } - } -} diff --git a/src/handlers/radarr_handlers/edit_collection_handler_tests.rs b/src/handlers/radarr_handlers/edit_collection_handler_tests.rs new file mode 100644 index 0000000..3d1f3d7 --- /dev/null +++ b/src/handlers/radarr_handlers/edit_collection_handler_tests.rs @@ -0,0 +1,503 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + 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::edit_collection_handler::EditCollectionHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::MinimumAvailability; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use crate::{test_enum_scroll, test_iterable_scroll}; + + use super::*; + + test_enum_scroll!( + test_edit_collection_select_minimum_availability_scroll, + EditCollectionHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + None + ); + + test_iterable_scroll!( + test_edit_collection_select_quality_profile_scroll, + EditCollectionHandler, + quality_profile_list, + ActiveRadarrBlock::EditCollectionSelectQualityProfile, + None + ); + + #[rstest] + fn test_edit_collection_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); + + EditCollectionHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + if key == Key::Up { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionToggleMonitored + ); + } else { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionSelectQualityProfile + ); + } + } + } + + mod test_handle_home_end { + use strum::IntoEnumIterator; + + use crate::{test_enum_home_and_end, test_iterable_home_and_end, test_text_box_home_end_keys}; + + use super::*; + + test_enum_home_and_end!( + test_edit_collection_select_minimum_availability_home_end, + EditCollectionHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + None + ); + + test_iterable_home_and_end!( + test_edit_collection_select_quality_profile_scroll, + EditCollectionHandler, + quality_profile_list, + ActiveRadarrBlock::EditCollectionSelectQualityProfile, + None + ); + + #[test] + fn test_edit_collection_root_folder_path_input_home_end_keys() { + test_text_box_home_end_keys!( + EditCollectionHandler, + ActiveRadarrBlock::EditCollectionRootFolderPathInput, + edit_path + ); + } + } + + mod test_handle_left_right_action { + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + + EditCollectionHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + EditCollectionHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_edit_collection_root_folder_path_input_left_right_keys() { + test_text_box_left_right_keys!( + EditCollectionHandler, + ActiveRadarrBlock::EditCollectionRootFolderPathInput, + edit_path + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::models::{BlockSelectionState, Route}; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_edit_collection_root_folder_path_input_submit() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.data.radarr_data.edit_path = "Test Path".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionRootFolderPathInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app.data.radarr_data.edit_path.text.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditCollectionPrompt.into() + ); + } + + #[test] + fn test_edit_collection_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[test] + fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::EditCollection) + ); + assert!(app.should_refresh); + } + + #[test] + fn test_edit_collection_toggle_monitored_submit() { + let current_route = Route::from(( + ActiveRadarrBlock::EditCollectionPrompt, + Some(ActiveRadarrBlock::Collections), + )); + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app.push_navigation_stack(current_route); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_monitored, Some(false)); + } + + #[test] + fn test_edit_collection_toggle_search_on_add_submit() { + let current_route = Route::from(( + ActiveRadarrBlock::EditCollectionPrompt, + Some(ActiveRadarrBlock::Collections), + )); + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 2); + app.push_navigation_stack(current_route); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_search_on_add, Some(true)); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_search_on_add, Some(false)); + } + + #[rstest] + #[case(ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, 1)] + #[case(ActiveRadarrBlock::EditCollectionSelectQualityProfile, 2)] + #[case(ActiveRadarrBlock::EditCollectionRootFolderPathInput, 3)] + fn test_edit_collection_prompt_selected_block_submit( + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack( + ( + ActiveRadarrBlock::EditCollectionPrompt, + Some(ActiveRadarrBlock::Collections), + ) + .into(), + ); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &(selected_block, Some(ActiveRadarrBlock::Collections)).into() + ); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + + if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { + assert!(app.should_ignore_quit_key); + } + } + + #[rstest] + fn test_edit_collection_prompt_selecting_preferences_blocks_submit( + #[values( + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + ActiveRadarrBlock::EditCollectionSelectQualityProfile, + ActiveRadarrBlock::EditCollectionRootFolderPathInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.push_navigation_stack(active_radarr_block.into()); + + EditCollectionHandler::with( + &SUBMIT_KEY, + &mut app, + &active_radarr_block, + &Some(ActiveRadarrBlock::Collections), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditCollectionPrompt.into() + ); + + if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { + assert!(!app.should_ignore_quit_key); + } + } + } + + 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_edit_media_reset, assert_preferences_selections_reset}; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_edit_collection_root_folder_path_input_esc() { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); + + EditCollectionHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionRootFolderPathInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditCollectionPrompt.into() + ); + } + + #[test] + fn test_edit_collection_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); + app.data.radarr_data = create_test_radarr_data(); + + EditCollectionHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::EditCollectionPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + let radarr_data = &app.data.radarr_data; + + assert_preferences_selections_reset!(radarr_data); + assert_edit_media_reset!(radarr_data); + assert!(!radarr_data.prompt_confirm); + } + + #[rstest] + fn test_edit_collection_esc( + #[values( + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + ActiveRadarrBlock::EditCollectionSelectQualityProfile + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(active_radarr_block.into()); + + EditCollectionHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + } + + mod test_handle_key_char { + use super::*; + + #[test] + fn test_edit_collection_root_folder_path_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.edit_path = "Test".to_owned().into(); + + EditCollectionHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::EditCollectionRootFolderPathInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "Tes"); + } + + #[test] + fn test_edit_collection_root_folder_path_input_char_key() { + let mut app = App::default(); + + EditCollectionHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::EditCollectionRootFolderPathInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); + } + } +} diff --git a/src/handlers/radarr_handlers/edit_movie_handler.rs b/src/handlers/radarr_handlers/edit_movie_handler.rs index cecfdbe..d9f169f 100644 --- a/src/handlers/radarr_handlers/edit_movie_handler.rs +++ b/src/handlers/radarr_handlers/edit_movie_handler.rs @@ -7,6 +7,10 @@ use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; +#[cfg(test)] +#[path = "edit_movie_handler_tests.rs"] +mod edit_movie_handler_tests; + pub(super) struct EditMovieHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -197,510 +201,3 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, } } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - 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::edit_movie_handler::EditMovieHandler; - use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::MinimumAvailability; - - mod test_handle_scroll_up_and_down { - use pretty_assertions::assert_eq; - use rstest::rstest; - use strum::IntoEnumIterator; - - use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; - use crate::models::BlockSelectionState; - use crate::{test_enum_scroll, test_iterable_scroll}; - - use super::*; - - test_enum_scroll!( - test_edit_movie_select_minimum_availability_scroll, - EditMovieHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - None - ); - - test_iterable_scroll!( - test_edit_movie_select_quality_profile_scroll, - EditMovieHandler, - quality_profile_list, - ActiveRadarrBlock::EditMovieSelectQualityProfile, - None - ); - - #[rstest] - fn test_edit_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { - let mut app = App::default(); - app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.next(); - - EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); - - if key == Key::Up { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditMovieToggleMonitored - ); - } else { - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditMovieSelectQualityProfile - ); - } - } - } - - mod test_handle_home_end { - use strum::IntoEnumIterator; - - use crate::{test_enum_home_and_end, test_iterable_home_and_end, test_text_box_home_end_keys}; - - use super::*; - - test_enum_home_and_end!( - test_edit_movie_select_minimum_availability_home_end, - EditMovieHandler, - MinimumAvailability, - minimum_availability_list, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - None - ); - - test_iterable_home_and_end!( - test_edit_movie_select_quality_profile_scroll, - EditMovieHandler, - quality_profile_list, - ActiveRadarrBlock::EditMovieSelectQualityProfile, - None - ); - - #[test] - fn test_edit_movie_path_input_home_end_keys() { - test_text_box_home_end_keys!( - EditMovieHandler, - ActiveRadarrBlock::EditMoviePathInput, - edit_path - ); - } - - #[test] - fn test_edit_movie_tags_input_home_end_keys() { - test_text_box_home_end_keys!( - EditMovieHandler, - ActiveRadarrBlock::EditMovieTagsInput, - edit_tags - ); - } - } - - mod test_handle_left_right_action { - use rstest::rstest; - - use crate::test_text_box_left_right_keys; - - use super::*; - - #[rstest] - fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { - let mut app = App::default(); - - EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[test] - fn test_edit_movie_path_input_left_right_keys() { - test_text_box_left_right_keys!( - EditMovieHandler, - ActiveRadarrBlock::EditMoviePathInput, - edit_path - ); - } - - #[test] - fn test_edit_movie_tags_input_left_right_keys() { - test_text_box_left_right_keys!( - EditMovieHandler, - ActiveRadarrBlock::EditMovieTagsInput, - edit_tags - ); - } - } - - mod test_handle_submit { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::{EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS}; - use crate::models::{BlockSelectionState, Route}; - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_edit_movie_path_input_submit() { - let mut app = App::default(); - app.should_ignore_quit_key = true; - app.data.radarr_data.edit_path = "Test Path".to_owned().into(); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePathInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert!(!app.data.radarr_data.edit_path.text.is_empty()); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditMoviePrompt.into() - ); - } - - #[test] - fn test_edit_movie_tags_input_submit() { - let mut app = App::default(); - app.should_ignore_quit_key = true; - app.data.radarr_data.edit_tags = "Test Tags".to_owned().into(); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMovieTagsInput, - &None, - ) - .handle(); - - assert!(!app.should_ignore_quit_key); - assert!(!app.data.radarr_data.edit_tags.text.is_empty()); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditMoviePrompt.into() - ); - } - - #[test] - fn test_edit_movie_prompt_prompt_decline_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[test] - fn test_edit_movie_confirm_prompt_prompt_confirmation_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - app - .data - .radarr_data - .selected_block - .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditMovie) - ); - assert!(app.should_refresh); - } - - #[test] - fn test_edit_movie_toggle_monitored_submit() { - let current_route = Route::from(( - ActiveRadarrBlock::EditMoviePrompt, - Some(ActiveRadarrBlock::Movies), - )); - let mut app = App::default(); - app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - app.push_navigation_stack(current_route); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &Some(ActiveRadarrBlock::Movies), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &Some(ActiveRadarrBlock::Movies), - ) - .handle(); - - assert_eq!(app.get_current_route(), ¤t_route); - assert_eq!(app.data.radarr_data.edit_monitored, Some(false)); - } - - #[rstest] - #[case(ActiveRadarrBlock::EditMovieSelectMinimumAvailability, 1)] - #[case(ActiveRadarrBlock::EditMovieSelectQualityProfile, 2)] - #[case(ActiveRadarrBlock::EditMoviePathInput, 3)] - #[case(ActiveRadarrBlock::EditMovieTagsInput, 4)] - fn test_edit_movie_prompt_selected_block_submit( - #[case] selected_block: ActiveRadarrBlock, - #[case] index: usize, - ) { - let mut app = App::default(); - app.push_navigation_stack( - ( - ActiveRadarrBlock::EditMoviePrompt, - Some(ActiveRadarrBlock::Movies), - ) - .into(), - ); - app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.set_index(index); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &Some(ActiveRadarrBlock::Movies), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &(selected_block, Some(ActiveRadarrBlock::Movies)).into() - ); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - - if selected_block == ActiveRadarrBlock::EditMoviePathInput - || selected_block == ActiveRadarrBlock::EditMovieTagsInput - { - assert!(app.should_ignore_quit_key); - } - } - - #[rstest] - fn test_edit_movie_prompt_selecting_preferences_blocks_submit( - #[values( - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - ActiveRadarrBlock::EditMovieSelectQualityProfile, - ActiveRadarrBlock::EditMoviePathInput, - ActiveRadarrBlock::EditMovieTagsInput - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.push_navigation_stack(active_radarr_block.into()); - - EditMovieHandler::with( - &SUBMIT_KEY, - &mut app, - &active_radarr_block, - &Some(ActiveRadarrBlock::Movies), - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditMoviePrompt.into() - ); - - if active_radarr_block == ActiveRadarrBlock::EditMoviePathInput - || active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput - { - assert!(!app.should_ignore_quit_key); - } - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::radarr_test_utils::create_test_radarr_data; - use crate::{assert_edit_media_reset, assert_preferences_selections_reset}; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[rstest] - fn test_edit_movie_input_esc( - #[values( - ActiveRadarrBlock::EditMovieTagsInput, - ActiveRadarrBlock::EditMoviePathInput - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.push_navigation_stack(active_radarr_block.into()); - - EditMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert!(!app.should_ignore_quit_key); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::EditMoviePrompt.into() - ); - } - - #[test] - fn test_edit_movie_prompt_esc() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.data.radarr_data = create_test_radarr_data(); - - EditMovieHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::EditMoviePrompt, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - let radarr_data = &app.data.radarr_data; - - assert_preferences_selections_reset!(radarr_data); - assert_edit_media_reset!(radarr_data); - assert!(!radarr_data.prompt_confirm); - } - - #[rstest] - fn test_edit_movie_esc( - #[values( - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - ActiveRadarrBlock::EditMovieSelectQualityProfile - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.push_navigation_stack(active_radarr_block.into()); - - EditMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - } - } - - mod test_handle_key_char { - use super::*; - - #[test] - fn test_edit_movie_path_input_backspace() { - let mut app = App::default(); - app.data.radarr_data.edit_path = "Test".to_owned().into(); - - EditMovieHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::EditMoviePathInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "Tes"); - } - - #[test] - fn test_edit_movie_tags_input_backspace() { - let mut app = App::default(); - app.data.radarr_data.edit_tags = "Test".to_owned().into(); - - EditMovieHandler::with( - &DEFAULT_KEYBINDINGS.backspace.key, - &mut app, - &ActiveRadarrBlock::EditMovieTagsInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_tags.text, "Tes"); - } - - #[test] - fn test_edit_movie_path_input_char_key() { - let mut app = App::default(); - - EditMovieHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::EditMoviePathInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); - } - - #[test] - fn test_edit_movie_tags_input_char_key() { - let mut app = App::default(); - - EditMovieHandler::with( - &Key::Char('h'), - &mut app, - &ActiveRadarrBlock::EditMovieTagsInput, - &None, - ) - .handle(); - - assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); - } - } -} diff --git a/src/handlers/radarr_handlers/edit_movie_handler_tests.rs b/src/handlers/radarr_handlers/edit_movie_handler_tests.rs new file mode 100644 index 0000000..fa09b10 --- /dev/null +++ b/src/handlers/radarr_handlers/edit_movie_handler_tests.rs @@ -0,0 +1,506 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + 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::edit_movie_handler::EditMovieHandler; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::MinimumAvailability; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use crate::{test_enum_scroll, test_iterable_scroll}; + + use super::*; + + test_enum_scroll!( + test_edit_movie_select_minimum_availability_scroll, + EditMovieHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + None + ); + + test_iterable_scroll!( + test_edit_movie_select_quality_profile_scroll, + EditMovieHandler, + quality_profile_list, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + None + ); + + #[rstest] + fn test_edit_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); + + EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); + + if key == Key::Up { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieToggleMonitored + ); + } else { + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieSelectQualityProfile + ); + } + } + } + + mod test_handle_home_end { + use strum::IntoEnumIterator; + + use crate::{test_enum_home_and_end, test_iterable_home_and_end, test_text_box_home_end_keys}; + + use super::*; + + test_enum_home_and_end!( + test_edit_movie_select_minimum_availability_home_end, + EditMovieHandler, + MinimumAvailability, + minimum_availability_list, + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + None + ); + + test_iterable_home_and_end!( + test_edit_movie_select_quality_profile_scroll, + EditMovieHandler, + quality_profile_list, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + None + ); + + #[test] + fn test_edit_movie_path_input_home_end_keys() { + test_text_box_home_end_keys!( + EditMovieHandler, + ActiveRadarrBlock::EditMoviePathInput, + edit_path + ); + } + + #[test] + fn test_edit_movie_tags_input_home_end_keys() { + test_text_box_home_end_keys!( + EditMovieHandler, + ActiveRadarrBlock::EditMovieTagsInput, + edit_tags + ); + } + } + + mod test_handle_left_right_action { + use rstest::rstest; + + use crate::test_text_box_left_right_keys; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + + EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[test] + fn test_edit_movie_path_input_left_right_keys() { + test_text_box_left_right_keys!( + EditMovieHandler, + ActiveRadarrBlock::EditMoviePathInput, + edit_path + ); + } + + #[test] + fn test_edit_movie_tags_input_left_right_keys() { + test_text_box_left_right_keys!( + EditMovieHandler, + ActiveRadarrBlock::EditMovieTagsInput, + edit_tags + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::app::radarr::{EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS}; + use crate::models::{BlockSelectionState, Route}; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_edit_movie_path_input_submit() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.data.radarr_data.edit_path = "Test Path".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePathInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app.data.radarr_data.edit_path.text.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditMoviePrompt.into() + ); + } + + #[test] + fn test_edit_movie_tags_input_submit() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.data.radarr_data.edit_tags = "Test Tags".to_owned().into(); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMovieTagsInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app.data.radarr_data.edit_tags.text.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditMoviePrompt.into() + ); + } + + #[test] + fn test_edit_movie_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[test] + fn test_edit_movie_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::EditMovie) + ); + assert!(app.should_refresh); + } + + #[test] + fn test_edit_movie_toggle_monitored_submit() { + let current_route = Route::from(( + ActiveRadarrBlock::EditMoviePrompt, + Some(ActiveRadarrBlock::Movies), + )); + let mut app = App::default(); + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app.push_navigation_stack(current_route); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &Some(ActiveRadarrBlock::Movies), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &Some(ActiveRadarrBlock::Movies), + ) + .handle(); + + assert_eq!(app.get_current_route(), ¤t_route); + assert_eq!(app.data.radarr_data.edit_monitored, Some(false)); + } + + #[rstest] + #[case(ActiveRadarrBlock::EditMovieSelectMinimumAvailability, 1)] + #[case(ActiveRadarrBlock::EditMovieSelectQualityProfile, 2)] + #[case(ActiveRadarrBlock::EditMoviePathInput, 3)] + #[case(ActiveRadarrBlock::EditMovieTagsInput, 4)] + fn test_edit_movie_prompt_selected_block_submit( + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack( + ( + ActiveRadarrBlock::EditMoviePrompt, + Some(ActiveRadarrBlock::Movies), + ) + .into(), + ); + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &Some(ActiveRadarrBlock::Movies), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &(selected_block, Some(ActiveRadarrBlock::Movies)).into() + ); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + + if selected_block == ActiveRadarrBlock::EditMoviePathInput + || selected_block == ActiveRadarrBlock::EditMovieTagsInput + { + assert!(app.should_ignore_quit_key); + } + } + + #[rstest] + fn test_edit_movie_prompt_selecting_preferences_blocks_submit( + #[values( + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + ActiveRadarrBlock::EditMoviePathInput, + ActiveRadarrBlock::EditMovieTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.push_navigation_stack(active_radarr_block.into()); + + EditMovieHandler::with( + &SUBMIT_KEY, + &mut app, + &active_radarr_block, + &Some(ActiveRadarrBlock::Movies), + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditMoviePrompt.into() + ); + + if active_radarr_block == ActiveRadarrBlock::EditMoviePathInput + || active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput + { + assert!(!app.should_ignore_quit_key); + } + } + } + + 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_edit_media_reset, assert_preferences_selections_reset}; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_edit_movie_input_esc( + #[values( + ActiveRadarrBlock::EditMovieTagsInput, + ActiveRadarrBlock::EditMoviePathInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.push_navigation_stack(active_radarr_block.into()); + + EditMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::EditMoviePrompt.into() + ); + } + + #[test] + fn test_edit_movie_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); + app.data.radarr_data = create_test_radarr_data(); + + EditMovieHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::EditMoviePrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + let radarr_data = &app.data.radarr_data; + + assert_preferences_selections_reset!(radarr_data); + assert_edit_media_reset!(radarr_data); + assert!(!radarr_data.prompt_confirm); + } + + #[rstest] + fn test_edit_movie_esc( + #[values( + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + ActiveRadarrBlock::EditMovieSelectQualityProfile + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(active_radarr_block.into()); + + EditMovieHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + } + + mod test_handle_key_char { + use super::*; + + #[test] + fn test_edit_movie_path_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.edit_path = "Test".to_owned().into(); + + EditMovieHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::EditMoviePathInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "Tes"); + } + + #[test] + fn test_edit_movie_tags_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.edit_tags = "Test".to_owned().into(); + + EditMovieHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::EditMovieTagsInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_tags.text, "Tes"); + } + + #[test] + fn test_edit_movie_path_input_char_key() { + let mut app = App::default(); + + EditMovieHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::EditMoviePathInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); + } + + #[test] + fn test_edit_movie_tags_input_char_key() { + let mut app = App::default(); + + EditMovieHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::EditMovieTagsInput, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_tags.text, "h"); + } + } +} diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index fb0a833..69182c3 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -24,6 +24,14 @@ mod edit_collection_handler; mod edit_movie_handler; mod movie_details_handler; +#[cfg(test)] +#[path = "radarr_handler_tests.rs"] +mod radarr_handler_tests; + +#[cfg(test)] +#[path = "radarr_handler_test_utils.rs"] +mod radarr_handler_test_utils; + pub(super) struct RadarrHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -612,1422 +620,3 @@ impl<'a, 'b> RadarrHandler<'a, 'b> { filter_matches } } - -#[cfg(test)] -#[macro_use] -mod radarr_handler_test_utils { - #[macro_export] - macro_rules! test_edit_movie_key { - ($handler:ident, $block:expr, $context:expr) => { - let mut app = App::default(); - let mut radarr_data = RadarrData { - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, - edit_search_on_add: None, - quality_profile_map: BiMap::from_iter([ - (2222, "HD - 1080p".to_owned()), - (1111, "Any".to_owned()), - ]), - tags_map: BiMap::from_iter([(1, "test".to_owned())]), - filtered_movies: StatefulTable::default(), - ..create_test_radarr_data() - }; - radarr_data.movies.set_items(vec![Movie { - path: "/nfs/movies/Test".to_owned().into(), - monitored: true, - quality_profile_id: Number::from(2222), - minimum_availability: MinimumAvailability::Released, - 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(); - - assert_eq!( - app.get_current_route(), - &(ActiveRadarrBlock::EditMoviePrompt, Some($context)).into() - ); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditMovieToggleMonitored - ); - assert_eq!( - app.data.radarr_data.minimum_availability_list.items, - Vec::from_iter(MinimumAvailability::iter()) - ); - assert_eq!( - app - .data - .radarr_data - .minimum_availability_list - .current_selection(), - &MinimumAvailability::Released - ); - assert_eq!( - app.data.radarr_data.quality_profile_list.items, - vec!["Any".to_owned(), "HD - 1080p".to_owned()] - ); - assert_str_eq!( - app - .data - .radarr_data - .quality_profile_list - .current_selection(), - "HD - 1080p" - ); - 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)); - }; - } - - #[macro_export] - macro_rules! test_edit_collection_key { - ($handler:ident, $block:expr, $context:expr) => { - let mut app = App::default(); - let mut radarr_data = RadarrData { - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, - edit_search_on_add: None, - quality_profile_map: BiMap::from_iter([ - (2222, "HD - 1080p".to_owned()), - (1111, "Any".to_owned()), - ]), - filtered_collections: StatefulTable::default(), - ..create_test_radarr_data() - }; - radarr_data.collections.set_items(vec![Collection { - root_folder_path: "/nfs/movies/Test".to_owned().into(), - monitored: true, - search_on_add: true, - quality_profile_id: Number::from(2222), - 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(); - - assert_eq!( - app.get_current_route(), - &(ActiveRadarrBlock::EditCollectionPrompt, Some($context)).into() - ); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::EditCollectionToggleMonitored - ); - assert_eq!( - app.data.radarr_data.minimum_availability_list.items, - Vec::from_iter(MinimumAvailability::iter()) - ); - assert_eq!( - app - .data - .radarr_data - .minimum_availability_list - .current_selection(), - &MinimumAvailability::Released - ); - assert_eq!( - app.data.radarr_data.quality_profile_list.items, - vec!["Any".to_owned(), "HD - 1080p".to_owned()] - ); - assert_str_eq!( - app - .data - .radarr_data - .quality_profile_list - .current_selection(), - "HD - 1080p" - ); - 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)); - }; - } -} - -#[cfg(test)] -mod tests { - use pretty_assertions::{assert_eq, assert_str_eq}; - use rstest::rstest; - - 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::KeyEventHandler; - use crate::models::radarr_models::{Collection, 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, 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_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, 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_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 super::*; - - const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; - - #[test] - fn test_movies_delete() { - let mut app = App::default(); - - RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::DeleteMoviePrompt.into() - ); - assert_eq!( - app.data.radarr_data.selected_block.get_active_block(), - &ActiveRadarrBlock::DeleteMovieToggleDeleteFile - ); - } - - #[test] - fn test_downloads_delete() { - let mut app = App::default(); - - RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::DeleteDownloadPrompt.into() - ); - } - - #[test] - fn test_root_folder_delete() { - let mut app = App::default(); - - RadarrHandler::with( - &DELETE_KEY, - &mut app, - &ActiveRadarrBlock::RootFolders, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::DeleteRootFolderPrompt.into() - ); - } - } - - 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, 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::System)] - #[case(ActiveRadarrBlock::System, 4, 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::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; - - #[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::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::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::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".to_owned()); - 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::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 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_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 - )] - 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()); - } - - #[rstest] - fn test_refresh_key( - #[values( - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::Collections, - ActiveRadarrBlock::Downloads, - ActiveRadarrBlock::RootFolders - )] - 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_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(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 2".to_owned().into(); - app.data.radarr_data.is_searching = true; - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - - 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); - - assert_eq!(index, Some(1)); - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert!(!app.data.radarr_data.is_searching); - assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.text.is_empty()); - } - - #[test] - fn test_search_table_no_search_hits() { - let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.search = "Test 5".to_owned().into(); - app.data.radarr_data.is_searching = true; - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - - 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); - - assert_eq!(index, None); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::SearchMovie.into() - ); - assert!(!app.data.radarr_data.is_searching); - assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.text.is_empty()); - } - - #[test] - fn test_filter_table() { - let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = "Test 2".to_owned().into(); - app.data.radarr_data.is_searching = true; - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - - 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); - - assert_eq!(filter_matches.len(), 1); - assert_str_eq!(filter_matches[0].title.text, "Test 2"); - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert!(!app.data.radarr_data.is_filtering); - assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.filter.text.is_empty()); - } - - #[test] - fn test_filter_table_no_filter_matches() { - let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = "Test 5".to_owned().into(); - app.data.radarr_data.is_filtering = true; - app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - - 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); - - assert!(filter_matches.is_empty()); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::FilterMovies.into() - ); - assert!(!app.data.radarr_data.is_searching); - assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.filter.text.is_empty()); - } - - #[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!(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::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - 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::EditMoviePrompt, - ActiveRadarrBlock::EditMoviePathInput, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - ActiveRadarrBlock::EditMovieSelectQualityProfile, - ActiveRadarrBlock::EditMovieTagsInput - )] - 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!( - ActiveRadarrBlock::Movies, - ActiveRadarrBlock::DeleteMoviePrompt - ); - } - - #[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!(ActiveRadarrBlock::Collections, active_radarr_block); - } -} diff --git a/src/handlers/radarr_handlers/movie_details_handler.rs b/src/handlers/radarr_handlers/movie_details_handler.rs index 9832efb..b4f127f 100644 --- a/src/handlers/radarr_handlers/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/movie_details_handler.rs @@ -12,6 +12,10 @@ use crate::models::radarr_models::{Language, Release, ReleaseField}; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; +#[cfg(test)] +#[path = "movie_details_handler_tests.rs"] +mod movie_details_handler_tests; + pub(super) struct MovieDetailsHandler<'a, 'b> { key: &'a Key, app: &'a mut App<'b>, @@ -330,684 +334,3 @@ fn sort_releases_by_selected_field( releases } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - use rstest::rstest; - use serde_json::Number; - - 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::movie_details_handler::{ - sort_releases_by_selected_field, MovieDetailsHandler, - }; - use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::{ - Credit, Language, MovieHistoryItem, Quality, QualityWrapper, Release, ReleaseField, - }; - use crate::models::{HorizontallyScrollableText, ScrollableText}; - - mod test_handle_scroll_up_and_down { - use pretty_assertions::assert_eq; - use rstest::rstest; - use strum::IntoEnumIterator; - - use crate::models::radarr_models::ReleaseField; - use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; - - use super::*; - - #[test] - fn test_movie_details_scroll() { - let mut app = App::default(); - app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.up.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 0); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.down.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 1); - } - - test_iterable_scroll!( - test_movie_history_scroll, - MovieDetailsHandler, - movie_history, - simple_stateful_iterable_vec!(MovieHistoryItem, HorizontallyScrollableText, source_title), - ActiveRadarrBlock::MovieHistory, - None, - source_title, - to_string - ); - - test_iterable_scroll!( - test_cast_scroll, - MovieDetailsHandler, - movie_cast, - simple_stateful_iterable_vec!(Credit, String, person_name), - ActiveRadarrBlock::Cast, - None, - person_name, - to_owned - ); - - test_iterable_scroll!( - test_crew_scroll, - MovieDetailsHandler, - movie_crew, - simple_stateful_iterable_vec!(Credit, String, person_name), - ActiveRadarrBlock::Crew, - None, - person_name, - to_owned - ); - - test_iterable_scroll!( - test_manual_search_scroll, - MovieDetailsHandler, - movie_releases, - simple_stateful_iterable_vec!(Release, HorizontallyScrollableText), - ActiveRadarrBlock::ManualSearch, - None, - title, - to_string - ); - - test_enum_scroll!( - test_manual_search_sort_scroll, - MovieDetailsHandler, - ReleaseField, - movie_releases_sort, - ActiveRadarrBlock::ManualSearchSortPrompt, - None - ); - } - - mod test_handle_home_end { - use strum::IntoEnumIterator; - - use crate::models::radarr_models::ReleaseField; - use crate::{ - extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, - }; - - use super::*; - - #[test] - fn test_movie_details_home_end() { - let mut app = App::default(); - app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.end.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 1); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.home.key, - &mut app, - &ActiveRadarrBlock::MovieDetails, - &None, - ) - .handle(); - - assert_eq!(app.data.radarr_data.movie_details.offset, 0); - } - - test_iterable_home_and_end!( - test_movie_history_home_end, - MovieDetailsHandler, - movie_history, - extended_stateful_iterable_vec!(MovieHistoryItem, HorizontallyScrollableText, source_title), - ActiveRadarrBlock::MovieHistory, - None, - source_title, - to_string - ); - - test_iterable_home_and_end!( - test_cast_home_end, - MovieDetailsHandler, - movie_cast, - extended_stateful_iterable_vec!(Credit, String, person_name), - ActiveRadarrBlock::Cast, - None, - person_name, - to_owned - ); - - test_iterable_home_and_end!( - test_crew_home_end, - MovieDetailsHandler, - movie_crew, - extended_stateful_iterable_vec!(Credit, String, person_name), - ActiveRadarrBlock::Crew, - None, - person_name, - to_owned - ); - - test_iterable_home_and_end!( - test_manual_search_home_end, - MovieDetailsHandler, - movie_releases, - extended_stateful_iterable_vec!(Release, HorizontallyScrollableText), - ActiveRadarrBlock::ManualSearch, - None, - title, - to_string - ); - - test_enum_home_and_end!( - test_manual_search_sort_home_end, - MovieDetailsHandler, - ReleaseField, - movie_releases_sort, - ActiveRadarrBlock::ManualSearchSortPrompt, - None - ); - } - - mod test_handle_left_right_action { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use super::*; - - #[rstest] - fn test_left_right_prompt_toggle( - #[values( - ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - ActiveRadarrBlock::UpdateAndScanPrompt, - ActiveRadarrBlock::ManualSearchConfirmPrompt - )] - active_radarr_block: ActiveRadarrBlock, - #[values(Key::Left, Key::Right)] key: Key, - ) { - let mut app = App::default(); - - MovieDetailsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - - MovieDetailsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[rstest] - #[case(ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory)] - #[case(ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::FileInfo)] - #[case(ActiveRadarrBlock::FileInfo, ActiveRadarrBlock::Cast)] - #[case(ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew)] - #[case(ActiveRadarrBlock::Crew, ActiveRadarrBlock::ManualSearch)] - #[case(ActiveRadarrBlock::ManualSearch, ActiveRadarrBlock::MovieDetails)] - fn test_movie_info_tabs_left_right_action( - #[case] left_block: ActiveRadarrBlock, - #[case] right_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(right_block.into()); - app.data.radarr_data.movie_info_tabs.index = app - .data - .radarr_data - .movie_info_tabs - .tabs - .iter() - .position(|tab_route| tab_route.route == right_block.into()) - .unwrap_or_default(); - - MovieDetailsHandler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &right_block, &None) - .handle(); - - assert_eq!( - app.get_current_route(), - app.data.radarr_data.movie_info_tabs.get_active_route() - ); - assert_eq!(app.get_current_route(), &left_block.into()); - - MovieDetailsHandler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &left_block, &None) - .handle(); - - assert_eq!( - app.get_current_route(), - app.data.radarr_data.movie_info_tabs.get_active_route() - ); - assert_eq!(app.get_current_route(), &right_block.into()); - } - } - - mod test_handle_submit { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::models::radarr_models::ReleaseField; - use crate::network::radarr_network::RadarrEvent; - - use super::*; - - const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; - - #[test] - fn test_manual_search_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::ManualSearch.into()); - - MovieDetailsHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::ManualSearch, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::ManualSearchConfirmPrompt.into() - ); - } - - #[rstest] - #[case( - ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - RadarrEvent::TriggerAutomaticSearch - )] - #[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan)] - #[case( - ActiveRadarrBlock::ManualSearchConfirmPrompt, - RadarrEvent::DownloadRelease - )] - fn test_movie_info_prompt_confirm_submit( - #[case] prompt_block: ActiveRadarrBlock, - #[case] expected_action: RadarrEvent, - ) { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = true; - app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); - app.push_navigation_stack(prompt_block.into()); - - MovieDetailsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); - - assert!(app.data.radarr_data.prompt_confirm); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::MovieDetails.into() - ); - assert_eq!( - app.data.radarr_data.prompt_confirm_action, - Some(expected_action) - ); - } - - #[rstest] - fn test_movie_info_prompt_decline_submit( - #[values( - ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - ActiveRadarrBlock::UpdateAndScanPrompt, - ActiveRadarrBlock::ManualSearchConfirmPrompt - )] - prompt_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); - app.push_navigation_stack(prompt_block.into()); - - MovieDetailsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::MovieDetails.into() - ); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[test] - fn test_manual_search_sort_prompt_submit() { - let mut app = App::default(); - app.push_navigation_stack(ActiveRadarrBlock::ManualSearch.into()); - app.push_navigation_stack(ActiveRadarrBlock::ManualSearchSortPrompt.into()); - app.data.radarr_data.sort_ascending = Some(true); - app - .data - .radarr_data - .movie_releases_sort - .set_items(vec![ReleaseField::default()]); - app.data.radarr_data.movie_releases.set_items(release_vec()); - - let mut expected_vec = release_vec(); - expected_vec.reverse(); - - MovieDetailsHandler::with( - &SUBMIT_KEY, - &mut app, - &ActiveRadarrBlock::ManualSearchSortPrompt, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::ManualSearch.into() - ); - assert_eq!(app.data.radarr_data.movie_releases.items, expected_vec); - } - } - - mod test_handle_esc { - use pretty_assertions::assert_eq; - use rstest::rstest; - - use crate::app::radarr::radarr_test_utils::create_test_radarr_data; - use crate::assert_movie_info_tabs_reset; - - use super::*; - - const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - - #[rstest] - fn test_movie_info_tabs_esc( - #[values( - ActiveRadarrBlock::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - ActiveRadarrBlock::ManualSearch - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data = create_test_radarr_data(); - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(active_radarr_block.into()); - - MovieDetailsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); - - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_movie_info_tabs_reset!(app.data.radarr_data); - } - - #[rstest] - fn test_movie_info_prompts_esc( - #[values( - ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - ActiveRadarrBlock::UpdateAndScanPrompt, - ActiveRadarrBlock::ManualSearchConfirmPrompt, - ActiveRadarrBlock::ManualSearchSortPrompt - )] - prompt_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = true; - app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(prompt_block.into()); - - MovieDetailsHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - } - } - - mod test_handle_key_char { - use bimap::BiMap; - use pretty_assertions::{assert_eq, assert_str_eq}; - use rstest::rstest; - use strum::IntoEnumIterator; - - use crate::app::radarr::radarr_test_utils::create_test_radarr_data; - 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; - - use super::*; - - #[rstest] - fn test_search_key( - #[values( - ActiveRadarrBlock::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - ActiveRadarrBlock::ManualSearch - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.search.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into() - ); - } - - #[test] - fn test_sort_key() { - let mut app = App::default(); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.sort.key, - &mut app, - &ActiveRadarrBlock::ManualSearch, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::ManualSearchSortPrompt.into() - ); - assert!(!app.data.radarr_data.movie_releases_sort.items.is_empty()); - assert!(app.data.radarr_data.sort_ascending.is_some()); - assert_eq!(app.data.radarr_data.sort_ascending, Some(false)); - } - - #[rstest] - fn test_edit_key( - #[values( - ActiveRadarrBlock::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - ActiveRadarrBlock::ManualSearch - )] - active_radarr_block: ActiveRadarrBlock, - ) { - test_edit_movie_key!( - MovieDetailsHandler, - active_radarr_block, - active_radarr_block - ); - } - - #[rstest] - fn test_update_key( - #[values( - ActiveRadarrBlock::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - ActiveRadarrBlock::ManualSearch - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.update.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::UpdateAndScanPrompt.into() - ); - } - - #[rstest] - fn test_refresh_key( - #[values( - ActiveRadarrBlock::MovieDetails, - ActiveRadarrBlock::MovieHistory, - ActiveRadarrBlock::FileInfo, - ActiveRadarrBlock::Cast, - ActiveRadarrBlock::Crew, - ActiveRadarrBlock::ManualSearch - )] - active_radarr_block: ActiveRadarrBlock, - ) { - let mut app = App::default(); - - MovieDetailsHandler::with( - &DEFAULT_KEYBINDINGS.refresh.key, - &mut app, - &active_radarr_block, - &None, - ) - .handle(); - - assert_eq!(app.get_current_route(), &active_radarr_block.into()); - assert!(app.is_routing); - } - } - - #[rstest] - fn test_sort_releases_by_selected_field( - #[values( - ReleaseField::Source, - ReleaseField::Age, - ReleaseField::Title, - ReleaseField::Indexer, - ReleaseField::Size, - ReleaseField::Peers, - ReleaseField::Language, - ReleaseField::Quality - )] - field: ReleaseField, - ) { - let mut expected_vec = release_vec(); - - let sorted_releases = sort_releases_by_selected_field(release_vec(), field, true); - - assert_eq!(sorted_releases, expected_vec); - - let sorted_releases = sort_releases_by_selected_field(release_vec(), field, false); - - expected_vec.reverse(); - assert_eq!(sorted_releases, expected_vec); - } - - #[test] - fn test_sort_releases_by_selected_field_rejected() { - let mut expected_vec = Vec::from(&release_vec()[1..]); - expected_vec.push(release_vec()[0].clone()); - - let sorted_releases = - sort_releases_by_selected_field(release_vec(), ReleaseField::Rejected, true); - - assert_eq!(sorted_releases, expected_vec); - - let sorted_releases = - sort_releases_by_selected_field(release_vec(), ReleaseField::Rejected, false); - - assert_eq!(sorted_releases, release_vec()); - } - - fn release_vec() -> Vec { - let release_a = Release { - protocol: "Protocol A".to_owned(), - age: Number::from(1), - title: HorizontallyScrollableText::from("Title A".to_owned()), - indexer: "Indexer A".to_owned(), - size: Number::from(1), - rejected: true, - seeders: Some(Number::from(1)), - languages: Some(vec![Language { - name: "Language A".to_owned(), - }]), - quality: QualityWrapper { - quality: Quality { - name: "Quality A".to_owned(), - }, - }, - ..Release::default() - }; - let release_b = Release { - protocol: "Protocol B".to_owned(), - age: Number::from(2), - title: HorizontallyScrollableText::from("Title B".to_owned()), - indexer: "Indexer B".to_owned(), - size: Number::from(2), - rejected: false, - seeders: Some(Number::from(2)), - languages: Some(vec![Language { - name: "Language B".to_owned(), - }]), - quality: QualityWrapper { - quality: Quality { - name: "Quality B".to_owned(), - }, - }, - ..Release::default() - }; - let release_c = Release { - protocol: "Protocol C".to_owned(), - age: Number::from(3), - title: HorizontallyScrollableText::from("Title C".to_owned()), - indexer: "Indexer C".to_owned(), - size: Number::from(3), - rejected: false, - seeders: None, - languages: None, - quality: QualityWrapper { - quality: Quality { - name: "Quality C".to_owned(), - }, - }, - ..Release::default() - }; - - vec![release_a, release_b, release_c] - } -} diff --git a/src/handlers/radarr_handlers/movie_details_handler_tests.rs b/src/handlers/radarr_handlers/movie_details_handler_tests.rs new file mode 100644 index 0000000..e707083 --- /dev/null +++ b/src/handlers/radarr_handlers/movie_details_handler_tests.rs @@ -0,0 +1,680 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use rstest::rstest; + use serde_json::Number; + + 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::movie_details_handler::{ + sort_releases_by_selected_field, MovieDetailsHandler, + }; + use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::{ + Credit, Language, MovieHistoryItem, Quality, QualityWrapper, Release, ReleaseField, + }; + use crate::models::{HorizontallyScrollableText, ScrollableText}; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::models::radarr_models::ReleaseField; + use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; + + use super::*; + + #[test] + fn test_movie_details_scroll() { + let mut app = App::default(); + app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.up.key, + &mut app, + &ActiveRadarrBlock::MovieDetails, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.movie_details.offset, 0); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.down.key, + &mut app, + &ActiveRadarrBlock::MovieDetails, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.movie_details.offset, 1); + } + + test_iterable_scroll!( + test_movie_history_scroll, + MovieDetailsHandler, + movie_history, + simple_stateful_iterable_vec!(MovieHistoryItem, HorizontallyScrollableText, source_title), + ActiveRadarrBlock::MovieHistory, + None, + source_title, + to_string + ); + + test_iterable_scroll!( + test_cast_scroll, + MovieDetailsHandler, + movie_cast, + simple_stateful_iterable_vec!(Credit, String, person_name), + ActiveRadarrBlock::Cast, + None, + person_name, + to_owned + ); + + test_iterable_scroll!( + test_crew_scroll, + MovieDetailsHandler, + movie_crew, + simple_stateful_iterable_vec!(Credit, String, person_name), + ActiveRadarrBlock::Crew, + None, + person_name, + to_owned + ); + + test_iterable_scroll!( + test_manual_search_scroll, + MovieDetailsHandler, + movie_releases, + simple_stateful_iterable_vec!(Release, HorizontallyScrollableText), + ActiveRadarrBlock::ManualSearch, + None, + title, + to_string + ); + + test_enum_scroll!( + test_manual_search_sort_scroll, + MovieDetailsHandler, + ReleaseField, + movie_releases_sort, + ActiveRadarrBlock::ManualSearchSortPrompt, + None + ); + } + + mod test_handle_home_end { + use strum::IntoEnumIterator; + + use crate::models::radarr_models::ReleaseField; + use crate::{ + extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, + }; + + use super::*; + + #[test] + fn test_movie_details_home_end() { + let mut app = App::default(); + app.data.radarr_data.movie_details = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::MovieDetails, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.movie_details.offset, 1); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::MovieDetails, + &None, + ) + .handle(); + + assert_eq!(app.data.radarr_data.movie_details.offset, 0); + } + + test_iterable_home_and_end!( + test_movie_history_home_end, + MovieDetailsHandler, + movie_history, + extended_stateful_iterable_vec!(MovieHistoryItem, HorizontallyScrollableText, source_title), + ActiveRadarrBlock::MovieHistory, + None, + source_title, + to_string + ); + + test_iterable_home_and_end!( + test_cast_home_end, + MovieDetailsHandler, + movie_cast, + extended_stateful_iterable_vec!(Credit, String, person_name), + ActiveRadarrBlock::Cast, + None, + person_name, + to_owned + ); + + test_iterable_home_and_end!( + test_crew_home_end, + MovieDetailsHandler, + movie_crew, + extended_stateful_iterable_vec!(Credit, String, person_name), + ActiveRadarrBlock::Crew, + None, + person_name, + to_owned + ); + + test_iterable_home_and_end!( + test_manual_search_home_end, + MovieDetailsHandler, + movie_releases, + extended_stateful_iterable_vec!(Release, HorizontallyScrollableText), + ActiveRadarrBlock::ManualSearch, + None, + title, + to_string + ); + + test_enum_home_and_end!( + test_manual_search_sort_home_end, + MovieDetailsHandler, + ReleaseField, + movie_releases_sort, + ActiveRadarrBlock::ManualSearchSortPrompt, + None + ); + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle( + #[values( + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + ActiveRadarrBlock::UpdateAndScanPrompt, + ActiveRadarrBlock::ManualSearchConfirmPrompt + )] + active_radarr_block: ActiveRadarrBlock, + #[values(Key::Left, Key::Right)] key: Key, + ) { + let mut app = App::default(); + + MovieDetailsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + MovieDetailsHandler::with(&key, &mut app, &active_radarr_block, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[rstest] + #[case(ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory)] + #[case(ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::FileInfo)] + #[case(ActiveRadarrBlock::FileInfo, ActiveRadarrBlock::Cast)] + #[case(ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew)] + #[case(ActiveRadarrBlock::Crew, ActiveRadarrBlock::ManualSearch)] + #[case(ActiveRadarrBlock::ManualSearch, ActiveRadarrBlock::MovieDetails)] + fn test_movie_info_tabs_left_right_action( + #[case] left_block: ActiveRadarrBlock, + #[case] right_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(right_block.into()); + app.data.radarr_data.movie_info_tabs.index = app + .data + .radarr_data + .movie_info_tabs + .tabs + .iter() + .position(|tab_route| tab_route.route == right_block.into()) + .unwrap_or_default(); + + MovieDetailsHandler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &right_block, &None) + .handle(); + + assert_eq!( + app.get_current_route(), + app.data.radarr_data.movie_info_tabs.get_active_route() + ); + assert_eq!(app.get_current_route(), &left_block.into()); + + MovieDetailsHandler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &left_block, &None) + .handle(); + + assert_eq!( + app.get_current_route(), + app.data.radarr_data.movie_info_tabs.get_active_route() + ); + assert_eq!(app.get_current_route(), &right_block.into()); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::radarr_models::ReleaseField; + use crate::network::radarr_network::RadarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_manual_search_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::ManualSearch.into()); + + MovieDetailsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::ManualSearch, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::ManualSearchConfirmPrompt.into() + ); + } + + #[rstest] + #[case( + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + RadarrEvent::TriggerAutomaticSearch + )] + #[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan)] + #[case( + ActiveRadarrBlock::ManualSearchConfirmPrompt, + RadarrEvent::DownloadRelease + )] + fn test_movie_info_prompt_confirm_submit( + #[case] prompt_block: ActiveRadarrBlock, + #[case] expected_action: RadarrEvent, + ) { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + MovieDetailsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::MovieDetails.into() + ); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(expected_action) + ); + } + + #[rstest] + fn test_movie_info_prompt_decline_submit( + #[values( + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + ActiveRadarrBlock::UpdateAndScanPrompt, + ActiveRadarrBlock::ManualSearchConfirmPrompt + )] + prompt_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + MovieDetailsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::MovieDetails.into() + ); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[test] + fn test_manual_search_sort_prompt_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::ManualSearch.into()); + app.push_navigation_stack(ActiveRadarrBlock::ManualSearchSortPrompt.into()); + app.data.radarr_data.sort_ascending = Some(true); + app + .data + .radarr_data + .movie_releases_sort + .set_items(vec![ReleaseField::default()]); + app.data.radarr_data.movie_releases.set_items(release_vec()); + + let mut expected_vec = release_vec(); + expected_vec.reverse(); + + MovieDetailsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::ManualSearchSortPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::ManualSearch.into() + ); + assert_eq!(app.data.radarr_data.movie_releases.items, expected_vec); + } + } + + 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_movie_info_tabs_reset; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_movie_info_tabs_esc( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::ManualSearch + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(active_radarr_block.into()); + + MovieDetailsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert_movie_info_tabs_reset!(app.data.radarr_data); + } + + #[rstest] + fn test_movie_info_prompts_esc( + #[values( + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + ActiveRadarrBlock::UpdateAndScanPrompt, + ActiveRadarrBlock::ManualSearchConfirmPrompt, + ActiveRadarrBlock::ManualSearchSortPrompt + )] + prompt_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(prompt_block.into()); + + MovieDetailsHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + } + + mod test_handle_key_char { + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + 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, Movie}; + use crate::models::BlockSelectionState; + use crate::models::HorizontallyScrollableText; + use crate::models::StatefulTable; + use crate::test_edit_movie_key; + + use super::*; + + #[rstest] + fn test_search_key( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::ManualSearch + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.search.key, + &mut app, + &active_radarr_block, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into() + ); + } + + #[test] + fn test_sort_key() { + let mut app = App::default(); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.sort.key, + &mut app, + &ActiveRadarrBlock::ManualSearch, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::ManualSearchSortPrompt.into() + ); + assert!(!app.data.radarr_data.movie_releases_sort.items.is_empty()); + assert!(app.data.radarr_data.sort_ascending.is_some()); + assert_eq!(app.data.radarr_data.sort_ascending, Some(false)); + } + + #[rstest] + fn test_edit_key( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::ManualSearch + )] + active_radarr_block: ActiveRadarrBlock, + ) { + test_edit_movie_key!( + MovieDetailsHandler, + active_radarr_block, + active_radarr_block + ); + } + + #[rstest] + fn test_update_key( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::ManualSearch + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.update.key, + &mut app, + &active_radarr_block, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::UpdateAndScanPrompt.into() + ); + } + + #[rstest] + fn test_refresh_key( + #[values( + ActiveRadarrBlock::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + ActiveRadarrBlock::ManualSearch + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + + MovieDetailsHandler::with( + &DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + &active_radarr_block, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &active_radarr_block.into()); + assert!(app.is_routing); + } + } + + #[rstest] + fn test_sort_releases_by_selected_field( + #[values( + ReleaseField::Source, + ReleaseField::Age, + ReleaseField::Title, + ReleaseField::Indexer, + ReleaseField::Size, + ReleaseField::Peers, + ReleaseField::Language, + ReleaseField::Quality + )] + field: ReleaseField, + ) { + let mut expected_vec = release_vec(); + + let sorted_releases = sort_releases_by_selected_field(release_vec(), field, true); + + assert_eq!(sorted_releases, expected_vec); + + let sorted_releases = sort_releases_by_selected_field(release_vec(), field, false); + + expected_vec.reverse(); + assert_eq!(sorted_releases, expected_vec); + } + + #[test] + fn test_sort_releases_by_selected_field_rejected() { + let mut expected_vec = Vec::from(&release_vec()[1..]); + expected_vec.push(release_vec()[0].clone()); + + let sorted_releases = + sort_releases_by_selected_field(release_vec(), ReleaseField::Rejected, true); + + assert_eq!(sorted_releases, expected_vec); + + let sorted_releases = + sort_releases_by_selected_field(release_vec(), ReleaseField::Rejected, false); + + assert_eq!(sorted_releases, release_vec()); + } + + fn release_vec() -> Vec { + let release_a = Release { + protocol: "Protocol A".to_owned(), + age: Number::from(1), + title: HorizontallyScrollableText::from("Title A".to_owned()), + indexer: "Indexer A".to_owned(), + size: Number::from(1), + rejected: true, + seeders: Some(Number::from(1)), + languages: Some(vec![Language { + name: "Language A".to_owned(), + }]), + quality: QualityWrapper { + quality: Quality { + name: "Quality A".to_owned(), + }, + }, + ..Release::default() + }; + let release_b = Release { + protocol: "Protocol B".to_owned(), + age: Number::from(2), + title: HorizontallyScrollableText::from("Title B".to_owned()), + indexer: "Indexer B".to_owned(), + size: Number::from(2), + rejected: false, + seeders: Some(Number::from(2)), + languages: Some(vec![Language { + name: "Language B".to_owned(), + }]), + quality: QualityWrapper { + quality: Quality { + name: "Quality B".to_owned(), + }, + }, + ..Release::default() + }; + let release_c = Release { + protocol: "Protocol C".to_owned(), + age: Number::from(3), + title: HorizontallyScrollableText::from("Title C".to_owned()), + indexer: "Indexer C".to_owned(), + size: Number::from(3), + rejected: false, + seeders: None, + languages: None, + quality: QualityWrapper { + quality: Quality { + name: "Quality C".to_owned(), + }, + }, + ..Release::default() + }; + + vec![release_a, release_b, release_c] + } +} diff --git a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs new file mode 100644 index 0000000..ab3fb45 --- /dev/null +++ b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs @@ -0,0 +1,138 @@ +#[cfg(test)] +#[macro_use] +mod utils { + #[macro_export] + macro_rules! test_edit_movie_key { + ($handler:ident, $block:expr, $context:expr) => { + let mut app = App::default(); + let mut radarr_data = RadarrData { + edit_path: HorizontallyScrollableText::default(), + edit_tags: HorizontallyScrollableText::default(), + edit_monitored: None, + edit_search_on_add: None, + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + tags_map: BiMap::from_iter([(1, "test".to_owned())]), + filtered_movies: StatefulTable::default(), + ..create_test_radarr_data() + }; + radarr_data.movies.set_items(vec![Movie { + path: "/nfs/movies/Test".to_owned().into(), + monitored: true, + quality_profile_id: Number::from(2222), + minimum_availability: MinimumAvailability::Released, + 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(); + + assert_eq!( + app.get_current_route(), + &(ActiveRadarrBlock::EditMoviePrompt, Some($context)).into() + ); + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieToggleMonitored + ); + assert_eq!( + app.data.radarr_data.minimum_availability_list.items, + Vec::from_iter(MinimumAvailability::iter()) + ); + assert_eq!( + app + .data + .radarr_data + .minimum_availability_list + .current_selection(), + &MinimumAvailability::Released + ); + assert_eq!( + app.data.radarr_data.quality_profile_list.items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_str_eq!( + app + .data + .radarr_data + .quality_profile_list + .current_selection(), + "HD - 1080p" + ); + 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)); + }; + } + + #[macro_export] + macro_rules! test_edit_collection_key { + ($handler:ident, $block:expr, $context:expr) => { + let mut app = App::default(); + let mut radarr_data = RadarrData { + edit_path: HorizontallyScrollableText::default(), + edit_tags: HorizontallyScrollableText::default(), + edit_monitored: None, + edit_search_on_add: None, + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + filtered_collections: StatefulTable::default(), + ..create_test_radarr_data() + }; + radarr_data.collections.set_items(vec![Collection { + root_folder_path: "/nfs/movies/Test".to_owned().into(), + monitored: true, + search_on_add: true, + quality_profile_id: Number::from(2222), + 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(); + + assert_eq!( + app.get_current_route(), + &(ActiveRadarrBlock::EditCollectionPrompt, Some($context)).into() + ); + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionToggleMonitored + ); + assert_eq!( + app.data.radarr_data.minimum_availability_list.items, + Vec::from_iter(MinimumAvailability::iter()) + ); + assert_eq!( + app + .data + .radarr_data + .minimum_availability_list + .current_selection(), + &MinimumAvailability::Released + ); + assert_eq!( + app.data.radarr_data.quality_profile_list.items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_str_eq!( + app + .data + .radarr_data + .quality_profile_list + .current_selection(), + "HD - 1080p" + ); + 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)); + }; + } +} diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs new file mode 100644 index 0000000..240a83d --- /dev/null +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -0,0 +1,1280 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + + 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::KeyEventHandler; + use crate::models::radarr_models::{Collection, 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, 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_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, 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_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 super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_movies_delete() { + let mut app = App::default(); + + RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::DeleteMoviePrompt.into() + ); + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieToggleDeleteFile + ); + } + + #[test] + fn test_downloads_delete() { + let mut app = App::default(); + + RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::DeleteDownloadPrompt.into() + ); + } + + #[test] + fn test_root_folder_delete() { + let mut app = App::default(); + + RadarrHandler::with( + &DELETE_KEY, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::DeleteRootFolderPrompt.into() + ); + } + } + + 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, 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::System)] + #[case(ActiveRadarrBlock::System, 4, 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::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; + + #[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::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::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".to_owned()); + 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_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 + )] + 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()); + } + + #[rstest] + fn test_refresh_key( + #[values( + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::Collections, + ActiveRadarrBlock::Downloads, + ActiveRadarrBlock::RootFolders + )] + 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_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(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.is_searching = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + + 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); + + assert_eq!(index, Some(1)); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.is_searching); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.text.is_empty()); + } + + #[test] + fn test_search_table_no_search_hits() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = "Test 5".to_owned().into(); + app.data.radarr_data.is_searching = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + + 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); + + assert_eq!(index, None); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SearchMovie.into() + ); + assert!(!app.data.radarr_data.is_searching); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.text.is_empty()); + } + + #[test] + fn test_filter_table() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = "Test 2".to_owned().into(); + app.data.radarr_data.is_searching = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + + 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); + + assert_eq!(filter_matches.len(), 1); + assert_str_eq!(filter_matches[0].title.text, "Test 2"); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.is_filtering); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.text.is_empty()); + } + + #[test] + fn test_filter_table_no_filter_matches() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = "Test 5".to_owned().into(); + app.data.radarr_data.is_filtering = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + + 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); + + assert!(filter_matches.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMovies.into() + ); + assert!(!app.data.radarr_data.is_searching); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.text.is_empty()); + } + + #[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!(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::MovieDetails, + ActiveRadarrBlock::MovieHistory, + ActiveRadarrBlock::FileInfo, + ActiveRadarrBlock::Cast, + ActiveRadarrBlock::Crew, + 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::EditMoviePrompt, + ActiveRadarrBlock::EditMoviePathInput, + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + ActiveRadarrBlock::EditMovieTagsInput + )] + 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!( + ActiveRadarrBlock::Movies, + ActiveRadarrBlock::DeleteMoviePrompt + ); + } + + #[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!(ActiveRadarrBlock::Collections, active_radarr_block); + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 21a1502..acf9376 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -8,6 +8,10 @@ use crate::app::radarr::ActiveRadarrBlock; pub mod radarr_models; +#[cfg(test)] +#[path = "model_tests.rs"] +mod model_tests; + // Allowing dead code for now since we'll eventually be implementing additional Servarr support and we'll need it then #[allow(dead_code)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -343,532 +347,3 @@ where self.index = index; } } - -#[cfg(test)] -mod tests { - use std::cell::RefCell; - - use pretty_assertions::{assert_eq, assert_str_eq}; - - use crate::app::radarr::ActiveRadarrBlock; - use crate::models::{ - BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulTable, - TabRoute, TabState, - }; - - const BLOCKS: [ActiveRadarrBlock; 6] = [ - ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieSelectMonitor, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - ActiveRadarrBlock::AddMovieTagsInput, - ActiveRadarrBlock::AddMovieConfirmPrompt, - ]; - - #[test] - fn test_stateful_table_scroll() { - let mut stateful_table = create_test_stateful_table(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.scroll_down(); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.scroll_down(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.scroll_up(); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.scroll_up(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.scroll_to_bottom(); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.scroll_to_top(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - } - - #[test] - fn test_stateful_table_set_items() { - let items_vec = vec!["Test 1", "Test 2", "Test 3"]; - let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); - - stateful_table.set_items(items_vec.clone()); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.state.select(Some(1)); - stateful_table.set_items(items_vec.clone()); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.state.select(Some(3)); - stateful_table.set_items(items_vec); - - assert_eq!(stateful_table.state.selected(), Some(2)); - } - - #[test] - fn test_stateful_table_current_selection() { - let mut stateful_table = create_test_stateful_table(); - - assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[0]); - - stateful_table.state.select(Some(1)); - - assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]); - } - - #[test] - fn test_stateful_table_select_index() { - let mut stateful_table = create_test_stateful_table(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.select_index(Some(1)); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.select_index(None); - - assert_eq!(stateful_table.state.selected(), None); - } - - #[test] - fn test_stateful_table_scroll_up() { - let mut stateful_table = create_test_stateful_table(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - - stateful_table.scroll_up(); - - assert_eq!(stateful_table.state.selected(), Some(1)); - - stateful_table.scroll_up(); - - assert_eq!(stateful_table.state.selected(), Some(0)); - } - - #[test] - fn test_scrollable_text_with_string() { - let scrollable_text = ScrollableText::with_string("Test \n String \n".to_owned()); - - assert_eq!(scrollable_text.items.len(), 3); - assert_eq!(scrollable_text.items, vec!["Test ", " String ", ""]); - assert_eq!(scrollable_text.offset, 0); - } - - #[test] - fn test_scrollable_text_get_text() { - let test_text = "Test \nString"; - let scrollable_text = ScrollableText::with_string(test_text.to_owned()); - - assert_str_eq!(scrollable_text.get_text(), test_text); - } - - #[test] - fn test_scrollable_text_scroll() { - let mut scrollable_text = ScrollableText::with_string("Test \nString".to_owned()); - - scrollable_text.scroll_down(); - - assert_eq!(scrollable_text.offset, 1); - - scrollable_text.scroll_down(); - - assert_eq!(scrollable_text.offset, 1); - - scrollable_text.scroll_up(); - - assert_eq!(scrollable_text.offset, 0); - - scrollable_text.scroll_up(); - - assert_eq!(scrollable_text.offset, 0); - - scrollable_text.scroll_to_bottom(); - - assert_eq!(scrollable_text.offset, 1); - - scrollable_text.scroll_to_top(); - - assert_eq!(scrollable_text.offset, 0); - } - - #[test] - fn test_horizontally_scrollable_text_from() { - let test_text = "Test string"; - let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - assert_str_eq!(horizontally_scrollable_text.text, test_text); - } - - #[test] - fn test_horizontally_scrollable_text_to_string() { - let test_text = "Test string"; - let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - - assert_str_eq!(horizontally_scrollable_text.to_string(), test_text); - - let horizontally_scrollable_text = HorizontallyScrollableText { - text: test_text.to_owned(), - offset: RefCell::new(test_text.len() - 1), - }; - - assert_str_eq!(horizontally_scrollable_text.to_string(), "g"); - - let horizontally_scrollable_text = HorizontallyScrollableText { - text: test_text.to_owned(), - offset: RefCell::new(test_text.len()), - }; - - assert!(horizontally_scrollable_text.to_string().is_empty()); - } - - #[test] - fn test_horizontally_scrollable_text_new() { - let test_text = "Test string"; - let horizontally_scrollable_text = HorizontallyScrollableText::new(test_text.to_owned()); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - assert_str_eq!(horizontally_scrollable_text.text, test_text); - } - - #[test] - fn test_horizontally_scrollable_text_scroll_text_left() { - let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - for i in 1..horizontally_scrollable_text.text.len() - 1 { - horizontally_scrollable_text.scroll_left(); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), i); - } - - horizontally_scrollable_text.scroll_left(); - - assert_eq!( - *horizontally_scrollable_text.offset.borrow(), - horizontally_scrollable_text.text.len() - 1 - ); - } - - #[test] - fn test_horizontally_scrollable_text_scroll_text_right() { - let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); - *horizontally_scrollable_text.offset.borrow_mut() = horizontally_scrollable_text.text.len(); - - for i in 1..horizontally_scrollable_text.text.len() { - horizontally_scrollable_text.scroll_right(); - - assert_eq!( - *horizontally_scrollable_text.offset.borrow(), - horizontally_scrollable_text.text.len() - i - ); - } - - horizontally_scrollable_text.scroll_right(); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - - #[test] - fn test_horizontally_scrollable_text_scroll_home() { - let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); - - horizontally_scrollable_text.scroll_home(); - - assert_eq!( - *horizontally_scrollable_text.offset.borrow(), - horizontally_scrollable_text.text.len() - ); - } - - #[test] - fn test_horizontally_scrollable_text_reset_offset() { - let horizontally_scrollable_text = HorizontallyScrollableText { - text: "Test string".to_owned(), - offset: RefCell::new(1), - }; - - horizontally_scrollable_text.reset_offset(); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - - #[test] - fn test_horizontally_scrollable_text_scroll_or_reset() { - let width = 3; - let test_text = "Test string"; - let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - - horizontally_scrollable_text.scroll_left_or_reset(width, true, true); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); - - horizontally_scrollable_text.scroll_left_or_reset(width, false, true); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - horizontally_scrollable_text.scroll_left_or_reset(width, true, false); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - horizontally_scrollable_text.scroll_left_or_reset(width, true, true); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); - - horizontally_scrollable_text.scroll_left_or_reset(test_text.len(), false, true); - - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - - #[test] - fn test_horizontally_scrollable_test_scroll_or_reset_resets_when_text_unselected() { - let horizontally_scrollable_test = HorizontallyScrollableText::from("Test string".to_owned()); - horizontally_scrollable_test.scroll_left(); - - assert_eq!(*horizontally_scrollable_test.offset.borrow(), 1); - - horizontally_scrollable_test.scroll_left_or_reset(3, false, false); - - assert_eq!(*horizontally_scrollable_test.offset.borrow(), 0); - } - - #[test] - fn test_horizontally_scrollable_text_drain() { - let test_text = "Test string"; - let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - - assert_str_eq!(horizontally_scrollable_text.drain(), test_text); - assert!(horizontally_scrollable_text.text.is_empty()); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - - #[test] - fn test_horizontally_scrollable_text_pop() { - let test_text = "Test string"; - let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - horizontally_scrollable_text.pop(); - - assert_str_eq!(horizontally_scrollable_text.text, "Test strin"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - horizontally_scrollable_text.scroll_left(); - horizontally_scrollable_text.pop(); - - assert_str_eq!(horizontally_scrollable_text.text, "Test strn"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); - - horizontally_scrollable_text.scroll_right(); - horizontally_scrollable_text.scroll_right(); - horizontally_scrollable_text.pop(); - - assert_str_eq!(horizontally_scrollable_text.text, "Test str"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - horizontally_scrollable_text.scroll_home(); - horizontally_scrollable_text.pop(); - - assert_str_eq!(horizontally_scrollable_text.text, "Test str"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 8); - } - - #[test] - fn test_horizontally_scrollable_text_push() { - let test_text = "Test string"; - let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); - horizontally_scrollable_text.push('h'); - - assert_str_eq!(horizontally_scrollable_text.text, "Test stringh"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - - horizontally_scrollable_text.scroll_left(); - horizontally_scrollable_text.push('l'); - - assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); - - horizontally_scrollable_text.scroll_right(); - horizontally_scrollable_text.scroll_right(); - horizontally_scrollable_text.push('0'); - - assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh0"); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - - #[test] - fn test_tab_state_new() { - let tab_state = TabState::new(create_test_tab_routes()); - - assert_eq!(tab_state.index, 0); - } - - #[test] - fn test_tab_state_set_index() { - let mut tab_state = TabState::new(create_test_tab_routes()); - - let result = tab_state.set_index(1); - - assert_eq!(result, &create_test_tab_routes()[1]); - assert_eq!(tab_state.index, 1); - } - - #[test] - fn test_tab_state_get_active_route() { - let tabs = create_test_tab_routes(); - let second_tab = tabs[1].route; - let tab_state = TabState { tabs, index: 1 }; - - let active_route = tab_state.get_active_route(); - - assert_eq!(active_route, &second_tab); - } - - #[test] - fn test_tab_state_get_active_tab_help() { - let tabs = create_test_tab_routes(); - let second_tab_help = tabs[1].help; - let tab_state = TabState { tabs, index: 1 }; - - let tab_help = tab_state.get_active_tab_help(); - - assert_str_eq!(tab_help, second_tab_help); - } - - #[test] - fn test_tab_state_get_active_tab_contextual_help() { - let tabs = create_test_tab_routes(); - let second_tab_contextual_help = tabs[1].contextual_help.unwrap(); - let tab_state = TabState { tabs, index: 1 }; - - let tab_contextual_help = tab_state.get_active_tab_contextual_help(); - - assert!(tab_contextual_help.is_some()); - assert_str_eq!(tab_contextual_help.unwrap(), second_tab_contextual_help); - } - - #[test] - fn test_tab_state_next() { - let tab_routes = create_test_tab_routes(); - let mut tab_state = TabState::new(create_test_tab_routes()); - - assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); - - tab_state.next(); - - assert_eq!(tab_state.get_active_route(), &tab_routes[1].route); - - tab_state.next(); - - assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); - } - - #[test] - fn test_tab_state_previous() { - let tab_routes = create_test_tab_routes(); - let mut tab_state = TabState::new(create_test_tab_routes()); - - assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); - - tab_state.previous(); - - assert_eq!(tab_state.get_active_route(), &tab_routes[1].route); - - tab_state.previous(); - - assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); - } - - #[test] - fn test_block_selection_state_new() { - let block_selection_state = BlockSelectionState::new(&BLOCKS); - - assert_eq!(block_selection_state.index, 0); - } - - #[test] - fn test_block_selection_state_get_active_block() { - let second_block = BLOCKS[1]; - let block_selection_state = BlockSelectionState { - blocks: &BLOCKS, - index: 1, - }; - - let active_block = block_selection_state.get_active_block(); - - assert_eq!(active_block, &second_block); - } - - #[test] - fn test_block_selection_state_next() { - let blocks = [ - ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieSelectMonitor, - ]; - let mut block_selection_state = BlockSelectionState::new(&blocks); - - assert_eq!(block_selection_state.get_active_block(), &blocks[0]); - - block_selection_state.next(); - - assert_eq!(block_selection_state.get_active_block(), &blocks[1]); - - block_selection_state.next(); - - assert_eq!(block_selection_state.get_active_block(), &blocks[0]); - } - - #[test] - fn test_block_selection_state_previous() { - let blocks = [ - ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieSelectMonitor, - ]; - let mut block_selection_state = BlockSelectionState::new(&blocks); - - assert_eq!(block_selection_state.get_active_block(), &blocks[0]); - - block_selection_state.previous(); - - assert_eq!(block_selection_state.get_active_block(), &blocks[1]); - - block_selection_state.previous(); - - assert_eq!(block_selection_state.get_active_block(), &blocks[0]); - } - - fn create_test_tab_routes() -> Vec { - vec![ - TabRoute { - title: "Test 1", - route: ActiveRadarrBlock::Movies.into(), - help: "Help for Test 1", - contextual_help: Some("Contextual Help for Test 1"), - }, - TabRoute { - title: "Test 2", - route: ActiveRadarrBlock::Collections.into(), - help: "Help for Test 2", - contextual_help: Some("Contextual Help for Test 2"), - }, - ] - } - - fn create_test_stateful_table() -> StatefulTable<&'static str> { - let mut stateful_table = StatefulTable::default(); - stateful_table.set_items(vec!["Test 1", "Test 2"]); - - stateful_table - } -} diff --git a/src/models/model_tests.rs b/src/models/model_tests.rs new file mode 100644 index 0000000..ff569c2 --- /dev/null +++ b/src/models/model_tests.rs @@ -0,0 +1,528 @@ +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use pretty_assertions::{assert_eq, assert_str_eq}; + + use crate::app::radarr::ActiveRadarrBlock; + use crate::models::{ + BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulTable, + TabRoute, TabState, + }; + + const BLOCKS: [ActiveRadarrBlock; 6] = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieTagsInput, + ActiveRadarrBlock::AddMovieConfirmPrompt, + ]; + + #[test] + fn test_stateful_table_scroll() { + let mut stateful_table = create_test_stateful_table(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.scroll_down(); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.scroll_down(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.scroll_up(); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.scroll_up(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.scroll_to_bottom(); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.scroll_to_top(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + } + + #[test] + fn test_stateful_table_set_items() { + let items_vec = vec!["Test 1", "Test 2", "Test 3"]; + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + + stateful_table.set_items(items_vec.clone()); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.state.select(Some(1)); + stateful_table.set_items(items_vec.clone()); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.state.select(Some(3)); + stateful_table.set_items(items_vec); + + assert_eq!(stateful_table.state.selected(), Some(2)); + } + + #[test] + fn test_stateful_table_current_selection() { + let mut stateful_table = create_test_stateful_table(); + + assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[0]); + + stateful_table.state.select(Some(1)); + + assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]); + } + + #[test] + fn test_stateful_table_select_index() { + let mut stateful_table = create_test_stateful_table(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.select_index(Some(1)); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.select_index(None); + + assert_eq!(stateful_table.state.selected(), None); + } + + #[test] + fn test_stateful_table_scroll_up() { + let mut stateful_table = create_test_stateful_table(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + + stateful_table.scroll_up(); + + assert_eq!(stateful_table.state.selected(), Some(1)); + + stateful_table.scroll_up(); + + assert_eq!(stateful_table.state.selected(), Some(0)); + } + + #[test] + fn test_scrollable_text_with_string() { + let scrollable_text = ScrollableText::with_string("Test \n String \n".to_owned()); + + assert_eq!(scrollable_text.items.len(), 3); + assert_eq!(scrollable_text.items, vec!["Test ", " String ", ""]); + assert_eq!(scrollable_text.offset, 0); + } + + #[test] + fn test_scrollable_text_get_text() { + let test_text = "Test \nString"; + let scrollable_text = ScrollableText::with_string(test_text.to_owned()); + + assert_str_eq!(scrollable_text.get_text(), test_text); + } + + #[test] + fn test_scrollable_text_scroll() { + let mut scrollable_text = ScrollableText::with_string("Test \nString".to_owned()); + + scrollable_text.scroll_down(); + + assert_eq!(scrollable_text.offset, 1); + + scrollable_text.scroll_down(); + + assert_eq!(scrollable_text.offset, 1); + + scrollable_text.scroll_up(); + + assert_eq!(scrollable_text.offset, 0); + + scrollable_text.scroll_up(); + + assert_eq!(scrollable_text.offset, 0); + + scrollable_text.scroll_to_bottom(); + + assert_eq!(scrollable_text.offset, 1); + + scrollable_text.scroll_to_top(); + + assert_eq!(scrollable_text.offset, 0); + } + + #[test] + fn test_horizontally_scrollable_text_from() { + let test_text = "Test string"; + let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + assert_str_eq!(horizontally_scrollable_text.text, test_text); + } + + #[test] + fn test_horizontally_scrollable_text_to_string() { + let test_text = "Test string"; + let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + + assert_str_eq!(horizontally_scrollable_text.to_string(), test_text); + + let horizontally_scrollable_text = HorizontallyScrollableText { + text: test_text.to_owned(), + offset: RefCell::new(test_text.len() - 1), + }; + + assert_str_eq!(horizontally_scrollable_text.to_string(), "g"); + + let horizontally_scrollable_text = HorizontallyScrollableText { + text: test_text.to_owned(), + offset: RefCell::new(test_text.len()), + }; + + assert!(horizontally_scrollable_text.to_string().is_empty()); + } + + #[test] + fn test_horizontally_scrollable_text_new() { + let test_text = "Test string"; + let horizontally_scrollable_text = HorizontallyScrollableText::new(test_text.to_owned()); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + assert_str_eq!(horizontally_scrollable_text.text, test_text); + } + + #[test] + fn test_horizontally_scrollable_text_scroll_text_left() { + let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + for i in 1..horizontally_scrollable_text.text.len() - 1 { + horizontally_scrollable_text.scroll_left(); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), i); + } + + horizontally_scrollable_text.scroll_left(); + + assert_eq!( + *horizontally_scrollable_text.offset.borrow(), + horizontally_scrollable_text.text.len() - 1 + ); + } + + #[test] + fn test_horizontally_scrollable_text_scroll_text_right() { + let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); + *horizontally_scrollable_text.offset.borrow_mut() = horizontally_scrollable_text.text.len(); + + for i in 1..horizontally_scrollable_text.text.len() { + horizontally_scrollable_text.scroll_right(); + + assert_eq!( + *horizontally_scrollable_text.offset.borrow(), + horizontally_scrollable_text.text.len() - i + ); + } + + horizontally_scrollable_text.scroll_right(); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + } + + #[test] + fn test_horizontally_scrollable_text_scroll_home() { + let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); + + horizontally_scrollable_text.scroll_home(); + + assert_eq!( + *horizontally_scrollable_text.offset.borrow(), + horizontally_scrollable_text.text.len() + ); + } + + #[test] + fn test_horizontally_scrollable_text_reset_offset() { + let horizontally_scrollable_text = HorizontallyScrollableText { + text: "Test string".to_owned(), + offset: RefCell::new(1), + }; + + horizontally_scrollable_text.reset_offset(); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + } + + #[test] + fn test_horizontally_scrollable_text_scroll_or_reset() { + let width = 3; + let test_text = "Test string"; + let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + + horizontally_scrollable_text.scroll_left_or_reset(width, true, true); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); + + horizontally_scrollable_text.scroll_left_or_reset(width, false, true); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + horizontally_scrollable_text.scroll_left_or_reset(width, true, false); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + horizontally_scrollable_text.scroll_left_or_reset(width, true, true); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); + + horizontally_scrollable_text.scroll_left_or_reset(test_text.len(), false, true); + + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + } + + #[test] + fn test_horizontally_scrollable_test_scroll_or_reset_resets_when_text_unselected() { + let horizontally_scrollable_test = HorizontallyScrollableText::from("Test string".to_owned()); + horizontally_scrollable_test.scroll_left(); + + assert_eq!(*horizontally_scrollable_test.offset.borrow(), 1); + + horizontally_scrollable_test.scroll_left_or_reset(3, false, false); + + assert_eq!(*horizontally_scrollable_test.offset.borrow(), 0); + } + + #[test] + fn test_horizontally_scrollable_text_drain() { + let test_text = "Test string"; + let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + + assert_str_eq!(horizontally_scrollable_text.drain(), test_text); + assert!(horizontally_scrollable_text.text.is_empty()); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + } + + #[test] + fn test_horizontally_scrollable_text_pop() { + let test_text = "Test string"; + let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + horizontally_scrollable_text.pop(); + + assert_str_eq!(horizontally_scrollable_text.text, "Test strin"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + horizontally_scrollable_text.scroll_left(); + horizontally_scrollable_text.pop(); + + assert_str_eq!(horizontally_scrollable_text.text, "Test strn"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); + + horizontally_scrollable_text.scroll_right(); + horizontally_scrollable_text.scroll_right(); + horizontally_scrollable_text.pop(); + + assert_str_eq!(horizontally_scrollable_text.text, "Test str"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + horizontally_scrollable_text.scroll_home(); + horizontally_scrollable_text.pop(); + + assert_str_eq!(horizontally_scrollable_text.text, "Test str"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 8); + } + + #[test] + fn test_horizontally_scrollable_text_push() { + let test_text = "Test string"; + let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); + horizontally_scrollable_text.push('h'); + + assert_str_eq!(horizontally_scrollable_text.text, "Test stringh"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + + horizontally_scrollable_text.scroll_left(); + horizontally_scrollable_text.push('l'); + + assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); + + horizontally_scrollable_text.scroll_right(); + horizontally_scrollable_text.scroll_right(); + horizontally_scrollable_text.push('0'); + + assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh0"); + assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); + } + + #[test] + fn test_tab_state_new() { + let tab_state = TabState::new(create_test_tab_routes()); + + assert_eq!(tab_state.index, 0); + } + + #[test] + fn test_tab_state_set_index() { + let mut tab_state = TabState::new(create_test_tab_routes()); + + let result = tab_state.set_index(1); + + assert_eq!(result, &create_test_tab_routes()[1]); + assert_eq!(tab_state.index, 1); + } + + #[test] + fn test_tab_state_get_active_route() { + let tabs = create_test_tab_routes(); + let second_tab = tabs[1].route; + let tab_state = TabState { tabs, index: 1 }; + + let active_route = tab_state.get_active_route(); + + assert_eq!(active_route, &second_tab); + } + + #[test] + fn test_tab_state_get_active_tab_help() { + let tabs = create_test_tab_routes(); + let second_tab_help = tabs[1].help; + let tab_state = TabState { tabs, index: 1 }; + + let tab_help = tab_state.get_active_tab_help(); + + assert_str_eq!(tab_help, second_tab_help); + } + + #[test] + fn test_tab_state_get_active_tab_contextual_help() { + let tabs = create_test_tab_routes(); + let second_tab_contextual_help = tabs[1].contextual_help.unwrap(); + let tab_state = TabState { tabs, index: 1 }; + + let tab_contextual_help = tab_state.get_active_tab_contextual_help(); + + assert!(tab_contextual_help.is_some()); + assert_str_eq!(tab_contextual_help.unwrap(), second_tab_contextual_help); + } + + #[test] + fn test_tab_state_next() { + let tab_routes = create_test_tab_routes(); + let mut tab_state = TabState::new(create_test_tab_routes()); + + assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); + + tab_state.next(); + + assert_eq!(tab_state.get_active_route(), &tab_routes[1].route); + + tab_state.next(); + + assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); + } + + #[test] + fn test_tab_state_previous() { + let tab_routes = create_test_tab_routes(); + let mut tab_state = TabState::new(create_test_tab_routes()); + + assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); + + tab_state.previous(); + + assert_eq!(tab_state.get_active_route(), &tab_routes[1].route); + + tab_state.previous(); + + assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); + } + + #[test] + fn test_block_selection_state_new() { + let block_selection_state = BlockSelectionState::new(&BLOCKS); + + assert_eq!(block_selection_state.index, 0); + } + + #[test] + fn test_block_selection_state_get_active_block() { + let second_block = BLOCKS[1]; + let block_selection_state = BlockSelectionState { + blocks: &BLOCKS, + index: 1, + }; + + let active_block = block_selection_state.get_active_block(); + + assert_eq!(active_block, &second_block); + } + + #[test] + fn test_block_selection_state_next() { + let blocks = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ]; + let mut block_selection_state = BlockSelectionState::new(&blocks); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + + block_selection_state.next(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[1]); + + block_selection_state.next(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + } + + #[test] + fn test_block_selection_state_previous() { + let blocks = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ]; + let mut block_selection_state = BlockSelectionState::new(&blocks); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + + block_selection_state.previous(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[1]); + + block_selection_state.previous(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + } + + fn create_test_tab_routes() -> Vec { + vec![ + TabRoute { + title: "Test 1", + route: ActiveRadarrBlock::Movies.into(), + help: "Help for Test 1", + contextual_help: Some("Contextual Help for Test 1"), + }, + TabRoute { + title: "Test 2", + route: ActiveRadarrBlock::Collections.into(), + help: "Help for Test 2", + contextual_help: Some("Contextual Help for Test 2"), + }, + ] + } + + fn create_test_stateful_table() -> StatefulTable<&'static str> { + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(vec!["Test 1", "Test 2"]); + + stateful_table + } +} diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 0676632..a20c550 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -8,6 +8,10 @@ use strum_macros::{Display, EnumIter}; use crate::models::HorizontallyScrollableText; +#[cfg(test)] +#[path = "radarr_models_tests.rs"] +mod radarr_models_tests; + #[derive(Deserialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DiskSpace { @@ -404,48 +408,32 @@ impl Monitor { } } -#[cfg(test)] -mod tests { - use pretty_assertions::assert_str_eq; - - use crate::models::radarr_models::{MinimumAvailability, Monitor}; - - #[test] - fn test_minimum_availability_display() { - assert_str_eq!(MinimumAvailability::Tba.to_string(), "tba"); - assert_str_eq!(MinimumAvailability::Announced.to_string(), "announced"); - assert_str_eq!(MinimumAvailability::InCinemas.to_string(), "inCinemas"); - assert_str_eq!(MinimumAvailability::Released.to_string(), "released"); - } - - #[test] - fn test_minimum_availability_to_display_str() { - assert_str_eq!(MinimumAvailability::Tba.to_display_str(), "TBA"); - assert_str_eq!(MinimumAvailability::Announced.to_display_str(), "Announced"); - assert_str_eq!( - MinimumAvailability::InCinemas.to_display_str(), - "In Cinemas" - ); - assert_str_eq!(MinimumAvailability::Released.to_display_str(), "Released"); - } - - #[test] - fn test_monitor_display() { - assert_str_eq!(Monitor::MovieOnly.to_string(), "movieOnly"); - assert_str_eq!( - Monitor::MovieAndCollection.to_string(), - "movieAndCollection" - ); - assert_str_eq!(Monitor::None.to_string(), "none"); - } - - #[test] - fn test_monitor_to_display_str() { - assert_str_eq!(Monitor::MovieOnly.to_display_str(), "Movie only"); - assert_str_eq!( - Monitor::MovieAndCollection.to_display_str(), - "Movie and Collection" - ); - assert_str_eq!(Monitor::None.to_display_str(), "None"); - } +#[derive(Default, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Log { + pub time: DateTime, + pub exception: Option, + pub exception_type: Option, + pub level: Option, + pub logger: Option, + pub message: Option, + pub method: Option, +} + +#[derive(Default, Deserialize, Debug, Eq, PartialEq)] +pub struct LogResponse { + pub records: Vec, +} + +#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derivative(Default)] +#[serde(rename_all = "camelCase")] +pub struct Task { + pub name: String, + pub task_name: String, + #[derivative(Default(value = "Number::from(0)"))] + pub interval: Number, + pub last_execution: DateTime, + pub last_duration: String, + pub next_execution: DateTime, } diff --git a/src/models/radarr_models_tests.rs b/src/models/radarr_models_tests.rs new file mode 100644 index 0000000..ebe3067 --- /dev/null +++ b/src/models/radarr_models_tests.rs @@ -0,0 +1,45 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + use crate::models::radarr_models::{MinimumAvailability, Monitor}; + + #[test] + fn test_minimum_availability_display() { + assert_str_eq!(MinimumAvailability::Tba.to_string(), "tba"); + assert_str_eq!(MinimumAvailability::Announced.to_string(), "announced"); + assert_str_eq!(MinimumAvailability::InCinemas.to_string(), "inCinemas"); + assert_str_eq!(MinimumAvailability::Released.to_string(), "released"); + } + + #[test] + fn test_minimum_availability_to_display_str() { + assert_str_eq!(MinimumAvailability::Tba.to_display_str(), "TBA"); + assert_str_eq!(MinimumAvailability::Announced.to_display_str(), "Announced"); + assert_str_eq!( + MinimumAvailability::InCinemas.to_display_str(), + "In Cinemas" + ); + assert_str_eq!(MinimumAvailability::Released.to_display_str(), "Released"); + } + + #[test] + fn test_monitor_display() { + assert_str_eq!(Monitor::MovieOnly.to_string(), "movieOnly"); + assert_str_eq!( + Monitor::MovieAndCollection.to_string(), + "movieAndCollection" + ); + assert_str_eq!(Monitor::None.to_string(), "none"); + } + + #[test] + fn test_monitor_to_display_str() { + assert_str_eq!(Monitor::MovieOnly.to_display_str(), "Movie only"); + assert_str_eq!( + Monitor::MovieAndCollection.to_display_str(), + "Movie and Collection" + ); + assert_str_eq!(Monitor::None.to_display_str(), "None"); + } +} diff --git a/src/network/mod.rs b/src/network/mod.rs index aa66190..8aef7a7 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -13,9 +13,13 @@ use tokio::sync::{Mutex, MutexGuard}; use crate::app::App; use crate::network::radarr_network::RadarrEvent; -pub(crate) mod radarr_network; +pub mod radarr_network; mod utils; +#[cfg(test)] +#[path = "network_tests.rs"] +mod network_tests; + #[derive(PartialEq, Eq, Debug)] pub enum NetworkEvent { Radarr(RadarrEvent), @@ -150,305 +154,3 @@ pub struct RequestProps { pub body: Option, pub api_token: String, } - -#[cfg(test)] -mod tests { - use std::fmt::Debug; - use std::string::ToString; - use std::sync::Arc; - - use mockito::{Mock, Server, ServerGuard}; - use pretty_assertions::assert_str_eq; - use rstest::rstest; - use serde::{Deserialize, Serialize}; - use tokio::sync::Mutex; - - use crate::app::{App, RadarrConfig}; - use crate::models::HorizontallyScrollableText; - use crate::network::radarr_network::RadarrEvent; - use crate::network::{Network, RequestMethod, RequestProps}; - - #[tokio::test] - async fn test_handle_network_event_radarr_event() { - let mut server = Server::new_async().await; - let radarr_server = server - .mock("GET", "/api/v3/health") - .with_status(200) - .create_async() - .await; - let host = server.host_with_port().split(':').collect::>()[0].to_owned(); - let port = Some( - server.host_with_port().split(':').collect::>()[1] - .parse() - .unwrap(), - ); - let mut app = App::default(); - app.is_loading = true; - let radarr_config = RadarrConfig { - host, - api_token: String::default(), - port, - }; - app.config.radarr = radarr_config; - let app_arc = Arc::new(Mutex::new(app)); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_network_event(RadarrEvent::HealthCheck.into()) - .await; - - radarr_server.assert_async().await; - assert!(!app_arc.lock().await.is_loading); - } - - #[rstest] - #[tokio::test] - async fn test_handle_request_no_response_body( - #[values(RequestMethod::Post, RequestMethod::Put, RequestMethod::Delete)] - request_method: RequestMethod, - ) { - let mut server = Server::new_async().await; - let async_server = server - .mock(&request_method.to_string().to_uppercase(), "/test") - .match_header("X-Api-Key", "test1234") - .with_status(200) - .create_async() - .await; - let app_arc = Arc::new(Mutex::new(App::default())); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::( - RequestProps { - uri: format!("{}/test", server.url()), - method: request_method, - body: Some(Test { - value: "Test".to_owned(), - }), - api_token: "test1234".to_owned(), - }, - |_, _| (), - ) - .await; - - async_server.assert_async().await; - } - - #[rstest] - #[tokio::test] - async fn test_handle_request_with_response_body( - #[values(RequestMethod::Get, RequestMethod::Post)] request_method: RequestMethod, - ) { - let (async_server, app_arc, server) = mock_api(request_method, 200, true).await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::<(), Test>( - RequestProps { - uri: format!("{}/test", server.url()), - method: request_method, - body: None, - api_token: "test1234".to_owned(), - }, - |response, mut app| app.error = HorizontallyScrollableText::from(response.value), - ) - .await; - - async_server.assert_async().await; - assert_str_eq!(app_arc.lock().await.error.text, "Test"); - } - - #[tokio::test] - async fn test_handle_request_get_invalid_body() { - let mut server = Server::new_async().await; - let async_server = server - .mock("GET", "/test") - .match_header("X-Api-Key", "test1234") - .with_status(200) - .with_body(r#"{ "invalid": "INVALID" }"#) - .create_async() - .await; - let app_arc = Arc::new(Mutex::new(App::default())); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::<(), Test>( - RequestProps { - uri: format!("{}/test", server.url()), - method: RequestMethod::Get, - body: None, - api_token: "test1234".to_owned(), - }, - |response, mut app| app.error = HorizontallyScrollableText::from(response.value), - ) - .await; - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .error - .text - .starts_with("Failed to parse response!")); - } - - #[tokio::test] - async fn test_handle_request_failure_to_send_request() { - let app_arc = Arc::new(Mutex::new(App::default())); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::<(), Test>( - RequestProps { - uri: String::default(), - method: RequestMethod::Get, - body: None, - api_token: "test1234".to_owned(), - }, - |response, mut app| app.error = HorizontallyScrollableText::from(response.value), - ) - .await; - - assert!(app_arc - .lock() - .await - .error - .text - .starts_with("Failed to send request.")); - } - - #[rstest] - #[tokio::test] - async fn test_handle_request_non_success_code( - #[values( - RequestMethod::Get, - RequestMethod::Post, - RequestMethod::Put, - RequestMethod::Delete - )] - request_method: RequestMethod, - ) { - let (async_server, app_arc, server) = mock_api(request_method, 404, true).await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::<(), Test>( - RequestProps { - uri: format!("{}/test", server.url()), - method: request_method, - body: None, - api_token: "test1234".to_owned(), - }, - |response, mut app| app.error = HorizontallyScrollableText::from(response.value), - ) - .await; - - async_server.assert_async().await; - assert_str_eq!( - app_arc.lock().await.error.text, - r#"Request failed. Received 404 Not Found response code with body: { "value": "Test" }"# - ); - } - - #[tokio::test] - async fn test_handle_request_non_success_code_empty_response_body() { - let (async_server, app_arc, server) = mock_api(RequestMethod::Post, 404, false).await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_request::<(), Test>( - RequestProps { - uri: format!("{}/test", server.url()), - method: RequestMethod::Post, - body: None, - api_token: "test1234".to_owned(), - }, - |response, mut app| app.error = HorizontallyScrollableText::from(response.value), - ) - .await; - - async_server.assert_async().await; - assert_str_eq!( - app_arc.lock().await.error.text, - r#"Request failed. Received 404 Not Found response code with body: "# - ); - } - - #[rstest] - #[tokio::test] - async fn test_call_api( - #[values( - RequestMethod::Get, - RequestMethod::Post, - RequestMethod::Put, - RequestMethod::Delete - )] - request_method: RequestMethod, - ) { - let mut server = Server::new_async().await; - let mut async_server = server - .mock(&request_method.to_string().to_uppercase(), "/test") - .match_header("X-Api-Key", "test1234") - .with_status(200); - let mut body = None::; - - if request_method == RequestMethod::Post { - async_server = async_server.with_body( - r#"{ - "value": "Test" - }"#, - ); - body = Some(Test { - value: "Test".to_owned(), - }); - } - - async_server = async_server.create_async().await; - let app_arc = Arc::new(Mutex::new(App::default())); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .call_api(RequestProps { - uri: format!("{}/test", server.url()), - method: request_method, - body, - api_token: "test1234".to_owned(), - }) - .await - .send() - .await - .unwrap(); - - async_server.assert_async().await; - } - - #[derive(Serialize, Deserialize, Debug, Default)] - struct Test { - pub value: String, - } - - async fn mock_api<'a>( - method: RequestMethod, - response_status: usize, - has_response_body: bool, - ) -> (Mock, Arc>>, ServerGuard) { - let mut server = Server::new_async().await; - let mut async_server = server - .mock(&method.to_string().to_uppercase(), "/test") - .match_header("X-Api-Key", "test1234") - .with_status(response_status); - - if has_response_body { - async_server = async_server.with_body( - r#"{ - "value": "Test" - }"#, - ); - } - - async_server = async_server.create_async().await; - let app_arc = Arc::new(Mutex::new(App::default())); - - (async_server, app_arc, server) - } -} diff --git a/src/network/network_tests.rs b/src/network/network_tests.rs new file mode 100644 index 0000000..4925523 --- /dev/null +++ b/src/network/network_tests.rs @@ -0,0 +1,301 @@ +#[cfg(test)] +mod tests { + use std::fmt::Debug; + use std::string::ToString; + use std::sync::Arc; + + use mockito::{Mock, Server, ServerGuard}; + use pretty_assertions::assert_str_eq; + use rstest::rstest; + use serde::{Deserialize, Serialize}; + use tokio::sync::Mutex; + + use crate::app::{App, RadarrConfig}; + use crate::models::HorizontallyScrollableText; + use crate::network::radarr_network::RadarrEvent; + use crate::network::{Network, RequestMethod, RequestProps}; + + #[tokio::test] + async fn test_handle_network_event_radarr_event() { + let mut server = Server::new_async().await; + let radarr_server = server + .mock("GET", "/api/v3/health") + .with_status(200) + .create_async() + .await; + let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let port = Some( + server.host_with_port().split(':').collect::>()[1] + .parse() + .unwrap(), + ); + let mut app = App::default(); + app.is_loading = true; + let radarr_config = RadarrConfig { + host, + api_token: String::default(), + port, + }; + app.config.radarr = radarr_config; + let app_arc = Arc::new(Mutex::new(app)); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_network_event(RadarrEvent::HealthCheck.into()) + .await; + + radarr_server.assert_async().await; + assert!(!app_arc.lock().await.is_loading); + } + + #[rstest] + #[tokio::test] + async fn test_handle_request_no_response_body( + #[values(RequestMethod::Post, RequestMethod::Put, RequestMethod::Delete)] + request_method: RequestMethod, + ) { + let mut server = Server::new_async().await; + let async_server = server + .mock(&request_method.to_string().to_uppercase(), "/test") + .match_header("X-Api-Key", "test1234") + .with_status(200) + .create_async() + .await; + let app_arc = Arc::new(Mutex::new(App::default())); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::( + RequestProps { + uri: format!("{}/test", server.url()), + method: request_method, + body: Some(Test { + value: "Test".to_owned(), + }), + api_token: "test1234".to_owned(), + }, + |_, _| (), + ) + .await; + + async_server.assert_async().await; + } + + #[rstest] + #[tokio::test] + async fn test_handle_request_with_response_body( + #[values(RequestMethod::Get, RequestMethod::Post)] request_method: RequestMethod, + ) { + let (async_server, app_arc, server) = mock_api(request_method, 200, true).await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: request_method, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + async_server.assert_async().await; + assert_str_eq!(app_arc.lock().await.error.text, "Test"); + } + + #[tokio::test] + async fn test_handle_request_get_invalid_body() { + let mut server = Server::new_async().await; + let async_server = server + .mock("GET", "/test") + .match_header("X-Api-Key", "test1234") + .with_status(200) + .with_body(r#"{ "invalid": "INVALID" }"#) + .create_async() + .await; + let app_arc = Arc::new(Mutex::new(App::default())); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: RequestMethod::Get, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .error + .text + .starts_with("Failed to parse response!")); + } + + #[tokio::test] + async fn test_handle_request_failure_to_send_request() { + let app_arc = Arc::new(Mutex::new(App::default())); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: String::default(), + method: RequestMethod::Get, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + assert!(app_arc + .lock() + .await + .error + .text + .starts_with("Failed to send request.")); + } + + #[rstest] + #[tokio::test] + async fn test_handle_request_non_success_code( + #[values( + RequestMethod::Get, + RequestMethod::Post, + RequestMethod::Put, + RequestMethod::Delete + )] + request_method: RequestMethod, + ) { + let (async_server, app_arc, server) = mock_api(request_method, 404, true).await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: request_method, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc.lock().await.error.text, + r#"Request failed. Received 404 Not Found response code with body: { "value": "Test" }"# + ); + } + + #[tokio::test] + async fn test_handle_request_non_success_code_empty_response_body() { + let (async_server, app_arc, server) = mock_api(RequestMethod::Post, 404, false).await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: RequestMethod::Post, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc.lock().await.error.text, + r#"Request failed. Received 404 Not Found response code with body: "# + ); + } + + #[rstest] + #[tokio::test] + async fn test_call_api( + #[values( + RequestMethod::Get, + RequestMethod::Post, + RequestMethod::Put, + RequestMethod::Delete + )] + request_method: RequestMethod, + ) { + let mut server = Server::new_async().await; + let mut async_server = server + .mock(&request_method.to_string().to_uppercase(), "/test") + .match_header("X-Api-Key", "test1234") + .with_status(200); + let mut body = None::; + + if request_method == RequestMethod::Post { + async_server = async_server.with_body( + r#"{ + "value": "Test" + }"#, + ); + body = Some(Test { + value: "Test".to_owned(), + }); + } + + async_server = async_server.create_async().await; + let app_arc = Arc::new(Mutex::new(App::default())); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .call_api(RequestProps { + uri: format!("{}/test", server.url()), + method: request_method, + body, + api_token: "test1234".to_owned(), + }) + .await + .send() + .await + .unwrap(); + + async_server.assert_async().await; + } + + #[derive(Serialize, Deserialize, Debug, Default)] + struct Test { + pub value: String, + } + + async fn mock_api<'a>( + method: RequestMethod, + response_status: usize, + has_response_body: bool, + ) -> (Mock, Arc>>, ServerGuard) { + let mut server = Server::new_async().await; + let mut async_server = server + .mock(&method.to_string().to_uppercase(), "/test") + .match_header("X-Api-Key", "test1234") + .with_status(response_status); + + if has_response_body { + async_server = async_server.with_body( + r#"{ + "value": "Test" + }"#, + ); + } + + async_server = async_server.create_async().await; + let app_arc = Arc::new(Mutex::new(App::default())); + + (async_server, app_arc, server) + } +} diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 64cd347..cd78be0 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -10,14 +10,18 @@ use crate::app::radarr::ActiveRadarrBlock; use crate::app::RadarrConfig; use crate::models::radarr_models::{ AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie, - CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Movie, - MovieCommandBody, MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, RootFolder, - SystemStatus, Tag, + CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, LogResponse, + Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, + RootFolder, SystemStatus, Tag, Task, }; -use crate::models::{Route, ScrollableText}; +use crate::models::{Route, Scrollable, ScrollableText}; use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps}; use crate::utils::{convert_runtime, convert_to_gb}; +#[cfg(test)] +#[path = "radarr_network_tests.rs"] +mod radarr_network_tests; + #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum RadarrEvent { AddMovie, @@ -30,6 +34,7 @@ pub enum RadarrEvent { EditCollection, GetCollections, GetDownloads, + GetLogs, GetMovieCredits, GetMovieDetails, GetMovieHistory, @@ -40,6 +45,7 @@ pub enum RadarrEvent { GetRootFolders, GetStatus, GetTags, + GetTasks, HealthCheck, SearchNewMovie, TriggerAutomaticSearch, @@ -54,6 +60,7 @@ impl RadarrEvent { match self { RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection", RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue", + RadarrEvent::GetLogs => "/log", RadarrEvent::AddMovie | RadarrEvent::EditMovie | RadarrEvent::GetMovies @@ -70,6 +77,7 @@ impl RadarrEvent { } RadarrEvent::GetStatus => "/system/status", RadarrEvent::GetTags => "/tag", + RadarrEvent::GetTasks => "/system/task", RadarrEvent::TriggerAutomaticSearch | RadarrEvent::UpdateAndScan | RadarrEvent::UpdateAllMovies @@ -99,6 +107,7 @@ impl<'a, 'b> Network<'a, 'b> { RadarrEvent::EditCollection => self.edit_collection().await, RadarrEvent::GetCollections => self.get_collections().await, RadarrEvent::GetDownloads => self.get_downloads().await, + RadarrEvent::GetLogs => self.get_logs().await, RadarrEvent::GetMovieCredits => self.get_credits().await, RadarrEvent::GetMovieDetails => self.get_movie_details().await, RadarrEvent::GetMovieHistory => self.get_movie_history().await, @@ -109,6 +118,7 @@ impl<'a, 'b> Network<'a, 'b> { RadarrEvent::GetRootFolders => self.get_root_folders().await, RadarrEvent::GetStatus => self.get_status().await, RadarrEvent::GetTags => self.get_tags().await, + RadarrEvent::GetTasks => self.get_tasks().await, RadarrEvent::HealthCheck => self.get_healthcheck().await, RadarrEvent::SearchNewMovie => self.search_movie().await, RadarrEvent::TriggerAutomaticSearch => self.trigger_automatic_search().await, @@ -547,6 +557,28 @@ impl<'a, 'b> Network<'a, 'b> { .await; } + async fn get_logs(&self) { + info!("Fetching Radarr logs"); + + let resource = format!( + "{}?pageSize=1000&sortDirection=descending&sortKey=time", + RadarrEvent::GetLogs.resource() + ); + let request_props = self + .radarr_request_props_from(&resource, RequestMethod::Get, None::<()>) + .await; + + self + .handle_request::<(), LogResponse>(request_props, |log_response, mut app| { + let mut logs = log_response.records; + logs.reverse(); + + app.data.radarr_data.logs.set_items(logs); + app.data.radarr_data.logs.scroll_to_bottom(); + }) + .await; + } + async fn get_downloads(&self) { info!("Fetching Radarr downloads"); @@ -611,6 +643,24 @@ impl<'a, 'b> Network<'a, 'b> { .await; } + async fn get_tasks(&self) { + info!("Fetching Radarr tasks"); + + let request_props = self + .radarr_request_props_from( + RadarrEvent::GetTasks.resource(), + RequestMethod::Get, + None::<()>, + ) + .await; + + self + .handle_request::<(), Vec>(request_props, |tasks_vec, mut app| { + app.data.radarr_data.tasks.set_items(tasks_vec); + }) + .await; + } + async fn add_tag(&self, tag: String) { info!("Adding a new Radarr tag"); @@ -1229,1857 +1279,3 @@ fn get_movie_status(has_file: bool, downloads_vec: &[DownloadRecord], movie_id: "Downloaded".to_owned() } - -#[cfg(test)] -mod test { - use std::sync::Arc; - - use bimap::BiMap; - use chrono::{DateTime, Utc}; - use mockito::{Matcher, Mock, Server, ServerGuard}; - use pretty_assertions::{assert_eq, assert_str_eq}; - use rstest::rstest; - use serde_json::{json, Value}; - use strum::IntoEnumIterator; - use tokio::sync::Mutex; - - use crate::app::radarr::ActiveRadarrBlock; - use crate::models::radarr_models::{ - CollectionMovie, Language, MediaInfo, MinimumAvailability, Monitor, MovieFile, Quality, - QualityWrapper, Rating, RatingsList, - }; - use crate::models::HorizontallyScrollableText; - use crate::App; - - use super::*; - - const MOVIE_JSON: &str = r#"{ - "id": 1, - "title": "Test", - "tmdbId": 1234, - "originalLanguage": { - "name": "English" - }, - "sizeOnDisk": 3543348019, - "status": "Downloaded", - "overview": "Blah blah blah", - "path": "/nfs/movies", - "studio": "21st Century Alex", - "genres": ["cool", "family", "fun"], - "year": 2023, - "monitored": true, - "hasFile": true, - "runtime": 120, - "qualityProfileId": 2222, - "minimumAvailability": "announced", - "certification": "R", - "tags": [1], - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - }, - "movieFile": { - "relativePath": "Test.mkv", - "path": "/nfs/movies/Test.mkv", - "dateAdded": "2022-12-30T07:37:56Z", - "mediaInfo": { - "audioBitrate": 0, - "audioChannels": 7.1, - "audioCodec": "AAC", - "audioLanguages": "eng", - "audioStreamCount": 1, - "videoBitDepth": 10, - "videoBitrate": 0, - "videoCodec": "x265", - "videoFps": 23.976, - "resolution": "1920x804", - "runTime": "2:00:00", - "scanType": "Progressive" - } - }, - "collection": { - "id": 123, - "title": "Test Collection", - "rootFolderPath": "/nfs/movies", - "searchOnAdd": true, - "monitored": true, - "minimumAvailability": "released", - "overview": "Collection blah blah blah", - "qualityProfileId": 2222, - "movies": [ - { - "title": "Test", - "overview": "Collection blah blah blah", - "year": 2023, - "runtime": 120, - "tmdbId": 1234, - "genres": ["cool", "family", "fun"], - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - } - ] - } - }"#; - - #[rstest] - fn test_resource_movie( - #[values( - RadarrEvent::AddMovie, - RadarrEvent::GetMovies, - RadarrEvent::GetMovieDetails, - RadarrEvent::DeleteMovie - )] - event: RadarrEvent, - ) { - assert_str_eq!(event.resource(), "/movie"); - } - - #[rstest] - fn test_resource_release( - #[values(RadarrEvent::GetReleases, RadarrEvent::DownloadRelease)] event: RadarrEvent, - ) { - assert_str_eq!(event.resource(), "/release"); - } - - #[rstest] - fn test_resource_queue( - #[values(RadarrEvent::GetDownloads, RadarrEvent::DeleteDownload)] event: RadarrEvent, - ) { - assert_str_eq!(event.resource(), "/queue"); - } - - #[rstest] - fn test_resource_command( - #[values( - RadarrEvent::TriggerAutomaticSearch, - RadarrEvent::UpdateAndScan, - RadarrEvent::UpdateAllMovies, - RadarrEvent::UpdateDownloads, - RadarrEvent::UpdateCollections - )] - event: RadarrEvent, - ) { - assert_str_eq!(event.resource(), "/command"); - } - - #[rstest] - fn test_resource( - #[values( - RadarrEvent::GetCollections, - RadarrEvent::SearchNewMovie, - RadarrEvent::GetMovieCredits, - RadarrEvent::GetMovieHistory, - RadarrEvent::GetOverview, - RadarrEvent::GetQualityProfiles, - RadarrEvent::GetRootFolders, - RadarrEvent::GetStatus, - RadarrEvent::HealthCheck - )] - event: RadarrEvent, - ) { - let expected_resource = match event { - RadarrEvent::GetCollections => "/collection", - RadarrEvent::SearchNewMovie => "/movie/lookup", - RadarrEvent::GetMovieCredits => "/credit", - RadarrEvent::GetMovieHistory => "/history/movie", - RadarrEvent::GetOverview => "/diskspace", - RadarrEvent::GetQualityProfiles => "/qualityprofile", - RadarrEvent::GetRootFolders => "/rootfolder", - RadarrEvent::GetStatus => "/system/status", - RadarrEvent::HealthCheck => "/health", - _ => "", - }; - - assert_str_eq!(event.resource(), expected_resource); - } - - #[test] - fn test_from_radarr_event() { - assert_eq!( - NetworkEvent::Radarr(RadarrEvent::HealthCheck), - NetworkEvent::from(RadarrEvent::HealthCheck) - ); - } - - #[tokio::test] - async fn test_handle_get_healthcheck_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - None, - RadarrEvent::HealthCheck.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::HealthCheck).await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_get_diskspace_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(json!([ - { - "freeSpace": 1111, - "totalSpace": 2222, - }, - { - "freeSpace": 3333, - "totalSpace": 4444 - } - ])), - RadarrEvent::GetOverview.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetOverview).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.disk_space_vec, - vec![ - DiskSpace { - free_space: Number::from(1111), - total_space: Number::from(2222), - }, - DiskSpace { - free_space: Number::from(3333), - total_space: Number::from(4444), - }, - ] - ); - } - - #[tokio::test] - async fn test_handle_get_status_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(json!({ - "version": "v1", - "startTime": "2023-02-25T20:16:43Z" - })), - RadarrEvent::GetStatus.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetStatus).await; - - async_server.assert_async().await; - assert_str_eq!(app_arc.lock().await.data.radarr_data.version, "v1"); - assert_eq!( - app_arc.lock().await.data.radarr_data.start_time, - DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap()) - as DateTime - ); - } - - #[tokio::test] - async fn test_handle_get_movies_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(format!("[ {} ]", MOVIE_JSON).as_str()).unwrap()), - RadarrEvent::GetMovies.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetMovies).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.movies.items, - vec![movie()] - ); - } - - #[tokio::test] - async fn test_handle_get_releases_event() { - let release_json = json!([{ - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "name": "English" } ], - "quality": { "quality": { "name": "HD - 1080p" }} - }]); - let resource = format!("{}?movieId=1", RadarrEvent::GetReleases.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(release_json), &resource).await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetReleases).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.movie_releases.items, - vec![release()] - ); - } - - #[tokio::test] - async fn test_handle_search_new_movie_event() { - let add_movie_search_result_json = json!([{ - "tmdbId": 1234, - "title": "Test", - "originalLanguage": { "name": "English" }, - "status": "released", - "overview": "New movie blah blah blah", - "genres": ["cool", "family", "fun"], - "year": 2023, - "runtime": 120, - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - }]); - let resource = format!( - "{}?term=test%20term", - RadarrEvent::SearchNewMovie.resource() - ); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(add_movie_search_result_json), - &resource, - ) - .await; - app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::SearchNewMovie) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .radarr_data - .add_searched_movies - .items, - vec![add_movie_search_result()] - ); - } - - #[tokio::test] - async fn test_handle_search_new_movie_event_no_results() { - let resource = format!( - "{}?term=test%20term", - RadarrEvent::SearchNewMovie.resource() - ); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await; - app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::SearchNewMovie) - .await; - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .add_searched_movies - .items - .is_empty()); - assert_eq!( - app_arc.lock().await.get_current_route(), - &ActiveRadarrBlock::AddMovieEmptySearchResults.into() - ); - } - - #[tokio::test] - async fn test_handle_trigger_automatic_search_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "name": "MoviesSearch", - "movieIds": [ 1 ] - })), - None, - RadarrEvent::TriggerAutomaticSearch.resource(), - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::TriggerAutomaticSearch) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_and_scan_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshMovie", - "movieIds": [ 1 ] - })), - None, - RadarrEvent::UpdateAndScan.resource(), - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::UpdateAndScan) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_all_movies_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshMovie", - "movieIds": [] - })), - None, - RadarrEvent::UpdateAllMovies.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::UpdateAllMovies) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_downloads_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshMonitoredDownloads" - })), - None, - RadarrEvent::UpdateDownloads.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::UpdateDownloads) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_collections_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshCollections" - })), - None, - RadarrEvent::UpdateCollections.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::UpdateCollections) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_get_movie_details_event() { - let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(MOVIE_JSON).unwrap()), - &resource, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - app_arc.lock().await.data.radarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetMovieDetails) - .await; - - async_server.assert_async().await; - assert_str_eq!( - app_arc - .lock() - .await - .data - .radarr_data - .movie_details - .get_text(), - formatdoc!( - "Title: Test - Year: 2023 - Runtime: 2h 0m - Rating: R - Collection: Test Collection - Status: Downloaded - Description: Blah blah blah - TMDB: 99% - IMDB: 9.9 - Rotten Tomatoes: - Quality Profile: HD - 1080p - Size: 3.30 GB - Path: /nfs/movies - Studio: 21st Century Alex - Genres: cool, family, fun" - ) - ); - assert_str_eq!( - app_arc.lock().await.data.radarr_data.file_details, - formatdoc!( - "Relative Path: Test.mkv - Absolute Path: /nfs/movies/Test.mkv - Size: 3.30 GB - Date Added: 2022-12-30 07:37:56 UTC" - ) - ); - assert_str_eq!( - app_arc.lock().await.data.radarr_data.audio_details, - formatdoc!( - "Bitrate: 0 - Channels: 7.1 - Codec: AAC - Languages: eng - Stream Count: 1" - ) - ); - assert_str_eq!( - app_arc.lock().await.data.radarr_data.video_details, - formatdoc!( - "Bit Depth: 10 - Bitrate: 0 - Codec: x265 - FPS: 23.976 - Resolution: 1920x804 - Scan Type: Progressive - Runtime: 2:00:00" - ) - ); - } - - #[tokio::test] - async fn test_handle_get_movie_details_event_empty_options_give_correct_defaults() { - let movie_json_with_missing_fields = json!({ - "id": 1, - "title": "Test", - "originalLanguage": { - "name": "English" - }, - "sizeOnDisk": 0, - "status": "Downloaded", - "overview": "Blah blah blah", - "path": "/nfs/movies", - "studio": "21st Century Alex", - "genres": ["cool", "family", "fun"], - "year": 2023, - "monitored": true, - "hasFile": false, - "runtime": 120, - "tmdbId": 1234, - "qualityProfileId": 2222, - "tags": [1], - "minimumAvailability": "released", - "ratings": {} - }); - let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(movie_json_with_missing_fields), - &resource, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - app_arc.lock().await.data.radarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetMovieDetails) - .await; - - async_server.assert_async().await; - assert_str_eq!( - app_arc - .lock() - .await - .data - .radarr_data - .movie_details - .get_text(), - formatdoc!( - "Title: Test - Year: 2023 - Runtime: 2h 0m - Rating: - Collection: - Status: Missing - Description: Blah blah blah - TMDB: - IMDB: - Rotten Tomatoes: - Quality Profile: HD - 1080p - Size: 0.00 GB - Path: /nfs/movies - Studio: 21st Century Alex - Genres: cool, family, fun" - ) - ); - assert!(app_arc - .lock() - .await - .data - .radarr_data - .file_details - .is_empty()); - assert!(app_arc - .lock() - .await - .data - .radarr_data - .audio_details - .is_empty()); - assert!(app_arc - .lock() - .await - .data - .radarr_data - .video_details - .is_empty()); - } - - #[tokio::test] - async fn test_handle_get_movie_history_event() { - let movie_history_item_json = json!([{ - "sourceTitle": "Test", - "quality": { "quality": { "name": "HD - 1080p" }}, - "languages": [ { "name": "English" } ], - "date": "2022-12-30T07:37:56Z", - "eventType": "grabbed" - }]); - let resource = format!("{}?movieId=1", RadarrEvent::GetMovieHistory.resource()); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(movie_history_item_json), - &resource, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetMovieHistory) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.movie_history.items, - vec![movie_history_item()] - ); - } - - #[tokio::test] - async fn test_handle_get_collections_event() { - let collection_json = json!([{ - "id": 123, - "title": "Test Collection", - "rootFolderPath": "/nfs/movies", - "searchOnAdd": true, - "monitored": true, - "minimumAvailability": "released", - "overview": "Collection blah blah blah", - "qualityProfileId": 2222, - "movies": [{ - "title": "Test", - "overview": "Collection blah blah blah", - "year": 2023, - "runtime": 120, - "tmdbId": 1234, - "genres": ["cool", "family", "fun"], - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - }], - }]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(collection_json), - RadarrEvent::GetCollections.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetCollections) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.collections.items, - vec![collection()] - ); - } - - #[tokio::test] - async fn test_handle_get_downloads_event() { - let downloads_response_json = json!({ - "records": [{ - "title": "Test Download Title", - "status": "downloading", - "id": 1, - "movieId": 1, - "size": 3543348019u64, - "sizeleft": 1771674009, - "outputPath": "/nfs/movies/Test", - "indexer": "kickass torrents", - "downloadClient": "transmission", - }] - }); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(downloads_response_json), - RadarrEvent::GetDownloads.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetDownloads).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.downloads.items, - downloads_response().records - ); - } - - #[tokio::test] - async fn test_handle_get_quality_profiles_event() { - let quality_profile_json = json!([{ - "id": 2222, - "name": "HD - 1080p" - }]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(quality_profile_json), - RadarrEvent::GetQualityProfiles.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetQualityProfiles) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.quality_profile_map, - BiMap::from_iter([(2222u64, "HD - 1080p".to_owned())]) - ); - } - - #[tokio::test] - async fn test_handle_get_tags_event() { - let tags_json = json!([{ - "id": 2222, - "label": "usenet" - }]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(tags_json), - RadarrEvent::GetTags.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::GetTags).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.tags_map, - BiMap::from_iter([(2222u64, "usenet".to_owned())]) - ); - } - - #[tokio::test] - async fn test_add_tag() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ "label": "testing" })), - Some(json!({ "id": 3, "label": "testing" })), - RadarrEvent::GetTags.resource(), - ) - .await; - app_arc.lock().await.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.add_tag("testing".to_owned()).await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.tags_map, - BiMap::from_iter([ - (1, "usenet".to_owned()), - (2, "test".to_owned()), - (3, "testing".to_owned()) - ]) - ); - } - - #[tokio::test] - async fn test_handle_get_root_folders_event() { - let root_folder_json = json!([{ - "id": 1, - "path": "/nfs", - "accessible": true, - "freeSpace": 219902325555200u64, - }]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(root_folder_json), - RadarrEvent::GetRootFolders.resource(), - ) - .await; - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetRootFolders) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.root_folders.items, - vec![root_folder()] - ); - } - - #[tokio::test] - async fn test_handle_get_movie_credits_event() { - let credits_json = json!([ - { - "personName": "Madison Clarke", - "character": "Johnny Blaze", - "type": "cast", - }, - { - "personName": "Alex Clarke", - "department": "Music", - "job": "Composition", - "type": "crew", - } - ]); - let resource = format!("{}?movieId=1", RadarrEvent::GetMovieCredits.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(credits_json), &resource).await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::GetMovieCredits) - .await; - - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.movie_cast.items, - vec![cast_credit()] - ); - assert_eq!( - app_arc.lock().await.data.radarr_data.movie_crew.items, - vec![crew_credit()] - ); - } - - #[tokio::test] - async fn test_handle_delete_movie_event() { - let resource = format!( - "{}/1?deleteFiles=true&addImportExclusion=true", - RadarrEvent::DeleteMovie.resource() - ); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.movies.set_items(vec![movie()]); - app.data.radarr_data.delete_movie_files = true; - app.data.radarr_data.add_list_exclusion = true; - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::DeleteMovie).await; - - async_server.assert_async().await; - assert!(!app_arc.lock().await.data.radarr_data.delete_movie_files); - assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion); - } - - #[tokio::test] - async fn test_handle_delete_download_event() { - let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; - app_arc - .lock() - .await - .data - .radarr_data - .downloads - .set_items(vec![download_record()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::DeleteDownload) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_root_folder_event() { - let resource = format!("{}/1", RadarrEvent::DeleteRootFolder.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; - app_arc - .lock() - .await - .data - .radarr_data - .root_folders - .set_items(vec![root_folder()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::DeleteRootFolder) - .await; - - async_server.assert_async().await; - } - - #[rstest] - #[tokio::test] - async fn test_handle_add_movie_event(#[values(true, false)] movie_details_context: bool) { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "tmdbId": 1234, - "title": "Test", - "rootFolderPath": "/nfs", - "minimumAvailability": "announced", - "monitored": true, - "qualityProfileId": 2222, - "tags": [1, 2], - "addOptions": { - "monitor": "movieOnly", - "searchForMovie": true - } - })), - None, - RadarrEvent::AddMovie.resource(), - ) - .await; - - { - let mut app = app_arc.lock().await; - app.data.radarr_data.root_folders.set_items(vec![ - RootFolder { - id: Number::from(1), - path: "/nfs".to_owned(), - accessible: true, - free_space: Number::from(219902325555200u64), - unmapped_folders: None, - }, - RootFolder { - id: Number::from(2), - path: "/nfs2".to_owned(), - accessible: true, - free_space: Number::from(21990232555520u64), - unmapped_folders: None, - }, - ]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into(); - app - .data - .radarr_data - .quality_profile_list - .set_items(vec!["HD - 1080p".to_owned()]); - app - .data - .radarr_data - .monitor_list - .set_items(Vec::from_iter(Monitor::iter())); - app - .data - .radarr_data - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - if movie_details_context { - app - .data - .radarr_data - .collection_movies - .set_items(vec![collection_movie()]); - app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); - } else { - app - .data - .radarr_data - .add_searched_movies - .set_items(vec![add_movie_search_result()]); - } - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::AddMovie).await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_add_root_folder_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "path": "/nfs/test" - })), - None, - RadarrEvent::AddRootFolder.resource(), - ) - .await; - - app_arc.lock().await.data.radarr_data.edit_path = - HorizontallyScrollableText::from("/nfs/test".to_owned()); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::AddRootFolder) - .await; - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .edit_path - .text - .is_empty()); - } - - #[tokio::test] - async fn test_handle_edit_movie_event() { - let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); - *expected_body.get_mut("monitored").unwrap() = json!(false); - *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); - *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); - *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); - *expected_body.get_mut("tags").unwrap() = json!([1, 2]); - - let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); - let (async_details_server, app_arc, mut server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(MOVIE_JSON).unwrap()), - &resource, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!("/api/v3{}/1", RadarrEvent::EditMovie.resource()).as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into(); - app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned().into(); - app.data.radarr_data.edit_monitored = Some(false); - app - .data - .radarr_data - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - app - .data - .radarr_data - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.movies.set_items(vec![Movie { - monitored: false, - ..movie() - }]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - network.handle_radarr_event(RadarrEvent::EditMovie).await; - - async_details_server.assert_async().await; - async_edit_server.assert_async().await; - - let app = app_arc.lock().await; - assert!(app.response.is_empty()); - assert!(app.data.radarr_data.edit_path.text.is_empty()); - assert!(app.data.radarr_data.edit_tags.text.is_empty()); - assert!(app.data.radarr_data.movie_details.items.is_empty()); - } - - #[tokio::test] - async fn test_handle_edit_collection_event() { - let detailed_collection_body = json!({ - "id": 123, - "title": "Test Collection", - "rootFolderPath": "/nfs/movies", - "searchOnAdd": true, - "monitored": true, - "minimumAvailability": "released", - "overview": "Collection blah blah blah", - "qualityProfileId": 2222, - "movies": [ - { - "title": "Test", - "overview": "Collection blah blah blah", - "year": 2023, - "runtime": 120, - "tmdbId": 1234, - "genres": ["cool", "family", "fun"], - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - } - ] - }); - let mut expected_body = detailed_collection_body.clone(); - *expected_body.get_mut("monitored").unwrap() = json!(false); - *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); - *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); - *expected_body.get_mut("rootFolderPath").unwrap() = json!("/nfs/Test Path"); - *expected_body.get_mut("searchOnAdd").unwrap() = json!(false); - - let resource = format!("{}/123", RadarrEvent::GetCollections.resource()); - let (async_details_server, app_arc, mut server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(detailed_collection_body), - &resource, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!("/api/v3{}/123", RadarrEvent::EditCollection.resource()).as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned().into(); - app.data.radarr_data.edit_monitored = Some(false); - app.data.radarr_data.edit_search_on_add = Some(false); - app - .data - .radarr_data - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - app - .data - .radarr_data - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.collections.set_items(vec![Collection { - monitored: false, - search_on_add: false, - ..collection() - }]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::EditCollection) - .await; - - async_details_server.assert_async().await; - async_edit_server.assert_async().await; - - let app = app_arc.lock().await; - assert!(app.response.is_empty()); - assert!(app.data.radarr_data.edit_path.text.is_empty()); - assert!(app.data.radarr_data.movie_details.items.is_empty()); - } - - #[tokio::test] - async fn test_handle_download_release_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ - "guid": "1234", - "indexerId": 2 - })), - None, - RadarrEvent::DownloadRelease.resource(), - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movie_releases - .set_items(vec![release()]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - network - .handle_radarr_event(RadarrEvent::DownloadRelease) - .await; - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_extract_quality_profile_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - { - let mut app = app_arc.lock().await; - app - .data - .radarr_data - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1, "Any".to_owned()), (2, "HD - 1080p".to_owned())]); - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_quality_profile_id().await, 1); - } - - #[tokio::test] - async fn test_extract_and_add_tag_ids_vec() { - let app_arc = Arc::new(Mutex::new(App::default())); - { - let mut app = app_arc.lock().await; - app.data.radarr_data.edit_tags = " test,hi ,, usenet ".to_owned().into(); - app.data.radarr_data.tags_map = BiMap::from_iter([ - (1, "usenet".to_owned()), - (2, "test".to_owned()), - (3, "hi".to_owned()), - ]); - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_and_add_tag_ids_vec().await, vec![2, 3, 1]); - } - - #[tokio::test] - async fn test_extract_and_add_tag_ids_vec_add_missing_tags_first() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Post, - Some(json!({ "label": "testing" })), - Some(json!({ "id": 3, "label": "testing" })), - RadarrEvent::GetTags.resource(), - ) - .await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.edit_tags = "usenet, test, testing".to_owned().into(); - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]); - } - let network = Network::new(reqwest::Client::new(), &app_arc); - - let tag_ids_vec = network.extract_and_add_tag_ids_vec().await; - - async_server.assert_async().await; - assert_eq!(tag_ids_vec, vec![1, 2, 3]); - assert_eq!( - app_arc.lock().await.data.radarr_data.tags_map, - BiMap::from_iter([ - (1, "usenet".to_owned()), - (2, "test".to_owned()), - (3, "testing".to_owned()) - ]) - ); - } - - #[tokio::test] - async fn test_extract_movie_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![Movie { - id: Number::from(1), - ..Movie::default() - }]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_movie_id().await, 1); - } - - #[tokio::test] - async fn test_extract_movie_id_filtered_movies() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .filtered_movies - .set_items(vec![Movie { - id: Number::from(1), - ..Movie::default() - }]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_movie_id().await, 1); - } - - #[tokio::test] - async fn test_extract_collection_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .collections - .set_items(vec![Collection { - id: Number::from(1), - ..Collection::default() - }]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_collection_id().await, 1); - } - - #[tokio::test] - async fn test_extract_collection_id_filtered_collection() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .filtered_collections - .set_items(vec![Collection { - id: Number::from(1), - ..Collection::default() - }]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_eq!(network.extract_collection_id().await, 1); - } - - #[tokio::test] - async fn test_append_movie_id_param() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![Movie { - id: Number::from(1), - ..Movie::default() - }]); - let network = Network::new(reqwest::Client::new(), &app_arc); - - assert_str_eq!( - network.append_movie_id_param("/test").await, - "/test?movieId=1" - ); - } - - #[tokio::test] - async fn test_radarr_request_props_from_default_radarr_config() { - let app_arc = Arc::new(Mutex::new(App::default())); - let network = Network::new(reqwest::Client::new(), &app_arc); - - let request_props = network - .radarr_request_props_from("/test", RequestMethod::Get, None::<()>) - .await; - - assert_str_eq!(request_props.uri, "http://localhost:7878/api/v3/test"); - assert_eq!(request_props.method, RequestMethod::Get); - assert_eq!(request_props.body, None); - assert!(request_props.api_token.is_empty()); - - app_arc.lock().await.config.radarr = RadarrConfig { - host: "192.168.0.123".to_owned(), - port: Some(8080), - api_token: "testToken1234".to_owned(), - }; - } - - #[tokio::test] - async fn test_radarr_request_props_from_custom_radarr_config() { - let api_token = "testToken1234".to_owned(); - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc.lock().await.config.radarr = RadarrConfig { - host: "192.168.0.123".to_owned(), - port: Some(8080), - api_token: api_token.clone(), - }; - let network = Network::new(reqwest::Client::new(), &app_arc); - - let request_props = network - .radarr_request_props_from("/test", RequestMethod::Get, None::<()>) - .await; - - assert_str_eq!(request_props.uri, "http://192.168.0.123:8080/api/v3/test"); - assert_eq!(request_props.method, RequestMethod::Get); - assert_eq!(request_props.body, None); - assert_str_eq!(request_props.api_token, api_token); - } - - #[test] - fn test_get_movie_status_downloaded() { - assert_str_eq!(get_movie_status(true, &[], Number::from(0)), "Downloaded"); - } - - #[test] - fn test_get_movie_status_missing() { - let download_record = DownloadRecord { - movie_id: 1.into(), - ..DownloadRecord::default() - }; - - assert_str_eq!( - get_movie_status(false, &[download_record.clone()], 0.into()), - "Missing" - ); - - assert_str_eq!( - get_movie_status(false, &[download_record], 1.into()), - "Missing" - ); - } - - #[test] - fn test_get_movie_status_downloading() { - assert_str_eq!( - get_movie_status( - false, - &[DownloadRecord { - movie_id: 1.into(), - status: "downloading".to_owned(), - ..DownloadRecord::default() - }], - 1.into() - ), - "Downloading" - ); - } - - #[test] - fn test_get_movie_status_awaiting_import() { - assert_str_eq!( - get_movie_status( - false, - &[DownloadRecord { - movie_id: 1.into(), - status: "completed".to_owned(), - ..DownloadRecord::default() - }], - 1.into() - ), - "Awaiting Import" - ); - } - - async fn mock_radarr_api( - method: RequestMethod, - request_body: Option, - response_body: Option, - resource: &str, - ) -> (Mock, Arc>>, ServerGuard) { - let mut server = Server::new_async().await; - let mut async_server = server - .mock( - &method.to_string().to_uppercase(), - format!("/api/v3{}", resource).as_str(), - ) - .match_header("X-Api-Key", "test1234"); - - if let Some(body) = request_body { - async_server = async_server.match_body(Matcher::Json(body)); - } - - if let Some(body) = response_body { - async_server = async_server.with_body(body.to_string()); - } - - async_server = async_server.create_async().await; - - let host = server.host_with_port().split(':').collect::>()[0].to_owned(); - let port = Some( - server.host_with_port().split(':').collect::>()[1] - .parse() - .unwrap(), - ); - let mut app = App::default(); - let radarr_config = RadarrConfig { - host, - port, - api_token: "test1234".to_owned(), - }; - app.config.radarr = radarr_config; - let app_arc = Arc::new(Mutex::new(app)); - - (async_server, app_arc, server) - } - - fn language() -> Language { - Language { - name: "English".to_owned(), - } - } - - fn genres() -> Vec { - vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()] - } - - fn rating() -> Rating { - Rating { - value: Number::from_f64(9.9).unwrap(), - } - } - - fn ratings_list() -> RatingsList { - RatingsList { - imdb: Some(rating()), - tmdb: Some(rating()), - rotten_tomatoes: Some(rating()), - } - } - - fn media_info() -> MediaInfo { - MediaInfo { - audio_bitrate: Number::from(0), - audio_channels: Number::from_f64(7.1).unwrap(), - audio_codec: Some("AAC".to_owned()), - audio_languages: Some("eng".to_owned()), - audio_stream_count: Number::from(1), - video_bit_depth: Number::from(10), - video_bitrate: Number::from(0), - video_codec: "x265".to_owned(), - video_fps: Number::from_f64(23.976).unwrap(), - resolution: "1920x804".to_owned(), - run_time: "2:00:00".to_owned(), - scan_type: "Progressive".to_owned(), - } - } - - fn movie_file() -> MovieFile { - MovieFile { - relative_path: "Test.mkv".to_owned(), - path: "/nfs/movies/Test.mkv".to_owned(), - date_added: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()), - media_info: Some(media_info()), - } - } - - fn collection_movie() -> CollectionMovie { - CollectionMovie { - title: "Test".to_owned().into(), - overview: "Collection blah blah blah".to_owned(), - year: Number::from(2023), - runtime: Number::from(120), - tmdb_id: Number::from(1234), - genres: genres(), - ratings: ratings_list(), - } - } - - fn collection() -> Collection { - Collection { - id: Number::from(123), - title: "Test Collection".to_owned().into(), - root_folder_path: Some("/nfs/movies".to_owned()), - search_on_add: true, - monitored: true, - minimum_availability: MinimumAvailability::Released, - overview: Some("Collection blah blah blah".to_owned()), - quality_profile_id: Number::from(2222), - movies: Some(vec![collection_movie()]), - } - } - - fn movie() -> Movie { - Movie { - id: Number::from(1), - title: "Test".to_owned().into(), - original_language: language(), - size_on_disk: Number::from(3543348019u64), - status: "Downloaded".to_owned(), - overview: "Blah blah blah".to_owned(), - path: "/nfs/movies".to_owned(), - studio: "21st Century Alex".to_owned(), - genres: genres(), - year: Number::from(2023), - monitored: true, - has_file: true, - runtime: Number::from(120), - tmdb_id: Number::from(1234), - quality_profile_id: Number::from(2222), - minimum_availability: MinimumAvailability::Announced, - certification: Some("R".to_owned()), - tags: vec![Number::from(1)], - ratings: ratings_list(), - movie_file: Some(movie_file()), - collection: Some(collection()), - } - } - - fn rejections() -> Vec { - vec![ - "Unknown quality profile".to_owned(), - "Release is already mapped".to_owned(), - ] - } - - fn quality() -> Quality { - Quality { - name: "HD - 1080p".to_owned(), - } - } - - fn quality_wrapper() -> QualityWrapper { - QualityWrapper { quality: quality() } - } - - fn release() -> Release { - Release { - guid: "1234".to_owned(), - protocol: "torrent".to_owned(), - age: Number::from(1), - title: HorizontallyScrollableText::from("Test Release".to_owned()), - indexer: "kickass torrents".to_owned(), - indexer_id: Number::from(2), - size: Number::from(1234), - rejected: true, - rejections: Some(rejections()), - seeders: Some(Number::from(2)), - leechers: Some(Number::from(1)), - languages: Some(vec![language()]), - quality: quality_wrapper(), - } - } - - fn add_movie_search_result() -> AddMovieSearchResult { - AddMovieSearchResult { - tmdb_id: Number::from(1234), - title: HorizontallyScrollableText::from("Test".to_owned()), - original_language: language(), - status: "released".to_owned(), - overview: "New movie blah blah blah".to_owned(), - genres: genres(), - year: Number::from(2023), - runtime: Number::from(120), - ratings: ratings_list(), - } - } - - fn movie_history_item() -> MovieHistoryItem { - MovieHistoryItem { - source_title: HorizontallyScrollableText::from("Test".to_owned()), - quality: quality_wrapper(), - languages: vec![language()], - date: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()), - event_type: "grabbed".to_owned(), - } - } - - fn download_record() -> DownloadRecord { - DownloadRecord { - title: "Test Download Title".to_owned(), - status: "downloading".to_owned(), - id: Number::from(1), - movie_id: Number::from(1), - size: Number::from(3543348019u64), - sizeleft: Number::from(1771674009u64), - output_path: Some(HorizontallyScrollableText::from( - "/nfs/movies/Test".to_owned(), - )), - indexer: "kickass torrents".to_owned(), - download_client: "transmission".to_owned(), - } - } - - fn downloads_response() -> DownloadsResponse { - DownloadsResponse { - records: vec![download_record()], - } - } - - fn root_folder() -> RootFolder { - RootFolder { - id: Number::from(1), - path: "/nfs".to_owned(), - accessible: true, - free_space: Number::from(219902325555200u64), - unmapped_folders: None, - } - } - - fn cast_credit() -> Credit { - Credit { - person_name: "Madison Clarke".to_owned(), - character: Some("Johnny Blaze".to_owned()), - department: None, - job: None, - credit_type: CreditType::Cast, - } - } - - fn crew_credit() -> Credit { - Credit { - person_name: "Alex Clarke".to_owned(), - character: None, - department: Some("Music".to_owned()), - job: Some("Composition".to_owned()), - credit_type: CreditType::Crew, - } - } -} diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs new file mode 100644 index 0000000..73dec4e --- /dev/null +++ b/src/network/radarr_network_tests.rs @@ -0,0 +1,1986 @@ +#[cfg(test)] +mod test { + use std::sync::Arc; + + use bimap::BiMap; + use chrono::{DateTime, Utc}; + use mockito::{Matcher, Mock, Server, ServerGuard}; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use serde_json::{json, Value}; + use strum::IntoEnumIterator; + use tokio::sync::Mutex; + + use crate::app::radarr::ActiveRadarrBlock; + use crate::models::radarr_models::{ + CollectionMovie, Language, Log, MediaInfo, MinimumAvailability, Monitor, MovieFile, Quality, + QualityWrapper, Rating, RatingsList, + }; + use crate::models::HorizontallyScrollableText; + use crate::App; + + use super::super::*; + + const MOVIE_JSON: &str = r#"{ + "id": 1, + "title": "Test", + "tmdbId": 1234, + "originalLanguage": { + "name": "English" + }, + "sizeOnDisk": 3543348019, + "status": "Downloaded", + "overview": "Blah blah blah", + "path": "/nfs/movies", + "studio": "21st Century Alex", + "genres": ["cool", "family", "fun"], + "year": 2023, + "monitored": true, + "hasFile": true, + "runtime": 120, + "qualityProfileId": 2222, + "minimumAvailability": "announced", + "certification": "R", + "tags": [1], + "ratings": { + "imdb": { + "value": 9.9 + }, + "tmdb": { + "value": 9.9 + }, + "rottenTomatoes": { + "value": 9.9 + } + }, + "movieFile": { + "relativePath": "Test.mkv", + "path": "/nfs/movies/Test.mkv", + "dateAdded": "2022-12-30T07:37:56Z", + "mediaInfo": { + "audioBitrate": 0, + "audioChannels": 7.1, + "audioCodec": "AAC", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 10, + "videoBitrate": 0, + "videoCodec": "x265", + "videoFps": 23.976, + "resolution": "1920x804", + "runTime": "2:00:00", + "scanType": "Progressive" + } + }, + "collection": { + "id": 123, + "title": "Test Collection", + "rootFolderPath": "/nfs/movies", + "searchOnAdd": true, + "monitored": true, + "minimumAvailability": "released", + "overview": "Collection blah blah blah", + "qualityProfileId": 2222, + "movies": [ + { + "title": "Test", + "overview": "Collection blah blah blah", + "year": 2023, + "runtime": 120, + "tmdbId": 1234, + "genres": ["cool", "family", "fun"], + "ratings": { + "imdb": { + "value": 9.9 + }, + "tmdb": { + "value": 9.9 + }, + "rottenTomatoes": { + "value": 9.9 + } + } + } + ] + } + }"#; + + #[rstest] + fn test_resource_movie( + #[values( + RadarrEvent::AddMovie, + RadarrEvent::GetMovies, + RadarrEvent::GetMovieDetails, + RadarrEvent::DeleteMovie + )] + event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/movie"); + } + + #[rstest] + fn test_resource_release( + #[values(RadarrEvent::GetReleases, RadarrEvent::DownloadRelease)] event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/release"); + } + + #[rstest] + fn test_resource_queue( + #[values(RadarrEvent::GetDownloads, RadarrEvent::DeleteDownload)] event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/queue"); + } + + #[rstest] + fn test_resource_command( + #[values( + RadarrEvent::TriggerAutomaticSearch, + RadarrEvent::UpdateAndScan, + RadarrEvent::UpdateAllMovies, + RadarrEvent::UpdateDownloads, + RadarrEvent::UpdateCollections + )] + event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/command"); + } + + #[rstest] + fn test_resource( + #[values( + RadarrEvent::GetCollections, + RadarrEvent::SearchNewMovie, + RadarrEvent::GetMovieCredits, + RadarrEvent::GetMovieHistory, + RadarrEvent::GetOverview, + RadarrEvent::GetQualityProfiles, + RadarrEvent::GetRootFolders, + RadarrEvent::GetStatus, + RadarrEvent::HealthCheck + )] + event: RadarrEvent, + ) { + let expected_resource = match event { + RadarrEvent::GetCollections => "/collection", + RadarrEvent::SearchNewMovie => "/movie/lookup", + RadarrEvent::GetMovieCredits => "/credit", + RadarrEvent::GetMovieHistory => "/history/movie", + RadarrEvent::GetOverview => "/diskspace", + RadarrEvent::GetQualityProfiles => "/qualityprofile", + RadarrEvent::GetRootFolders => "/rootfolder", + RadarrEvent::GetStatus => "/system/status", + RadarrEvent::HealthCheck => "/health", + _ => "", + }; + + assert_str_eq!(event.resource(), expected_resource); + } + + #[test] + fn test_from_radarr_event() { + assert_eq!( + NetworkEvent::Radarr(RadarrEvent::HealthCheck), + NetworkEvent::from(RadarrEvent::HealthCheck) + ); + } + + #[tokio::test] + async fn test_handle_get_healthcheck_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + None, + RadarrEvent::HealthCheck.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::HealthCheck).await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_get_diskspace_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(json!([ + { + "freeSpace": 1111, + "totalSpace": 2222, + }, + { + "freeSpace": 3333, + "totalSpace": 4444 + } + ])), + RadarrEvent::GetOverview.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetOverview).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.disk_space_vec, + vec![ + DiskSpace { + free_space: Number::from(1111), + total_space: Number::from(2222), + }, + DiskSpace { + free_space: Number::from(3333), + total_space: Number::from(4444), + }, + ] + ); + } + + #[tokio::test] + async fn test_handle_get_status_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(json!({ + "version": "v1", + "startTime": "2023-02-25T20:16:43Z" + })), + RadarrEvent::GetStatus.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetStatus).await; + + async_server.assert_async().await; + assert_str_eq!(app_arc.lock().await.data.radarr_data.version, "v1"); + assert_eq!( + app_arc.lock().await.data.radarr_data.start_time, + DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap()) + as DateTime + ); + } + + #[tokio::test] + async fn test_handle_get_movies_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(format!("[ {} ]", MOVIE_JSON).as_str()).unwrap()), + RadarrEvent::GetMovies.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetMovies).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.movies.items, + vec![movie()] + ); + } + + #[tokio::test] + async fn test_handle_get_releases_event() { + let release_json = json!([{ + "guid": "1234", + "protocol": "torrent", + "age": 1, + "title": "Test Release", + "indexer": "kickass torrents", + "indexerId": 2, + "size": 1234, + "rejected": true, + "rejections": [ "Unknown quality profile", "Release is already mapped" ], + "seeders": 2, + "leechers": 1, + "languages": [ { "name": "English" } ], + "quality": { "quality": { "name": "HD - 1080p" }} + }]); + let resource = format!("{}?movieId=1", RadarrEvent::GetReleases.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(release_json), &resource).await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetReleases).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.movie_releases.items, + vec![release()] + ); + } + + #[tokio::test] + async fn test_handle_search_new_movie_event() { + let add_movie_search_result_json = json!([{ + "tmdbId": 1234, + "title": "Test", + "originalLanguage": { "name": "English" }, + "status": "released", + "overview": "New movie blah blah blah", + "genres": ["cool", "family", "fun"], + "year": 2023, + "runtime": 120, + "ratings": { + "imdb": { + "value": 9.9 + }, + "tmdb": { + "value": 9.9 + }, + "rottenTomatoes": { + "value": 9.9 + } + } + }]); + let resource = format!( + "{}?term=test%20term", + RadarrEvent::SearchNewMovie.resource() + ); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(add_movie_search_result_json), + &resource, + ) + .await; + app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::SearchNewMovie) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc + .lock() + .await + .data + .radarr_data + .add_searched_movies + .items, + vec![add_movie_search_result()] + ); + } + + #[tokio::test] + async fn test_handle_search_new_movie_event_no_results() { + let resource = format!( + "{}?term=test%20term", + RadarrEvent::SearchNewMovie.resource() + ); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await; + app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::SearchNewMovie) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .radarr_data + .add_searched_movies + .items + .is_empty()); + assert_eq!( + app_arc.lock().await.get_current_route(), + &ActiveRadarrBlock::AddMovieEmptySearchResults.into() + ); + } + + #[tokio::test] + async fn test_handle_trigger_automatic_search_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "name": "MoviesSearch", + "movieIds": [ 1 ] + })), + None, + RadarrEvent::TriggerAutomaticSearch.resource(), + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::TriggerAutomaticSearch) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_update_and_scan_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "name": "RefreshMovie", + "movieIds": [ 1 ] + })), + None, + RadarrEvent::UpdateAndScan.resource(), + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::UpdateAndScan) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_update_all_movies_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "name": "RefreshMovie", + "movieIds": [] + })), + None, + RadarrEvent::UpdateAllMovies.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::UpdateAllMovies) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_update_downloads_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "name": "RefreshMonitoredDownloads" + })), + None, + RadarrEvent::UpdateDownloads.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::UpdateDownloads) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_update_collections_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "name": "RefreshCollections" + })), + None, + RadarrEvent::UpdateCollections.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::UpdateCollections) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_get_movie_details_event() { + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(MOVIE_JSON).unwrap()), + &resource, + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + app_arc.lock().await.data.radarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetMovieDetails) + .await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc + .lock() + .await + .data + .radarr_data + .movie_details + .get_text(), + formatdoc!( + "Title: Test + Year: 2023 + Runtime: 2h 0m + Rating: R + Collection: Test Collection + Status: Downloaded + Description: Blah blah blah + TMDB: 99% + IMDB: 9.9 + Rotten Tomatoes: + Quality Profile: HD - 1080p + Size: 3.30 GB + Path: /nfs/movies + Studio: 21st Century Alex + Genres: cool, family, fun" + ) + ); + assert_str_eq!( + app_arc.lock().await.data.radarr_data.file_details, + formatdoc!( + "Relative Path: Test.mkv + Absolute Path: /nfs/movies/Test.mkv + Size: 3.30 GB + Date Added: 2022-12-30 07:37:56 UTC" + ) + ); + assert_str_eq!( + app_arc.lock().await.data.radarr_data.audio_details, + formatdoc!( + "Bitrate: 0 + Channels: 7.1 + Codec: AAC + Languages: eng + Stream Count: 1" + ) + ); + assert_str_eq!( + app_arc.lock().await.data.radarr_data.video_details, + formatdoc!( + "Bit Depth: 10 + Bitrate: 0 + Codec: x265 + FPS: 23.976 + Resolution: 1920x804 + Scan Type: Progressive + Runtime: 2:00:00" + ) + ); + } + + #[tokio::test] + async fn test_handle_get_movie_details_event_empty_options_give_correct_defaults() { + let movie_json_with_missing_fields = json!({ + "id": 1, + "title": "Test", + "originalLanguage": { + "name": "English" + }, + "sizeOnDisk": 0, + "status": "Downloaded", + "overview": "Blah blah blah", + "path": "/nfs/movies", + "studio": "21st Century Alex", + "genres": ["cool", "family", "fun"], + "year": 2023, + "monitored": true, + "hasFile": false, + "runtime": 120, + "tmdbId": 1234, + "qualityProfileId": 2222, + "tags": [1], + "minimumAvailability": "released", + "ratings": {} + }); + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(movie_json_with_missing_fields), + &resource, + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + app_arc.lock().await.data.radarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetMovieDetails) + .await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc + .lock() + .await + .data + .radarr_data + .movie_details + .get_text(), + formatdoc!( + "Title: Test + Year: 2023 + Runtime: 2h 0m + Rating: + Collection: + Status: Missing + Description: Blah blah blah + TMDB: + IMDB: + Rotten Tomatoes: + Quality Profile: HD - 1080p + Size: 0.00 GB + Path: /nfs/movies + Studio: 21st Century Alex + Genres: cool, family, fun" + ) + ); + assert!(app_arc + .lock() + .await + .data + .radarr_data + .file_details + .is_empty()); + assert!(app_arc + .lock() + .await + .data + .radarr_data + .audio_details + .is_empty()); + assert!(app_arc + .lock() + .await + .data + .radarr_data + .video_details + .is_empty()); + } + + #[tokio::test] + async fn test_handle_get_movie_history_event() { + let movie_history_item_json = json!([{ + "sourceTitle": "Test", + "quality": { "quality": { "name": "HD - 1080p" }}, + "languages": [ { "name": "English" } ], + "date": "2022-12-30T07:37:56Z", + "eventType": "grabbed" + }]); + let resource = format!("{}?movieId=1", RadarrEvent::GetMovieHistory.resource()); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(movie_history_item_json), + &resource, + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetMovieHistory) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.movie_history.items, + vec![movie_history_item()] + ); + } + + #[tokio::test] + async fn test_handle_get_collections_event() { + let collection_json = json!([{ + "id": 123, + "title": "Test Collection", + "rootFolderPath": "/nfs/movies", + "searchOnAdd": true, + "monitored": true, + "minimumAvailability": "released", + "overview": "Collection blah blah blah", + "qualityProfileId": 2222, + "movies": [{ + "title": "Test", + "overview": "Collection blah blah blah", + "year": 2023, + "runtime": 120, + "tmdbId": 1234, + "genres": ["cool", "family", "fun"], + "ratings": { + "imdb": { + "value": 9.9 + }, + "tmdb": { + "value": 9.9 + }, + "rottenTomatoes": { + "value": 9.9 + } + } + }], + }]); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(collection_json), + RadarrEvent::GetCollections.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetCollections) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.collections.items, + vec![collection()] + ); + } + + #[tokio::test] + async fn test_handle_get_downloads_event() { + let downloads_response_json = json!({ + "records": [{ + "title": "Test Download Title", + "status": "downloading", + "id": 1, + "movieId": 1, + "size": 3543348019u64, + "sizeleft": 1771674009, + "outputPath": "/nfs/movies/Test", + "indexer": "kickass torrents", + "downloadClient": "transmission", + }] + }); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(downloads_response_json), + RadarrEvent::GetDownloads.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetDownloads).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.downloads.items, + downloads_response().records + ); + } + + #[tokio::test] + async fn test_handle_get_logs_event() { + let resource = format!( + "{}?pageSize=1000&sortDirection=descending&sortKey=time", + RadarrEvent::GetLogs.resource() + ); + let timestamp = DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap()); + let expected_logs = vec![ + Log { + time: timestamp, + level: Some("fatal".to_owned()), + logger: Some("RadarrError".to_owned()), + exception: Some("test exception".to_owned()), + exception_type: Some("Some.Big.Bad.Exception".to_owned()), + ..Log::default() + }, + Log { + time: timestamp, + level: Some("info".to_owned()), + logger: Some("TestLogger".to_owned()), + message: Some("test message".to_owned()), + ..Log::default() + }, + ]; + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(json!({ + "page": 1, + "pageSize": 1000, + "sortKey": "time", + "sortDirection": "descending", + "totalRecords": 2, + "records": [ + { + "time": "2023-05-20T21:29:16Z", + "level": "info", + "logger": "TestLogger", + "message": "test message", + "id": 1 + }, + { + "time": "2023-05-20T21:29:16Z", + "level": "fatal", + "logger": "RadarrError", + "exception": "test exception", + "exceptionType": "Some.Big.Bad.Exception", + "id": 2 + } + ] + })), + &resource, + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetLogs).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.logs.items, + expected_logs + ); + assert_str_eq!( + app_arc + .lock() + .await + .data + .radarr_data + .logs + .current_selection() + .level + .as_ref() + .unwrap(), + "info" + ); + } + + #[tokio::test] + async fn test_handle_get_quality_profiles_event() { + let quality_profile_json = json!([{ + "id": 2222, + "name": "HD - 1080p" + }]); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(quality_profile_json), + RadarrEvent::GetQualityProfiles.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetQualityProfiles) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.quality_profile_map, + BiMap::from_iter([(2222u64, "HD - 1080p".to_owned())]) + ); + } + + #[tokio::test] + async fn test_handle_get_tags_event() { + let tags_json = json!([{ + "id": 2222, + "label": "usenet" + }]); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(tags_json), + RadarrEvent::GetTags.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetTags).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.tags_map, + BiMap::from_iter([(2222u64, "usenet".to_owned())]) + ); + } + + #[tokio::test] + async fn test_handle_get_tasks_event() { + let tasks_json = json!([{ + "name": "Application Check Update", + "taskName": "ApplicationCheckUpdate", + "interval": 360, + "lastExecution": "2023-05-20T21:29:16Z", + "nextExecution": "2023-05-20T21:29:16Z", + "lastDuration": "00:00:00.5111547", + }, + { + "name": "Backup", + "taskName": "Backup", + "interval": 10080, + "lastExecution": "2023-05-20T21:29:16Z", + "nextExecution": "2023-05-20T21:29:16Z", + "lastDuration": "00:00:00.5111547", + }]); + let timestamp = DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap()); + let expected_tasks = vec![ + Task { + name: "Application Check Update".to_owned(), + task_name: "ApplicationCheckUpdate".to_owned(), + interval: Number::from(360), + last_execution: timestamp, + next_execution: timestamp, + last_duration: "00:00:00.5111547".to_owned(), + }, + Task { + name: "Backup".to_owned(), + task_name: "Backup".to_owned(), + interval: Number::from(10080), + last_execution: timestamp, + next_execution: timestamp, + last_duration: "00:00:00.5111547".to_owned(), + }, + ]; + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(tasks_json), + RadarrEvent::GetTasks.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::GetTasks).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.tasks.items, + expected_tasks + ); + } + + #[tokio::test] + async fn test_add_tag() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ "label": "testing" })), + Some(json!({ "id": 3, "label": "testing" })), + RadarrEvent::GetTags.resource(), + ) + .await; + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.add_tag("testing".to_owned()).await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.tags_map, + BiMap::from_iter([ + (1, "usenet".to_owned()), + (2, "test".to_owned()), + (3, "testing".to_owned()) + ]) + ); + } + + #[tokio::test] + async fn test_handle_get_root_folders_event() { + let root_folder_json = json!([{ + "id": 1, + "path": "/nfs", + "accessible": true, + "freeSpace": 219902325555200u64, + }]); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(root_folder_json), + RadarrEvent::GetRootFolders.resource(), + ) + .await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetRootFolders) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.root_folders.items, + vec![root_folder()] + ); + } + + #[tokio::test] + async fn test_handle_get_movie_credits_event() { + let credits_json = json!([ + { + "personName": "Madison Clarke", + "character": "Johnny Blaze", + "type": "cast", + }, + { + "personName": "Alex Clarke", + "department": "Music", + "job": "Composition", + "type": "crew", + } + ]); + let resource = format!("{}?movieId=1", RadarrEvent::GetMovieCredits.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(credits_json), &resource).await; + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![movie()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::GetMovieCredits) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.movie_cast.items, + vec![cast_credit()] + ); + assert_eq!( + app_arc.lock().await.data.radarr_data.movie_crew.items, + vec![crew_credit()] + ); + } + + #[tokio::test] + async fn test_handle_delete_movie_event() { + let resource = format!( + "{}/1?deleteFiles=true&addImportExclusion=true", + RadarrEvent::DeleteMovie.resource() + ); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + { + let mut app = app_arc.lock().await; + app.data.radarr_data.movies.set_items(vec![movie()]); + app.data.radarr_data.delete_movie_files = true; + app.data.radarr_data.add_list_exclusion = true; + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::DeleteMovie).await; + + async_server.assert_async().await; + assert!(!app_arc.lock().await.data.radarr_data.delete_movie_files); + assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion); + } + + #[tokio::test] + async fn test_handle_delete_download_event() { + let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + app_arc + .lock() + .await + .data + .radarr_data + .downloads + .set_items(vec![download_record()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::DeleteDownload) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_delete_root_folder_event() { + let resource = format!("{}/1", RadarrEvent::DeleteRootFolder.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + app_arc + .lock() + .await + .data + .radarr_data + .root_folders + .set_items(vec![root_folder()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::DeleteRootFolder) + .await; + + async_server.assert_async().await; + } + + #[rstest] + #[tokio::test] + async fn test_handle_add_movie_event(#[values(true, false)] movie_details_context: bool) { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "tmdbId": 1234, + "title": "Test", + "rootFolderPath": "/nfs", + "minimumAvailability": "announced", + "monitored": true, + "qualityProfileId": 2222, + "tags": [1, 2], + "addOptions": { + "monitor": "movieOnly", + "searchForMovie": true + } + })), + None, + RadarrEvent::AddMovie.resource(), + ) + .await; + + { + let mut app = app_arc.lock().await; + app.data.radarr_data.root_folders.set_items(vec![ + RootFolder { + id: Number::from(1), + path: "/nfs".to_owned(), + accessible: true, + free_space: Number::from(219902325555200u64), + unmapped_folders: None, + }, + RootFolder { + id: Number::from(2), + path: "/nfs2".to_owned(), + accessible: true, + free_space: Number::from(21990232555520u64), + unmapped_folders: None, + }, + ]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + app.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into(); + app + .data + .radarr_data + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + app + .data + .radarr_data + .monitor_list + .set_items(Vec::from_iter(Monitor::iter())); + app + .data + .radarr_data + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + if movie_details_context { + app + .data + .radarr_data + .collection_movies + .set_items(vec![collection_movie()]); + app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); + } else { + app + .data + .radarr_data + .add_searched_movies + .set_items(vec![add_movie_search_result()]); + } + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::AddMovie).await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_add_root_folder_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "path": "/nfs/test" + })), + None, + RadarrEvent::AddRootFolder.resource(), + ) + .await; + + app_arc.lock().await.data.radarr_data.edit_path = + HorizontallyScrollableText::from("/nfs/test".to_owned()); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::AddRootFolder) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .radarr_data + .edit_path + .text + .is_empty()); + } + + #[tokio::test] + async fn test_handle_edit_movie_event() { + let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); + *expected_body.get_mut("monitored").unwrap() = json!(false); + *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); + *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); + *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); + *expected_body.get_mut("tags").unwrap() = json!([1, 2]); + + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); + let (async_details_server, app_arc, mut server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(MOVIE_JSON).unwrap()), + &resource, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", RadarrEvent::EditMovie.resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into(); + app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned().into(); + app.data.radarr_data.edit_monitored = Some(false); + app + .data + .radarr_data + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + app + .data + .radarr_data + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.movies.set_items(vec![Movie { + monitored: false, + ..movie() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + network.handle_radarr_event(RadarrEvent::EditMovie).await; + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + + let app = app_arc.lock().await; + assert!(app.response.is_empty()); + assert!(app.data.radarr_data.edit_path.text.is_empty()); + assert!(app.data.radarr_data.edit_tags.text.is_empty()); + assert!(app.data.radarr_data.movie_details.items.is_empty()); + } + + #[tokio::test] + async fn test_handle_edit_collection_event() { + let detailed_collection_body = json!({ + "id": 123, + "title": "Test Collection", + "rootFolderPath": "/nfs/movies", + "searchOnAdd": true, + "monitored": true, + "minimumAvailability": "released", + "overview": "Collection blah blah blah", + "qualityProfileId": 2222, + "movies": [ + { + "title": "Test", + "overview": "Collection blah blah blah", + "year": 2023, + "runtime": 120, + "tmdbId": 1234, + "genres": ["cool", "family", "fun"], + "ratings": { + "imdb": { + "value": 9.9 + }, + "tmdb": { + "value": 9.9 + }, + "rottenTomatoes": { + "value": 9.9 + } + } + } + ] + }); + let mut expected_body = detailed_collection_body.clone(); + *expected_body.get_mut("monitored").unwrap() = json!(false); + *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); + *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); + *expected_body.get_mut("rootFolderPath").unwrap() = json!("/nfs/Test Path"); + *expected_body.get_mut("searchOnAdd").unwrap() = json!(false); + + let resource = format!("{}/123", RadarrEvent::GetCollections.resource()); + let (async_details_server, app_arc, mut server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(detailed_collection_body), + &resource, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/123", RadarrEvent::EditCollection.resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned().into(); + app.data.radarr_data.edit_monitored = Some(false); + app.data.radarr_data.edit_search_on_add = Some(false); + app + .data + .radarr_data + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + app + .data + .radarr_data + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.collections.set_items(vec![Collection { + monitored: false, + search_on_add: false, + ..collection() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::EditCollection) + .await; + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + + let app = app_arc.lock().await; + assert!(app.response.is_empty()); + assert!(app.data.radarr_data.edit_path.text.is_empty()); + assert!(app.data.radarr_data.movie_details.items.is_empty()); + } + + #[tokio::test] + async fn test_handle_download_release_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "guid": "1234", + "indexerId": 2 + })), + None, + RadarrEvent::DownloadRelease.resource(), + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .movie_releases + .set_items(vec![release()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::DownloadRelease) + .await; + + async_server.assert_async().await; + } + + #[tokio::test] + async fn test_extract_quality_profile_id() { + let app_arc = Arc::new(Mutex::new(App::default())); + { + let mut app = app_arc.lock().await; + app + .data + .radarr_data + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1, "Any".to_owned()), (2, "HD - 1080p".to_owned())]); + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_quality_profile_id().await, 1); + } + + #[tokio::test] + async fn test_extract_and_add_tag_ids_vec() { + let app_arc = Arc::new(Mutex::new(App::default())); + { + let mut app = app_arc.lock().await; + app.data.radarr_data.edit_tags = " test,hi ,, usenet ".to_owned().into(); + app.data.radarr_data.tags_map = BiMap::from_iter([ + (1, "usenet".to_owned()), + (2, "test".to_owned()), + (3, "hi".to_owned()), + ]); + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_and_add_tag_ids_vec().await, vec![2, 3, 1]); + } + + #[tokio::test] + async fn test_extract_and_add_tag_ids_vec_add_missing_tags_first() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ "label": "testing" })), + Some(json!({ "id": 3, "label": "testing" })), + RadarrEvent::GetTags.resource(), + ) + .await; + { + let mut app = app_arc.lock().await; + app.data.radarr_data.edit_tags = "usenet, test, testing".to_owned().into(); + app.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]); + } + let network = Network::new(reqwest::Client::new(), &app_arc); + + let tag_ids_vec = network.extract_and_add_tag_ids_vec().await; + + async_server.assert_async().await; + assert_eq!(tag_ids_vec, vec![1, 2, 3]); + assert_eq!( + app_arc.lock().await.data.radarr_data.tags_map, + BiMap::from_iter([ + (1, "usenet".to_owned()), + (2, "test".to_owned()), + (3, "testing".to_owned()) + ]) + ); + } + + #[tokio::test] + async fn test_extract_movie_id() { + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![Movie { + id: Number::from(1), + ..Movie::default() + }]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_movie_id().await, 1); + } + + #[tokio::test] + async fn test_extract_movie_id_filtered_movies() { + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc + .lock() + .await + .data + .radarr_data + .filtered_movies + .set_items(vec![Movie { + id: Number::from(1), + ..Movie::default() + }]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_movie_id().await, 1); + } + + #[tokio::test] + async fn test_extract_collection_id() { + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc + .lock() + .await + .data + .radarr_data + .collections + .set_items(vec![Collection { + id: Number::from(1), + ..Collection::default() + }]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_collection_id().await, 1); + } + + #[tokio::test] + async fn test_extract_collection_id_filtered_collection() { + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc + .lock() + .await + .data + .radarr_data + .filtered_collections + .set_items(vec![Collection { + id: Number::from(1), + ..Collection::default() + }]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_eq!(network.extract_collection_id().await, 1); + } + + #[tokio::test] + async fn test_append_movie_id_param() { + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc + .lock() + .await + .data + .radarr_data + .movies + .set_items(vec![Movie { + id: Number::from(1), + ..Movie::default() + }]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + assert_str_eq!( + network.append_movie_id_param("/test").await, + "/test?movieId=1" + ); + } + + #[tokio::test] + async fn test_radarr_request_props_from_default_radarr_config() { + let app_arc = Arc::new(Mutex::new(App::default())); + let network = Network::new(reqwest::Client::new(), &app_arc); + + let request_props = network + .radarr_request_props_from("/test", RequestMethod::Get, None::<()>) + .await; + + assert_str_eq!(request_props.uri, "http://localhost:7878/api/v3/test"); + assert_eq!(request_props.method, RequestMethod::Get); + assert_eq!(request_props.body, None); + assert!(request_props.api_token.is_empty()); + + app_arc.lock().await.config.radarr = RadarrConfig { + host: "192.168.0.123".to_owned(), + port: Some(8080), + api_token: "testToken1234".to_owned(), + }; + } + + #[tokio::test] + async fn test_radarr_request_props_from_custom_radarr_config() { + let api_token = "testToken1234".to_owned(); + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc.lock().await.config.radarr = RadarrConfig { + host: "192.168.0.123".to_owned(), + port: Some(8080), + api_token: api_token.clone(), + }; + let network = Network::new(reqwest::Client::new(), &app_arc); + + let request_props = network + .radarr_request_props_from("/test", RequestMethod::Get, None::<()>) + .await; + + assert_str_eq!(request_props.uri, "http://192.168.0.123:8080/api/v3/test"); + assert_eq!(request_props.method, RequestMethod::Get); + assert_eq!(request_props.body, None); + assert_str_eq!(request_props.api_token, api_token); + } + + #[test] + fn test_get_movie_status_downloaded() { + assert_str_eq!(get_movie_status(true, &[], Number::from(0)), "Downloaded"); + } + + #[test] + fn test_get_movie_status_missing() { + let download_record = DownloadRecord { + movie_id: 1.into(), + ..DownloadRecord::default() + }; + + assert_str_eq!( + get_movie_status(false, &[download_record.clone()], 0.into()), + "Missing" + ); + + assert_str_eq!( + get_movie_status(false, &[download_record], 1.into()), + "Missing" + ); + } + + #[test] + fn test_get_movie_status_downloading() { + assert_str_eq!( + get_movie_status( + false, + &[DownloadRecord { + movie_id: 1.into(), + status: "downloading".to_owned(), + ..DownloadRecord::default() + }], + 1.into() + ), + "Downloading" + ); + } + + #[test] + fn test_get_movie_status_awaiting_import() { + assert_str_eq!( + get_movie_status( + false, + &[DownloadRecord { + movie_id: 1.into(), + status: "completed".to_owned(), + ..DownloadRecord::default() + }], + 1.into() + ), + "Awaiting Import" + ); + } + + async fn mock_radarr_api( + method: RequestMethod, + request_body: Option, + response_body: Option, + resource: &str, + ) -> (Mock, Arc>>, ServerGuard) { + let mut server = Server::new_async().await; + let mut async_server = server + .mock( + &method.to_string().to_uppercase(), + format!("/api/v3{}", resource).as_str(), + ) + .match_header("X-Api-Key", "test1234"); + + if let Some(body) = request_body { + async_server = async_server.match_body(Matcher::Json(body)); + } + + if let Some(body) = response_body { + async_server = async_server.with_body(body.to_string()); + } + + async_server = async_server.create_async().await; + + let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let port = Some( + server.host_with_port().split(':').collect::>()[1] + .parse() + .unwrap(), + ); + let mut app = App::default(); + let radarr_config = RadarrConfig { + host, + port, + api_token: "test1234".to_owned(), + }; + app.config.radarr = radarr_config; + let app_arc = Arc::new(Mutex::new(app)); + + (async_server, app_arc, server) + } + + fn language() -> Language { + Language { + name: "English".to_owned(), + } + } + + fn genres() -> Vec { + vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()] + } + + fn rating() -> Rating { + Rating { + value: Number::from_f64(9.9).unwrap(), + } + } + + fn ratings_list() -> RatingsList { + RatingsList { + imdb: Some(rating()), + tmdb: Some(rating()), + rotten_tomatoes: Some(rating()), + } + } + + fn media_info() -> MediaInfo { + MediaInfo { + audio_bitrate: Number::from(0), + audio_channels: Number::from_f64(7.1).unwrap(), + audio_codec: Some("AAC".to_owned()), + audio_languages: Some("eng".to_owned()), + audio_stream_count: Number::from(1), + video_bit_depth: Number::from(10), + video_bitrate: Number::from(0), + video_codec: "x265".to_owned(), + video_fps: Number::from_f64(23.976).unwrap(), + resolution: "1920x804".to_owned(), + run_time: "2:00:00".to_owned(), + scan_type: "Progressive".to_owned(), + } + } + + fn movie_file() -> MovieFile { + MovieFile { + relative_path: "Test.mkv".to_owned(), + path: "/nfs/movies/Test.mkv".to_owned(), + date_added: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()), + media_info: Some(media_info()), + } + } + + fn collection_movie() -> CollectionMovie { + CollectionMovie { + title: "Test".to_owned().into(), + overview: "Collection blah blah blah".to_owned(), + year: Number::from(2023), + runtime: Number::from(120), + tmdb_id: Number::from(1234), + genres: genres(), + ratings: ratings_list(), + } + } + + fn collection() -> Collection { + Collection { + id: Number::from(123), + title: "Test Collection".to_owned().into(), + root_folder_path: Some("/nfs/movies".to_owned()), + search_on_add: true, + monitored: true, + minimum_availability: MinimumAvailability::Released, + overview: Some("Collection blah blah blah".to_owned()), + quality_profile_id: Number::from(2222), + movies: Some(vec![collection_movie()]), + } + } + + fn movie() -> Movie { + Movie { + id: Number::from(1), + title: "Test".to_owned().into(), + original_language: language(), + size_on_disk: Number::from(3543348019u64), + status: "Downloaded".to_owned(), + overview: "Blah blah blah".to_owned(), + path: "/nfs/movies".to_owned(), + studio: "21st Century Alex".to_owned(), + genres: genres(), + year: Number::from(2023), + monitored: true, + has_file: true, + runtime: Number::from(120), + tmdb_id: Number::from(1234), + quality_profile_id: Number::from(2222), + minimum_availability: MinimumAvailability::Announced, + certification: Some("R".to_owned()), + tags: vec![Number::from(1)], + ratings: ratings_list(), + movie_file: Some(movie_file()), + collection: Some(collection()), + } + } + + fn rejections() -> Vec { + vec![ + "Unknown quality profile".to_owned(), + "Release is already mapped".to_owned(), + ] + } + + fn quality() -> Quality { + Quality { + name: "HD - 1080p".to_owned(), + } + } + + fn quality_wrapper() -> QualityWrapper { + QualityWrapper { quality: quality() } + } + + fn release() -> Release { + Release { + guid: "1234".to_owned(), + protocol: "torrent".to_owned(), + age: Number::from(1), + title: HorizontallyScrollableText::from("Test Release".to_owned()), + indexer: "kickass torrents".to_owned(), + indexer_id: Number::from(2), + size: Number::from(1234), + rejected: true, + rejections: Some(rejections()), + seeders: Some(Number::from(2)), + leechers: Some(Number::from(1)), + languages: Some(vec![language()]), + quality: quality_wrapper(), + } + } + + fn add_movie_search_result() -> AddMovieSearchResult { + AddMovieSearchResult { + tmdb_id: Number::from(1234), + title: HorizontallyScrollableText::from("Test".to_owned()), + original_language: language(), + status: "released".to_owned(), + overview: "New movie blah blah blah".to_owned(), + genres: genres(), + year: Number::from(2023), + runtime: Number::from(120), + ratings: ratings_list(), + } + } + + fn movie_history_item() -> MovieHistoryItem { + MovieHistoryItem { + source_title: HorizontallyScrollableText::from("Test".to_owned()), + quality: quality_wrapper(), + languages: vec![language()], + date: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()), + event_type: "grabbed".to_owned(), + } + } + + fn download_record() -> DownloadRecord { + DownloadRecord { + title: "Test Download Title".to_owned(), + status: "downloading".to_owned(), + id: Number::from(1), + movie_id: Number::from(1), + size: Number::from(3543348019u64), + sizeleft: Number::from(1771674009u64), + output_path: Some(HorizontallyScrollableText::from( + "/nfs/movies/Test".to_owned(), + )), + indexer: "kickass torrents".to_owned(), + download_client: "transmission".to_owned(), + } + } + + fn downloads_response() -> DownloadsResponse { + DownloadsResponse { + records: vec![download_record()], + } + } + + fn root_folder() -> RootFolder { + RootFolder { + id: Number::from(1), + path: "/nfs".to_owned(), + accessible: true, + free_space: Number::from(219902325555200u64), + unmapped_folders: None, + } + } + + fn cast_credit() -> Credit { + Credit { + person_name: "Madison Clarke".to_owned(), + character: Some("Johnny Blaze".to_owned()), + department: None, + job: None, + credit_type: CreditType::Cast, + } + } + + fn crew_credit() -> Credit { + Credit { + person_name: "Alex Clarke".to_owned(), + character: None, + department: Some("Music".to_owned()), + job: Some("Composition".to_owned()), + credit_type: CreditType::Crew, + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 3544cf9..944b52d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -566,7 +566,7 @@ pub fn draw_drop_down_menu_button( draw_button_with_icon(f, horizontal_chunks[1], selection, "▼", is_selected); } -pub fn draw_drop_down_list<'a, B: Backend, T>( +pub fn draw_selectable_list<'a, B: Backend, T>( f: &mut Frame<'_, B>, area: Rect, content: &'a mut StatefulList, @@ -580,6 +580,26 @@ pub fn draw_drop_down_list<'a, B: Backend, T>( f.render_stateful_widget(list, area, &mut content.state); } +pub fn draw_list_box<'a, B: Backend, T>( + f: &mut Frame<'_, B>, + area: Rect, + content: &'a mut StatefulList, + title: &str, + item_mapper: impl Fn(&T) -> ListItem<'a>, + is_loading: bool, +) { + let block = title_block(title); + + if !content.items.is_empty() { + let items: Vec> = content.items.iter().map(item_mapper).collect(); + let list = List::new(items).block(title_block(title)); + + f.render_stateful_widget(list, area, &mut content.state); + } else { + loading(f, block, area, is_loading); + } +} + pub fn draw_text_box( f: &mut Frame<'_, B>, text_box_area: Rect, diff --git a/src/ui/radarr_ui/add_movie_ui.rs b/src/ui/radarr_ui/add_movie_ui.rs index cd1f798..82e48a4 100644 --- a/src/ui/radarr_ui/add_movie_ui.rs +++ b/src/ui/radarr_ui/add_movie_ui.rs @@ -20,8 +20,8 @@ use crate::ui::utils::{ vertical_chunks_with_margin, }; use crate::ui::{ - draw_button, draw_drop_down_list, draw_drop_down_menu_button, draw_drop_down_popup, - draw_error_popup, draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, + draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup, + draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list, draw_table, draw_text_box, draw_text_box_with_label, DrawUi, TableProps, }; use crate::utils::convert_runtime; @@ -311,7 +311,7 @@ fn draw_select_monitor_popup( app: &mut App<'_>, popup_area: Rect, ) { - draw_drop_down_list( + draw_selectable_list( f, popup_area, &mut app.data.radarr_data.monitor_list, diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index 718ffc4..86b0d11 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -18,9 +18,10 @@ use crate::app::App; use crate::logos::RADARR_LOGO; use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}; use crate::models::Route; -use crate::ui::draw_drop_down_list; +use crate::ui::draw_selectable_list; use crate::ui::draw_tabs; use crate::ui::loading; +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, @@ -45,6 +46,7 @@ mod edit_movie_ui; mod library_ui; mod movie_details_ui; mod root_folders_ui; +mod system_ui; pub(super) struct RadarrUi {} @@ -70,6 +72,7 @@ impl DrawUi for RadarrUi { ActiveRadarrBlock::RootFolders | ActiveRadarrBlock::AddRootFolderPrompt | ActiveRadarrBlock::DeleteRootFolderPrompt => RootFoldersUi::draw(f, app, content_rect), + ActiveRadarrBlock::System => SystemUi::draw(f, app, content_rect), _ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => { MovieDetailsUi::draw(f, app, content_rect) } @@ -265,7 +268,7 @@ fn draw_select_minimum_availability_popup( app: &mut App<'_>, popup_area: Rect, ) { - draw_drop_down_list( + draw_selectable_list( f, popup_area, &mut app.data.radarr_data.minimum_availability_list, @@ -278,7 +281,7 @@ fn draw_select_quality_profile_popup( app: &mut App<'_>, popup_area: Rect, ) { - draw_drop_down_list( + draw_selectable_list( f, popup_area, &mut app.data.radarr_data.quality_profile_list, @@ -291,7 +294,7 @@ fn draw_select_root_folder_popup( app: &mut App<'_>, popup_area: Rect, ) { - draw_drop_down_list( + draw_selectable_list( f, popup_area, &mut app.data.radarr_data.root_folder_list, diff --git a/src/ui/radarr_ui/movie_details_ui.rs b/src/ui/radarr_ui/movie_details_ui.rs index 8fa14f5..f0d34e4 100644 --- a/src/ui/radarr_ui/movie_details_ui.rs +++ b/src/ui/radarr_ui/movie_details_ui.rs @@ -18,9 +18,9 @@ use crate::ui::utils::{ style_primary, style_success, style_warning, vertical_chunks, }; use crate::ui::{ - draw_drop_down_list, draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, - draw_prompt_box_with_content, draw_prompt_popup_over, draw_small_popup_over, draw_table, - draw_tabs, loading, DrawUi, TableProps, + draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, + draw_prompt_popup_over, draw_selectable_list, draw_small_popup_over, draw_table, draw_tabs, + loading, DrawUi, TableProps, }; use crate::utils::convert_to_gb; @@ -58,7 +58,7 @@ impl DrawUi for MovieDetailsUi { content_area, draw_movie_info, |f, app, content_area| { - draw_drop_down_list( + draw_selectable_list( f, content_area, &mut app.data.radarr_data.movie_releases_sort, diff --git a/src/ui/radarr_ui/system_ui.rs b/src/ui/radarr_ui/system_ui.rs new file mode 100644 index 0000000..77e5c7e --- /dev/null +++ b/src/ui/radarr_ui/system_ui.rs @@ -0,0 +1,147 @@ +use crate::ui::utils::{style_primary, style_secondary}; +use crate::ui::{draw_table, TableProps}; +use crate::{ + app::{radarr::ActiveRadarrBlock, App}, + models::Route, + ui::{ + draw_list_box, + utils::{horizontal_chunks, style_default, style_failure, title_block, vertical_chunks}, + DrawUi, + }, +}; +use chrono::DateTime; +use std::ops::Sub; +use tui::style::Modifier; +use tui::text::{Span, Text}; +use tui::widgets::{Cell, Row}; +use tui::{ + backend::Backend, + layout::{Constraint, Rect}, + style::{Color, Style}, + widgets::ListItem, + Frame, +}; + +pub(super) struct SystemUi {} + +impl DrawUi for SystemUi { + 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) + } + } +} + +fn draw_system_ui_layout(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let vertical_chunks = + vertical_chunks(vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], area); + + let horizontal_chunks = horizontal_chunks( + vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], + vertical_chunks[0], + ); + + draw_tasks(f, app, horizontal_chunks[0]); + f.render_widget(title_block("Queue"), horizontal_chunks[1]); + draw_logs(f, app, vertical_chunks[1]); +} + +fn draw_tasks(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let block = title_block("Tasks"); + draw_table( + f, + area, + block, + TableProps { + content: &mut app.data.radarr_data.tasks, + table_headers: vec![ + "Name", + "Interval", + "Last Execution", + "Last Duration", + "Next Duration", + ], + constraints: vec![ + Constraint::Percentage(30), + Constraint::Percentage(12), + Constraint::Percentage(16), + Constraint::Percentage(16), + Constraint::Percentage(16), + ], + help: None, + }, + |task| { + let interval = format!("{} hours", task.interval.as_u64().as_ref().unwrap() / 60); + let last_duration = &task.last_duration[..8]; + let next_execution = task.next_execution.sub(DateTime::default()).num_minutes(); + let next_execution_string = if next_execution > 60 { + format!("{} hours", next_execution / 60) + } else { + format!("{} minutes", next_execution) + }; + let last_execution = task.last_execution.sub(DateTime::default()).num_minutes(); + let last_execution_string = if last_execution > 60 { + format!("{} hours", last_execution / 60) + } else { + format!("{} minutes", last_execution) + }; + + Row::new(vec![ + Cell::from(task.name.clone()), + Cell::from(interval), + Cell::from(last_execution_string), + Cell::from(last_duration.to_owned()), + Cell::from(next_execution_string), + ]) + .style(style_primary()) + }, + app.is_loading, + ); +} + +fn draw_logs(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_list_box( + f, + area, + &mut app.data.radarr_data.logs, + "Logs", + |log| { + let log_line = if log.exception.is_some() { + Text::from(Span::raw(format!( + "{}|{}|{}|{}|{}", + log.time, + log.level.as_ref().unwrap().to_uppercase(), + log.logger.as_ref().unwrap(), + log.exception_type.as_ref().unwrap(), + log.exception.as_ref().unwrap() + ))) + } else { + Text::from(Span::raw(format!( + "{}|{}|{}|{}", + log.time, + log.level.as_ref().unwrap().to_uppercase(), + log.logger.as_ref().unwrap(), + log.message.as_ref().unwrap() + ))) + }; + + ListItem::new(log_line).style(determine_log_style_by_level(log.level.as_ref().unwrap())) + }, + app.is_loading, + ); +} + +fn determine_log_style_by_level(level: &str) -> Style { + match level.to_lowercase().as_str() { + "trace" => Style::default().fg(Color::Gray), + "debug" => Style::default().fg(Color::Blue), + "info" => style_default(), + "warn" => style_secondary(), + "error" => style_failure(), + "fatal" => style_failure().add_modifier(Modifier::BOLD), + _ => style_default(), + } +} diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 189879f..be1db7d 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -16,6 +16,10 @@ pub const COLOR_ORANGE: Color = Color::Rgb(255, 170, 66); pub const COLOR_WHITE: Color = Color::White; pub const COLOR_MAGENTA: Color = Color::Magenta; +#[cfg(test)] +#[path = "utils_tests.rs"] +mod utils_tests; + pub fn horizontal_chunks(constraints: Vec, area: Rect) -> Rc<[Rect]> { layout_with_constraints(constraints) .direction(Direction::Horizontal) @@ -271,413 +275,3 @@ pub fn show_cursor(f: &mut Frame<'_, B>, area: Rect, offset: usize, pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize { (area.width as f64 * (percentage as f64 / 100.0)) as usize } - -#[cfg(test)] -mod test { - use pretty_assertions::assert_eq; - use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; - use tui::style::{Color, Modifier, Style}; - use tui::text::{Span, Spans}; - use tui::widgets::{Block, BorderType, Borders}; - - use crate::ui::utils::{ - borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks, - horizontal_chunks_with_margin, layout_block, layout_block_bottom_border, - layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title, - layout_with_constraints, logo_block, spans_info_default, spans_info_primary, - spans_info_with_style, style_block_highlight, style_bold, style_default, style_default_bold, - style_failure, style_help, style_highlight, style_primary, style_secondary, style_success, - style_system_function, style_unmonitored, style_warning, title_block, title_block_centered, - title_style, vertical_chunks, vertical_chunks_with_margin, - }; - - #[test] - fn test_horizontal_chunks() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Horizontal) - .split(area); - - assert_eq!(horizontal_chunks(constraints.into(), area), expected_layout); - } - - #[test] - fn test_horizontal_chunks_with_margin() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Horizontal) - .margin(1) - .split(area); - - assert_eq!( - horizontal_chunks_with_margin(constraints.into(), area, 1), - expected_layout - ); - } - - #[test] - fn test_vertical_chunks() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Vertical) - .split(area); - - assert_eq!(vertical_chunks(constraints.into(), area), expected_layout); - } - - #[test] - fn test_vertical_chunks_with_margin() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Vertical) - .margin(1) - .split(area); - - assert_eq!( - vertical_chunks_with_margin(constraints.into(), area, 1), - expected_layout - ); - } - - #[test] - fn test_layout_with_constraints() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let expected_layout = Layout::default().constraints(constraints); - - assert_eq!(layout_with_constraints(constraints.into()), expected_layout); - } - - #[test] - fn test_layout_block() { - assert_eq!( - layout_block(), - Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - ); - } - - #[test] - fn test_layout_block_with_title() { - let title_span = Span::styled( - "title", - Style::default() - .fg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ); - let expected_block = Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .title(title_span.clone()); - - assert_eq!(layout_block_with_title(title_span), expected_block); - } - - #[test] - fn test_layout_block_top_border_with_title() { - let title_span = Span::styled( - "title", - Style::default() - .fg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ); - let expected_block = Block::default() - .borders(Borders::TOP) - .title(title_span.clone()); - - assert_eq!( - layout_block_top_border_with_title(title_span), - expected_block - ); - } - - #[test] - fn test_layout_block_top_border() { - assert_eq!( - layout_block_top_border(), - Block::default().borders(Borders::TOP) - ); - } - - #[test] - fn test_layout_block_bottom_border() { - assert_eq!( - layout_block_bottom_border(), - Block::default().borders(Borders::BOTTOM) - ); - } - - #[test] - fn test_borderless_block() { - assert_eq!(borderless_block(), Block::default()); - } - - #[test] - fn test_spans_info_with_style() { - let first_style = Style::default() - .fg(Color::DarkGray) - .add_modifier(Modifier::BOLD); - let second_style = Style::default() - .fg(Color::LightYellow) - .add_modifier(Modifier::ITALIC); - let expected_spans = Spans::from(vec![ - Span::styled("title".to_owned(), first_style), - Span::styled("content".to_owned(), second_style), - ]); - - assert_eq!( - spans_info_with_style( - "title".to_owned(), - "content".to_owned(), - first_style, - second_style - ), - expected_spans - ); - } - - #[test] - fn test_spans_info_default() { - let expected_spans = Spans::from(vec![ - Span::styled( - "title".to_owned(), - Style::default().add_modifier(Modifier::BOLD), - ), - Span::styled("content".to_owned(), Style::default().fg(Color::White)), - ]); - - assert_eq!( - spans_info_default("title".to_owned(), "content".to_owned()), - expected_spans - ); - } - - #[test] - fn test_spans_info_primary() { - let expected_spans = Spans::from(vec![ - Span::styled( - "title".to_owned(), - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - Span::styled("content".to_owned(), Style::default().fg(Color::White)), - ]); - - assert_eq!( - spans_info_primary("title".to_owned(), "content".to_owned()), - expected_spans - ); - } - - #[test] - fn test_style_bold() { - assert_eq!(style_bold(), Style::default().add_modifier(Modifier::BOLD)); - } - - #[test] - fn test_style_highlight() { - assert_eq!( - style_highlight(), - Style::default().add_modifier(Modifier::REVERSED) - ); - } - - #[test] - fn test_style_unmonitored() { - assert_eq!(style_unmonitored(), Style::default().fg(Color::White)); - } - - #[test] - fn test_style_default() { - assert_eq!(style_default(), Style::default().fg(Color::White)); - } - - #[test] - fn test_style_default_bold() { - assert_eq!( - style_default_bold(), - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD) - ); - } - - #[test] - fn test_style_primary() { - assert_eq!(style_primary(), Style::default().fg(Color::Cyan)); - } - - #[test] - fn test_style_secondary() { - assert_eq!(style_secondary(), Style::default().fg(Color::Yellow)); - } - - #[test] - fn test_style_system_function() { - assert_eq!(style_system_function(), Style::default().fg(Color::Yellow)); - } - - #[test] - fn test_style_success() { - assert_eq!(style_success(), Style::default().fg(Color::Green)); - } - - #[test] - fn test_style_warning() { - assert_eq!(style_warning(), Style::default().fg(Color::Magenta)); - } - - #[test] - fn test_style_failure() { - assert_eq!(style_failure(), Style::default().fg(Color::Red)); - } - - #[test] - fn test_style_help() { - assert_eq!(style_help(), Style::default().fg(Color::LightBlue)); - } - - #[test] - fn test_style_button_highlight_selected() { - let expected_style = Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD); - - assert_eq!(style_block_highlight(true), expected_style); - } - - #[test] - fn test_style_button_highlight_unselected() { - let expected_style = Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD); - - assert_eq!(style_block_highlight(false), expected_style); - } - - #[test] - fn test_title_style() { - let expected_span = Span::styled(" test ", Style::default().add_modifier(Modifier::BOLD)); - - assert_eq!(title_style("test"), expected_span); - } - - #[test] - fn test_title_block() { - let expected_block = Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .title(Span::styled( - " test ", - Style::default().add_modifier(Modifier::BOLD), - )); - - assert_eq!(title_block("test"), expected_block); - } - - #[test] - fn test_title_block_centered() { - let expected_block = Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .title(Span::styled( - " test ", - Style::default().add_modifier(Modifier::BOLD), - )) - .title_alignment(Alignment::Center); - - assert_eq!(title_block_centered("test"), expected_block); - } - - #[test] - fn test_logo_block() { - let expected_block = Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .title(Span::styled( - " Managarr - A Servarr management TUI ", - Style::default() - .fg(Color::Magenta) - .add_modifier(Modifier::BOLD) - .add_modifier(Modifier::ITALIC), - )); - - assert_eq!(logo_block(), expected_block); - } - - #[test] - fn test_centered_rect() { - let expected_rect = Rect { - x: 30, - y: 45, - width: 60, - height: 90, - }; - - assert_eq!(centered_rect(50, 50, rect()), expected_rect); - } - - #[test] - fn test_get_width_from_percentage() { - assert_eq!( - get_width_from_percentage( - Rect { - x: 0, - y: 0, - width: 100, - height: 10 - }, - 30 - ), - 30 - ); - } - - fn rect() -> Rect { - Rect { - x: 0, - y: 0, - width: 120, - height: 180, - } - } -} diff --git a/src/ui/utils_tests.rs b/src/ui/utils_tests.rs new file mode 100644 index 0000000..0c27702 --- /dev/null +++ b/src/ui/utils_tests.rs @@ -0,0 +1,409 @@ +#[cfg(test)] +mod test { + use pretty_assertions::assert_eq; + use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; + use tui::style::{Color, Modifier, Style}; + use tui::text::{Span, Spans}; + use tui::widgets::{Block, BorderType, Borders}; + + use crate::ui::utils::{ + borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks, + horizontal_chunks_with_margin, layout_block, layout_block_bottom_border, + layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title, + layout_with_constraints, logo_block, spans_info_default, spans_info_primary, + spans_info_with_style, style_block_highlight, style_bold, style_default, style_default_bold, + style_failure, style_help, style_highlight, style_primary, style_secondary, style_success, + style_system_function, style_unmonitored, style_warning, title_block, title_block_centered, + title_style, vertical_chunks, vertical_chunks_with_margin, + }; + + #[test] + fn test_horizontal_chunks() { + let constraints = [ + Constraint::Percentage(10), + Constraint::Max(20), + Constraint::Min(10), + Constraint::Length(30), + Constraint::Ratio(3, 4), + ]; + let area = rect(); + let expected_layout = Layout::default() + .constraints(constraints) + .direction(Direction::Horizontal) + .split(area); + + assert_eq!(horizontal_chunks(constraints.into(), area), expected_layout); + } + + #[test] + fn test_horizontal_chunks_with_margin() { + let constraints = [ + Constraint::Percentage(10), + Constraint::Max(20), + Constraint::Min(10), + Constraint::Length(30), + Constraint::Ratio(3, 4), + ]; + let area = rect(); + let expected_layout = Layout::default() + .constraints(constraints) + .direction(Direction::Horizontal) + .margin(1) + .split(area); + + assert_eq!( + horizontal_chunks_with_margin(constraints.into(), area, 1), + expected_layout + ); + } + + #[test] + fn test_vertical_chunks() { + let constraints = [ + Constraint::Percentage(10), + Constraint::Max(20), + Constraint::Min(10), + Constraint::Length(30), + Constraint::Ratio(3, 4), + ]; + let area = rect(); + let expected_layout = Layout::default() + .constraints(constraints) + .direction(Direction::Vertical) + .split(area); + + assert_eq!(vertical_chunks(constraints.into(), area), expected_layout); + } + + #[test] + fn test_vertical_chunks_with_margin() { + let constraints = [ + Constraint::Percentage(10), + Constraint::Max(20), + Constraint::Min(10), + Constraint::Length(30), + Constraint::Ratio(3, 4), + ]; + let area = rect(); + let expected_layout = Layout::default() + .constraints(constraints) + .direction(Direction::Vertical) + .margin(1) + .split(area); + + assert_eq!( + vertical_chunks_with_margin(constraints.into(), area, 1), + expected_layout + ); + } + + #[test] + fn test_layout_with_constraints() { + let constraints = [ + Constraint::Percentage(10), + Constraint::Max(20), + Constraint::Min(10), + Constraint::Length(30), + Constraint::Ratio(3, 4), + ]; + let expected_layout = Layout::default().constraints(constraints); + + assert_eq!(layout_with_constraints(constraints.into()), expected_layout); + } + + #[test] + fn test_layout_block() { + assert_eq!( + layout_block(), + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + ); + } + + #[test] + fn test_layout_block_with_title() { + let title_span = Span::styled( + "title", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ); + let expected_block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .title(title_span.clone()); + + assert_eq!(layout_block_with_title(title_span), expected_block); + } + + #[test] + fn test_layout_block_top_border_with_title() { + let title_span = Span::styled( + "title", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ); + let expected_block = Block::default() + .borders(Borders::TOP) + .title(title_span.clone()); + + assert_eq!( + layout_block_top_border_with_title(title_span), + expected_block + ); + } + + #[test] + fn test_layout_block_top_border() { + assert_eq!( + layout_block_top_border(), + Block::default().borders(Borders::TOP) + ); + } + + #[test] + fn test_layout_block_bottom_border() { + assert_eq!( + layout_block_bottom_border(), + Block::default().borders(Borders::BOTTOM) + ); + } + + #[test] + fn test_borderless_block() { + assert_eq!(borderless_block(), Block::default()); + } + + #[test] + fn test_spans_info_with_style() { + let first_style = Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD); + let second_style = Style::default() + .fg(Color::LightYellow) + .add_modifier(Modifier::ITALIC); + let expected_spans = Spans::from(vec![ + Span::styled("title".to_owned(), first_style), + Span::styled("content".to_owned(), second_style), + ]); + + assert_eq!( + spans_info_with_style( + "title".to_owned(), + "content".to_owned(), + first_style, + second_style + ), + expected_spans + ); + } + + #[test] + fn test_spans_info_default() { + let expected_spans = Spans::from(vec![ + Span::styled( + "title".to_owned(), + Style::default().add_modifier(Modifier::BOLD), + ), + Span::styled("content".to_owned(), Style::default().fg(Color::White)), + ]); + + assert_eq!( + spans_info_default("title".to_owned(), "content".to_owned()), + expected_spans + ); + } + + #[test] + fn test_spans_info_primary() { + let expected_spans = Spans::from(vec![ + Span::styled( + "title".to_owned(), + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ), + Span::styled("content".to_owned(), Style::default().fg(Color::White)), + ]); + + assert_eq!( + spans_info_primary("title".to_owned(), "content".to_owned()), + expected_spans + ); + } + + #[test] + fn test_style_bold() { + assert_eq!(style_bold(), Style::default().add_modifier(Modifier::BOLD)); + } + + #[test] + fn test_style_highlight() { + assert_eq!( + style_highlight(), + Style::default().add_modifier(Modifier::REVERSED) + ); + } + + #[test] + fn test_style_unmonitored() { + assert_eq!(style_unmonitored(), Style::default().fg(Color::White)); + } + + #[test] + fn test_style_default() { + assert_eq!(style_default(), Style::default().fg(Color::White)); + } + + #[test] + fn test_style_default_bold() { + assert_eq!( + style_default_bold(), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD) + ); + } + + #[test] + fn test_style_primary() { + assert_eq!(style_primary(), Style::default().fg(Color::Cyan)); + } + + #[test] + fn test_style_secondary() { + assert_eq!(style_secondary(), Style::default().fg(Color::Yellow)); + } + + #[test] + fn test_style_system_function() { + assert_eq!(style_system_function(), Style::default().fg(Color::Yellow)); + } + + #[test] + fn test_style_success() { + assert_eq!(style_success(), Style::default().fg(Color::Green)); + } + + #[test] + fn test_style_warning() { + assert_eq!(style_warning(), Style::default().fg(Color::Magenta)); + } + + #[test] + fn test_style_failure() { + assert_eq!(style_failure(), Style::default().fg(Color::Red)); + } + + #[test] + fn test_style_help() { + assert_eq!(style_help(), Style::default().fg(Color::LightBlue)); + } + + #[test] + fn test_style_button_highlight_selected() { + let expected_style = Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD); + + assert_eq!(style_block_highlight(true), expected_style); + } + + #[test] + fn test_style_button_highlight_unselected() { + let expected_style = Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD); + + assert_eq!(style_block_highlight(false), expected_style); + } + + #[test] + fn test_title_style() { + let expected_span = Span::styled(" test ", Style::default().add_modifier(Modifier::BOLD)); + + assert_eq!(title_style("test"), expected_span); + } + + #[test] + fn test_title_block() { + let expected_block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .title(Span::styled( + " test ", + Style::default().add_modifier(Modifier::BOLD), + )); + + assert_eq!(title_block("test"), expected_block); + } + + #[test] + fn test_title_block_centered() { + let expected_block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .title(Span::styled( + " test ", + Style::default().add_modifier(Modifier::BOLD), + )) + .title_alignment(Alignment::Center); + + assert_eq!(title_block_centered("test"), expected_block); + } + + #[test] + fn test_logo_block() { + let expected_block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .title(Span::styled( + " Managarr - A Servarr management TUI ", + Style::default() + .fg(Color::Magenta) + .add_modifier(Modifier::BOLD) + .add_modifier(Modifier::ITALIC), + )); + + assert_eq!(logo_block(), expected_block); + } + + #[test] + fn test_centered_rect() { + let expected_rect = Rect { + x: 30, + y: 45, + width: 60, + height: 90, + }; + + assert_eq!(centered_rect(50, 50, rect()), expected_rect); + } + + #[test] + fn test_get_width_from_percentage() { + assert_eq!( + get_width_from_percentage( + Rect { + x: 0, + y: 0, + width: 100, + height: 10 + }, + 30 + ), + 30 + ); + } + + fn rect() -> Rect { + Rect { + x: 0, + y: 0, + width: 120, + height: 180, + } + } +} diff --git a/src/utils.rs b/src/utils.rs index 7fa5bd4..ccd9adb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,10 @@ use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; use regex::Regex; +#[cfg(test)] +#[path = "utils_tests.rs"] +mod utils_tests; + pub fn init_logging_config() -> log4rs::Config { let file_path = "/tmp/managarr.log"; let logfile = FileAppender::builder() @@ -40,32 +44,3 @@ pub fn strip_non_search_characters(input: &str) -> String { .replace_all(&input.to_lowercase(), "") .to_string() } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - - use crate::utils::{convert_runtime, convert_to_gb, strip_non_search_characters}; - - #[test] - fn test_convert_to_gb() { - assert_eq!(convert_to_gb(2147483648), 2f64); - assert_eq!(convert_to_gb(2662879723), 2.4799999995157123); - } - - #[test] - fn test_convert_runtime() { - let (hours, minutes) = convert_runtime(154); - - assert_eq!(hours, 2); - assert_eq!(minutes, 34); - } - - #[test] - fn test_strip_non_alphanumeric_characters() { - assert_eq!( - strip_non_search_characters("Te$t S7r!ng::'~-@_`,(.)/*}^&%#+="), - "tet s7rng::'-,./".to_owned() - ) - } -} diff --git a/src/utils_tests.rs b/src/utils_tests.rs new file mode 100644 index 0000000..64bc321 --- /dev/null +++ b/src/utils_tests.rs @@ -0,0 +1,28 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use crate::utils::{convert_runtime, convert_to_gb, strip_non_search_characters}; + + #[test] + fn test_convert_to_gb() { + assert_eq!(convert_to_gb(2147483648), 2f64); + assert_eq!(convert_to_gb(2662879723), 2.4799999995157123); + } + + #[test] + fn test_convert_runtime() { + let (hours, minutes) = convert_runtime(154); + + assert_eq!(hours, 2); + assert_eq!(minutes, 34); + } + + #[test] + fn test_strip_non_alphanumeric_characters() { + assert_eq!( + strip_non_search_characters("Te$t S7r!ng::'~-@_`,(.)/*}^&%#+="), + "tet s7rng::'-,./".to_owned() + ) + } +}