From c3fb5dcd5f63d1e31e82dc3398a20dd577e57047 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Sun, 1 Dec 2024 13:48:48 -0700 Subject: [PATCH] feat(handlers): Sonarr key support for the Series table --- src/handlers/handler_test_utils.rs | 106 +- src/handlers/handlers_tests.rs | 32 +- src/handlers/mod.rs | 24 +- .../blocklist/blocklist_handler_tests.rs | 2 + .../collection_details_handler_tests.rs | 2 + .../collections/collections_handler_tests.rs | 2 + .../downloads/downloads_handler_tests.rs | 2 + .../indexers/indexers_handler_tests.rs | 2 + .../library/library_handler_tests.rs | 2 + .../radarr_handler_test_utils.rs | 44 - .../root_folders_handler_tests.rs | 2 + .../system/system_details_handler_tests.rs | 2 + .../library/library_handler_tests.rs | 1821 +++++++++++++++++ src/handlers/sonarr_handlers/library/mod.rs | 460 +++++ src/handlers/sonarr_handlers/mod.rs | 96 + .../sonarr_handler_test_utils.rs | 157 ++ .../sonarr_handlers/sonarr_handler_tests.rs | 122 ++ src/models/servarr_data/sonarr/sonarr_data.rs | 29 + .../servarr_data/sonarr/sonarr_data_tests.rs | 75 +- 19 files changed, 2900 insertions(+), 82 deletions(-) create mode 100644 src/handlers/sonarr_handlers/library/library_handler_tests.rs create mode 100644 src/handlers/sonarr_handlers/library/mod.rs create mode 100644 src/handlers/sonarr_handlers/mod.rs create mode 100644 src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs create mode 100644 src/handlers/sonarr_handlers/sonarr_handler_tests.rs diff --git a/src/handlers/handler_test_utils.rs b/src/handlers/handler_test_utils.rs index 82a2d97..36e5d5f 100644 --- a/src/handlers/handler_test_utils.rs +++ b/src/handlers/handler_test_utils.rs @@ -99,86 +99,92 @@ mod test_utils { #[macro_export] macro_rules! test_iterable_scroll { - ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { + ($func:ident, $handler:ident, $servarr_data: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 + .$servarr_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"); + assert_str_eq!( + app.data.$servarr_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"); + assert_str_eq!( + app.data.$servarr_data.$data_ref.current_selection(), + "Test 1" + ); } }; - ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { + ($func:ident, $handler:ident, $servarr_data: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 + .$servarr_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, + app.data.$servarr_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, + app.data.$servarr_data.$data_ref.current_selection().$field, "Test 1" ); } }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { + ($func:ident, $handler:ident, $servarr_data: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); + app.data.$servarr_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, + app.data.$servarr_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, + app.data.$servarr_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) => { + ($func:ident, $handler:ident, $servarr_data: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); + app.data.$servarr_data.$data_ref.set_items($items); $handler::with(key, &mut app, $block, $context).handle(); assert_str_eq!( app .data - .radarr_data + .$servarr_data .$data_ref .current_selection() .$field @@ -191,7 +197,7 @@ mod test_utils { assert_str_eq!( app .data - .radarr_data + .$servarr_data .$data_ref .current_selection() .$field @@ -204,11 +210,11 @@ mod test_utils { #[macro_export] macro_rules! test_iterable_home_and_end { - ($func:ident, $handler:ident, $data_ref:ident, $block:expr, $context:expr) => { + ($func:ident, $handler:ident, $servarr_data: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![ + app.data.$servarr_data.$data_ref.set_items(vec![ "Test 1".to_owned(), "Test 2".to_owned(), "Test 3".to_owned(), @@ -216,74 +222,80 @@ mod test_utils { $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle(); - assert_str_eq!(app.data.radarr_data.$data_ref.current_selection(), "Test 3"); + assert_str_eq!( + app.data.$servarr_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"); + assert_str_eq!( + app.data.$servarr_data.$data_ref.current_selection(), + "Test 1" + ); } }; - ($func:ident, $handler:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { + ($func:ident, $handler:ident, $servarr_data:ident, $data_ref:ident, $items:ident, $block:expr, $context:expr, $field:ident) => { #[test] fn $func() { let mut app = App::default(); app .data - .radarr_data + .$servarr_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, + app.data.$servarr_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, + app.data.$servarr_data.$data_ref.current_selection().$field, "Test 1" ); } }; - ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => { + ($func:ident, $handler:ident, $servarr_data: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); + app.data.$servarr_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, + app.data.$servarr_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, + app.data.$servarr_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) => { + ($func:ident, $handler:ident, $servarr_data: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); + app.data.$servarr_data.$data_ref.set_items($items); $handler::with(DEFAULT_KEYBINDINGS.end.key, &mut app, $block, $context).handle(); assert_str_eq!( app .data - .radarr_data + .$servarr_data .$data_ref .current_selection() .$field @@ -296,7 +308,7 @@ mod test_utils { assert_str_eq!( app .data - .radarr_data + .$servarr_data .$data_ref .current_selection() .$field @@ -319,4 +331,34 @@ mod test_utils { assert_eq!(app.get_current_route(), $base.into()); }; } + + #[macro_export] + macro_rules! assert_delete_prompt { + ($handler:ident, $block:expr, $expected_block:expr) => { + let mut app = App::default(); + + $handler::with(DELETE_KEY, &mut app, $block, None).handle(); + + assert_eq!(app.get_current_route(), $expected_block.into()); + }; + + ($handler:ident, $app:expr, $block:expr, $expected_block:expr) => { + $handler::with(DELETE_KEY, &mut $app, $block, None).handle(); + + assert_eq!($app.get_current_route(), $expected_block.into()); + }; + } + + #[macro_export] + macro_rules! assert_refresh_key { + ($handler:ident, $block:expr) => { + let mut app = App::default(); + app.push_navigation_stack($block.into()); + + $handler::with(DEFAULT_KEYBINDINGS.refresh.key, &mut app, $block, None).handle(); + + assert_eq!(app.get_current_route(), $block.into()); + assert!(app.should_refresh); + }; + } } diff --git a/src/handlers/handlers_tests.rs b/src/handlers/handlers_tests.rs index 7b2503e..d204e18 100644 --- a/src/handlers/handlers_tests.rs +++ b/src/handlers/handlers_tests.rs @@ -23,6 +23,19 @@ mod tests { assert!(app.error.text.is_empty()); } + #[rstest] + #[case(ActiveRadarrBlock::Movies.into(), ActiveRadarrBlock::SearchMovie.into())] + #[case(ActiveSonarrBlock::Series.into(), ActiveSonarrBlock::SearchSeries.into())] + fn test_handle_events(#[case] base_block: Route, #[case] top_block: Route) { + let mut app = App::default(); + app.push_navigation_stack(base_block); + app.push_navigation_stack(top_block); + + handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app); + + assert_eq!(app.get_current_route(), base_block); + } + #[rstest] #[case(0, ActiveSonarrBlock::Series, ActiveSonarrBlock::Series)] #[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Movies)] @@ -54,8 +67,9 @@ mod tests { } #[rstest] - fn test_handle_prompt_toggle_left_right(#[values(Key::Left, Key::Right)] key: Key) { + fn test_handle_prompt_toggle_left_right_radarr(#[values(Key::Left, Key::Right)] key: Key) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.prompt_confirm); @@ -67,4 +81,20 @@ mod tests { assert!(!app.data.radarr_data.prompt_confirm); } + + #[rstest] + fn test_handle_prompt_toggle_left_right_sonarr(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + + assert!(!app.data.sonarr_data.prompt_confirm); + + handle_prompt_toggle(&mut app, key); + + assert!(app.data.sonarr_data.prompt_confirm); + + handle_prompt_toggle(&mut app, key); + + assert!(!app.data.sonarr_data.prompt_confirm); + } } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 95a8a33..1f6f88e 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,4 +1,5 @@ use radarr_handlers::RadarrHandler; +use sonarr_handlers::SonarrHandler; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; @@ -6,6 +7,7 @@ use crate::event::Key; use crate::models::{HorizontallyScrollableText, Route}; mod radarr_handlers; +mod sonarr_handlers; #[cfg(test)] #[path = "handlers_tests.rs"] @@ -89,8 +91,16 @@ pub fn handle_events(key: Key, app: &mut App<'_>) { app.reset(); app.server_tabs.previous(); app.pop_and_push_navigation_stack(app.server_tabs.get_active_route()); - } else if let Route::Radarr(active_radarr_block, context) = app.get_current_route() { - RadarrHandler::with(key, app, active_radarr_block, context).handle() + } else { + match app.get_current_route() { + Route::Radarr(active_radarr_block, context) => { + RadarrHandler::with(key, app, active_radarr_block, context).handle() + } + Route::Sonarr(active_sonarr_block, context) => { + SonarrHandler::with(key, app, active_sonarr_block, context).handle() + } + _ => (), + } } } @@ -103,8 +113,14 @@ fn handle_clear_errors(app: &mut App<'_>) { fn handle_prompt_toggle(app: &mut App<'_>, key: Key) { match key { _ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => { - if let Route::Radarr(_, _) = app.get_current_route() { - app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm; + match app.get_current_route() { + Route::Radarr(_, _) => { + app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm + } + Route::Sonarr(_, _) => { + app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm + } + _ => (), } } _ => (), diff --git a/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs b/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs index 812b30f..a48fdfa 100644 --- a/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs +++ b/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs @@ -28,6 +28,7 @@ mod tests { test_iterable_scroll!( test_blocklist_scroll, BlocklistHandler, + radarr_data, blocklist, simple_stateful_iterable_vec!(BlocklistItem, String, source_title), ActiveRadarrBlock::Blocklist, @@ -136,6 +137,7 @@ mod tests { test_iterable_home_and_end!( test_blocklist_home_and_end, BlocklistHandler, + radarr_data, blocklist, extended_stateful_iterable_vec!(BlocklistItem, String, source_title), ActiveRadarrBlock::Blocklist, diff --git a/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs b/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs index 32c40b4..b2cbf33 100644 --- a/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collection_details_handler_tests.rs @@ -24,6 +24,7 @@ mod tests { test_iterable_scroll!( test_collection_details_scroll, CollectionDetailsHandler, + radarr_data, collection_movies, simple_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), ActiveRadarrBlock::CollectionDetails, @@ -88,6 +89,7 @@ mod tests { test_iterable_home_and_end!( test_collection_details_home_end, CollectionDetailsHandler, + radarr_data, collection_movies, extended_stateful_iterable_vec!(CollectionMovie, HorizontallyScrollableText), ActiveRadarrBlock::CollectionDetails, diff --git a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs index a85b7ab..34a5d84 100644 --- a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs @@ -34,6 +34,7 @@ mod tests { test_iterable_scroll!( test_collections_scroll, CollectionsHandler, + radarr_data, collections, simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText), ActiveRadarrBlock::Collections, @@ -153,6 +154,7 @@ mod tests { test_iterable_home_and_end!( test_collections_home_end, CollectionsHandler, + radarr_data, collections, extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText), ActiveRadarrBlock::Collections, diff --git a/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs index 8dee3af..f47a498 100644 --- a/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs +++ b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs @@ -22,6 +22,7 @@ mod tests { test_iterable_scroll!( test_downloads_scroll, DownloadsHandler, + radarr_data, downloads, DownloadRecord, ActiveRadarrBlock::Downloads, @@ -69,6 +70,7 @@ mod tests { test_iterable_home_and_end!( test_downloads_home_end, DownloadsHandler, + radarr_data, downloads, DownloadRecord, ActiveRadarrBlock::Downloads, diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index aa875f5..ac4c0f3 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -25,6 +25,7 @@ mod tests { test_iterable_scroll!( test_indexers_scroll, IndexersHandler, + radarr_data, indexers, simple_stateful_iterable_vec!(Indexer, String, protocol), ActiveRadarrBlock::Indexers, @@ -71,6 +72,7 @@ mod tests { test_iterable_home_and_end!( test_indexers_home_end, IndexersHandler, + radarr_data, indexers, extended_stateful_iterable_vec!(Indexer, String, protocol), ActiveRadarrBlock::Indexers, diff --git a/src/handlers/radarr_handlers/library/library_handler_tests.rs b/src/handlers/radarr_handlers/library/library_handler_tests.rs index f79ba5a..49f8738 100644 --- a/src/handlers/radarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/library_handler_tests.rs @@ -30,6 +30,7 @@ mod tests { test_iterable_scroll!( test_movies_scroll, LibraryHandler, + radarr_data, movies, simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText), ActiveRadarrBlock::Movies, @@ -134,6 +135,7 @@ mod tests { test_iterable_home_and_end!( test_movies_home_end, LibraryHandler, + radarr_data, movies, extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText), ActiveRadarrBlock::Movies, diff --git a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs index b14cd28..849202f 100644 --- a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs +++ b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs @@ -228,48 +228,4 @@ mod utils { ); }; } - - #[macro_export] - macro_rules! assert_delete_prompt { - ($block:expr, $expected_block:expr) => { - let mut app = App::default(); - - RadarrHandler::with(DELETE_KEY, &mut app, $block, None).handle(); - - assert_eq!(app.get_current_route(), $expected_block.into()); - }; - - ($handler:ident, $block:expr, $expected_block:expr) => { - let mut app = App::default(); - - $handler::with(DELETE_KEY, &mut app, $block, None).handle(); - - assert_eq!(app.get_current_route(), $expected_block.into()); - }; - - ($app:expr, $block:expr, $expected_block:expr) => { - RadarrHandler::with(DELETE_KEY, &mut $app, $block, None).handle(); - - assert_eq!($app.get_current_route(), $expected_block.into()); - }; - - ($handler:ident, $app:expr, $block:expr, $expected_block:expr) => { - $handler::with(DELETE_KEY, &mut $app, $block, None).handle(); - - assert_eq!($app.get_current_route(), $expected_block.into()); - }; - } - - #[macro_export] - macro_rules! assert_refresh_key { - ($handler:ident, $block:expr) => { - let mut app = App::default(); - app.push_navigation_stack($block.into()); - - $handler::with(DEFAULT_KEYBINDINGS.refresh.key, &mut app, $block, None).handle(); - - assert_eq!(app.get_current_route(), $block.into()); - assert!(app.should_refresh); - }; - } } diff --git a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs index 356fc41..670eb9e 100644 --- a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs +++ b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs @@ -23,6 +23,7 @@ mod tests { test_iterable_scroll!( test_root_folders_scroll, RootFoldersHandler, + radarr_data, root_folders, simple_stateful_iterable_vec!(RootFolder, String, path), ActiveRadarrBlock::RootFolders, @@ -71,6 +72,7 @@ mod tests { test_iterable_home_and_end!( test_root_folders_home_end, RootFoldersHandler, + radarr_data, root_folders, extended_stateful_iterable_vec!(RootFolder, String, path), ActiveRadarrBlock::RootFolders, diff --git a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs index a6b5bf4..89d1f80 100644 --- a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs @@ -26,6 +26,7 @@ mod tests { test_iterable_scroll!( test_log_details_scroll, SystemDetailsHandler, + radarr_data, log_details, simple_stateful_iterable_vec!(HorizontallyScrollableText, String, text), ActiveRadarrBlock::SystemLogs, @@ -241,6 +242,7 @@ mod tests { test_iterable_home_and_end!( test_log_details_home_end, SystemDetailsHandler, + radarr_data, log_details, extended_stateful_iterable_vec!(HorizontallyScrollableText, String, text), ActiveRadarrBlock::SystemLogs, diff --git a/src/handlers/sonarr_handlers/library/library_handler_tests.rs b/src/handlers/sonarr_handlers/library/library_handler_tests.rs new file mode 100644 index 0000000..ca8bece --- /dev/null +++ b/src/handlers/sonarr_handlers/library/library_handler_tests.rs @@ -0,0 +1,1821 @@ +#[cfg(test)] +mod tests { + use core::sync::atomic::Ordering::SeqCst; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use std::cmp::Ordering; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler}; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SERIES_BLOCKS}; + use crate::models::sonarr_models::{Series, SeriesType}; + use crate::models::stateful_table::SortOption; + use crate::models::HorizontallyScrollableText; + + mod test_handle_scroll_up_and_down { + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + use pretty_assertions::assert_eq; + + use super::*; + + test_iterable_scroll!( + test_series_scroll, + LibraryHandler, + sonarr_data, + series, + simple_stateful_iterable_vec!(Series, HorizontallyScrollableText), + ActiveSonarrBlock::Series, + None, + title, + to_string + ); + + #[rstest] + fn test_series_scroll_no_op_when_not_ready( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.is_loading = true; + app + .data + .sonarr_data + .series + .set_items(simple_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + + LibraryHandler::with(key, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .series + .current_selection() + .title + .to_string(), + "Test 1" + ); + + LibraryHandler::with(key, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .series + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[rstest] + fn test_series_sort_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let series_field_vec = sort_options(); + let mut app = App::default(); + app.data.sonarr_data.series.sorting(sort_options()); + + if key == Key::Up { + for i in (0..series_field_vec.len()).rev() { + LibraryHandler::with(key, &mut app, ActiveSonarrBlock::SeriesSortPrompt, None).handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .sort + .as_ref() + .unwrap() + .current_selection(), + &series_field_vec[i] + ); + } + } else { + for i in 0..series_field_vec.len() { + LibraryHandler::with(key, &mut app, ActiveSonarrBlock::SeriesSortPrompt, None).handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .sort + .as_ref() + .unwrap() + .current_selection(), + &series_field_vec[(i + 1) % series_field_vec.len()] + ); + } + } + } + } + + mod test_handle_home_end { + use pretty_assertions::assert_eq; + + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_series_home_end, + LibraryHandler, + sonarr_data, + series, + extended_stateful_iterable_vec!(Series, HorizontallyScrollableText), + ActiveSonarrBlock::Series, + None, + title, + to_string + ); + + #[test] + fn test_series_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app + .data + .sonarr_data + .series + .set_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .series + .current_selection() + .title + .to_string(), + "Test 1" + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .series + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[test] + fn test_series_search_box_home_end_keys() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.search = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .search + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 4 + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .search + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 0 + ); + } + + #[test] + fn test_series_filter_box_home_end_keys() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.filter = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .filter + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 4 + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .filter + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 0 + ); + } + + #[test] + fn test_series_sort_home_end() { + let series_field_vec = sort_options(); + let mut app = App::default(); + app.data.sonarr_data.series.sorting(sort_options()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SeriesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .sort + .as_ref() + .unwrap() + .current_selection(), + &series_field_vec[series_field_vec.len() - 1] + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SeriesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .sort + .as_ref() + .unwrap() + .current_selection(), + &series_field_vec[0] + ); + } + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use crate::assert_delete_prompt; + use crate::models::servarr_data::sonarr::sonarr_data::DELETE_SERIES_SELECTION_BLOCKS; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_series_delete() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + assert_delete_prompt!( + LibraryHandler, + app, + ActiveSonarrBlock::Series, + ActiveSonarrBlock::DeleteSeriesPrompt + ); + assert_eq!( + app.data.sonarr_data.selected_block.blocks, + DELETE_SERIES_SELECTION_BLOCKS + ); + } + + #[test] + fn test_series_delete_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with(DELETE_KEY, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_series_tab_left(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(0); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + ActiveSonarrBlock::System.into() + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[rstest] + fn test_series_tab_right(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(0); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + ActiveSonarrBlock::Downloads.into() + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Downloads.into()); + } + + #[rstest] + fn test_left_right_update_all_series_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + + LibraryHandler::with( + key, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + LibraryHandler::with( + key, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + + #[test] + fn test_series_search_box_left_right_keys() { + let mut app = App::default(); + app.data.sonarr_data.series.search = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .search + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 1 + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .search + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 0 + ); + } + + #[test] + fn test_series_filter_box_left_right_keys() { + let mut app = App::default(); + app.data.sonarr_data.series.filter = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .filter + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 1 + ); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .series + .filter + .as_ref() + .unwrap() + .offset + .load(SeqCst), + 0 + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::extended_stateful_iterable_vec; + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_series_details_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SeriesDetails.into() + ); + } + + #[test] + fn test_series_details_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_search_series_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::SearchSeries.into()); + app + .data + .sonarr_data + .series + .set_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + app.data.sonarr_data.series.search = Some("Test 2".into()); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.series.current_selection().title.text, + "Test 2" + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_search_series_submit_error_on_no_search_hits() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::SearchSeries.into()); + app + .data + .sonarr_data + .series + .set_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + app.data.sonarr_data.series.search = Some("Test 5".into()); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.series.current_selection().title.text, + "Test 1" + ); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SearchSeriesError.into() + ); + } + + #[test] + fn test_search_filtered_series_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::SearchSeries.into()); + app + .data + .sonarr_data + .series + .set_filtered_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + app.data.sonarr_data.series.search = Some("Test 2".into()); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.series.current_selection().title.text, + "Test 2" + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_filter_series_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::FilterSeries.into()); + app + .data + .sonarr_data + .series + .set_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + app.data.sonarr_data.series.filter = Some("Test".into()); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::FilterSeries, None).handle(); + + assert!(app.data.sonarr_data.series.filtered_items.is_some()); + assert!(!app.should_ignore_quit_key); + assert_eq!( + app + .data + .sonarr_data + .series + .filtered_items + .as_ref() + .unwrap() + .len(), + 3 + ); + assert_str_eq!( + app.data.sonarr_data.series.current_selection().title.text, + "Test 1" + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_filter_series_submit_error_on_no_filter_matches() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::FilterSeries.into()); + app + .data + .sonarr_data + .series + .set_items(extended_stateful_iterable_vec!( + Series, + HorizontallyScrollableText + )); + app.data.sonarr_data.series.filter = Some("Test 5".into()); + + LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::FilterSeries, None).handle(); + + assert!(!app.should_ignore_quit_key); + assert!(app.data.sonarr_data.series.filtered_items.is_none()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::FilterSeriesError.into() + ); + } + + #[test] + fn test_update_all_series_prompt_confirm_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); + + LibraryHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::UpdateAllSeries) + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_update_all_series_prompt_decline_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); + + LibraryHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_series_sort_prompt_submit() { + let mut app = App::default(); + app.data.sonarr_data.series.sort_asc = true; + app.data.sonarr_data.series.sorting(sort_options()); + app.data.sonarr_data.series.set_items(series_vec()); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::SeriesSortPrompt.into()); + + let mut expected_vec = series_vec(); + expected_vec.sort_by(|a, b| a.id.cmp(&b.id)); + expected_vec.reverse(); + + LibraryHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::SeriesSortPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert_eq!(app.data.sonarr_data.series.items, expected_vec); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + use ratatui::widgets::TableState; + + use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; + use crate::models::stateful_table::StatefulTable; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_search_series_block_esc( + #[values(ActiveSonarrBlock::SearchSeries, ActiveSonarrBlock::SearchSeriesError)] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(active_sonarr_block.into()); + app.data.sonarr_data = create_test_sonarr_data(); + app.data.sonarr_data.series.search = Some("Test".into()); + + LibraryHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_ignore_quit_key); + assert_eq!(app.data.sonarr_data.series.search, None); + } + + #[rstest] + fn test_filter_series_block_esc( + #[values(ActiveSonarrBlock::FilterSeries, ActiveSonarrBlock::FilterSeriesError)] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(active_sonarr_block.into()); + app.data.sonarr_data = create_test_sonarr_data(); + app.data.sonarr_data.series = StatefulTable { + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; + + LibraryHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_ignore_quit_key); + assert_eq!(app.data.sonarr_data.series.filter, None); + assert_eq!(app.data.sonarr_data.series.filtered_items, None); + assert_eq!(app.data.sonarr_data.series.filtered_state, None); + } + + #[test] + fn test_update_all_series_prompt_blocks_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); + app.data.sonarr_data.prompt_confirm = true; + + LibraryHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.data.sonarr_data.prompt_confirm); + } + + #[test] + fn test_series_sort_prompt_block_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::SeriesSortPrompt.into()); + + LibraryHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::SeriesSortPrompt, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[rstest] + fn test_default_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data = create_test_sonarr_data(); + app.data.sonarr_data.series = StatefulTable { + search: Some("Test".into()), + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; + + LibraryHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::Series, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(app.error.text.is_empty()); + assert_eq!(app.data.sonarr_data.series.search, None); + assert_eq!(app.data.sonarr_data.series.filter, None); + assert_eq!(app.data.sonarr_data.series.filtered_items, None); + assert_eq!(app.data.sonarr_data.series.filtered_state, None); + } + } + + 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::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; + use crate::models::servarr_data::sonarr::sonarr_data::SonarrData; + use crate::models::sonarr_models::SeriesType; + + use crate::network::sonarr_network::SonarrEvent; + use crate::test_edit_series_key; + + use super::*; + + #[test] + fn test_search_series_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.search.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SearchSeries.into() + ); + assert!(app.should_ignore_quit_key); + assert_eq!( + app.data.sonarr_data.series.search, + Some(HorizontallyScrollableText::default()) + ); + } + + #[test] + fn test_search_series_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.search.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_ignore_quit_key); + assert_eq!(app.data.sonarr_data.series.search, None); + } + + #[test] + fn test_filter_series_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.filter.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::FilterSeries.into() + ); + assert!(app.should_ignore_quit_key); + assert!(app.data.sonarr_data.series.filter.is_some()); + } + + #[test] + fn test_filter_series_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.filter.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_ignore_quit_key); + assert!(app.data.sonarr_data.series.filter.is_none()); + } + + #[test] + fn test_filter_series_key_resets_previous_filter() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data = create_test_sonarr_data(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.filter = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.filter.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::FilterSeries.into() + ); + assert!(app.should_ignore_quit_key); + assert_eq!( + app.data.sonarr_data.series.filter, + Some(HorizontallyScrollableText::default()) + ); + assert!(app.data.sonarr_data.series.filtered_items.is_none()); + assert!(app.data.sonarr_data.series.filtered_state.is_none()); + } + + #[test] + fn test_series_add_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.add.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchInput.into() + ); + assert!(app.should_ignore_quit_key); + assert!(app.data.sonarr_data.add_series_search.is_some()); + } + + #[test] + fn test_series_add_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.add.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_ignore_quit_key); + assert!(app.data.sonarr_data.add_series_search.is_none()); + } + + #[test] + fn test_series_edit_key() { + test_edit_series_key!( + LibraryHandler, + ActiveSonarrBlock::Series, + ActiveSonarrBlock::Series + ); + } + + #[test] + fn test_series_edit_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.edit.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(app.data.sonarr_data.edit_series_modal.is_none()); + } + + #[test] + fn test_update_all_series_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::UpdateAllSeriesPrompt.into() + ); + } + + #[test] + fn test_update_all_series_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + + #[test] + fn test_refresh_series_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(app.should_refresh); + } + + #[test] + fn test_refresh_series_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_search_series_box_backspace_key() { + let mut app = App::default(); + app.data.sonarr_data.series.search = Some("Test".into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.series.search.as_ref().unwrap().text, + "Tes" + ); + } + + #[test] + fn test_filter_series_box_backspace_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.filter = Some("Test".into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.series.filter.as_ref().unwrap().text, + "Tes" + ); + } + + #[test] + fn test_search_series_box_char_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.search = Some(HorizontallyScrollableText::default()); + + LibraryHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::SearchSeries, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.series.search.as_ref().unwrap().text, + "h" + ); + } + + #[test] + fn test_filter_series_box_char_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.data.sonarr_data.series.filter = Some(HorizontallyScrollableText::default()); + + LibraryHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::FilterSeries, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.series.filter.as_ref().unwrap().text, + "h" + ); + } + + #[test] + fn test_sort_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.sort.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SeriesSortPrompt.into() + ); + assert_eq!( + app.data.sonarr_data.series.sort.as_ref().unwrap().items, + series_sorting_options() + ); + assert!(!app.data.sonarr_data.series.sort_asc); + } + + #[test] + fn test_sort_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.sort.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert!(app.data.sonarr_data.series.sort.is_none()); + } + + #[test] + fn test_update_all_series_prompt_confirm() { + let mut app = App::default(); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); + + LibraryHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::UpdateAllSeries) + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + } + + // #[rstest] + // fn test_delegates_add_series_blocks_to_add_series_handler( + // #[values( + // ActiveSonarrBlock::AddSeriesSearchInput, + // ActiveSonarrBlock::AddSeriesSearchResults, + // ActiveSonarrBlock::AddSeriesPrompt, + // ActiveSonarrBlock::AddSeriesSelectMonitor, + // ActiveSonarrBlock::AddSeriesSelectSeriesType, + // ActiveSonarrBlock::AddSeriesSelectQualityProfile, + // ActiveSonarrBlock::AddSeriesSelectRootFolder, + // ActiveSonarrBlock::AddSeriesAlreadyInLibrary, + // ActiveSonarrBlock::AddSeriesTagsInput + // )] + // active_sonarr_block: ActiveSonarrBlock, + // ) { + // test_handler_delegation!( + // LibraryHandler, + // ActiveSonarrBlock::Series, + // active_sonarr_block + // ); + // } + + // #[rstest] + // fn test_delegates_series_details_blocks_to_series_details_handler( + // #[values( + // ActiveSonarrBlock::SeriesDetails, + // ActiveSonarrBlock::SeriesHistory, + // ActiveSonarrBlock::FileInfo, + // ActiveSonarrBlock::Cast, + // ActiveSonarrBlock::Crew, + // ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, + // ActiveSonarrBlock::UpdateAndScanPrompt, + // ActiveSonarrBlock::ManualSearch, + // ActiveSonarrBlock::ManualSearchConfirmPrompt + // )] + // active_sonarr_block: ActiveSonarrBlock, + // ) { + // test_handler_delegation!( + // LibraryHandler, + // ActiveSonarrBlock::Series, + // active_sonarr_block + // ); + // } + + // #[rstest] + // fn test_delegates_edit_series_blocks_to_edit_series_handler( + // #[values( + // ActiveSonarrBlock::EditSeriesPrompt, + // ActiveSonarrBlock::EditSeriesPathInput, + // ActiveSonarrBlock::EditSeriesSelectMinimumAvailability, + // ActiveSonarrBlock::EditSeriesSelectQualityProfile, + // ActiveSonarrBlock::EditSeriesTagsInput + // )] + // active_sonarr_block: ActiveSonarrBlock, + // ) { + // test_handler_delegation!( + // LibraryHandler, + // ActiveSonarrBlock::Series, + // active_sonarr_block + // ); + // } + + // #[test] + // fn test_delegates_delete_series_blocks_to_delete_series_handler() { + // test_handler_delegation!( + // LibraryHandler, + // ActiveSonarrBlock::Series, + // ActiveSonarrBlock::DeleteSeriesPrompt + // ); + // } + + #[test] + fn test_series_sorting_options_title() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| { + a.title + .text + .to_lowercase() + .cmp(&b.title.text.to_lowercase()) + }; + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[0].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Title"); + } + + #[test] + fn test_series_sorting_options_year() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| a.year.cmp(&b.year); + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[1].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Year"); + } + + #[test] + fn test_series_sorting_options_network() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| { + a.network + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase() + .cmp(&b.network.as_ref().unwrap_or(&String::new()).to_lowercase()) + }; + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[2].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Network"); + } + + #[test] + fn test_series_sorting_options_runtime() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| a.runtime.cmp(&b.runtime); + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[3].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Runtime"); + } + + #[test] + fn test_series_sorting_options_rating() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| { + a.certification + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase() + .cmp( + &b.certification + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase(), + ) + }; + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[4].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Rating"); + } + + #[test] + fn test_series_sorting_options_type() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| { + a.series_type + .to_string() + .to_lowercase() + .cmp(&b.series_type.to_string().to_lowercase()) + }; + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[5].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Type"); + } + + #[test] + fn test_series_sorting_options_quality() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = + |a, b| a.quality_profile_id.cmp(&b.quality_profile_id); + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[6].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Quality"); + } + + #[test] + fn test_series_sorting_options_language() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = + |a, b| a.language_profile_id.cmp(&b.language_profile_id); + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[7].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Language"); + } + + #[test] + fn test_series_sorting_options_monitored() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| a.monitored.cmp(&b.monitored); + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[8].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Monitored"); + } + + #[test] + fn test_series_sorting_options_tags() { + let expected_cmp_fn: fn(&Series, &Series) -> Ordering = |a, b| { + let a_str = a + .tags + .iter() + .map(|tag| tag.as_i64().unwrap().to_string()) + .collect::>() + .join(","); + let b_str = b + .tags + .iter() + .map(|tag| tag.as_i64().unwrap().to_string()) + .collect::>() + .join(","); + + a_str.cmp(&b_str) + }; + let mut expected_series_vec = series_vec(); + expected_series_vec.sort_by(expected_cmp_fn); + + let sort_option = series_sorting_options()[9].clone(); + let mut sorted_series_vec = series_vec(); + sorted_series_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_series_vec, expected_series_vec); + assert_str_eq!(sort_option.name, "Tags"); + } + + #[test] + fn test_library_handler_accepts() { + let mut library_handler_blocks = Vec::new(); + library_handler_blocks.extend(SERIES_BLOCKS); + + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if library_handler_blocks.contains(&active_sonarr_block) { + assert!(LibraryHandler::accepts(active_sonarr_block)); + } else { + assert!(!LibraryHandler::accepts(active_sonarr_block)); + } + }); + } + + #[test] + fn test_library_handler_not_ready_when_loading() { + let mut app = App::default(); + app.is_loading = true; + + let handler = LibraryHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_library_handler_not_ready_when_series_is_empty() { + let mut app = App::default(); + app.is_loading = false; + + let handler = LibraryHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_library_handler_ready_when_not_loading_and_series_is_not_empty() { + let mut app = App::default(); + app.is_loading = false; + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + let handler = LibraryHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Series, + None, + ); + + assert!(handler.is_ready()); + } + + fn series_vec() -> Vec { + vec![ + Series { + id: 3, + title: "test 1".into(), + network: Some("Network 1".to_owned()), + year: 2024, + monitored: false, + season_folder: false, + runtime: 12.into(), + quality_profile_id: 1, + language_profile_id: 1, + certification: Some("TV-MA".to_owned()), + series_type: SeriesType::Daily, + tags: vec![1.into(), 2.into()], + ..Series::default() + }, + Series { + id: 2, + title: "test 2".into(), + network: Some("Network 2".to_owned()), + year: 1998, + monitored: false, + season_folder: false, + runtime: 60.into(), + quality_profile_id: 2, + language_profile_id: 2, + certification: Some("TV-PG".to_owned()), + series_type: SeriesType::Anime, + tags: vec![1.into(), 3.into()], + ..Series::default() + }, + Series { + id: 1, + title: "test 3".into(), + network: Some("network 3".to_owned()), + year: 1954, + monitored: true, + season_folder: false, + runtime: 120.into(), + quality_profile_id: 3, + language_profile_id: 3, + certification: Some("TV-G".to_owned()), + tags: vec![2.into(), 3.into()], + series_type: SeriesType::Standard, + ..Series::default() + }, + ] + } + + fn sort_options() -> Vec> { + vec![SortOption { + name: "Test 1", + cmp_fn: Some(|a, b| { + b.title + .text + .to_lowercase() + .cmp(&a.title.text.to_lowercase()) + }), + }] + } +} diff --git a/src/handlers/sonarr_handlers/library/mod.rs b/src/handlers/sonarr_handlers/library/mod.rs new file mode 100644 index 0000000..3cde86b --- /dev/null +++ b/src/handlers/sonarr_handlers/library/mod.rs @@ -0,0 +1,460 @@ +use crate::{ + app::App, + event::Key, + handle_text_box_keys, handle_text_box_left_right_keys, + handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}, + models::{ + servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS, + SERIES_BLOCKS, + }, + sonarr_models::Series, + stateful_table::SortOption, + BlockSelectionState, HorizontallyScrollableText, Scrollable, + }, + network::sonarr_network::SonarrEvent, +}; + +use super::handle_change_tab_left_right_keys; +use crate::app::key_binding::DEFAULT_KEYBINDINGS; + +#[cfg(test)] +#[path = "library_handler_tests.rs"] +mod library_handler_tests; + +pub(super) struct LibraryHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_sonarr_block { + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: ActiveSonarrBlock) -> bool { + SERIES_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + _context: Option, + ) -> LibraryHandler<'a, 'b> { + LibraryHandler { + key, + app, + active_sonarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading && !self.app.data.sonarr_data.series.is_empty() + } + + fn handle_scroll_up(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => self.app.data.sonarr_data.series.scroll_up(), + ActiveSonarrBlock::SeriesSortPrompt => self + .app + .data + .sonarr_data + .series + .sort + .as_mut() + .unwrap() + .scroll_up(), + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => self.app.data.sonarr_data.series.scroll_down(), + ActiveSonarrBlock::SeriesSortPrompt => self + .app + .data + .sonarr_data + .series + .sort + .as_mut() + .unwrap() + .scroll_down(), + _ => (), + } + } + + fn handle_home(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => self.app.data.sonarr_data.series.scroll_to_top(), + ActiveSonarrBlock::SearchSeries => { + self + .app + .data + .sonarr_data + .series + .search + .as_mut() + .unwrap() + .scroll_home(); + } + ActiveSonarrBlock::FilterSeries => { + self + .app + .data + .sonarr_data + .series + .filter + .as_mut() + .unwrap() + .scroll_home(); + } + ActiveSonarrBlock::SeriesSortPrompt => self + .app + .data + .sonarr_data + .series + .sort + .as_mut() + .unwrap() + .scroll_to_top(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => self.app.data.sonarr_data.series.scroll_to_bottom(), + ActiveSonarrBlock::SearchSeries => self + .app + .data + .sonarr_data + .series + .search + .as_mut() + .unwrap() + .reset_offset(), + ActiveSonarrBlock::FilterSeries => self + .app + .data + .sonarr_data + .series + .filter + .as_mut() + .unwrap() + .reset_offset(), + ActiveSonarrBlock::SeriesSortPrompt => self + .app + .data + .sonarr_data + .series + .sort + .as_mut() + .unwrap() + .scroll_to_bottom(), + _ => (), + } + } + + fn handle_delete(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Series { + self + .app + .push_navigation_stack(ActiveSonarrBlock::DeleteSeriesPrompt.into()); + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS); + } + } + + fn handle_left_right_action(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => handle_change_tab_left_right_keys(self.app, self.key), + ActiveSonarrBlock::UpdateAllSeriesPrompt => handle_prompt_toggle(self.app, self.key), + ActiveSonarrBlock::SearchSeries => { + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.sonarr_data.series.search.as_mut().unwrap() + ) + } + ActiveSonarrBlock::FilterSeries => { + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.sonarr_data.series.filter.as_mut().unwrap() + ) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Series => self + .app + .push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into()), + ActiveSonarrBlock::SearchSeries => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.sonarr_data.series.search.is_some() { + let has_match = self + .app + .data + .sonarr_data + .series + .apply_search(|series| &series.title.text); + + if !has_match { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SearchSeriesError.into()); + } + } + } + ActiveSonarrBlock::FilterSeries => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.sonarr_data.series.filter.is_some() { + let has_matches = self + .app + .data + .sonarr_data + .series + .apply_filter(|series| &series.title.text); + + if !has_matches { + self + .app + .push_navigation_stack(ActiveSonarrBlock::FilterSeriesError.into()); + } + } + } + ActiveSonarrBlock::UpdateAllSeriesPrompt => { + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries); + } + + self.app.pop_navigation_stack(); + } + ActiveSonarrBlock::SeriesSortPrompt => { + self + .app + .data + .sonarr_data + .series + .items + .sort_by(|a, b| a.id.cmp(&b.id)); + self.app.data.sonarr_data.series.apply_sorting(); + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::FilterSeries | ActiveSonarrBlock::FilterSeriesError => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.series.reset_filter(); + self.app.should_ignore_quit_key = false; + } + ActiveSonarrBlock::SearchSeries | ActiveSonarrBlock::SearchSeriesError => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.series.reset_search(); + self.app.should_ignore_quit_key = false; + } + ActiveSonarrBlock::UpdateAllSeriesPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.prompt_confirm = false; + } + ActiveSonarrBlock::SeriesSortPrompt => { + self.app.pop_navigation_stack(); + } + _ => { + self.app.data.sonarr_data.series.reset_search(); + self.app.data.sonarr_data.series.reset_filter(); + handle_clear_errors(self.app); + } + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_sonarr_block { + ActiveSonarrBlock::Series => match self.key { + _ if key == DEFAULT_KEYBINDINGS.search.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SearchSeries.into()); + self.app.data.sonarr_data.series.search = Some(HorizontallyScrollableText::default()); + self.app.should_ignore_quit_key = true; + } + _ if key == DEFAULT_KEYBINDINGS.filter.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::FilterSeries.into()); + self.app.data.sonarr_data.series.reset_filter(); + self.app.data.sonarr_data.series.filter = Some(HorizontallyScrollableText::default()); + self.app.should_ignore_quit_key = true; + } + _ if key == DEFAULT_KEYBINDINGS.edit.key => { + self.app.push_navigation_stack( + ( + ActiveSonarrBlock::EditSeriesPrompt, + Some(ActiveSonarrBlock::Series), + ) + .into(), + ); + self.app.data.sonarr_data.edit_series_modal = Some((&self.app.data.sonarr_data).into()); + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); + } + _ if key == DEFAULT_KEYBINDINGS.add.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); + self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); + self.app.should_ignore_quit_key = true; + } + _ if key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); + } + _ if key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ if key == DEFAULT_KEYBINDINGS.sort.key => { + self + .app + .data + .sonarr_data + .series + .sorting(series_sorting_options()); + self + .app + .push_navigation_stack(ActiveSonarrBlock::SeriesSortPrompt.into()); + } + _ => (), + }, + ActiveSonarrBlock::SearchSeries => { + handle_text_box_keys!( + self, + key, + self.app.data.sonarr_data.series.search.as_mut().unwrap() + ) + } + ActiveSonarrBlock::FilterSeries => { + handle_text_box_keys!( + self, + key, + self.app.data.sonarr_data.series.filter.as_mut().unwrap() + ) + } + ActiveSonarrBlock::UpdateAllSeriesPrompt => { + if key == DEFAULT_KEYBINDINGS.confirm.key { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries); + + self.app.pop_navigation_stack(); + } + } + _ => (), + } + } +} + +fn series_sorting_options() -> Vec> { + vec![ + SortOption { + name: "Title", + cmp_fn: Some(|a, b| { + a.title + .text + .to_lowercase() + .cmp(&b.title.text.to_lowercase()) + }), + }, + SortOption { + name: "Year", + cmp_fn: Some(|a, b| a.year.cmp(&b.year)), + }, + SortOption { + name: "Network", + cmp_fn: Some(|a, b| { + a.network + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase() + .cmp(&b.network.as_ref().unwrap_or(&String::new()).to_lowercase()) + }), + }, + SortOption { + name: "Runtime", + cmp_fn: Some(|a, b| a.runtime.cmp(&b.runtime)), + }, + SortOption { + name: "Rating", + cmp_fn: Some(|a, b| { + a.certification + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase() + .cmp( + &b.certification + .as_ref() + .unwrap_or(&String::new()) + .to_lowercase(), + ) + }), + }, + SortOption { + name: "Type", + cmp_fn: Some(|a, b| a.series_type.to_string().cmp(&b.series_type.to_string())), + }, + SortOption { + name: "Quality", + cmp_fn: Some(|a, b| a.quality_profile_id.cmp(&b.quality_profile_id)), + }, + SortOption { + name: "Language", + cmp_fn: Some(|a, b| a.language_profile_id.cmp(&b.language_profile_id)), + }, + SortOption { + name: "Monitored", + cmp_fn: Some(|a, b| a.monitored.cmp(&b.monitored)), + }, + SortOption { + name: "Tags", + cmp_fn: Some(|a, b| { + let a_str = a + .tags + .iter() + .map(|tag| tag.as_i64().unwrap().to_string()) + .collect::>() + .join(","); + let b_str = b + .tags + .iter() + .map(|tag| tag.as_i64().unwrap().to_string()) + .collect::>() + .join(","); + + a_str.cmp(&b_str) + }), + }, + ] +} diff --git a/src/handlers/sonarr_handlers/mod.rs b/src/handlers/sonarr_handlers/mod.rs new file mode 100644 index 0000000..0dd5014 --- /dev/null +++ b/src/handlers/sonarr_handlers/mod.rs @@ -0,0 +1,96 @@ +use library::LibraryHandler; + +use crate::{ + app::{key_binding::DEFAULT_KEYBINDINGS, App}, + event::Key, + models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock, +}; + +use super::KeyEventHandler; + +mod library; + +#[cfg(test)] +#[path = "sonarr_handler_tests.rs"] +mod sonarr_handler_tests; + +#[cfg(test)] +#[path = "sonarr_handler_test_utils.rs"] +mod sonarr_handler_test_utils; + +pub(super) struct SonarrHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_sonarr_block { + _ if LibraryHandler::accepts(self.active_sonarr_block) => { + LibraryHandler::with(self.key, self.app, self.active_sonarr_block, self.context).handle(); + } + _ => self.handle_key_event(), + } + } + + fn accepts(_active_block: ActiveSonarrBlock) -> bool { + true + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + context: Option, + ) -> SonarrHandler<'a, 'b> { + SonarrHandler { + key, + app, + active_sonarr_block: active_block, + context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + true + } + + fn handle_scroll_up(&mut self) {} + + fn handle_scroll_down(&mut self) {} + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) {} + + fn handle_submit(&mut self) {} + + fn handle_esc(&mut self) {} + + fn handle_char_key_event(&mut self) {} +} + +pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) { + let key_ref = key; + match key_ref { + _ if key == DEFAULT_KEYBINDINGS.left.key => { + app.data.sonarr_data.main_tabs.previous(); + app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); + } + _ if key == DEFAULT_KEYBINDINGS.right.key => { + app.data.sonarr_data.main_tabs.next(); + app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); + } + _ => (), + } +} diff --git a/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs b/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs new file mode 100644 index 0000000..759a2d3 --- /dev/null +++ b/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs @@ -0,0 +1,157 @@ +#[cfg(test)] +#[macro_use] +mod utils { + + #[macro_export] + macro_rules! test_edit_series_key { + ($handler:ident, $block:expr, $context:expr) => { + let mut app = App::default(); + let mut sonarr_data = SonarrData { + quality_profile_map: BiMap::from_iter([ + (2222, "HD - 1080p".to_owned()), + (1111, "Any".to_owned()), + ]), + language_profiles_map: BiMap::from_iter([ + (2222, "English".to_owned()), + (1111, "Any".to_owned()), + ]), + tags_map: BiMap::from_iter([(1, "test".to_owned())]), + ..create_test_sonarr_data() + }; + sonarr_data.series.set_items(vec![Series { + path: "/nfs/series/Test".to_owned().into(), + monitored: true, + season_folder: true, + quality_profile_id: 2222, + language_profile_id: 2222, + series_type: SeriesType::Anime, + tags: vec![Number::from(1)], + ..Series::default() + }]); + app.data.sonarr_data = sonarr_data; + + $handler::with(DEFAULT_KEYBINDINGS.edit.key, &mut app, $block, None).handle(); + + assert_eq!( + app.get_current_route(), + (ActiveSonarrBlock::EditSeriesPrompt, Some($context)).into() + ); + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditSeriesToggleMonitored + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .series_type_list + .items, + Vec::from_iter(SeriesType::iter()) + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .series_type_list + .current_selection(), + &SeriesType::Anime + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .items, + vec!["Any".to_owned(), "HD - 1080p".to_owned()] + ); + assert_str_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "HD - 1080p" + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .language_profile_list + .items, + vec!["Any".to_owned(), "English".to_owned()] + ); + assert_str_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .language_profile_list + .current_selection(), + "English" + ); + assert_str_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .path + .text, + "/nfs/series/Test" + ); + assert_str_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .tags + .text, + "test" + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .monitored, + Some(true) + ); + assert_eq!( + app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .use_season_folders, + Some(true) + ); + assert_eq!( + app.data.sonarr_data.selected_block.blocks, + $crate::models::servarr_data::sonarr::sonarr_data::EDIT_SERIES_SELECTION_BLOCKS + ); + }; + } +} diff --git a/src/handlers/sonarr_handlers/sonarr_handler_tests.rs b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs new file mode 100644 index 0000000..4fcc1bd --- /dev/null +++ b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs @@ -0,0 +1,122 @@ +#[cfg(test)] +mod tests { + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; + use crate::handlers::sonarr_handlers::SonarrHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; + use crate::test_handler_delegation; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + #[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)] + #[case(1, ActiveSonarrBlock::Series, ActiveSonarrBlock::Blocklist)] + #[case(2, ActiveSonarrBlock::Downloads, ActiveSonarrBlock::History)] + #[case(3, ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::RootFolders)] + #[case(4, ActiveSonarrBlock::History, ActiveSonarrBlock::Indexers)] + #[case(5, ActiveSonarrBlock::RootFolders, ActiveSonarrBlock::System)] + #[case(6, ActiveSonarrBlock::Indexers, ActiveSonarrBlock::Series)] + fn test_sonarr_handler_change_tab_left_right_keys( + #[case] index: usize, + #[case] left_block: ActiveSonarrBlock, + #[case] right_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.data.sonarr_data.main_tabs.set_index(index); + + handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.key); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + left_block.into() + ); + assert_eq!(app.get_current_route(), left_block.into()); + + app.data.sonarr_data.main_tabs.set_index(index); + + handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.key); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + right_block.into() + ); + assert_eq!(app.get_current_route(), right_block.into()); + } + + #[rstest] + fn test_delegates_library_blocks_to_library_handler( + #[values( + // ActiveSonarrBlock::AddSeriesAlreadyInLibrary, + // ActiveSonarrBlock::AddSeriesConfirmPrompt, + // ActiveSonarrBlock::AddSeriesEmptySearchResults, + // ActiveSonarrBlock::AddSeriesPrompt, + // ActiveSonarrBlock::AddSeriesSearchInput, + // ActiveSonarrBlock::AddSeriesSearchResults, + // ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + // ActiveSonarrBlock::AddSeriesSelectMonitor, + // ActiveSonarrBlock::AddSeriesSelectQualityProfile, + // ActiveSonarrBlock::AddSeriesSelectRootFolder, + // ActiveSonarrBlock::AddSeriesSelectSeriesType, + // ActiveSonarrBlock::AddSeriesTagsInput, + // ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder, + // ActiveSonarrBlock::AutomaticallySearchEpisodePrompt, + // ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, + // ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, + // ActiveSonarrBlock::DeleteEpisodeFilePrompt, + // ActiveSonarrBlock::DeleteSeriesConfirmPrompt, + // ActiveSonarrBlock::DeleteSeriesPrompt, + // ActiveSonarrBlock::DeleteSeriesToggleAddListExclusion, + // ActiveSonarrBlock::DeleteSeriesToggleDeleteFile, + // ActiveSonarrBlock::EditSeriesPrompt, + // ActiveSonarrBlock::EditSeriesConfirmPrompt, + // ActiveSonarrBlock::EditSeriesPathInput, + // ActiveSonarrBlock::EditSeriesSelectSeriesType, + // ActiveSonarrBlock::EditSeriesSelectQualityProfile, + // ActiveSonarrBlock::EditSeriesSelectLanguageProfile, + // ActiveSonarrBlock::EditSeriesTagsInput, + // ActiveSonarrBlock::EditSeriesToggleMonitored, + // ActiveSonarrBlock::EditSeriesToggleSeasonFolder, + // ActiveSonarrBlock::EpisodeDetails, + // ActiveSonarrBlock::EpisodeFile, + // ActiveSonarrBlock::EpisodeHistory, + // ActiveSonarrBlock::EpisodesSortPrompt, + // ActiveSonarrBlock::FilterEpisodes, + // ActiveSonarrBlock::FilterEpisodesError, + ActiveSonarrBlock::FilterSeries, + ActiveSonarrBlock::FilterSeriesError, + // ActiveSonarrBlock::FilterSeriesHistory, + // ActiveSonarrBlock::FilterSeriesHistoryError, + // ActiveSonarrBlock::ManualEpisodeSearch, + // ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt, + // ActiveSonarrBlock::ManualEpisodeSearchSortPrompt, + // ActiveSonarrBlock::ManualSeasonSearch, + // ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt, + // ActiveSonarrBlock::ManualSeasonSearchSortPrompt, + // ActiveSonarrBlock::SearchEpisodes, + // ActiveSonarrBlock::SearchEpisodesError, + // ActiveSonarrBlock::SearchSeason, + // ActiveSonarrBlock::SearchSeasonError, + ActiveSonarrBlock::SearchSeries, + ActiveSonarrBlock::SearchSeriesError, + // ActiveSonarrBlock::SearchSeriesHistory, + // ActiveSonarrBlock::SearchSeriesHistoryError, + // ActiveSonarrBlock::SeasonDetails, + ActiveSonarrBlock::Series, + // ActiveSonarrBlock::SeriesDetails, + // ActiveSonarrBlock::SeriesHistory, + // ActiveSonarrBlock::SeriesHistorySortPrompt, + ActiveSonarrBlock::SeriesSortPrompt, + ActiveSonarrBlock::UpdateAllSeriesPrompt, + // ActiveSonarrBlock::UpdateAndScanSeriesPrompt + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + SonarrHandler, + ActiveSonarrBlock::Series, + active_sonarr_block + ); + } +} diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 168a84c..6e77c89 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -295,12 +295,41 @@ pub static SERIES_BLOCKS: [ActiveSonarrBlock; 7] = [ ActiveSonarrBlock::UpdateAllSeriesPrompt, ]; +pub static EDIT_SERIES_BLOCKS: [ActiveSonarrBlock; 9] = [ + ActiveSonarrBlock::EditSeriesPrompt, + ActiveSonarrBlock::EditSeriesConfirmPrompt, + ActiveSonarrBlock::EditSeriesPathInput, + ActiveSonarrBlock::EditSeriesSelectSeriesType, + ActiveSonarrBlock::EditSeriesSelectQualityProfile, + ActiveSonarrBlock::EditSeriesSelectLanguageProfile, + ActiveSonarrBlock::EditSeriesTagsInput, + ActiveSonarrBlock::EditSeriesToggleMonitored, + ActiveSonarrBlock::EditSeriesToggleSeasonFolder, +]; + +pub static EDIT_SERIES_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ActiveSonarrBlock::EditSeriesToggleMonitored], + &[ActiveSonarrBlock::EditSeriesToggleSeasonFolder], + &[ActiveSonarrBlock::EditSeriesSelectQualityProfile], + &[ActiveSonarrBlock::EditSeriesSelectLanguageProfile], + &[ActiveSonarrBlock::EditSeriesSelectSeriesType], + &[ActiveSonarrBlock::EditSeriesPathInput], + &[ActiveSonarrBlock::EditSeriesTagsInput], + &[ActiveSonarrBlock::EditSeriesConfirmPrompt], +]; + pub static DOWNLOADS_BLOCKS: [ActiveSonarrBlock; 3] = [ ActiveSonarrBlock::Downloads, ActiveSonarrBlock::DeleteDownloadPrompt, ActiveSonarrBlock::UpdateDownloadsPrompt, ]; +pub const DELETE_SERIES_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ActiveSonarrBlock::DeleteSeriesToggleDeleteFile], + &[ActiveSonarrBlock::DeleteSeriesToggleAddListExclusion], + &[ActiveSonarrBlock::DeleteSeriesConfirmPrompt], +]; + impl From for Route { fn from(active_sonarr_block: ActiveSonarrBlock) -> Route { Route::Sonarr(active_sonarr_block, None) diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index 02e590a..aef207e 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -202,7 +202,8 @@ mod tests { mod active_sonarr_block_tests { use crate::models::servarr_data::sonarr::sonarr_data::{ - ActiveSonarrBlock, DOWNLOADS_BLOCKS, SERIES_BLOCKS, + ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_SERIES_BLOCKS, + EDIT_SERIES_SELECTION_BLOCKS, SERIES_BLOCKS, }; #[test] @@ -217,6 +218,59 @@ mod tests { assert!(SERIES_BLOCKS.contains(&ActiveSonarrBlock::UpdateAllSeriesPrompt)); } + #[test] + fn test_edit_movie_blocks_contents() { + assert_eq!(EDIT_SERIES_BLOCKS.len(), 9); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesPrompt)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesConfirmPrompt)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesPathInput)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesSelectSeriesType)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesSelectQualityProfile)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesSelectLanguageProfile)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesTagsInput)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesToggleMonitored)); + assert!(EDIT_SERIES_BLOCKS.contains(&ActiveSonarrBlock::EditSeriesToggleSeasonFolder)); + } + + #[test] + fn test_edit_series_selection_blocks_ordering() { + let mut edit_series_block_iter = EDIT_SERIES_SELECTION_BLOCKS.iter(); + + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesToggleMonitored] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesToggleSeasonFolder] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesSelectQualityProfile] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesSelectLanguageProfile] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesSelectSeriesType] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesPathInput] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesTagsInput] + ); + assert_eq!( + edit_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::EditSeriesConfirmPrompt] + ); + assert_eq!(edit_series_block_iter.next(), None); + } + #[test] fn test_downloads_blocks_contents() { assert_eq!(DOWNLOADS_BLOCKS.len(), 3); @@ -224,5 +278,24 @@ mod tests { assert!(DOWNLOADS_BLOCKS.contains(&ActiveSonarrBlock::DeleteDownloadPrompt)); assert!(DOWNLOADS_BLOCKS.contains(&ActiveSonarrBlock::UpdateDownloadsPrompt)); } + + #[test] + fn test_delete_series_selection_blocks_ordering() { + let mut delete_series_block_iter = DELETE_SERIES_SELECTION_BLOCKS.iter(); + + assert_eq!( + delete_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::DeleteSeriesToggleDeleteFile] + ); + assert_eq!( + delete_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::DeleteSeriesToggleAddListExclusion] + ); + assert_eq!( + delete_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::DeleteSeriesConfirmPrompt] + ); + assert_eq!(delete_series_block_iter.next(), None); + } } }