From d7f6d12f59529dee08548eeffe2403c67175e27f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Mon, 2 Dec 2024 12:43:17 -0700 Subject: [PATCH] feat(handler): Add series support for Sonarr --- .../library/add_series_handler.rs | 550 +++++ .../library/add_series_handler_tests.rs | 1849 +++++++++++++++++ .../library/library_handler_tests.rs | 47 +- src/handlers/sonarr_handlers/library/mod.rs | 9 +- src/models/servarr_data/sonarr/sonarr_data.rs | 27 + .../servarr_data/sonarr/sonarr_data_tests.rs | 62 +- 6 files changed, 2519 insertions(+), 25 deletions(-) create mode 100644 src/handlers/sonarr_handlers/library/add_series_handler.rs create mode 100644 src/handlers/sonarr_handlers/library/add_series_handler_tests.rs diff --git a/src/handlers/sonarr_handlers/library/add_series_handler.rs b/src/handlers/sonarr_handlers/library/add_series_handler.rs new file mode 100644 index 0000000..03bce76 --- /dev/null +++ b/src/handlers/sonarr_handlers/library/add_series_handler.rs @@ -0,0 +1,550 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, +}; +use crate::models::{BlockSelectionState, Scrollable}; +use crate::network::sonarr_network::SonarrEvent; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; + +#[cfg(test)] +#[path = "add_series_handler_tests.rs"] +mod add_series_handler_tests; + +pub(super) struct AddSeriesHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, 'b> { + fn accepts(active_block: ActiveSonarrBlock) -> bool { + ADD_SERIES_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + _context: Option, + ) -> AddSeriesHandler<'a, 'b> { + AddSeriesHandler { + key, + app, + active_sonarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + } + + fn handle_scroll_up(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchResults => self + .app + .data + .sonarr_data + .add_searched_series + .as_mut() + .unwrap() + .scroll_up(), + ActiveSonarrBlock::AddSeriesSelectMonitor => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .scroll_up(), + ActiveSonarrBlock::AddSeriesSelectSeriesType => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .scroll_up(), + ActiveSonarrBlock::AddSeriesSelectQualityProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .scroll_up(), + ActiveSonarrBlock::AddSeriesSelectLanguageProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .scroll_up(), + ActiveSonarrBlock::AddSeriesSelectRootFolder => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .scroll_up(), + ActiveSonarrBlock::AddSeriesPrompt => self.app.data.sonarr_data.selected_block.up(), + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchResults => self + .app + .data + .sonarr_data + .add_searched_series + .as_mut() + .unwrap() + .scroll_down(), + ActiveSonarrBlock::AddSeriesSelectMonitor => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .scroll_down(), + ActiveSonarrBlock::AddSeriesSelectSeriesType => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .scroll_down(), + ActiveSonarrBlock::AddSeriesSelectQualityProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .scroll_down(), + ActiveSonarrBlock::AddSeriesSelectLanguageProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .scroll_down(), + ActiveSonarrBlock::AddSeriesSelectRootFolder => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .scroll_down(), + ActiveSonarrBlock::AddSeriesPrompt => self.app.data.sonarr_data.selected_block.down(), + _ => (), + } + } + + fn handle_home(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchResults => self + .app + .data + .sonarr_data + .add_searched_series + .as_mut() + .unwrap() + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSelectMonitor => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSelectSeriesType => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSelectQualityProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSelectLanguageProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSelectRootFolder => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .scroll_to_top(), + ActiveSonarrBlock::AddSeriesSearchInput => self + .app + .data + .sonarr_data + .add_series_search + .as_mut() + .unwrap() + .scroll_home(), + ActiveSonarrBlock::AddSeriesTagsInput => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .tags + .scroll_home(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchResults => self + .app + .data + .sonarr_data + .add_searched_series + .as_mut() + .unwrap() + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSelectMonitor => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSelectSeriesType => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSelectQualityProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSelectLanguageProfile => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSelectRootFolder => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .scroll_to_bottom(), + ActiveSonarrBlock::AddSeriesSearchInput => self + .app + .data + .sonarr_data + .add_series_search + .as_mut() + .unwrap() + .reset_offset(), + ActiveSonarrBlock::AddSeriesTagsInput => self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .tags + .reset_offset(), + _ => (), + } + } + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesPrompt => handle_prompt_toggle(self.app, self.key), + ActiveSonarrBlock::AddSeriesSearchInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .add_series_search + .as_mut() + .unwrap() + ) + } + ActiveSonarrBlock::AddSeriesTagsInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .tags + ) + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + _ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchInput + && !self + .app + .data + .sonarr_data + .add_series_search + .as_mut() + .unwrap() + .text + .is_empty() => + { + self + .app + .push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); + self.app.should_ignore_quit_key = false; + } + _ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchResults + && self.app.data.sonarr_data.add_searched_series.is_some() => + { + let tvdb_id = self + .app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .tvdb_id; + + if self + .app + .data + .sonarr_data + .series + .items + .iter() + .any(|series| series.tvdb_id == tvdb_id) + { + self + .app + .push_navigation_stack(ActiveSonarrBlock::AddSeriesAlreadyInLibrary.into()); + } else { + self + .app + .push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + self.app.data.sonarr_data.add_series_modal = Some((&self.app.data.sonarr_data).into()); + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + } + } + ActiveSonarrBlock::AddSeriesPrompt => { + match self.app.data.sonarr_data.selected_block.get_active_block() { + ActiveSonarrBlock::AddSeriesConfirmPrompt => { + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddSeries(None)); + } + + self.app.pop_navigation_stack(); + } + ActiveSonarrBlock::AddSeriesSelectMonitor + | ActiveSonarrBlock::AddSeriesSelectSeriesType + | ActiveSonarrBlock::AddSeriesSelectQualityProfile + | ActiveSonarrBlock::AddSeriesSelectLanguageProfile + | ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.push_navigation_stack( + self + .app + .data + .sonarr_data + .selected_block + .get_active_block() + .into(), + ), + ActiveSonarrBlock::AddSeriesTagsInput => { + self.app.push_navigation_stack( + self + .app + .data + .sonarr_data + .selected_block + .get_active_block() + .into(), + ); + self.app.should_ignore_quit_key = true; + } + ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder => { + self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .use_season_folder = !self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .use_season_folder; + } + _ => (), + } + } + ActiveSonarrBlock::AddSeriesSelectMonitor + | ActiveSonarrBlock::AddSeriesSelectSeriesType + | ActiveSonarrBlock::AddSeriesSelectQualityProfile + | ActiveSonarrBlock::AddSeriesSelectLanguageProfile + | ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(), + ActiveSonarrBlock::AddSeriesTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchInput => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.add_series_search = None; + self.app.should_ignore_quit_key = false; + } + ActiveSonarrBlock::AddSeriesSearchResults + | ActiveSonarrBlock::AddSeriesEmptySearchResults => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.add_searched_series = None; + self.app.should_ignore_quit_key = true; + } + ActiveSonarrBlock::AddSeriesPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.add_series_modal = None; + self.app.data.sonarr_data.prompt_confirm = false; + } + ActiveSonarrBlock::AddSeriesSelectMonitor + | ActiveSonarrBlock::AddSeriesSelectSeriesType + | ActiveSonarrBlock::AddSeriesSelectQualityProfile + | ActiveSonarrBlock::AddSeriesSelectLanguageProfile + | ActiveSonarrBlock::AddSeriesAlreadyInLibrary + | ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(), + ActiveSonarrBlock::AddSeriesTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + _ => (), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_sonarr_block { + ActiveSonarrBlock::AddSeriesSearchInput => { + handle_text_box_keys!( + self, + key, + self + .app + .data + .sonarr_data + .add_series_search + .as_mut() + .unwrap() + ) + } + ActiveSonarrBlock::AddSeriesTagsInput => { + handle_text_box_keys!( + self, + key, + self + .app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .tags + ) + } + ActiveSonarrBlock::AddSeriesPrompt => { + if self.app.data.sonarr_data.selected_block.get_active_block() + == ActiveSonarrBlock::AddSeriesConfirmPrompt + && key == DEFAULT_KEYBINDINGS.confirm.key + { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddSeries(None)); + self.app.pop_navigation_stack(); + } + } + _ => (), + } + } +} diff --git a/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs b/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs new file mode 100644 index 0000000..63d28b2 --- /dev/null +++ b/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs @@ -0,0 +1,1849 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::library::add_series_handler::AddSeriesHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ADD_SERIES_BLOCKS}; + use crate::models::servarr_models::RootFolder; + use crate::models::sonarr_models::{AddSeriesSearchResult, SeriesMonitor, SeriesType}; + use crate::models::HorizontallyScrollableText; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; + use crate::models::servarr_data::sonarr::sonarr_data::ADD_SERIES_SELECTION_BLOCKS; + use crate::models::stateful_table::StatefulTable; + use crate::models::BlockSelectionState; + use crate::simple_stateful_iterable_vec; + + use super::*; + + #[rstest] + fn test_add_series_search_results_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(simple_stateful_iterable_vec!( + AddSeriesSearchResult, + HorizontallyScrollableText + )); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 2" + ); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[rstest] + fn test_add_series_search_results_scroll_no_op_when_not_ready( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.is_loading = true; + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(simple_stateful_iterable_vec!( + AddSeriesSearchResult, + HorizontallyScrollableText + )); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[rstest] + fn test_add_series_select_monitor_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let monitor_vec = Vec::from_iter(SeriesMonitor::iter()); + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .set_items(monitor_vec.clone()); + + if key == Key::Up { + for i in (0..monitor_vec.len()).rev() { + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectMonitor, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .monitor_list + .current_selection(), + &monitor_vec[i] + ); + } + } else { + for i in 0..monitor_vec.len() { + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectMonitor, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .monitor_list + .current_selection(), + &monitor_vec[(i + 1) % monitor_vec.len()] + ); + } + } + } + + #[rstest] + fn test_add_series_select_series_type_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let series_type_vec = Vec::from_iter(SeriesType::iter()); + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .set_items(series_type_vec.clone()); + + if key == Key::Up { + for i in (0..series_type_vec.len()).rev() { + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .series_type_list + .current_selection(), + &series_type_vec[i] + ); + } + } else { + for i in 0..series_type_vec.len() { + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .series_type_list + .current_selection(), + &series_type_vec[(i + 1) % series_type_vec.len()] + ); + } + } + } + + #[rstest] + fn test_add_series_select_quality_profile_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "Test 2" + ); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "Test 1" + ); + } + + #[rstest] + fn test_add_series_select_language_profile_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .set_items(vec!["Test 1".to_owned(), "Test 2".to_owned()]); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .language_profile_list + .current_selection(), + "Test 2" + ); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .language_profile_list + .current_selection(), + "Test 1" + ); + } + + #[rstest] + fn test_add_series_select_root_folder_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .set_items(simple_stateful_iterable_vec!(RootFolder, String, path)); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .root_folder_list + .current_selection() + .path, + "Test 2" + ); + + AddSeriesHandler::with( + key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .root_folder_list + .current_selection() + .path, + "Test 1" + ); + } + + #[rstest] + fn test_add_series_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + AddSeriesHandler::with(key, &mut app, ActiveSonarrBlock::AddSeriesPrompt, None).handle(); + + if key == Key::Up { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::AddSeriesSelectRootFolder + ); + } else { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::AddSeriesSelectQualityProfile + ); + } + } + + #[rstest] + fn test_add_series_prompt_scroll_no_op_when_not_ready(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.is_loading = true; + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + AddSeriesHandler::with(key, &mut app, ActiveSonarrBlock::AddSeriesPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::AddSeriesSelectMonitor + ); + } + } + + mod test_handle_home_end { + use std::sync::atomic::Ordering; + + use strum::IntoEnumIterator; + + use crate::extended_stateful_iterable_vec; + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; + use crate::models::stateful_table::StatefulTable; + + use super::*; + + #[test] + fn test_add_series_search_results_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(extended_stateful_iterable_vec!( + AddSeriesSearchResult, + HorizontallyScrollableText + )); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 3" + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[test] + fn test_add_series_search_results_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.is_loading = true; + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(extended_stateful_iterable_vec!( + AddSeriesSearchResult, + HorizontallyScrollableText + )); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .title + .to_string(), + "Test 1" + ); + } + + #[test] + fn test_add_series_select_monitor_home_end() { + let monitor_vec = Vec::from_iter(SeriesMonitor::iter()); + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .monitor_list + .set_items(monitor_vec.clone()); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectMonitor, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .monitor_list + .current_selection(), + &monitor_vec[monitor_vec.len() - 1] + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectMonitor, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .monitor_list + .current_selection(), + &monitor_vec[0] + ); + } + + #[test] + fn test_add_series_select_series_type_home_end() { + let series_type_vec = Vec::from_iter(SeriesType::iter()); + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .series_type_list + .set_items(series_type_vec.clone()); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .series_type_list + .current_selection(), + &series_type_vec[series_type_vec.len() - 1] + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .series_type_list + .current_selection(), + &series_type_vec[0] + ); + } + + #[test] + fn test_add_series_select_quality_profile_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .quality_profile_list + .set_items(vec![ + "Test 1".to_owned(), + "Test 2".to_owned(), + "Test 3".to_owned(), + ]); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "Test 3" + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "Test 1" + ); + } + + #[test] + fn test_add_series_select_language_profile_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .language_profile_list + .set_items(vec![ + "Test 1".to_owned(), + "Test 2".to_owned(), + "Test 3".to_owned(), + ]); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .language_profile_list + .current_selection(), + "Test 3" + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .language_profile_list + .current_selection(), + "Test 1" + ); + } + + #[test] + fn test_add_series_select_root_folder_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app + .data + .sonarr_data + .add_series_modal + .as_mut() + .unwrap() + .root_folder_list + .set_items(extended_stateful_iterable_vec!(RootFolder, String, path)); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .root_folder_list + .current_selection() + .path, + "Test 3" + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .root_folder_list + .current_selection() + .path, + "Test 1" + ); + } + + #[test] + fn test_add_series_search_input_home_end_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_search = Some("Test".into()); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .offset + .load(Ordering::SeqCst), + 4 + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_add_series_tags_input_home_end_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal { + tags: "Test".into(), + ..AddSeriesModal::default() + }); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 4 + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 0 + ); + } + } + + mod test_handle_left_right_action { + use std::sync::atomic::Ordering; + + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + + AddSeriesHandler::with(key, &mut app, ActiveSonarrBlock::AddSeriesPrompt, None).handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + AddSeriesHandler::with(key, &mut app, ActiveSonarrBlock::AddSeriesPrompt, None).handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + + #[test] + fn test_add_series_search_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_search = Some("Test".into()); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .offset + .load(Ordering::SeqCst), + 1 + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_add_series_tags_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal { + tags: "Test".into(), + ..AddSeriesModal::default() + }); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 1 + ); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 0 + ); + } + } + + mod test_handle_submit { + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; + use crate::models::servarr_data::sonarr::sonarr_data::ADD_SERIES_SELECTION_BLOCKS; + use crate::models::sonarr_models::Series; + use crate::models::stateful_table::StatefulTable; + use crate::models::BlockSelectionState; + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_add_series_search_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.add_series_search = Some("test".into()); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchResults.into() + ); + } + + #[test] + fn test_add_series_search_input_submit_noop_on_empty_search() { + let mut app = App::default(); + app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); + app.should_ignore_quit_key = true; + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert!(app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchInput.into() + ); + } + + #[test] + fn test_add_series_search_results_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![AddSeriesSearchResult::default()]); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::AddSeriesSelectRootFolder + ); + assert!(app.data.sonarr_data.add_series_modal.is_some()); + assert!(!app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .monitor_list + .items + .is_empty()); + assert!(!app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .series_type_list + .items + .is_empty()); + assert!(!app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .items + .is_empty()); + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .quality_profile_list + .current_selection(), + "A - Test 1" + ); + } + + #[test] + fn test_add_series_search_results_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![AddSeriesSearchResult::default()]); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchResults.into() + ); + assert!(app.data.sonarr_data.add_series_modal.is_none()); + } + + #[test] + fn test_add_series_search_results_submit_does_nothing_on_empty_table() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchResults.into() + ); + } + + #[test] + fn test_add_series_search_results_submit_series_already_in_library() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![AddSeriesSearchResult::default()]); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + app + .data + .sonarr_data + .series + .set_items(vec![Series::default()]); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesAlreadyInLibrary.into() + ); + } + + #[test] + fn test_add_series_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, ADD_SERIES_SELECTION_BLOCKS.len() - 1); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + } + + #[test] + fn test_add_series_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.data.sonarr_data.prompt_confirm = true; + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, ADD_SERIES_SELECTION_BLOCKS.len() - 1); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::AddSeries(None)) + ); + assert!(app.data.sonarr_data.add_series_modal.is_some()); + } + + #[rstest] + #[case(ActiveSonarrBlock::AddSeriesSelectRootFolder, 0)] + #[case(ActiveSonarrBlock::AddSeriesSelectMonitor, 1)] + #[case(ActiveSonarrBlock::AddSeriesSelectQualityProfile, 2)] + #[case(ActiveSonarrBlock::AddSeriesSelectLanguageProfile, 3)] + #[case(ActiveSonarrBlock::AddSeriesSelectSeriesType, 4)] + #[case(ActiveSonarrBlock::AddSeriesTagsInput, 6)] + fn test_add_series_prompt_selected_block_submit( + #[case] selected_block: ActiveSonarrBlock, + #[case] y_index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, y_index); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), selected_block.into()); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + + if selected_block == ActiveSonarrBlock::AddSeriesTagsInput { + assert!(app.should_ignore_quit_key); + } + } + + #[rstest] + fn test_add_series_prompt_selecting_preferences_blocks_submit( + #[values( + ActiveSonarrBlock::AddSeriesSelectMonitor, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + ActiveSonarrBlock::AddSeriesTagsInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.push_navigation_stack(active_sonarr_block.into()); + + AddSeriesHandler::with(SUBMIT_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + + if active_sonarr_block == ActiveSonarrBlock::AddSeriesTagsInput { + assert!(!app.should_ignore_quit_key); + } + } + + #[test] + fn test_add_series_toggle_use_season_folder_submit() { + let mut app = App::default(); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, 5); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + assert!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .use_season_folder + ); + + AddSeriesHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + assert!( + !app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .use_season_folder + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; + use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; + use crate::models::stateful_table::StatefulTable; + use crate::simple_stateful_iterable_vec; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_add_series_search_input_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.data.sonarr_data = create_test_sonarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); + + AddSeriesHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert_eq!(app.data.sonarr_data.add_series_search, None); + } + + #[test] + fn test_add_series_input_esc() { + let mut app = App::default(); + app.data.sonarr_data = create_test_sonarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into()); + + AddSeriesHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + } + + #[rstest] + fn test_add_series_search_results_esc( + #[values( + ActiveSonarrBlock::AddSeriesSearchResults, + ActiveSonarrBlock::AddSeriesEmptySearchResults + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); + app.push_navigation_stack(active_sonarr_block.into()); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(simple_stateful_iterable_vec!( + AddSeriesSearchResult, + HorizontallyScrollableText + )); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + + AddSeriesHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchInput.into() + ); + assert!(app.data.sonarr_data.add_searched_series.is_none()); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_add_series_already_in_library_esc() { + let mut app = App::default(); + app.data.sonarr_data = create_test_sonarr_data(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesAlreadyInLibrary.into()); + + AddSeriesHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesAlreadyInLibrary, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchResults.into() + ); + } + + #[test] + fn test_add_series_prompt_esc() { + let mut app = App::default(); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app.data.sonarr_data = create_test_sonarr_data(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + + AddSeriesHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::AddSeriesPrompt, None).handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesSearchResults.into() + ); + assert!(app.data.sonarr_data.add_series_modal.is_none()); + } + + #[test] + fn test_add_series_tags_input_esc() { + let mut app = App::default(); + app.data.sonarr_data = create_test_sonarr_data(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into()); + + AddSeriesHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + } + + #[rstest] + fn test_selecting_preferences_blocks_esc( + #[values( + ActiveSonarrBlock::AddSeriesSelectMonitor, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + ActiveSonarrBlock::AddSeriesSelectRootFolder + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.push_navigation_stack(active_sonarr_block.into()); + + AddSeriesHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AddSeriesPrompt.into() + ); + } + } + + mod test_handle_key_char { + use super::*; + use crate::{ + models::{ + servarr_data::sonarr::{modals::AddSeriesModal, sonarr_data::ADD_SERIES_SELECTION_BLOCKS}, + BlockSelectionState, + }, + network::sonarr_network::SonarrEvent, + }; + + #[test] + fn test_add_series_search_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_search = Some("Test".into()); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .text, + "Tes" + ); + } + + #[test] + fn test_add_series_tags_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal { + tags: "Test".into(), + ..AddSeriesModal::default() + }); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .text, + "Tes" + ); + } + + #[test] + fn test_add_series_search_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); + + AddSeriesHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::AddSeriesSearchInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_search + .as_ref() + .unwrap() + .text, + "h" + ); + } + + #[test] + fn test_add_series_tags_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + + AddSeriesHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::AddSeriesTagsInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .text, + "h" + ); + } + + #[test] + fn test_add_series_confirm_prompt_prompt_confirmation_confirm() { + let mut app = App::default(); + app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); + app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, ADD_SERIES_SELECTION_BLOCKS.len() - 1); + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::AddSeries(None)) + ); + assert!(app.data.sonarr_data.add_series_modal.is_some()); + } + } + + #[test] + fn test_add_series_handler_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if ADD_SERIES_BLOCKS.contains(&active_sonarr_block) { + assert!(AddSeriesHandler::accepts(active_sonarr_block)); + } else { + assert!(!AddSeriesHandler::accepts(active_sonarr_block)); + } + }); + } + + #[test] + fn test_add_series_handler_is_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.is_loading = true; + + let handler = AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_add_series_handler_is_ready_when_not_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Series.into()); + app.is_loading = false; + + let handler = AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/library/library_handler_tests.rs b/src/handlers/sonarr_handlers/library/library_handler_tests.rs index 35ce42f..207d0cb 100644 --- a/src/handlers/sonarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/library_handler_tests.rs @@ -12,7 +12,7 @@ mod tests { use crate::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler}; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ - ActiveSonarrBlock, DELETE_SERIES_BLOCKS, LIBRARY_BLOCKS, + ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, LIBRARY_BLOCKS, }; use crate::models::sonarr_models::{Series, SeriesStatus, SeriesType}; use crate::models::stateful_table::SortOption; @@ -1441,27 +1441,29 @@ mod tests { } } - // #[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_add_series_blocks_to_add_series_handler( + #[values( + ActiveSonarrBlock::AddSeriesAlreadyInLibrary, + ActiveSonarrBlock::AddSeriesEmptySearchResults, + ActiveSonarrBlock::AddSeriesPrompt, + ActiveSonarrBlock::AddSeriesSearchInput, + ActiveSonarrBlock::AddSeriesSearchResults, + ActiveSonarrBlock::AddSeriesSelectLanguageProfile, + ActiveSonarrBlock::AddSeriesSelectMonitor, + ActiveSonarrBlock::AddSeriesSelectQualityProfile, + ActiveSonarrBlock::AddSeriesSelectRootFolder, + ActiveSonarrBlock::AddSeriesSelectSeriesType, + 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( @@ -1705,6 +1707,7 @@ mod tests { fn test_library_handler_accepts() { let mut library_handler_blocks = Vec::new(); library_handler_blocks.extend(LIBRARY_BLOCKS); + library_handler_blocks.extend(ADD_SERIES_BLOCKS); library_handler_blocks.extend(DELETE_SERIES_BLOCKS); ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { diff --git a/src/handlers/sonarr_handlers/library/mod.rs b/src/handlers/sonarr_handlers/library/mod.rs index 3a1a05e..fee9089 100644 --- a/src/handlers/sonarr_handlers/library/mod.rs +++ b/src/handlers/sonarr_handlers/library/mod.rs @@ -1,3 +1,4 @@ +use add_series_handler::AddSeriesHandler; use delete_series_handler::DeleteSeriesHandler; use crate::{ @@ -20,6 +21,7 @@ use crate::{ use super::handle_change_tab_left_right_keys; use crate::app::key_binding::DEFAULT_KEYBINDINGS; +mod add_series_handler; mod delete_series_handler; #[cfg(test)] @@ -36,6 +38,9 @@ pub(super) struct LibraryHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, 'b> { fn handle(&mut self) { match self.active_sonarr_block { + _ if AddSeriesHandler::accepts(self.active_sonarr_block) => { + AddSeriesHandler::with(self.key, self.app, self.active_sonarr_block, self.context).handle(); + } _ if DeleteSeriesHandler::accepts(self.active_sonarr_block) => { DeleteSeriesHandler::with(self.key, self.app, self.active_sonarr_block, self.context) .handle(); @@ -45,7 +50,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, ' } fn accepts(active_block: ActiveSonarrBlock) -> bool { - DeleteSeriesHandler::accepts(active_block) || LIBRARY_BLOCKS.contains(&active_block) + AddSeriesHandler::accepts(active_block) + || DeleteSeriesHandler::accepts(active_block) + || LIBRARY_BLOCKS.contains(&active_block) } fn with( diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 7709042..335b54a 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -295,6 +295,33 @@ pub static LIBRARY_BLOCKS: [ActiveSonarrBlock; 7] = [ ActiveSonarrBlock::UpdateAllSeriesPrompt, ]; +pub static ADD_SERIES_BLOCKS: [ActiveSonarrBlock; 13] = [ + 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, +]; + +pub const ADD_SERIES_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ActiveSonarrBlock::AddSeriesSelectRootFolder], + &[ActiveSonarrBlock::AddSeriesSelectMonitor], + &[ActiveSonarrBlock::AddSeriesSelectQualityProfile], + &[ActiveSonarrBlock::AddSeriesSelectLanguageProfile], + &[ActiveSonarrBlock::AddSeriesSelectSeriesType], + &[ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder], + &[ActiveSonarrBlock::AddSeriesTagsInput], + &[ActiveSonarrBlock::AddSeriesConfirmPrompt], +]; + pub static EDIT_SERIES_BLOCKS: [ActiveSonarrBlock; 9] = [ ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesConfirmPrompt, diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index 7af3986..0f60183 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -202,8 +202,9 @@ mod tests { mod active_sonarr_block_tests { use crate::models::servarr_data::sonarr::sonarr_data::{ - ActiveSonarrBlock, DELETE_SERIES_BLOCKS, DELETE_SERIES_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, - EDIT_SERIES_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS, LIBRARY_BLOCKS, + ActiveSonarrBlock, ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, DELETE_SERIES_BLOCKS, + DELETE_SERIES_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_SERIES_BLOCKS, + EDIT_SERIES_SELECTION_BLOCKS, LIBRARY_BLOCKS, }; #[test] @@ -218,6 +219,63 @@ mod tests { assert!(LIBRARY_BLOCKS.contains(&ActiveSonarrBlock::UpdateAllSeriesPrompt)); } + #[test] + fn test_add_series_blocks_contents() { + assert_eq!(ADD_SERIES_BLOCKS.len(), 13); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesAlreadyInLibrary)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesConfirmPrompt)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesEmptySearchResults)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesPrompt)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSearchInput)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSearchResults)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSelectLanguageProfile)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSelectMonitor)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSelectQualityProfile)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSelectRootFolder)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesSelectSeriesType)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesTagsInput)); + assert!(ADD_SERIES_BLOCKS.contains(&ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder)); + } + + #[test] + fn test_add_series_selection_blocks_ordering() { + let mut add_series_block_iter = ADD_SERIES_SELECTION_BLOCKS.iter(); + + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesSelectRootFolder] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesSelectMonitor] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesSelectQualityProfile] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesSelectLanguageProfile] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesSelectSeriesType] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesTagsInput] + ); + assert_eq!( + add_series_block_iter.next().unwrap(), + &[ActiveSonarrBlock::AddSeriesConfirmPrompt] + ); + assert_eq!(add_series_block_iter.next(), None); + } + #[test] fn test_edit_movie_blocks_contents() { assert_eq!(EDIT_SERIES_BLOCKS.len(), 9);