diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs index daf054c..7c0f936 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs @@ -45,14 +45,42 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' } fn handle_scroll_up(&mut self) { - if self.active_radarr_block == ActiveRadarrBlock::EditIndexerPrompt { - self.app.data.radarr_data.selected_block.up(); + match self.active_radarr_block { + ActiveRadarrBlock::EditIndexerPrompt => { + self.app.data.radarr_data.selected_block.up(); + } + ActiveRadarrBlock::EditIndexerPriorityInput => { + self + .app + .data + .radarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .priority += 1; + } + _ => (), } } fn handle_scroll_down(&mut self) { - if self.active_radarr_block == ActiveRadarrBlock::EditIndexerPrompt { - self.app.data.radarr_data.selected_block.down(); + match self.active_radarr_block { + ActiveRadarrBlock::EditIndexerPrompt => { + self.app.data.radarr_data.selected_block.down(); + } + ActiveRadarrBlock::EditIndexerPriorityInput => { + let edit_indexer_modal = self + .app + .data + .radarr_data + .edit_indexer_modal + .as_mut() + .unwrap(); + if edit_indexer_modal.priority > 0 { + edit_indexer_modal.priority -= 1; + } + } + _ => (), } } @@ -287,6 +315,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' self.app.push_navigation_stack(selected_block.into()); self.app.should_ignore_quit_key = true; } + ActiveRadarrBlock::EditIndexerPriorityInput => self + .app + .push_navigation_stack(ActiveRadarrBlock::EditIndexerPriorityInput.into()), ActiveRadarrBlock::EditIndexerToggleEnableRss => { let indexer = self .app @@ -330,6 +361,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' self.app.pop_navigation_stack(); self.app.should_ignore_quit_key = false; } + ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(), _ => (), } } @@ -345,6 +377,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' | ActiveRadarrBlock::EditIndexerUrlInput | ActiveRadarrBlock::EditIndexerApiKeyInput | ActiveRadarrBlock::EditIndexerSeedRatioInput + | ActiveRadarrBlock::EditIndexerPriorityInput | ActiveRadarrBlock::EditIndexerTagsInput => { self.app.pop_navigation_stack(); self.app.should_ignore_quit_key = false; diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs index 507de3c..9325c65 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs @@ -20,9 +20,86 @@ mod tests { use super::*; + #[rstest] + fn test_edit_indexer_priority_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + key, + &mut app, + ActiveRadarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + + if key == Key::Up { + assert_eq!( + app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 1 + ); + } else { + assert_eq!( + app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 0 + ); + + EditIndexerHandler::with( + Key::Up, + &mut app, + ActiveRadarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 1 + ); + + EditIndexerHandler::with( + key, + &mut app, + ActiveRadarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + assert_eq!( + app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 0 + ); + } + } + #[rstest] fn test_edit_indexer_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); @@ -48,6 +125,7 @@ mod tests { #[values(Key::Up, Key::Down)] key: Key, ) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.is_loading = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.selected_block = @@ -75,6 +153,7 @@ mod tests { #[test] fn test_edit_indexer_name_input_home_end() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { name: "Test".into(), ..EditIndexerModal::default() @@ -126,6 +205,7 @@ mod tests { #[test] fn test_edit_indexer_url_input_home_end() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { url: "Test".into(), ..EditIndexerModal::default() @@ -177,6 +257,7 @@ mod tests { #[test] fn test_edit_indexer_api_key_input_home_end() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { api_key: "Test".into(), ..EditIndexerModal::default() @@ -228,6 +309,7 @@ mod tests { #[test] fn test_edit_indexer_seed_ratio_input_home_end() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { seed_ratio: "Test".into(), ..EditIndexerModal::default() @@ -279,6 +361,7 @@ mod tests { #[test] fn test_edit_indexer_tags_input_home_end() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { tags: "Test".into(), ..EditIndexerModal::default() @@ -345,6 +428,7 @@ mod tests { #[rstest] fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); app.data.radarr_data.selected_block.y = EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1; @@ -386,6 +470,7 @@ mod tests { #[case] right_block: ActiveRadarrBlock, ) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); app.data.radarr_data.selected_block.y = starting_y_index; @@ -426,6 +511,11 @@ mod tests { ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch, ActiveRadarrBlock::EditIndexerTagsInput )] + #[case( + 3, + ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveRadarrBlock::EditIndexerPriorityInput + )] fn test_left_right_block_toggle_nzb( #[values(Key::Left, Key::Right)] key: Key, #[case] starting_y_index: usize, @@ -433,6 +523,7 @@ mod tests { #[case] right_block: ActiveRadarrBlock, ) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS); app.data.radarr_data.selected_block.y = starting_y_index; @@ -458,18 +549,19 @@ mod tests { } #[rstest] - fn test_left_right_block_toggle_nzb_empty_row_to_prompt_confirm( + fn test_left_right_block_toggle_torren_empty_row_to_prompt_confirm( #[values(Key::Left, Key::Right)] key: Key, ) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.selected_block = - BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS); - app.data.radarr_data.selected_block.y = 3; + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.y = 4; app.data.radarr_data.prompt_confirm = false; assert_eq!( app.data.radarr_data.selected_block.get_active_block(), - ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch + ActiveRadarrBlock::EditIndexerPriorityInput ); EditIndexerHandler::with(key, &mut app, ActiveRadarrBlock::EditIndexerPrompt, None).handle(); @@ -491,6 +583,7 @@ mod tests { #[test] fn test_edit_indexer_name_input_left_right_keys() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { name: "Test".into(), ..EditIndexerModal::default() @@ -542,6 +635,7 @@ mod tests { #[test] fn test_edit_indexer_url_input_left_right_keys() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { url: "Test".into(), ..EditIndexerModal::default() @@ -593,6 +687,7 @@ mod tests { #[test] fn test_edit_indexer_api_key_input_left_right_keys() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { api_key: "Test".into(), ..EditIndexerModal::default() @@ -644,6 +739,7 @@ mod tests { #[test] fn test_edit_indexer_seed_ratio_input_left_right_keys() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { seed_ratio: "Test".into(), ..EditIndexerModal::default() @@ -695,6 +791,7 @@ mod tests { #[test] fn test_edit_indexer_tags_input_left_right_keys() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { tags: "Test".into(), ..EditIndexerModal::default() @@ -857,6 +954,7 @@ mod tests { #[case] block: ActiveRadarrBlock, ) { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.push_navigation_stack(ActiveRadarrBlock::EditIndexerPrompt.into()); app.data.radarr_data.selected_block = @@ -879,9 +977,35 @@ mod tests { assert!(app.should_ignore_quit_key); } + #[test] + fn test_edit_indexer_priority_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.push_navigation_stack(ActiveRadarrBlock::EditIndexerPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(0, 4); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveRadarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveRadarrBlock::EditIndexerPriorityInput.into() + ); + assert!(!app.should_ignore_quit_key); + } + #[test] fn test_edit_indexer_toggle_enable_rss_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); @@ -934,6 +1058,7 @@ mod tests { #[test] fn test_edit_indexer_toggle_enable_automatic_search_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); @@ -986,6 +1111,7 @@ mod tests { #[test] fn test_edit_indexer_toggle_enable_interactive_search_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); @@ -1038,6 +1164,7 @@ mod tests { #[test] fn test_edit_indexer_name_input_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.should_ignore_quit_key = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { name: "Test".into(), @@ -1073,6 +1200,7 @@ mod tests { #[test] fn test_edit_indexer_url_input_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.should_ignore_quit_key = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { url: "Test".into(), @@ -1108,6 +1236,7 @@ mod tests { #[test] fn test_edit_indexer_api_key_input_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.should_ignore_quit_key = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { api_key: "Test".into(), @@ -1143,6 +1272,7 @@ mod tests { #[test] fn test_edit_indexer_seed_ratio_input_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.should_ignore_quit_key = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { seed_ratio: "Test".into(), @@ -1178,6 +1308,7 @@ mod tests { #[test] fn test_edit_indexer_tags_input_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.should_ignore_quit_key = true; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { tags: "Test".into(), @@ -1249,7 +1380,8 @@ mod tests { ActiveRadarrBlock::EditIndexerUrlInput, ActiveRadarrBlock::EditIndexerApiKeyInput, ActiveRadarrBlock::EditIndexerSeedRatioInput, - ActiveRadarrBlock::EditIndexerTagsInput + ActiveRadarrBlock::EditIndexerTagsInput, + ActiveRadarrBlock::EditIndexerPriorityInput )] active_radarr_block: ActiveRadarrBlock, ) { @@ -1283,6 +1415,7 @@ mod tests { #[test] fn test_edit_indexer_name_input_backspace() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { name: "Test".into(), ..EditIndexerModal::default() @@ -1312,6 +1445,7 @@ mod tests { #[test] fn test_edit_indexer_url_input_backspace() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { url: "Test".into(), ..EditIndexerModal::default() @@ -1341,6 +1475,7 @@ mod tests { #[test] fn test_edit_indexer_api_key_input_backspace() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { api_key: "Test".into(), ..EditIndexerModal::default() @@ -1370,6 +1505,7 @@ mod tests { #[test] fn test_edit_indexer_seed_ratio_input_backspace() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { seed_ratio: "Test".into(), ..EditIndexerModal::default() @@ -1399,6 +1535,7 @@ mod tests { #[test] fn test_edit_indexer_tags_input_backspace() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { tags: "Test".into(), ..EditIndexerModal::default() @@ -1428,6 +1565,7 @@ mod tests { #[test] fn test_edit_indexer_name_input_char_key() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); EditIndexerHandler::with( @@ -1454,6 +1592,7 @@ mod tests { #[test] fn test_edit_indexer_url_input_char_key() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); EditIndexerHandler::with( @@ -1480,6 +1619,7 @@ mod tests { #[test] fn test_edit_indexer_api_key_input_char_key() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); EditIndexerHandler::with( @@ -1506,6 +1646,7 @@ mod tests { #[test] fn test_edit_indexer_seed_ratio_input_char_key() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); EditIndexerHandler::with( @@ -1532,6 +1673,7 @@ mod tests { #[test] fn test_edit_indexer_tags_input_char_key() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); EditIndexerHandler::with( @@ -1588,7 +1730,7 @@ mod tests { } #[test] - fn test_indexer_settings_handler_accepts() { + fn test_edit_indexer_handler_accepts() { ActiveRadarrBlock::iter().for_each(|active_radarr_block| { if EDIT_INDEXER_BLOCKS.contains(&active_radarr_block) { assert!(EditIndexerHandler::accepts(active_radarr_block)); @@ -1601,6 +1743,7 @@ mod tests { #[test] fn test_edit_indexer_handler_is_not_ready_when_loading() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.is_loading = true; let handler = EditIndexerHandler::with( @@ -1616,6 +1759,7 @@ mod tests { #[test] fn test_edit_indexer_handler_is_not_ready_when_edit_indexer_modal_is_none() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.is_loading = false; let handler = EditIndexerHandler::with( @@ -1631,6 +1775,7 @@ mod tests { #[test] fn test_edit_indexer_handler_is_ready_when_edit_indexer_modal_is_some() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.is_loading = false; app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index ac4c0f3..5911968 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -459,51 +459,6 @@ mod tests { use super::*; - #[test] - fn test_indexer_add() { - let mut app = App::default(); - app - .data - .radarr_data - .indexers - .set_items(vec![Indexer::default()]); - - IndexersHandler::with( - DEFAULT_KEYBINDINGS.add.key, - &mut app, - ActiveRadarrBlock::Indexers, - None, - ) - .handle(); - - assert_eq!( - app.get_current_route(), - ActiveRadarrBlock::AddIndexer.into() - ); - } - - #[test] - fn test_indexer_add_no_op_when_not_ready() { - let mut app = App::default(); - app.is_loading = true; - app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); - app - .data - .radarr_data - .indexers - .set_items(vec![Indexer::default()]); - - IndexersHandler::with( - DEFAULT_KEYBINDINGS.add.key, - &mut app, - ActiveRadarrBlock::Indexers, - None, - ) - .handle(); - - assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); - } - #[test] fn test_refresh_indexers_key() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index 49c39ce..d080eab 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -169,11 +169,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, let key = self.key; match self.active_radarr_block { ActiveRadarrBlock::Indexers => match self.key { - _ if key == DEFAULT_KEYBINDINGS.add.key => { - self - .app - .push_navigation_stack(ActiveRadarrBlock::AddIndexer.into()); - } _ if key == DEFAULT_KEYBINDINGS.refresh.key => { self.app.should_refresh = true; } diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs new file mode 100644 index 0000000..ca41e51 --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs @@ -0,0 +1,476 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::App; +use crate::event::Key; +use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; +use crate::network::sonarr_network::SonarrEvent; +use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; + +#[cfg(test)] +#[path = "edit_indexer_handler_tests.rs"] +mod edit_indexer_handler_tests; + +pub(super) struct EditIndexerHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'a, 'b> { + fn accepts(active_block: ActiveSonarrBlock) -> bool { + EDIT_INDEXER_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + _context: Option, + ) -> EditIndexerHandler<'a, 'b> { + EditIndexerHandler { + 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.edit_indexer_modal.is_some() + } + + fn handle_scroll_up(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerPrompt => { + self.app.data.sonarr_data.selected_block.up(); + } + ActiveSonarrBlock::EditIndexerPriorityInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .priority += 1; + } + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerPrompt => { + self.app.data.sonarr_data.selected_block.down(); + } + ActiveSonarrBlock::EditIndexerPriorityInput => { + let edit_indexer_modal = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap(); + if edit_indexer_modal.priority > 0 { + edit_indexer_modal.priority -= 1; + } + } + _ => (), + } + } + + fn handle_home(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerNameInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .name + .scroll_home(); + } + ActiveSonarrBlock::EditIndexerUrlInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .url + .scroll_home(); + } + ActiveSonarrBlock::EditIndexerApiKeyInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .api_key + .scroll_home(); + } + ActiveSonarrBlock::EditIndexerSeedRatioInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .seed_ratio + .scroll_home(); + } + ActiveSonarrBlock::EditIndexerTagsInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .tags + .scroll_home(); + } + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerNameInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .name + .reset_offset(); + } + ActiveSonarrBlock::EditIndexerUrlInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .url + .reset_offset(); + } + ActiveSonarrBlock::EditIndexerApiKeyInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .api_key + .reset_offset(); + } + ActiveSonarrBlock::EditIndexerSeedRatioInput => { + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .seed_ratio + .reset_offset(); + } + ActiveSonarrBlock::EditIndexerTagsInput => { + self + .app + .data + .sonarr_data + .edit_indexer_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::EditIndexerPrompt => { + handle_prompt_left_right_keys!( + self, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + sonarr_data + ); + } + ActiveSonarrBlock::EditIndexerNameInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .name + ); + } + ActiveSonarrBlock::EditIndexerUrlInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .url + ); + } + ActiveSonarrBlock::EditIndexerApiKeyInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .api_key + ); + } + ActiveSonarrBlock::EditIndexerSeedRatioInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .seed_ratio + ); + } + ActiveSonarrBlock::EditIndexerTagsInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .tags + ); + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerPrompt => { + let selected_block = self.app.data.sonarr_data.selected_block.get_active_block(); + match selected_block { + ActiveSonarrBlock::EditIndexerConfirmPrompt => { + let sonarr_data = &mut self.app.data.sonarr_data; + if sonarr_data.prompt_confirm { + sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditIndexer(None)); + self.app.should_refresh = true; + } else { + sonarr_data.edit_indexer_modal = None; + } + + self.app.pop_navigation_stack(); + } + ActiveSonarrBlock::EditIndexerNameInput + | ActiveSonarrBlock::EditIndexerUrlInput + | ActiveSonarrBlock::EditIndexerApiKeyInput + | ActiveSonarrBlock::EditIndexerSeedRatioInput + | ActiveSonarrBlock::EditIndexerTagsInput => { + self.app.push_navigation_stack(selected_block.into()); + self.app.should_ignore_quit_key = true; + } + ActiveSonarrBlock::EditIndexerPriorityInput => self + .app + .push_navigation_stack(ActiveSonarrBlock::EditIndexerPriorityInput.into()), + ActiveSonarrBlock::EditIndexerToggleEnableRss => { + let indexer = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap(); + indexer.enable_rss = Some(!indexer.enable_rss.unwrap_or_default()); + } + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch => { + let indexer = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap(); + indexer.enable_automatic_search = + Some(!indexer.enable_automatic_search.unwrap_or_default()); + } + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch => { + let indexer = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap(); + indexer.enable_interactive_search = + Some(!indexer.enable_interactive_search.unwrap_or_default()); + } + _ => (), + } + } + ActiveSonarrBlock::EditIndexerNameInput + | ActiveSonarrBlock::EditIndexerUrlInput + | ActiveSonarrBlock::EditIndexerApiKeyInput + | ActiveSonarrBlock::EditIndexerSeedRatioInput + | ActiveSonarrBlock::EditIndexerTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + ActiveSonarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(), + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.prompt_confirm = false; + self.app.data.sonarr_data.edit_indexer_modal = None; + } + ActiveSonarrBlock::EditIndexerNameInput + | ActiveSonarrBlock::EditIndexerUrlInput + | ActiveSonarrBlock::EditIndexerApiKeyInput + | ActiveSonarrBlock::EditIndexerSeedRatioInput + | ActiveSonarrBlock::EditIndexerPriorityInput + | ActiveSonarrBlock::EditIndexerTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + _ => self.app.pop_navigation_stack(), + } + } + + fn handle_char_key_event(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::EditIndexerNameInput => { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .name + ); + } + ActiveSonarrBlock::EditIndexerUrlInput => { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .url + ); + } + ActiveSonarrBlock::EditIndexerApiKeyInput => { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .api_key + ); + } + ActiveSonarrBlock::EditIndexerSeedRatioInput => { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .seed_ratio + ); + } + ActiveSonarrBlock::EditIndexerTagsInput => { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_mut() + .unwrap() + .tags + ); + } + ActiveSonarrBlock::EditIndexerPrompt => { + if self.app.data.sonarr_data.selected_block.get_active_block() + == ActiveSonarrBlock::EditIndexerConfirmPrompt + && self.key == DEFAULT_KEYBINDINGS.confirm.key + { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditIndexer(None)); + self.app.should_refresh = true; + + self.app.pop_navigation_stack(); + } + } + _ => (), + } + } +} diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs new file mode 100644 index 0000000..1008ec2 --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs @@ -0,0 +1,1791 @@ +#[cfg(test)] +mod tests { + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; + use strum::IntoEnumIterator; + + mod test_handle_scroll_up_and_down { + use crate::app::App; + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::servarr_data::sonarr::sonarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + + use super::*; + + #[rstest] + fn test_edit_indexer_priority_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + key, + &mut app, + ActiveSonarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + + if key == Key::Up { + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 1 + ); + } else { + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 0 + ); + + EditIndexerHandler::with( + Key::Up, + &mut app, + ActiveSonarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 1 + ); + + EditIndexerHandler::with( + key, + &mut app, + ActiveSonarrBlock::EditIndexerPriorityInput, + None, + ) + .handle(); + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .priority, + 0 + ); + } + } + + #[rstest] + fn test_edit_indexer_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + if key == Key::Up { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerNameInput + ); + } else { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch + ); + } + } + + #[rstest] + fn test_edit_indexer_prompt_scroll_no_op_when_not_ready( + #[values(Key::Up, Key::Down)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerToggleEnableRss + ); + } + } + + mod test_handle_home_end { + use std::sync::atomic::Ordering; + + use crate::app::App; + use crate::models::servarr_data::modals::EditIndexerModal; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_edit_indexer_name_input_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + name: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .offset + .load(Ordering::SeqCst), + 4 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_url_input_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + url: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .offset + .load(Ordering::SeqCst), + 4 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_api_key_input_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + api_key: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .offset + .load(Ordering::SeqCst), + 4 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_seed_ratio_input_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + seed_ratio: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .offset + .load(Ordering::SeqCst), + 4 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_tags_input_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + tags: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 4 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 0 + ); + } + } + + mod test_handle_left_right_action { + use std::sync::atomic::Ordering; + + use crate::app::App; + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::servarr_data::sonarr::sonarr_data::{ + EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, + }; + use crate::models::BlockSelectionState; + use pretty_assertions::assert_eq; + 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::Indexers.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.y = EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1; + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + + #[rstest] + #[case( + 0, + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput + )] + #[case( + 1, + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput + )] + #[case( + 2, + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerSeedRatioInput + )] + #[case( + 3, + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerTagsInput + )] + fn test_left_right_block_toggle_torrents( + #[values(Key::Left, Key::Right)] key: Key, + #[case] starting_y_index: usize, + #[case] left_block: ActiveSonarrBlock, + #[case] right_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.y = starting_y_index; + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + left_block + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + right_block + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + left_block + ); + } + + #[rstest] + #[case( + 0, + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput + )] + #[case( + 1, + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput + )] + #[case( + 2, + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerTagsInput + )] + #[case( + 3, + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerPriorityInput + )] + fn test_left_right_block_toggle_nzb( + #[values(Key::Left, Key::Right)] key: Key, + #[case] starting_y_index: usize, + #[case] left_block: ActiveSonarrBlock, + #[case] right_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.y = starting_y_index; + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + left_block + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + right_block + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + left_block + ); + } + + #[rstest] + fn test_left_right_block_toggle_torren_empty_row_to_prompt_confirm( + #[values(Key::Left, Key::Right)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.y = 4; + app.data.sonarr_data.prompt_confirm = false; + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerPriorityInput + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerConfirmPrompt + ); + + EditIndexerHandler::with(key, &mut app, ActiveSonarrBlock::EditIndexerPrompt, None).handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::EditIndexerConfirmPrompt + ); + assert!(app.data.sonarr_data.prompt_confirm); + } + + #[test] + fn test_edit_indexer_name_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + name: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .offset + .load(Ordering::SeqCst), + 1 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_url_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + url: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .offset + .load(Ordering::SeqCst), + 1 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_api_key_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + api_key: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .offset + .load(Ordering::SeqCst), + 1 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_seed_ratio_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + seed_ratio: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .offset + .load(Ordering::SeqCst), + 1 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .offset + .load(Ordering::SeqCst), + 0 + ); + } + + #[test] + fn test_edit_indexer_tags_input_left_right_keys() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + tags: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 1 + ); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .offset + .load(Ordering::SeqCst), + 0 + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::app::App; + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::{ + servarr_data::sonarr::sonarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, BlockSelectionState, + }; + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_edit_indexer_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + assert!(!app.should_refresh); + assert_eq!(app.data.sonarr_data.edit_indexer_modal, None); + } + + #[test] + fn test_edit_indexer_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.prompt_confirm = true; + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(app.data.sonarr_data.edit_indexer_modal.is_some()); + assert!(app.should_refresh); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::EditIndexer(None)) + ); + } + + #[test] + fn test_edit_indexer_prompt_prompt_confirmation_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.prompt_confirm = true; + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(app.data.sonarr_data.edit_indexer_modal.is_some()); + assert!(!app.should_refresh); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + } + + #[rstest] + #[case(0, 0, ActiveSonarrBlock::EditIndexerNameInput)] + #[case(0, 1, ActiveSonarrBlock::EditIndexerUrlInput)] + #[case(1, 1, ActiveSonarrBlock::EditIndexerApiKeyInput)] + #[case(2, 1, ActiveSonarrBlock::EditIndexerSeedRatioInput)] + #[case(3, 1, ActiveSonarrBlock::EditIndexerTagsInput)] + fn test_edit_indexer_prompt_submit_input_fields( + #[case] starting_y: usize, + #[case] starting_x: usize, + #[case] block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(starting_x, starting_y); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), block.into()); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_edit_indexer_priority_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, 4); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPriorityInput.into() + ); + assert!(!app.should_ignore_quit_key); + } + + #[test] + fn test_edit_indexer_toggle_enable_rss_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, 1); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_rss + .unwrap()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_rss + .unwrap()); + } + + #[test] + fn test_edit_indexer_toggle_enable_automatic_search_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, 2); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_automatic_search + .unwrap()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_automatic_search + .unwrap()); + } + + #[test] + fn test_edit_indexer_toggle_enable_interactive_search_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, 3); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_interactive_search + .unwrap()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .enable_interactive_search + .unwrap()); + } + + #[test] + fn test_edit_indexer_name_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + name: "Test".into(), + ..EditIndexerModal::default() + }); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerNameInput.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + } + + #[test] + fn test_edit_indexer_url_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + url: "Test".into(), + ..EditIndexerModal::default() + }); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerUrlInput.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + } + + #[test] + fn test_edit_indexer_api_key_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + api_key: "Test".into(), + ..EditIndexerModal::default() + }); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerApiKeyInput.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + } + + #[test] + fn test_edit_indexer_seed_ratio_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + seed_ratio: "Test".into(), + ..EditIndexerModal::default() + }); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerSeedRatioInput.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + } + + #[test] + fn test_edit_indexer_tags_input_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.should_ignore_quit_key = true; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + tags: "Test".into(), + ..EditIndexerModal::default() + }); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerTagsInput.into()); + + EditIndexerHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + } + } + + mod test_handle_esc { + use super::*; + use crate::app::App; + use crate::event::Key; + use crate::models::servarr_data::modals::EditIndexerModal; + use pretty_assertions::assert_eq; + use rstest::rstest; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_edit_indexer_prompt_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.data.sonarr_data.edit_indexer_modal, None); + } + + #[rstest] + fn test_edit_indexer_input_fields_esc( + #[values( + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + ActiveSonarrBlock::EditIndexerTagsInput, + ActiveSonarrBlock::EditIndexerPriorityInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(active_sonarr_block.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + app.should_ignore_quit_key = true; + + EditIndexerHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.data.sonarr_data.edit_indexer_modal, + Some(EditIndexerModal::default()) + ); + } + } + + mod test_handle_key_char { + use crate::app::App; + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::servarr_data::sonarr::sonarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use crate::network::sonarr_network::SonarrEvent; + use pretty_assertions::assert_str_eq; + + use super::*; + + #[test] + fn test_edit_indexer_name_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + name: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_url_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + url: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_api_key_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + api_key: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_seed_ratio_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + seed_ratio: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_tags_input_backspace() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { + tags: "Test".into(), + ..EditIndexerModal::default() + }); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_name_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::EditIndexerNameInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .name + .text, + "h" + ); + } + + #[test] + fn test_edit_indexer_url_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::EditIndexerUrlInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .url + .text, + "h" + ); + } + + #[test] + fn test_edit_indexer_api_key_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::EditIndexerApiKeyInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .api_key + .text, + "h" + ); + } + + #[test] + fn test_edit_indexer_seed_ratio_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .seed_ratio + .text, + "h" + ); + } + + #[test] + fn test_edit_indexer_tags_input_char_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + Key::Char('h'), + &mut app, + ActiveSonarrBlock::EditIndexerTagsInput, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text, + "h" + ); + } + + #[test] + fn test_edit_indexer_prompt_prompt_confirmation_confirm() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(app.data.sonarr_data.edit_indexer_modal.is_some()); + assert!(app.should_refresh); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::EditIndexer(None)) + ); + } + } + + #[test] + fn test_edit_indexer_handler_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if EDIT_INDEXER_BLOCKS.contains(&active_sonarr_block) { + assert!(EditIndexerHandler::accepts(active_sonarr_block)); + } else { + assert!(!EditIndexerHandler::accepts(active_sonarr_block)); + } + }) + } + + #[test] + fn test_edit_indexer_handler_is_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + + let handler = EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_edit_indexer_handler_is_not_ready_when_edit_indexer_modal_is_none() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + + let handler = EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_edit_indexer_handler_is_ready_when_edit_indexer_modal_is_some() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + + let handler = EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs new file mode 100644 index 0000000..94afa1d --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -0,0 +1,182 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::App; +use crate::event::Key; +use crate::handle_prompt_left_right_keys; +use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, +}; +use crate::network::sonarr_network::SonarrEvent; + +#[cfg(test)] +#[path = "edit_indexer_settings_handler_tests.rs"] +mod edit_indexer_settings_handler_tests; + +pub(super) struct IndexerSettingsHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandler<'a, 'b> { + fn accepts(active_block: ActiveSonarrBlock) -> bool { + INDEXER_SETTINGS_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + _context: Option, + ) -> IndexerSettingsHandler<'a, 'b> { + IndexerSettingsHandler { + 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.indexer_settings.is_some() + } + + fn handle_scroll_up(&mut self) { + let indexer_settings = self.app.data.sonarr_data.indexer_settings.as_mut().unwrap(); + match self.active_sonarr_block { + ActiveSonarrBlock::AllIndexerSettingsPrompt => { + self.app.data.sonarr_data.selected_block.up(); + } + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput => { + indexer_settings.minimum_age += 1; + } + ActiveSonarrBlock::IndexerSettingsRetentionInput => { + indexer_settings.retention += 1; + } + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput => { + indexer_settings.maximum_size += 1; + } + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput => { + indexer_settings.rss_sync_interval += 1; + } + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + let indexer_settings = self.app.data.sonarr_data.indexer_settings.as_mut().unwrap(); + match self.active_sonarr_block { + ActiveSonarrBlock::AllIndexerSettingsPrompt => { + self.app.data.sonarr_data.selected_block.down() + } + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput => { + if indexer_settings.minimum_age > 0 { + indexer_settings.minimum_age -= 1; + } + } + ActiveSonarrBlock::IndexerSettingsRetentionInput => { + if indexer_settings.retention > 0 { + indexer_settings.retention -= 1; + } + } + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput => { + if indexer_settings.maximum_size > 0 { + indexer_settings.maximum_size -= 1; + } + } + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput => { + if indexer_settings.rss_sync_interval > 0 { + indexer_settings.rss_sync_interval -= 1; + } + } + _ => (), + } + } + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt { + handle_prompt_left_right_keys!( + self, + ActiveSonarrBlock::IndexerSettingsConfirmPrompt, + sonarr_data + ); + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AllIndexerSettingsPrompt => { + match self.app.data.sonarr_data.selected_block.get_active_block() { + ActiveSonarrBlock::IndexerSettingsConfirmPrompt => { + let sonarr_data = &mut self.app.data.sonarr_data; + if sonarr_data.prompt_confirm { + sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(None)); + self.app.should_refresh = true; + } else { + sonarr_data.indexer_settings = None; + } + + self.app.pop_navigation_stack(); + } + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput + | ActiveSonarrBlock::IndexerSettingsRetentionInput + | ActiveSonarrBlock::IndexerSettingsMaximumSizeInput + | ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput => { + self.app.push_navigation_stack( + ( + self.app.data.sonarr_data.selected_block.get_active_block(), + None, + ) + .into(), + ) + } + + _ => (), + } + } + + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput + | ActiveSonarrBlock::IndexerSettingsRetentionInput + | ActiveSonarrBlock::IndexerSettingsMaximumSizeInput + | ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput => self.app.pop_navigation_stack(), + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::AllIndexerSettingsPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.prompt_confirm = false; + self.app.data.sonarr_data.indexer_settings = None; + } + _ => self.app.pop_navigation_stack(), + } + } + + fn handle_char_key_event(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt + && self.app.data.sonarr_data.selected_block.get_active_block() + == ActiveSonarrBlock::IndexerSettingsConfirmPrompt + && self.key == DEFAULT_KEYBINDINGS.confirm.key + { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::EditAllIndexerSettings(None)); + self.app.should_refresh = true; + + self.app.pop_navigation_stack(); + } + } +} diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs new file mode 100644 index 0000000..8301125 --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs @@ -0,0 +1,570 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, + }; + use crate::models::sonarr_models::IndexerSettings; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS; + use crate::models::sonarr_models::IndexerSettings; + use crate::models::BlockSelectionState; + + use super::*; + + macro_rules! test_i64_counter_scroll_value { + ($block:expr, $key:expr, $data_ref:ident, $negatives:literal) => { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with($key, &mut app, $block, None).handle(); + + if $key == Key::Up { + assert_eq!( + app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 1 + ); + } else { + if $negatives { + assert_eq!( + app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + -1 + ); + } else { + assert_eq!( + app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 0 + ); + + IndexerSettingsHandler::with(Key::Up, &mut app, $block, None).handle(); + + assert_eq!( + app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 1 + ); + + IndexerSettingsHandler::with($key, &mut app, $block, None).handle(); + assert_eq!( + app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 0 + ); + } + } + }; + } + + #[rstest] + fn test_edit_indexer_settings_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + IndexerSettingsHandler::with( + key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + if key == Key::Up { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput + ); + } else { + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput + ); + } + } + + #[rstest] + fn test_edit_indexer_settings_prompt_scroll_no_op_when_not_ready( + #[values(Key::Up, Key::Down)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.down(); + + IndexerSettingsHandler::with( + key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.selected_block.get_active_block(), + ActiveSonarrBlock::IndexerSettingsRetentionInput + ); + } + + #[rstest] + fn test_edit_indexer_settings_minimum_age_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + key, + minimum_age, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_retention_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveSonarrBlock::IndexerSettingsRetentionInput, + key, + retention, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_maximum_size_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + key, + maximum_size, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_rss_sync_interval_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput, + key, + rss_sync_interval, + false + ); + } + } + + mod test_handle_left_right_action { + use crate::models::servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS; + + use crate::models::BlockSelectionState; + 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::Indexers.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.y = INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1; + + IndexerSettingsHandler::with( + key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + IndexerSettingsHandler::with( + key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::{ + models::{ + servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, + sonarr_models::IndexerSettings, BlockSelectionState, + }, + network::sonarr_network::SonarrEvent, + }; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_edit_indexer_settings_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + assert!(!app.should_refresh); + assert_eq!(app.data.sonarr_data.indexer_settings, None); + } + + #[test] + fn test_edit_indexer_settings_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.prompt_confirm = true; + + IndexerSettingsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::EditAllIndexerSettings(None)) + ); + assert!(app.data.sonarr_data.indexer_settings.is_some()); + assert!(app.should_refresh); + } + + #[test] + fn test_edit_indexer_settings_prompt_prompt_confirmation_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.prompt_confirm = true; + + IndexerSettingsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AllIndexerSettingsPrompt.into() + ); + assert!(!app.should_refresh); + } + + #[rstest] + #[case(ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, 0)] + #[case(ActiveSonarrBlock::IndexerSettingsRetentionInput, 1)] + #[case(ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, 2)] + #[case(ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput, 3)] + fn test_edit_indexer_settings_prompt_submit_selected_block( + #[case] selected_block: ActiveSonarrBlock, + #[case] y_index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, y_index); + + IndexerSettingsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), selected_block.into()); + } + + #[rstest] + fn test_edit_indexer_settings_prompt_submit_selected_block_no_op_when_not_ready( + #[values(0, 1, 2, 3, 4)] y_index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.sonarr_data.selected_block.set_index(0, y_index); + + IndexerSettingsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AllIndexerSettingsPrompt.into() + ); + } + + #[rstest] + fn test_edit_indexer_settings_selected_block_submit( + #[values( + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + ActiveSonarrBlock::IndexerSettingsRetentionInput, + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.push_navigation_stack(active_sonarr_block.into()); + + IndexerSettingsHandler::with(SUBMIT_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AllIndexerSettingsPrompt.into() + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::sonarr_models::IndexerSettings; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_edit_indexer_settings_prompt_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.data.sonarr_data.indexer_settings, None); + } + + #[rstest] + fn test_edit_indexer_settings_selected_blocks_esc( + #[values( + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + ActiveSonarrBlock::IndexerSettingsRetentionInput, + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(active_sonarr_block.into()); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!( + app.data.sonarr_data.indexer_settings, + Some(IndexerSettings::default()) + ); + } + } + + mod test_handle_key_char { + use crate::{ + models::{ + servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, + sonarr_models::IndexerSettings, BlockSelectionState, + }, + network::sonarr_network::SonarrEvent, + }; + + use super::*; + + #[test] + fn test_edit_indexer_settings_prompt_prompt_confirmation_confirm() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + app + .data + .sonarr_data + .selected_block + .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::EditAllIndexerSettings(None)) + ); + assert!(app.data.sonarr_data.indexer_settings.is_some()); + assert!(app.should_refresh); + } + } + + #[test] + fn test_indexer_settings_handler_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if INDEXER_SETTINGS_BLOCKS.contains(&active_sonarr_block) { + assert!(IndexerSettingsHandler::accepts(active_sonarr_block)); + } else { + assert!(!IndexerSettingsHandler::accepts(active_sonarr_block)); + } + }) + } + + #[test] + fn test_edit_indexer_settings_handler_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + + let handler = IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_edit_indexer_settings_handler_not_ready_when_indexer_settings_is_none() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + + let handler = IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_edit_indexer_settings_handler_ready_when_not_loading_and_indexer_settings_is_some() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + + let handler = IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs new file mode 100644 index 0000000..0ccf3a8 --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs @@ -0,0 +1,805 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::indexers::IndexersHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, + }; + use crate::models::servarr_models::Indexer; + use crate::test_handler_delegation; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_indexers_scroll, + IndexersHandler, + sonarr_data, + indexers, + simple_stateful_iterable_vec!(Indexer, String, protocol), + ActiveSonarrBlock::Indexers, + None, + protocol + ); + + #[rstest] + fn test_indexers_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::Indexers.into()); + app.is_loading = true; + app + .data + .sonarr_data + .indexers + .set_items(simple_stateful_iterable_vec!(Indexer, String, protocol)); + + IndexersHandler::with(key, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.indexers.current_selection().protocol, + "Test 1" + ); + + IndexersHandler::with(key, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.indexers.current_selection().protocol, + "Test 1" + ); + } + } + + mod test_handle_home_end { + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_indexers_home_end, + IndexersHandler, + sonarr_data, + indexers, + extended_stateful_iterable_vec!(Indexer, String, protocol), + ActiveSonarrBlock::Indexers, + None, + protocol + ); + + #[test] + fn test_indexers_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + app + .data + .sonarr_data + .indexers + .set_items(extended_stateful_iterable_vec!(Indexer, String, protocol)); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.indexers.current_selection().protocol, + "Test 1" + ); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.indexers.current_selection().protocol, + "Test 1" + ); + } + } + + mod test_handle_delete { + use pretty_assertions::assert_eq; + + use super::*; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_delete_indexer_prompt() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with(DELETE_KEY, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::DeleteIndexerPrompt.into() + ); + } + + #[test] + fn test_delete_indexer_prompt_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with(DELETE_KEY, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_indexers_tab_left(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(5); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + ActiveSonarrBlock::RootFolders.into() + ); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::RootFolders.into() + ); + } + + #[rstest] + fn test_indexers_tab_right(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(5); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::Indexers, + 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_left_right_delete_indexer_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + + IndexersHandler::with(key, &mut app, ActiveSonarrBlock::DeleteIndexerPrompt, None).handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + IndexersHandler::with(key, &mut app, ActiveSonarrBlock::DeleteIndexerPrompt, None).handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use crate::models::servarr_data::modals::EditIndexerModal; + use crate::models::servarr_data::sonarr::sonarr_data::{ + SonarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, + }; + use crate::models::servarr_models::{Indexer, IndexerField}; + use bimap::BiMap; + use pretty_assertions::assert_eq; + use serde_json::{Number, Value}; + + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[rstest] + fn test_edit_indexer_submit(#[values(true, false)] torrent_protocol: bool) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + let protocol = if torrent_protocol { + "torrent".to_owned() + } else { + "usenet".to_owned() + }; + let mut expected_edit_indexer_modal = EditIndexerModal { + name: "Test".into(), + enable_rss: Some(true), + enable_automatic_search: Some(true), + enable_interactive_search: Some(true), + url: "https://test.com".into(), + api_key: "1234".into(), + tags: "usenet, test".into(), + ..EditIndexerModal::default() + }; + let mut sonarr_data = SonarrData { + tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), + ..SonarrData::default() + }; + let mut fields = vec![ + IndexerField { + name: Some("baseUrl".to_owned()), + value: Some(Value::String("https://test.com".to_owned())), + }, + IndexerField { + name: Some("apiKey".to_owned()), + value: Some(Value::String("1234".to_owned())), + }, + ]; + + if torrent_protocol { + fields.push(IndexerField { + name: Some("seedCriteria.seedRatio".to_owned()), + value: Some(Value::from(1.2f64)), + }); + expected_edit_indexer_modal.seed_ratio = "1.2".into(); + } + + let indexer = Indexer { + name: Some("Test".to_owned()), + enable_rss: true, + enable_automatic_search: true, + enable_interactive_search: true, + protocol, + tags: vec![Number::from(1), Number::from(2)], + fields: Some(fields), + ..Indexer::default() + }; + sonarr_data.indexers.set_items(vec![indexer]); + app.data.sonarr_data = sonarr_data; + + IndexersHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::EditIndexerPrompt.into() + ); + assert_eq!( + app.data.sonarr_data.edit_indexer_modal, + Some((&app.data.sonarr_data).into()) + ); + assert_eq!( + app.data.sonarr_data.edit_indexer_modal, + Some(expected_edit_indexer_modal) + ); + if torrent_protocol { + assert_eq!( + app.data.sonarr_data.selected_block.blocks, + EDIT_INDEXER_TORRENT_SELECTION_BLOCKS + ); + } else { + assert_eq!( + app.data.sonarr_data.selected_block.blocks, + EDIT_INDEXER_NZB_SELECTION_BLOCKS + ); + } + } + + #[test] + fn test_edit_indexer_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!(app.data.sonarr_data.edit_indexer_modal, None); + } + + #[test] + fn test_delete_indexer_prompt_confirm_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + app.data.sonarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); + + IndexersHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::DeleteIndexerPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::DeleteIndexer(None)) + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + + #[test] + fn test_prompt_decline_submit() { + let mut app = App::default(); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); + + IndexersHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::DeleteIndexerPrompt, + 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::Indexers.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_delete_indexer_prompt_block_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); + app.data.sonarr_data.prompt_confirm = true; + + IndexersHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::DeleteIndexerPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.data.sonarr_data.prompt_confirm); + } + + #[rstest] + fn test_test_indexer_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.data.sonarr_data.indexer_test_error = Some("test result".to_owned()); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::TestIndexer.into()); + + IndexersHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::TestIndexer, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert_eq!(app.data.sonarr_data.indexer_test_error, None); + } + + #[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::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + + IndexersHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::Indexers, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::assert_eq; + + use crate::{ + models::servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, + network::sonarr_network::SonarrEvent, + }; + + use super::*; + + #[test] + fn test_refresh_indexers_key() { + let mut app = App::default(); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(app.should_refresh); + } + + #[test] + fn test_refresh_indexers_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_indexer_settings_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.settings.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::AllIndexerSettingsPrompt.into() + ); + assert_eq!( + app.data.sonarr_data.selected_block.blocks, + INDEXER_SETTINGS_SELECTION_BLOCKS + ); + } + + #[test] + fn test_indexer_settings_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.settings.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + + #[test] + fn test_test_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.test.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::TestIndexer.into() + ); + } + + #[test] + fn test_test_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.test.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + + #[test] + fn test_test_all_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.test_all.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::TestAllIndexers.into() + ); + } + + #[test] + fn test_test_all_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.test_all.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + + #[test] + fn test_delete_indexer_prompt_confirm() { + let mut app = App::default(); + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); + + IndexersHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::DeleteIndexerPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::DeleteIndexer(None)) + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + } + + #[rstest] + fn test_delegates_edit_indexer_blocks_to_edit_indexer_handler( + #[values( + ActiveSonarrBlock::EditIndexerPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerUrlInput, + ActiveSonarrBlock::EditIndexerTagsInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + IndexersHandler, + ActiveSonarrBlock::Indexers, + active_sonarr_block + ); + } + + #[rstest] + fn test_delegates_indexer_settings_blocks_to_indexer_settings_handler( + #[values( + ActiveSonarrBlock::AllIndexerSettingsPrompt, + ActiveSonarrBlock::IndexerSettingsConfirmPrompt, + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + ActiveSonarrBlock::IndexerSettingsRetentionInput, + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + IndexersHandler, + ActiveSonarrBlock::Indexers, + active_sonarr_block + ); + } + + #[test] + fn test_delegates_test_all_indexers_block_to_test_all_indexers_handler() { + test_handler_delegation!( + IndexersHandler, + ActiveSonarrBlock::Indexers, + ActiveSonarrBlock::TestAllIndexers + ); + } + + #[test] + fn test_indexers_handler_accepts() { + let mut indexers_blocks = Vec::new(); + indexers_blocks.extend(INDEXERS_BLOCKS); + indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS); + indexers_blocks.extend(EDIT_INDEXER_BLOCKS); + indexers_blocks.push(ActiveSonarrBlock::TestAllIndexers); + + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if indexers_blocks.contains(&active_sonarr_block) { + assert!(IndexersHandler::accepts(active_sonarr_block)); + } else { + assert!(!IndexersHandler::accepts(active_sonarr_block)); + } + }) + } + + #[test] + fn test_indexers_handler_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + + let handler = IndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_indexers_handler_not_ready_when_indexers_is_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + + let handler = IndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_indexers_handler_ready_when_not_loading_and_indexers_is_not_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + app + .data + .sonarr_data + .indexers + .set_items(vec![Indexer::default()]); + + let handler = IndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/indexers/mod.rs b/src/handlers/sonarr_handlers/indexers/mod.rs new file mode 100644 index 0000000..1ed5b68 --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/mod.rs @@ -0,0 +1,205 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::App; +use crate::event::Key; +use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; +use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; +use crate::handlers::sonarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; +use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, + INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, +}; +use crate::models::BlockSelectionState; +use crate::models::Scrollable; +use crate::network::sonarr_network::SonarrEvent; + +mod edit_indexer_handler; +mod edit_indexer_settings_handler; +mod test_all_indexers_handler; + +#[cfg(test)] +#[path = "indexers_handler_tests.rs"] +mod indexers_handler_tests; + +pub(super) struct IndexersHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_sonarr_block { + _ if EditIndexerHandler::accepts(self.active_sonarr_block) => { + EditIndexerHandler::with(self.key, self.app, self.active_sonarr_block, self.context) + .handle() + } + _ if IndexerSettingsHandler::accepts(self.active_sonarr_block) => { + IndexerSettingsHandler::with(self.key, self.app, self.active_sonarr_block, self.context) + .handle() + } + _ if TestAllIndexersHandler::accepts(self.active_sonarr_block) => { + TestAllIndexersHandler::with(self.key, self.app, self.active_sonarr_block, self.context) + .handle() + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: ActiveSonarrBlock) -> bool { + EditIndexerHandler::accepts(active_block) + || IndexerSettingsHandler::accepts(active_block) + || TestAllIndexersHandler::accepts(active_block) + || INDEXERS_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + context: Option, + ) -> IndexersHandler<'a, 'b> { + IndexersHandler { + 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.indexers.is_empty() + } + + fn handle_scroll_up(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Indexers { + self.app.data.sonarr_data.indexers.scroll_up(); + } + } + + fn handle_scroll_down(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Indexers { + self.app.data.sonarr_data.indexers.scroll_down(); + } + } + + fn handle_home(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Indexers { + self.app.data.sonarr_data.indexers.scroll_to_top(); + } + } + + fn handle_end(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Indexers { + self.app.data.sonarr_data.indexers.scroll_to_bottom(); + } + } + + fn handle_delete(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::Indexers { + self + .app + .push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); + } + } + + fn handle_left_right_action(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::Indexers => handle_change_tab_left_right_keys(self.app, self.key), + ActiveSonarrBlock::DeleteIndexerPrompt => handle_prompt_toggle(self.app, self.key), + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::DeleteIndexerPrompt => { + let sonarr_data = &mut self.app.data.sonarr_data; + if sonarr_data.prompt_confirm { + sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteIndexer(None)); + } + + self.app.pop_navigation_stack(); + } + ActiveSonarrBlock::Indexers => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::EditIndexerPrompt.into()); + self.app.data.sonarr_data.edit_indexer_modal = Some((&self.app.data.sonarr_data).into()); + let protocol = &self + .app + .data + .sonarr_data + .indexers + .current_selection() + .protocol; + if protocol == "torrent" { + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS); + } else { + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS); + } + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::DeleteIndexerPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.prompt_confirm = false; + } + ActiveSonarrBlock::TestIndexer => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.indexer_test_error = None; + } + _ => handle_clear_errors(self.app), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_sonarr_block { + ActiveSonarrBlock::Indexers => match self.key { + _ if key == DEFAULT_KEYBINDINGS.refresh.key => { + self.app.should_refresh = true; + } + _ if key == DEFAULT_KEYBINDINGS.test.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::TestIndexer.into()); + } + _ if key == DEFAULT_KEYBINDINGS.test_all.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into()); + } + _ if key == DEFAULT_KEYBINDINGS.settings.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); + self.app.data.sonarr_data.selected_block = + BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS); + } + _ => (), + }, + ActiveSonarrBlock::DeleteIndexerPrompt => { + if key == DEFAULT_KEYBINDINGS.confirm.key { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteIndexer(None)); + + self.app.pop_navigation_stack(); + } + } + _ => (), + } + } +} diff --git a/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs new file mode 100644 index 0000000..cd97b9f --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs @@ -0,0 +1,117 @@ +use crate::app::App; +use crate::event::Key; +use crate::handlers::KeyEventHandler; +use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; +use crate::models::Scrollable; + +#[cfg(test)] +#[path = "test_all_indexers_handler_tests.rs"] +mod test_all_indexers_handler_tests; + +pub(super) struct TestAllIndexersHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandler<'a, 'b> { + fn accepts(active_block: ActiveSonarrBlock) -> bool { + active_block == ActiveSonarrBlock::TestAllIndexers + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + _context: Option, + ) -> TestAllIndexersHandler<'a, 'b> { + TestAllIndexersHandler { + key, + app, + active_sonarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + let table_is_ready = if let Some(table) = &self.app.data.sonarr_data.indexer_test_all_results { + !table.is_empty() + } else { + false + }; + + !self.app.is_loading && table_is_ready + } + + fn handle_scroll_up(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + self + .app + .data + .sonarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_up() + } + } + + fn handle_scroll_down(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + self + .app + .data + .sonarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_down() + } + } + + fn handle_home(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + self + .app + .data + .sonarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_to_top() + } + } + + fn handle_end(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + self + .app + .data + .sonarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_to_bottom() + } + } + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) {} + + fn handle_submit(&mut self) {} + + fn handle_esc(&mut self) { + if self.active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.indexer_test_all_results = None; + } + } + + fn handle_char_key_event(&mut self) {} +} diff --git a/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler_tests.rs new file mode 100644 index 0000000..7750b4d --- /dev/null +++ b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler_tests.rs @@ -0,0 +1,337 @@ +#[cfg(test)] +mod tests { + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; + use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; + use crate::models::stateful_table::StatefulTable; + use strum::IntoEnumIterator; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_str_eq; + use rstest::rstest; + + use crate::models::servarr_data::modals::IndexerTestResultModalItem; + use crate::models::stateful_table::StatefulTable; + use crate::simple_stateful_iterable_vec; + + use super::*; + + #[rstest] + fn test_test_all_indexers_results_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(simple_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.sonarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with(key, &mut app, ActiveSonarrBlock::TestAllIndexers, None) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 2" + ); + + TestAllIndexersHandler::with(key, &mut app, ActiveSonarrBlock::TestAllIndexers, None) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + + #[rstest] + fn test_test_all_indexers_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::Indexers.into()); + app.is_loading = true; + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(simple_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.sonarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with(key, &mut app, ActiveSonarrBlock::TestAllIndexers, None) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + + TestAllIndexersHandler::with(key, &mut app, ActiveSonarrBlock::TestAllIndexers, None) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + } + + mod test_handle_home_end { + use crate::extended_stateful_iterable_vec; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; + use crate::models::stateful_table::StatefulTable; + use pretty_assertions::assert_str_eq; + + use super::*; + + #[test] + fn test_test_all_indexers_results_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(extended_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.sonarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 3" + ); + + TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + + #[test] + fn test_test_all_indexers_results_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(extended_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.sonarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + + TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ) + .handle(); + + assert_str_eq!( + app + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + } + + mod test_handle_esc { + use super::*; + use crate::models::stateful_table::StatefulTable; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + fn test_test_all_indexers_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into()); + app.data.sonarr_data.indexer_test_all_results = Some(StatefulTable::default()); + + TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + assert!(!app.data.sonarr_data.prompt_confirm); + assert!(app.data.sonarr_data.indexer_test_all_results.is_none()); + } + } + + #[test] + fn test_test_all_indexers_handler_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if active_sonarr_block == ActiveSonarrBlock::TestAllIndexers { + assert!(TestAllIndexersHandler::accepts(active_sonarr_block)); + } else { + assert!(!TestAllIndexersHandler::accepts(active_sonarr_block)); + } + }); + } + + #[test] + fn test_test_all_indexers_handler_is_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = true; + + let handler = TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_test_all_indexers_handler_is_not_ready_when_results_is_none() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + + let handler = TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_test_all_indexers_handler_is_not_ready_when_results_is_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + app.data.sonarr_data.indexer_test_all_results = Some(StatefulTable::default()); + + let handler = TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_test_all_indexers_handler_is_ready_when_results_is_not_empty_and_is_loaded() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); + app.is_loading = false; + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(vec![IndexerTestResultModalItem::default()]); + app.data.sonarr_data.indexer_test_all_results = Some(indexer_test_results); + + let handler = TestAllIndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::TestAllIndexers, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/mod.rs b/src/handlers/sonarr_handlers/mod.rs index edf2cb8..eca86a0 100644 --- a/src/handlers/sonarr_handlers/mod.rs +++ b/src/handlers/sonarr_handlers/mod.rs @@ -1,6 +1,7 @@ use blocklist::BlocklistHandler; use downloads::DownloadsHandler; use history::HistoryHandler; +use indexers::IndexersHandler; use library::LibraryHandler; use root_folders::RootFoldersHandler; @@ -15,6 +16,7 @@ use super::KeyEventHandler; mod blocklist; mod downloads; mod history; +mod indexers; mod library; mod root_folders; @@ -52,6 +54,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b RootFoldersHandler::with(self.key, self.app, self.active_sonarr_block, self.context) .handle() } + _ if IndexersHandler::accepts(self.active_sonarr_block) => { + IndexersHandler::with(self.key, self.app, self.active_sonarr_block, self.context).handle() + } _ => self.handle_key_event(), } } diff --git a/src/handlers/sonarr_handlers/sonarr_handler_tests.rs b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs index 00fd74d..1e1702f 100644 --- a/src/handlers/sonarr_handlers/sonarr_handler_tests.rs +++ b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs @@ -181,4 +181,25 @@ mod tests { active_sonarr_block ); } + + #[rstest] + fn test_delegates_indexers_blocks_to_indexers_handler( + #[values( + ActiveSonarrBlock::DeleteIndexerPrompt, + ActiveSonarrBlock::Indexers, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + ActiveSonarrBlock::IndexerSettingsConfirmPrompt, + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + ActiveSonarrBlock::IndexerSettingsRetentionInput, + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + SonarrHandler, + ActiveSonarrBlock::Indexers, + active_sonarr_block + ); + } } diff --git a/src/models/servarr_data/modals.rs b/src/models/servarr_data/modals.rs index 0105249..46d329b 100644 --- a/src/models/servarr_data/modals.rs +++ b/src/models/servarr_data/modals.rs @@ -10,6 +10,7 @@ pub struct EditIndexerModal { pub api_key: HorizontallyScrollableText, pub seed_ratio: HorizontallyScrollableText, pub tags: HorizontallyScrollableText, + pub priority: i64, } #[derive(Default, Clone, Eq, PartialEq, Debug)] diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 0d9c621..236b122 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -209,7 +209,6 @@ impl<'a> Default for RadarrData<'a> { #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)] pub enum ActiveRadarrBlock { - AddIndexer, AddMovieAlreadyInLibrary, AddMovieSearchInput, AddMovieSearchResults, @@ -256,6 +255,7 @@ pub enum ActiveRadarrBlock { EditIndexerToggleEnableRss, EditIndexerToggleEnableAutomaticSearch, EditIndexerToggleEnableInteractiveSearch, + EditIndexerPriorityInput, EditIndexerUrlInput, EditIndexerTagsInput, EditMoviePrompt, @@ -327,8 +327,7 @@ pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::FilterCollectionsError, ActiveRadarrBlock::UpdateAllCollectionsPrompt, ]; -pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [ - ActiveRadarrBlock::AddIndexer, +pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 3] = [ ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::TestIndexer, @@ -431,7 +430,7 @@ pub const DELETE_MOVIE_SELECTION_BLOCKS: &[&[ActiveRadarrBlock]] = &[ &[ActiveRadarrBlock::DeleteMovieToggleAddListExclusion], &[ActiveRadarrBlock::DeleteMovieConfirmPrompt], ]; -pub static EDIT_INDEXER_BLOCKS: [ActiveRadarrBlock; 10] = [ +pub static EDIT_INDEXER_BLOCKS: [ActiveRadarrBlock; 11] = [ ActiveRadarrBlock::EditIndexerPrompt, ActiveRadarrBlock::EditIndexerConfirmPrompt, ActiveRadarrBlock::EditIndexerApiKeyInput, @@ -440,6 +439,7 @@ pub static EDIT_INDEXER_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::EditIndexerToggleEnableRss, ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch, ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveRadarrBlock::EditIndexerPriorityInput, ActiveRadarrBlock::EditIndexerUrlInput, ActiveRadarrBlock::EditIndexerTagsInput, ]; @@ -460,6 +460,10 @@ pub const EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: &[&[ActiveRadarrBlock]] = &[ ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, ActiveRadarrBlock::EditIndexerTagsInput, ], + &[ + ActiveRadarrBlock::EditIndexerPriorityInput, + ActiveRadarrBlock::EditIndexerConfirmPrompt, + ], &[ ActiveRadarrBlock::EditIndexerConfirmPrompt, ActiveRadarrBlock::EditIndexerConfirmPrompt, @@ -480,7 +484,7 @@ pub const EDIT_INDEXER_NZB_SELECTION_BLOCKS: &[&[ActiveRadarrBlock]] = &[ ], &[ ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, - ActiveRadarrBlock::EditIndexerConfirmPrompt, + ActiveRadarrBlock::EditIndexerPriorityInput, ], &[ ActiveRadarrBlock::EditIndexerConfirmPrompt, diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index 6d4a07d..4222f59 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -303,8 +303,7 @@ mod tests { #[test] fn test_indexers_blocks_contents() { - assert_eq!(INDEXERS_BLOCKS.len(), 4); - assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::AddIndexer)); + assert_eq!(INDEXERS_BLOCKS.len(), 3); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteIndexerPrompt)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::Indexers)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::TestIndexer)); @@ -413,7 +412,7 @@ mod tests { #[test] fn test_edit_indexer_blocks_contents() { - assert_eq!(EDIT_INDEXER_BLOCKS.len(), 10); + assert_eq!(EDIT_INDEXER_BLOCKS.len(), 11); assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerPrompt)); assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerConfirmPrompt)); assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerApiKeyInput)); @@ -428,6 +427,7 @@ mod tests { ); assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerUrlInput)); assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerTagsInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerPriorityInput)); } #[test] @@ -607,6 +607,13 @@ mod tests { ActiveRadarrBlock::EditIndexerTagsInput, ] ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveRadarrBlock::EditIndexerPriorityInput, + ActiveRadarrBlock::EditIndexerConfirmPrompt, + ] + ); assert_eq!( edit_indexer_torrent_selection_block_iter.next().unwrap(), &[ @@ -646,7 +653,7 @@ mod tests { edit_indexer_nzb_selection_block_iter.next().unwrap(), &[ ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, - ActiveRadarrBlock::EditIndexerConfirmPrompt, + ActiveRadarrBlock::EditIndexerPriorityInput, ] ); assert_eq!( diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 104e8d8..e736f10 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -216,6 +216,16 @@ pub enum ActiveSonarrBlock { DeleteSeriesToggleDeleteFile, Downloads, EditIndexerPrompt, + EditIndexerConfirmPrompt, + EditIndexerApiKeyInput, + EditIndexerNameInput, + EditIndexerSeedRatioInput, + EditIndexerToggleEnableRss, + EditIndexerToggleEnableAutomaticSearch, + EditIndexerToggleEnableInteractiveSearch, + EditIndexerUrlInput, + EditIndexerPriorityInput, + EditIndexerTagsInput, EditSeriesPrompt, EditSeriesConfirmPrompt, EditSeriesPathInput, @@ -370,6 +380,87 @@ pub const DELETE_SERIES_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ &[ActiveSonarrBlock::DeleteSeriesConfirmPrompt], ]; +pub static EDIT_INDEXER_BLOCKS: [ActiveSonarrBlock; 11] = [ + ActiveSonarrBlock::EditIndexerPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerPriorityInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ActiveSonarrBlock::EditIndexerTagsInput, +]; + +pub const EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerTagsInput, + ], + &[ + ActiveSonarrBlock::EditIndexerPriorityInput, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ], + &[ + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ], +]; + +pub const EDIT_INDEXER_NZB_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerTagsInput, + ], + &[ + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerPriorityInput, + ], + &[ + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ], +]; + +pub static INDEXER_SETTINGS_BLOCKS: [ActiveSonarrBlock; 6] = [ + ActiveSonarrBlock::AllIndexerSettingsPrompt, + ActiveSonarrBlock::IndexerSettingsConfirmPrompt, + ActiveSonarrBlock::IndexerSettingsMaximumSizeInput, + ActiveSonarrBlock::IndexerSettingsMinimumAgeInput, + ActiveSonarrBlock::IndexerSettingsRetentionInput, + ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput, +]; + +pub const INDEXER_SETTINGS_SELECTION_BLOCKS: &[&[ActiveSonarrBlock]] = &[ + &[ActiveSonarrBlock::IndexerSettingsMinimumAgeInput], + &[ActiveSonarrBlock::IndexerSettingsRetentionInput], + &[ActiveSonarrBlock::IndexerSettingsMaximumSizeInput], + &[ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput], + &[ActiveSonarrBlock::IndexerSettingsConfirmPrompt], +]; + pub static HISTORY_BLOCKS: [ActiveSonarrBlock; 7] = [ ActiveSonarrBlock::History, ActiveSonarrBlock::HistoryItemDetails, @@ -386,6 +477,12 @@ pub static ROOT_FOLDERS_BLOCKS: [ActiveSonarrBlock; 3] = [ ActiveSonarrBlock::DeleteRootFolderPrompt, ]; +pub static INDEXERS_BLOCKS: [ActiveSonarrBlock; 3] = [ + ActiveSonarrBlock::DeleteIndexerPrompt, + ActiveSonarrBlock::Indexers, + ActiveSonarrBlock::TestIndexer, +]; + 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 a5ed5a9..c0aceec 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -203,8 +203,10 @@ mod tests { mod active_sonarr_block_tests { use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, BLOCKLIST_BLOCKS, - DELETE_SERIES_BLOCKS, DELETE_SERIES_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_SERIES_BLOCKS, - EDIT_SERIES_SELECTION_BLOCKS, HISTORY_BLOCKS, LIBRARY_BLOCKS, ROOT_FOLDERS_BLOCKS, + DELETE_SERIES_BLOCKS, DELETE_SERIES_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_INDEXER_BLOCKS, + EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_SERIES_BLOCKS, + EDIT_SERIES_SELECTION_BLOCKS, HISTORY_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, + INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS, ROOT_FOLDERS_BLOCKS, }; #[test] @@ -375,6 +377,158 @@ mod tests { assert_eq!(delete_series_block_iter.next(), None); } + #[test] + fn test_edit_indexer_blocks_contents() { + assert_eq!(EDIT_INDEXER_BLOCKS.len(), 11); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerPrompt)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerConfirmPrompt)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerApiKeyInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerNameInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerSeedRatioInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerToggleEnableRss)); + assert!( + EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch) + ); + assert!( + EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch) + ); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerUrlInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerTagsInput)); + assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveSonarrBlock::EditIndexerPriorityInput)); + } + + #[test] + fn test_edit_indexer_nzb_selection_blocks_ordering() { + let mut edit_indexer_nzb_selection_block_iter = EDIT_INDEXER_NZB_SELECTION_BLOCKS.iter(); + + assert_eq!( + edit_indexer_nzb_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ] + ); + assert_eq!( + edit_indexer_nzb_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ] + ); + assert_eq!( + edit_indexer_nzb_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerTagsInput, + ] + ); + assert_eq!( + edit_indexer_nzb_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerPriorityInput, + ] + ); + assert_eq!( + edit_indexer_nzb_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ] + ); + assert_eq!(edit_indexer_nzb_selection_block_iter.next(), None); + } + + #[test] + fn test_edit_indexer_torrent_selection_blocks_ordering() { + let mut edit_indexer_torrent_selection_block_iter = + EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.iter(); + + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerNameInput, + ActiveSonarrBlock::EditIndexerUrlInput, + ] + ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableRss, + ActiveSonarrBlock::EditIndexerApiKeyInput, + ] + ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch, + ActiveSonarrBlock::EditIndexerSeedRatioInput, + ] + ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch, + ActiveSonarrBlock::EditIndexerTagsInput, + ] + ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerPriorityInput, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ] + ); + assert_eq!( + edit_indexer_torrent_selection_block_iter.next().unwrap(), + &[ + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ActiveSonarrBlock::EditIndexerConfirmPrompt, + ] + ); + assert_eq!(edit_indexer_torrent_selection_block_iter.next(), None); + } + + #[test] + fn test_indexer_settings_blocks_contents() { + assert_eq!(INDEXER_SETTINGS_BLOCKS.len(), 6); + assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::AllIndexerSettingsPrompt)); + assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::IndexerSettingsConfirmPrompt)); + assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::IndexerSettingsMaximumSizeInput)); + assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::IndexerSettingsMinimumAgeInput)); + assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::IndexerSettingsRetentionInput)); + assert!( + INDEXER_SETTINGS_BLOCKS.contains(&ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput) + ); + } + + #[test] + fn test_indexer_settings_selection_blocks_ordering() { + let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter(); + + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &[ActiveSonarrBlock::IndexerSettingsMinimumAgeInput,] + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &[ActiveSonarrBlock::IndexerSettingsRetentionInput,] + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &[ActiveSonarrBlock::IndexerSettingsMaximumSizeInput,] + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &[ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput,] + ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &[ActiveSonarrBlock::IndexerSettingsConfirmPrompt,] + ); + assert_eq!(indexer_settings_block_iter.next(), None); + } + #[test] fn test_history_blocks_contents() { assert_eq!(HISTORY_BLOCKS.len(), 7); @@ -394,5 +548,13 @@ mod tests { assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveSonarrBlock::AddRootFolderPrompt)); assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveSonarrBlock::DeleteRootFolderPrompt)); } + + #[test] + fn test_indexers_blocks_contents() { + assert_eq!(INDEXERS_BLOCKS.len(), 3); + assert!(INDEXERS_BLOCKS.contains(&ActiveSonarrBlock::DeleteIndexerPrompt)); + assert!(INDEXERS_BLOCKS.contains(&ActiveSonarrBlock::Indexers)); + assert!(INDEXERS_BLOCKS.contains(&ActiveSonarrBlock::TestIndexer)); + } } } diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 7722006..10a86f6 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -1056,6 +1056,7 @@ impl<'a, 'b> Network<'a, 'b> { url, api_key, seed_ratio, + priority, .. } = app.data.radarr_data.edit_indexer_modal.as_ref().unwrap(); @@ -1068,7 +1069,7 @@ impl<'a, 'b> Network<'a, 'b> { api_key.text.clone(), seed_ratio.text.clone(), tag_ids_vec, - priority, + *priority, ) }; diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index b1fb1cf..e3d3dd8 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -4082,7 +4082,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -4134,6 +4134,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); app.data.radarr_data.indexers.set_items(vec![indexer()]); @@ -4179,7 +4180,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -4227,6 +4228,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); let mut indexer = indexer(); @@ -4284,7 +4286,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -4336,6 +4338,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); let mut indexer = indexer(); diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index e55160b..92ec2ba 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -987,6 +987,7 @@ impl<'a, 'b> Network<'a, 'b> { url, api_key, seed_ratio, + priority, .. } = app.data.sonarr_data.edit_indexer_modal.as_ref().unwrap(); @@ -999,7 +1000,7 @@ impl<'a, 'b> Network<'a, 'b> { api_key.text.clone(), seed_ratio.text.clone(), tag_ids_vec, - priority, + *priority, ) }; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 197f0c8..ce27e68 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -1162,7 +1162,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -1218,6 +1218,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); app.data.sonarr_data.indexers.set_items(vec![indexer()]); @@ -1263,7 +1264,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -1315,6 +1316,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); let mut indexer = indexer(); @@ -1372,7 +1374,7 @@ mod test { "enableAutomaticSearch": false, "enableInteractiveSearch": false, "name": "Test Update", - "priority": 1, + "priority": 0, "fields": [ { "name": "baseUrl", @@ -1428,6 +1430,7 @@ mod test { api_key: "test1234".into(), seed_ratio: "1.3".into(), tags: "usenet, testing".into(), + priority: 0, }; app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); let mut indexer = indexer();