From f31810e48a05827f2b06db24fa9a7c86920e22f5 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 9 Jan 2026 17:21:10 -0700 Subject: [PATCH] feat: TUI support for deleting a Lidarr album from the artist details popup --- src/app/lidarr/lidarr_context_clues.rs | 7 +- src/app/lidarr/lidarr_context_clues_tests.rs | 8 +- .../library/artist_details_handler.rs | 30 +- .../library/artist_details_handler_tests.rs | 40 +- .../library/delete_album_handler.rs | 150 +++++++ .../library/delete_album_handler_tests.rs | 404 ++++++++++++++++++ .../library/delete_artist_handler.rs | 11 +- .../library/delete_artist_handler_tests.rs | 28 +- .../library/library_handler_tests.rs | 32 +- src/handlers/lidarr_handlers/library/mod.rs | 1 + src/models/servarr_data/lidarr/lidarr_data.rs | 27 +- .../servarr_data/lidarr/lidarr_data_tests.rs | 43 +- src/ui/lidarr_ui/library/artist_details_ui.rs | 6 +- .../library/artist_details_ui_tests.rs | 23 +- src/ui/lidarr_ui/library/delete_album_ui.rs | 57 +++ .../library/delete_album_ui_tests.rs | 43 ++ src/ui/lidarr_ui/library/delete_artist_ui.rs | 2 +- src/ui/lidarr_ui/library/library_ui_tests.rs | 5 +- src/ui/lidarr_ui/library/mod.rs | 3 +- ...lete_album_prompt_over_artist_details.snap | 52 +++ ..._delete_album_ui_renders_delete_album.snap | 38 ++ 21 files changed, 953 insertions(+), 57 deletions(-) create mode 100644 src/handlers/lidarr_handlers/library/delete_album_handler.rs create mode 100644 src/handlers/lidarr_handlers/library/delete_album_handler_tests.rs create mode 100644 src/ui/lidarr_ui/library/delete_album_ui.rs create mode 100644 src/ui/lidarr_ui/library/delete_album_ui_tests.rs create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__artist_details_ui_renders_delete_album_prompt_over_artist_details.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_album_ui__delete_album_ui_tests__tests__snapshot_tests__delete_album_ui_renders_delete_album.snap diff --git a/src/app/lidarr/lidarr_context_clues.rs b/src/app/lidarr/lidarr_context_clues.rs index 830c33f..b2bb8d7 100644 --- a/src/app/lidarr/lidarr_context_clues.rs +++ b/src/app/lidarr/lidarr_context_clues.rs @@ -36,15 +36,16 @@ pub static ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [ (DEFAULT_KEYBINDINGS.esc, "edit search"), ]; -pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 7] = [ +pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [ ( DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh.desc, ), - (DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc), + (DEFAULT_KEYBINDINGS.edit, "edit artist"), + (DEFAULT_KEYBINDINGS.delete, "delete album"), ( DEFAULT_KEYBINDINGS.toggle_monitoring, - DEFAULT_KEYBINDINGS.toggle_monitoring.desc, + "toggle album monitoring", ), (DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc), (DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc), diff --git a/src/app/lidarr/lidarr_context_clues_tests.rs b/src/app/lidarr/lidarr_context_clues_tests.rs index b698bf0..9a3e501 100644 --- a/src/app/lidarr/lidarr_context_clues_tests.rs +++ b/src/app/lidarr/lidarr_context_clues_tests.rs @@ -81,13 +81,17 @@ mod tests { ); assert_some_eq_x!( artist_details_context_clues_iter.next(), - &(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc) + &(DEFAULT_KEYBINDINGS.edit, "edit artist") + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &(DEFAULT_KEYBINDINGS.delete, "delete album") ); assert_some_eq_x!( artist_details_context_clues_iter.next(), &( DEFAULT_KEYBINDINGS.toggle_monitoring, - DEFAULT_KEYBINDINGS.toggle_monitoring.desc, + "toggle album monitoring", ) ); assert_some_eq_x!( diff --git a/src/handlers/lidarr_handlers/library/artist_details_handler.rs b/src/handlers/lidarr_handlers/library/artist_details_handler.rs index 174615c..0b6ddaa 100644 --- a/src/handlers/lidarr_handlers/library/artist_details_handler.rs +++ b/src/handlers/lidarr_handlers/library/artist_details_handler.rs @@ -1,11 +1,13 @@ use crate::app::App; use crate::event::Key; +use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler; use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; use crate::matches_key; use crate::models::lidarr_models::Album; use crate::models::servarr_data::lidarr::lidarr_data::{ - ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS, + ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS, + EDIT_ARTIST_SELECTION_BLOCKS, }; use crate::models::{BlockSelectionState, Route}; use crate::network::lidarr_network::LidarrEvent; @@ -18,7 +20,7 @@ pub struct ArtistDetailsHandler<'a, 'b> { key: Key, app: &'a mut App<'b>, active_lidarr_block: ActiveLidarrBlock, - _context: Option, + context: Option, } impl ArtistDetailsHandler<'_, '_> { @@ -44,12 +46,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler |app| &mut app.data.lidarr_data.albums, albums_table_handling_config, ) { - self.handle_key_event(); + match self.active_lidarr_block { + _ if DeleteAlbumHandler::accepts(self.active_lidarr_block) => { + DeleteAlbumHandler::new(self.key, self.app, self.active_lidarr_block, self.context) + .handle(); + } + _ => self.handle_key_event(), + }; } } fn accepts(active_block: ActiveLidarrBlock) -> bool { - ARTIST_DETAILS_BLOCKS.contains(&active_block) + DeleteAlbumHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block) } fn ignore_special_keys(&self) -> bool { @@ -60,13 +68,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler key: Key, app: &'a mut App<'b>, active_block: ActiveLidarrBlock, - _context: Option, + context: Option, ) -> ArtistDetailsHandler<'a, 'b> { ArtistDetailsHandler { key, app, active_lidarr_block: active_block, - _context, + context, } } @@ -86,7 +94,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler fn handle_end(&mut self) {} - fn handle_delete(&mut self) {} + fn handle_delete(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::ArtistDetails { + self + .app + .push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + self.app.data.lidarr_data.selected_block = + BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + } + } fn handle_left_right_action(&mut self) { match self.active_lidarr_block { diff --git a/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs b/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs index 1900776..162680e 100644 --- a/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs @@ -9,9 +9,42 @@ mod tests { use crate::handlers::KeyEventHandler; use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; use crate::models::servarr_data::lidarr::lidarr_data::{ - ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, + ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, }; + mod test_handle_delete { + use super::*; + use crate::assert_delete_prompt; + use crate::event::Key; + use crate::models::lidarr_models::Album; + use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS; + use pretty_assertions::assert_eq; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_album_delete() { + let mut app = App::test_default(); + app + .data + .lidarr_data + .albums + .set_items(vec![Album::default()]); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + + assert_delete_prompt!( + ArtistDetailsHandler, + app, + ActiveLidarrBlock::ArtistDetails, + ActiveLidarrBlock::DeleteAlbumPrompt + ); + assert_eq!( + app.data.lidarr_data.selected_block.blocks, + DELETE_ALBUM_SELECTION_BLOCKS + ); + } + } + mod test_handle_left_right_action { use rstest::rstest; @@ -436,8 +469,11 @@ mod tests { #[test] fn test_artist_details_handler_accepts() { + let mut artist_details_blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec(); + artist_details_blocks.extend(DELETE_ALBUM_BLOCKS); + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { - if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) { + if artist_details_blocks.contains(&active_lidarr_block) { assert!(ArtistDetailsHandler::accepts(active_lidarr_block)); } else { assert!(!ArtistDetailsHandler::accepts(active_lidarr_block)); diff --git a/src/handlers/lidarr_handlers/library/delete_album_handler.rs b/src/handlers/lidarr_handlers/library/delete_album_handler.rs new file mode 100644 index 0000000..ab8e82d --- /dev/null +++ b/src/handlers/lidarr_handlers/library/delete_album_handler.rs @@ -0,0 +1,150 @@ +use crate::models::Route; +use crate::models::lidarr_models::DeleteParams; +use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_BLOCKS; +use crate::network::lidarr_network::LidarrEvent; +use crate::{ + app::App, + event::Key, + handlers::{KeyEventHandler, handle_prompt_toggle}, + matches_key, + models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock, +}; + +#[cfg(test)] +#[path = "delete_album_handler_tests.rs"] +mod delete_album_handler_tests; + +pub(in crate::handlers::lidarr_handlers) struct DeleteAlbumHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_lidarr_block: ActiveLidarrBlock, + _context: Option, +} + +impl DeleteAlbumHandler<'_, '_> { + fn build_delete_album_params(&mut self) -> DeleteParams { + let id = self.app.data.lidarr_data.albums.current_selection().id; + let delete_files = self.app.data.lidarr_data.delete_files; + let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion; + self.app.data.lidarr_data.reset_delete_preferences(); + + DeleteParams { + id, + delete_files, + add_import_list_exclusion, + } + } +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteAlbumHandler<'a, 'b> { + fn accepts(active_block: ActiveLidarrBlock) -> bool { + DELETE_ALBUM_BLOCKS.contains(&active_block) + } + + fn ignore_special_keys(&self) -> bool { + self.app.ignore_special_keys_for_textbox_input + } + + fn new( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveLidarrBlock, + _context: Option, + ) -> Self { + DeleteAlbumHandler { + key, + app, + active_lidarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + } + + fn handle_scroll_up(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt { + self.app.data.lidarr_data.selected_block.up(); + } + } + + fn handle_scroll_down(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt { + self.app.data.lidarr_data.selected_block.down(); + } + } + + 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_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt { + handle_prompt_toggle(self.app, self.key); + } + } + + fn handle_submit(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt { + match self.app.data.lidarr_data.selected_block.get_active_block() { + ActiveLidarrBlock::DeleteAlbumConfirmPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::DeleteAlbum(self.build_delete_album_params())); + self.app.should_refresh = true; + } else { + self.app.data.lidarr_data.reset_delete_preferences(); + } + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::DeleteAlbumToggleDeleteFile => { + self.app.data.lidarr_data.delete_files = !self.app.data.lidarr_data.delete_files; + } + ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion => { + self.app.data.lidarr_data.add_import_list_exclusion = + !self.app.data.lidarr_data.add_import_list_exclusion; + } + _ => (), + } + } + } + + fn handle_esc(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.reset_delete_preferences(); + self.app.data.lidarr_data.prompt_confirm = false; + } + } + + fn handle_char_key_event(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt + && self.app.data.lidarr_data.selected_block.get_active_block() + == ActiveLidarrBlock::DeleteAlbumConfirmPrompt + && matches_key!(confirm, self.key) + { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::DeleteAlbum(self.build_delete_album_params())); + self.app.should_refresh = true; + + self.app.pop_navigation_stack(); + } + } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> Route { + self.app.get_current_route() + } +} diff --git a/src/handlers/lidarr_handlers/library/delete_album_handler_tests.rs b/src/handlers/lidarr_handlers/library/delete_album_handler_tests.rs new file mode 100644 index 0000000..65ddd49 --- /dev/null +++ b/src/handlers/lidarr_handlers/library/delete_album_handler_tests.rs @@ -0,0 +1,404 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::App; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::event::Key; + use crate::handlers::KeyEventHandler; + use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler; + use crate::models::lidarr_models::{Album, DeleteParams}; + use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ALBUM_BLOCKS}; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::BlockSelectionState; + use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS; + + use super::*; + + #[rstest] + fn test_delete_album_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { + let mut app = App::test_default(); + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app.data.lidarr_data.selected_block.down(); + + DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle(); + + if key == Key::Up { + assert_eq!( + app.data.lidarr_data.selected_block.get_active_block(), + ActiveLidarrBlock::DeleteAlbumToggleDeleteFile + ); + } else { + assert_eq!( + app.data.lidarr_data.selected_block.get_active_block(), + ActiveLidarrBlock::DeleteAlbumConfirmPrompt + ); + } + } + + #[rstest] + fn test_delete_album_prompt_scroll_no_op_when_not_ready( + #[values(Key::Up, Key::Down)] key: Key, + ) { + let mut app = App::test_default(); + app.is_loading = true; + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app.data.lidarr_data.selected_block.down(); + + DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle(); + + assert_eq!( + app.data.lidarr_data.selected_block.get_active_block(), + ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion + ); + } + } + + mod test_handle_left_right_action { + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + + DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + + DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::models::BlockSelectionState; + use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS; + use crate::network::lidarr_network::LidarrEvent; + + use super::*; + use crate::assert_navigation_popped; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_delete_album_prompt_prompt_decline_submit() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app + .data + .lidarr_data + .selected_block + .set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1); + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + + DeleteAlbumHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_none!(app.data.lidarr_data.prompt_confirm_action); + assert!(!app.data.lidarr_data.prompt_confirm); + assert!(!app.data.lidarr_data.delete_files); + assert!(!app.data.lidarr_data.add_import_list_exclusion); + } + + #[test] + fn test_delete_album_confirm_prompt_prompt_confirmation_submit() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.prompt_confirm = true; + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + app + .data + .lidarr_data + .albums + .set_items(vec![Album::default()]); + let expected_delete_album_params = DeleteParams { + id: 0, + delete_files: true, + add_import_list_exclusion: true, + }; + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app + .data + .lidarr_data + .selected_block + .set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1); + + DeleteAlbumHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_eq!( + app.data.lidarr_data.prompt_confirm_action, + Some(LidarrEvent::DeleteAlbum(expected_delete_album_params)) + ); + assert!(app.should_refresh); + assert!(app.data.lidarr_data.prompt_confirm); + assert!(!app.data.lidarr_data.delete_files); + assert!(!app.data.lidarr_data.add_import_list_exclusion); + } + + #[test] + fn test_delete_album_confirm_prompt_prompt_confirmation_submit_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.prompt_confirm = true; + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + + DeleteAlbumHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::DeleteAlbumPrompt.into() + ); + assert_none!(app.data.lidarr_data.prompt_confirm_action); + assert!(!app.should_refresh); + assert!(app.data.lidarr_data.prompt_confirm); + assert!(app.data.lidarr_data.delete_files); + assert!(app.data.lidarr_data.add_import_list_exclusion); + } + + #[test] + fn test_delete_album_toggle_delete_files_submit() { + let current_route = ActiveLidarrBlock::DeleteAlbumPrompt.into(); + let mut app = App::test_default(); + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + + DeleteAlbumHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), current_route); + assert_eq!(app.data.lidarr_data.delete_files, true); + + DeleteAlbumHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), current_route); + assert_eq!(app.data.lidarr_data.delete_files, false); + } + } + + mod test_handle_esc { + use super::*; + use crate::assert_navigation_popped; + use rstest::rstest; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_delete_album_prompt_esc(#[values(true, false)] is_ready: bool) { + let mut app = App::test_default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.prompt_confirm = true; + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + + DeleteAlbumHandler::new( + ESC_KEY, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert!(!app.data.lidarr_data.prompt_confirm); + assert!(!app.data.lidarr_data.delete_files); + assert!(!app.data.lidarr_data.add_import_list_exclusion); + } + } + + mod test_handle_key_char { + use crate::{ + assert_navigation_popped, + models::{ + BlockSelectionState, servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS, + }, + network::lidarr_network::LidarrEvent, + }; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_delete_album_confirm_prompt_prompt_confirm() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + app + .data + .lidarr_data + .albums + .set_items(vec![Album::default()]); + let expected_delete_album_params = DeleteParams { + id: 0, + delete_files: true, + add_import_list_exclusion: true, + }; + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app + .data + .lidarr_data + .selected_block + .set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1); + + DeleteAlbumHandler::new( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_eq!( + app.data.lidarr_data.prompt_confirm_action, + Some(LidarrEvent::DeleteAlbum(expected_delete_album_params)) + ); + assert!(app.should_refresh); + assert!(app.data.lidarr_data.prompt_confirm); + assert!(!app.data.lidarr_data.delete_files); + assert!(!app.data.lidarr_data.add_import_list_exclusion); + } + } + + #[test] + fn test_delete_album_handler_accepts() { + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { + if DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block) { + assert!(DeleteAlbumHandler::accepts(active_lidarr_block)); + } else { + assert!(!DeleteAlbumHandler::accepts(active_lidarr_block)); + } + }); + } + + #[rstest] + fn test_delete_album_handler_ignore_special_keys( + #[values(true, false)] ignore_special_keys_for_textbox_input: bool, + ) { + let mut app = App::test_default(); + app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input; + let handler = DeleteAlbumHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::default(), + None, + ); + + assert_eq!( + handler.ignore_special_keys(), + ignore_special_keys_for_textbox_input + ); + } + + #[test] + fn test_build_delete_album_params() { + let mut app = App::test_default(); + app + .data + .lidarr_data + .albums + .set_items(vec![Album::default()]); + app.data.lidarr_data.delete_files = true; + app.data.lidarr_data.add_import_list_exclusion = true; + let expected_delete_album_params = DeleteParams { + id: 0, + delete_files: true, + add_import_list_exclusion: true, + }; + + let delete_album_params = DeleteAlbumHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .build_delete_album_params(); + + assert_eq!(delete_album_params, expected_delete_album_params); + assert!(!app.data.lidarr_data.delete_files); + assert!(!app.data.lidarr_data.add_import_list_exclusion); + } + + #[test] + fn test_delete_album_handler_not_ready_when_loading() { + let mut app = App::test_default(); + app.is_loading = true; + + let handler = DeleteAlbumHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_delete_album_handler_ready_when_not_loading() { + let mut app = App::test_default(); + app.is_loading = false; + + let handler = DeleteAlbumHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/lidarr_handlers/library/delete_artist_handler.rs b/src/handlers/lidarr_handlers/library/delete_artist_handler.rs index 95d00d3..e94e0a7 100644 --- a/src/handlers/lidarr_handlers/library/delete_artist_handler.rs +++ b/src/handlers/lidarr_handlers/library/delete_artist_handler.rs @@ -23,9 +23,9 @@ pub(in crate::handlers::lidarr_handlers) struct DeleteArtistHandler<'a, 'b> { impl DeleteArtistHandler<'_, '_> { fn build_delete_artist_params(&mut self) -> DeleteParams { let id = self.app.data.lidarr_data.artists.current_selection().id; - let delete_files = self.app.data.lidarr_data.delete_artist_files; + let delete_files = self.app.data.lidarr_data.delete_files; let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion; - self.app.data.lidarr_data.reset_delete_artist_preferences(); + self.app.data.lidarr_data.reset_delete_preferences(); DeleteParams { id, @@ -99,14 +99,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteArtistHandler< Some(LidarrEvent::DeleteArtist(self.build_delete_artist_params())); self.app.should_refresh = true; } else { - self.app.data.lidarr_data.reset_delete_artist_preferences(); + self.app.data.lidarr_data.reset_delete_preferences(); } self.app.pop_navigation_stack(); } ActiveLidarrBlock::DeleteArtistToggleDeleteFile => { - self.app.data.lidarr_data.delete_artist_files = - !self.app.data.lidarr_data.delete_artist_files; + self.app.data.lidarr_data.delete_files = !self.app.data.lidarr_data.delete_files; } ActiveLidarrBlock::DeleteArtistToggleAddListExclusion => { self.app.data.lidarr_data.add_import_list_exclusion = @@ -120,7 +119,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteArtistHandler< fn handle_esc(&mut self) { if self.active_lidarr_block == ActiveLidarrBlock::DeleteArtistPrompt { self.app.pop_navigation_stack(); - self.app.data.lidarr_data.reset_delete_artist_preferences(); + self.app.data.lidarr_data.reset_delete_preferences(); self.app.data.lidarr_data.prompt_confirm = false; } } diff --git a/src/handlers/lidarr_handlers/library/delete_artist_handler_tests.rs b/src/handlers/lidarr_handlers/library/delete_artist_handler_tests.rs index 3fccce0..6c06840 100644 --- a/src/handlers/lidarr_handlers/library/delete_artist_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/delete_artist_handler_tests.rs @@ -106,7 +106,7 @@ mod tests { .lidarr_data .selected_block .set_index(0, DELETE_ARTIST_SELECTION_BLOCKS.len() - 1); - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; DeleteArtistHandler::new( @@ -120,7 +120,7 @@ mod tests { assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into()); assert_none!(app.data.lidarr_data.prompt_confirm_action); assert!(!app.data.lidarr_data.prompt_confirm); - assert!(!app.data.lidarr_data.delete_artist_files); + assert!(!app.data.lidarr_data.delete_files); assert!(!app.data.lidarr_data.add_import_list_exclusion); } @@ -130,7 +130,7 @@ mod tests { app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into()); app.data.lidarr_data.prompt_confirm = true; - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; app .data @@ -165,7 +165,7 @@ mod tests { ); assert!(app.should_refresh); assert!(app.data.lidarr_data.prompt_confirm); - assert!(!app.data.lidarr_data.delete_artist_files); + assert!(!app.data.lidarr_data.delete_files); assert!(!app.data.lidarr_data.add_import_list_exclusion); } @@ -176,7 +176,7 @@ mod tests { app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into()); app.data.lidarr_data.prompt_confirm = true; - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; DeleteArtistHandler::new( @@ -194,7 +194,7 @@ mod tests { assert_none!(app.data.lidarr_data.prompt_confirm_action); assert!(!app.should_refresh); assert!(app.data.lidarr_data.prompt_confirm); - assert!(app.data.lidarr_data.delete_artist_files); + assert!(app.data.lidarr_data.delete_files); assert!(app.data.lidarr_data.add_import_list_exclusion); } @@ -215,7 +215,7 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), current_route); - assert_eq!(app.data.lidarr_data.delete_artist_files, true); + assert_eq!(app.data.lidarr_data.delete_files, true); DeleteArtistHandler::new( SUBMIT_KEY, @@ -226,7 +226,7 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), current_route); - assert_eq!(app.data.lidarr_data.delete_artist_files, false); + assert_eq!(app.data.lidarr_data.delete_files, false); } } @@ -244,7 +244,7 @@ mod tests { app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into()); app.data.lidarr_data.prompt_confirm = true; - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; DeleteArtistHandler::new( @@ -257,7 +257,7 @@ mod tests { assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into()); assert!(!app.data.lidarr_data.prompt_confirm); - assert!(!app.data.lidarr_data.delete_artist_files); + assert!(!app.data.lidarr_data.delete_files); assert!(!app.data.lidarr_data.add_import_list_exclusion); } } @@ -279,7 +279,7 @@ mod tests { let mut app = App::test_default(); app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into()); - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; app .data @@ -314,7 +314,7 @@ mod tests { ); assert!(app.should_refresh); assert!(app.data.lidarr_data.prompt_confirm); - assert!(!app.data.lidarr_data.delete_artist_files); + assert!(!app.data.lidarr_data.delete_files); assert!(!app.data.lidarr_data.add_import_list_exclusion); } } @@ -357,7 +357,7 @@ mod tests { .lidarr_data .artists .set_items(vec![Artist::default()]); - app.data.lidarr_data.delete_artist_files = true; + app.data.lidarr_data.delete_files = true; app.data.lidarr_data.add_import_list_exclusion = true; let expected_delete_artist_params = DeleteParams { id: 0, @@ -374,7 +374,7 @@ mod tests { .build_delete_artist_params(); assert_eq!(delete_artist_params, expected_delete_artist_params); - assert!(!app.data.lidarr_data.delete_artist_files); + assert!(!app.data.lidarr_data.delete_files); assert!(!app.data.lidarr_data.add_import_list_exclusion); } diff --git a/src/handlers/lidarr_handlers/library/library_handler_tests.rs b/src/handlers/lidarr_handlers/library/library_handler_tests.rs index ea4f5bd..b88c5a4 100644 --- a/src/handlers/lidarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/library_handler_tests.rs @@ -11,10 +11,10 @@ mod tests { use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::handlers::KeyEventHandler; use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options}; - use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus}; + use crate::models::lidarr_models::{Album, Artist, ArtistStatistics, ArtistStatus}; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, - EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, + ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, + DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, }; use crate::models::servarr_data::lidarr::modals::EditArtistModal; use crate::network::lidarr_network::LidarrEvent; @@ -28,6 +28,7 @@ mod tests { library_handler_blocks.extend(LIBRARY_BLOCKS); library_handler_blocks.extend(ARTIST_DETAILS_BLOCKS); library_handler_blocks.extend(DELETE_ARTIST_BLOCKS); + library_handler_blocks.extend(DELETE_ALBUM_BLOCKS); library_handler_blocks.extend(EDIT_ARTIST_BLOCKS); library_handler_blocks.extend(ADD_ARTIST_BLOCKS); @@ -533,6 +534,31 @@ mod tests { assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into()); } + #[test] + fn test_delegates_delete_album_blocks_to_delete_album_handler() { + let mut app = App::test_default(); + app + .data + .lidarr_data + .albums + .set_items(vec![Album::default()]); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + + LibraryHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::DeleteAlbumPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::ArtistDetails.into() + ); + } + #[test] fn test_delegates_delete_artist_blocks_to_delete_artist_handler() { let mut app = App::test_default(); diff --git a/src/handlers/lidarr_handlers/library/mod.rs b/src/handlers/lidarr_handlers/library/mod.rs index 137f3d8..9a0d23e 100644 --- a/src/handlers/lidarr_handlers/library/mod.rs +++ b/src/handlers/lidarr_handlers/library/mod.rs @@ -20,6 +20,7 @@ use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; mod add_artist_handler; mod artist_details_handler; +mod delete_album_handler; mod delete_artist_handler; mod edit_artist_handler; diff --git a/src/models/servarr_data/lidarr/lidarr_data.rs b/src/models/servarr_data/lidarr/lidarr_data.rs index 46c4c8e..791bbc7 100644 --- a/src/models/servarr_data/lidarr/lidarr_data.rs +++ b/src/models/servarr_data/lidarr/lidarr_data.rs @@ -40,7 +40,7 @@ pub struct LidarrData<'a> { pub albums: StatefulTable, pub artist_info_tabs: TabState, pub artists: StatefulTable, - pub delete_artist_files: bool, + pub delete_files: bool, pub disk_space_vec: Vec, pub downloads: StatefulTable, pub edit_artist_modal: Option, @@ -57,8 +57,8 @@ pub struct LidarrData<'a> { } impl LidarrData<'_> { - pub fn reset_delete_artist_preferences(&mut self) { - self.delete_artist_files = false; + pub fn reset_delete_preferences(&mut self) { + self.delete_files = false; self.add_import_list_exclusion = false; } @@ -108,7 +108,7 @@ impl<'a> Default for LidarrData<'a> { add_searched_artists: None, albums: StatefulTable::default(), artists: StatefulTable::default(), - delete_artist_files: false, + delete_files: false, disk_space_vec: Vec::new(), downloads: StatefulTable::default(), edit_artist_modal: None, @@ -176,7 +176,7 @@ impl LidarrData<'_> { .set_items(vec![metadata_profile().name]); let mut lidarr_data = LidarrData { - delete_artist_files: true, + delete_files: true, disk_space_vec: vec![diskspace()], quality_profile_map: quality_profile_map(), metadata_profile_map: metadata_profile_map(), @@ -226,6 +226,10 @@ pub enum ActiveLidarrBlock { AddArtistSelectRootFolder, AddArtistTagsInput, AutomaticallySearchArtistPrompt, + DeleteAlbumPrompt, + DeleteAlbumConfirmPrompt, + DeleteAlbumToggleDeleteFile, + DeleteAlbumToggleAddListExclusion, DeleteArtistPrompt, DeleteArtistConfirmPrompt, DeleteArtistToggleDeleteFile, @@ -304,6 +308,19 @@ pub const DELETE_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[ &[ActiveLidarrBlock::DeleteArtistConfirmPrompt], ]; +pub static DELETE_ALBUM_BLOCKS: [ActiveLidarrBlock; 4] = [ + ActiveLidarrBlock::DeleteAlbumPrompt, + ActiveLidarrBlock::DeleteAlbumConfirmPrompt, + ActiveLidarrBlock::DeleteAlbumToggleDeleteFile, + ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion, +]; + +pub const DELETE_ALBUM_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[ + &[ActiveLidarrBlock::DeleteAlbumToggleDeleteFile], + &[ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion], + &[ActiveLidarrBlock::DeleteAlbumConfirmPrompt], +]; + pub static EDIT_ARTIST_BLOCKS: [ActiveLidarrBlock; 8] = [ ActiveLidarrBlock::EditArtistPrompt, ActiveLidarrBlock::EditArtistConfirmPrompt, diff --git a/src/models/servarr_data/lidarr/lidarr_data_tests.rs b/src/models/servarr_data/lidarr/lidarr_data_tests.rs index 55ad5c1..f995d5e 100644 --- a/src/models/servarr_data/lidarr/lidarr_data_tests.rs +++ b/src/models/servarr_data/lidarr/lidarr_data_tests.rs @@ -5,8 +5,9 @@ mod tests { }; use crate::models::lidarr_models::Album; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ARTIST_BLOCKS, - DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, + ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS, + DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, + EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, }; use crate::models::{ BlockSelectionState, Route, @@ -34,16 +35,16 @@ mod tests { } #[test] - fn test_reset_delete_artist_preferences() { + fn test_reset_delete_preferences() { let mut lidarr_data = LidarrData { - delete_artist_files: true, + delete_files: true, add_import_list_exclusion: true, ..LidarrData::default() }; - lidarr_data.reset_delete_artist_preferences(); + lidarr_data.reset_delete_preferences(); - assert!(!lidarr_data.delete_artist_files); + assert!(!lidarr_data.delete_files); assert!(!lidarr_data.add_import_list_exclusion); } @@ -129,7 +130,7 @@ mod tests { assert_none!(lidarr_data.add_searched_artists); assert_is_empty!(lidarr_data.albums); assert_is_empty!(lidarr_data.artists); - assert!(!lidarr_data.delete_artist_files); + assert!(!lidarr_data.delete_files); assert_is_empty!(lidarr_data.disk_space_vec); assert_is_empty!(lidarr_data.downloads); assert_none!(lidarr_data.edit_artist_modal); @@ -271,6 +272,34 @@ mod tests { assert_none!(delete_artist_block_iter.next()); } + #[test] + fn test_delete_album_blocks_contents() { + assert_eq!(DELETE_ALBUM_BLOCKS.len(), 4); + assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumPrompt)); + assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumConfirmPrompt)); + assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumToggleDeleteFile)); + assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion)); + } + + #[test] + fn test_delete_album_selection_blocks_ordering() { + let mut delete_album_block_iter = DELETE_ALBUM_SELECTION_BLOCKS.iter(); + + assert_eq!( + delete_album_block_iter.next().unwrap(), + &[ActiveLidarrBlock::DeleteAlbumToggleDeleteFile] + ); + assert_eq!( + delete_album_block_iter.next().unwrap(), + &[ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion] + ); + assert_eq!( + delete_album_block_iter.next().unwrap(), + &[ActiveLidarrBlock::DeleteAlbumConfirmPrompt] + ); + assert_none!(delete_album_block_iter.next()); + } + #[test] fn test_edit_artist_blocks() { assert_eq!(EDIT_ARTIST_BLOCKS.len(), 8); diff --git a/src/ui/lidarr_ui/library/artist_details_ui.rs b/src/ui/lidarr_ui/library/artist_details_ui.rs index faf70ac..398e06e 100644 --- a/src/ui/lidarr_ui/library/artist_details_ui.rs +++ b/src/ui/lidarr_ui/library/artist_details_ui.rs @@ -11,6 +11,7 @@ use crate::app::App; use crate::models::Route; use crate::models::lidarr_models::Album; use crate::models::servarr_data::lidarr::lidarr_data::{ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock}; +use crate::ui::lidarr_ui::library::delete_album_ui::DeleteAlbumUi; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_top_border, title_block, @@ -32,10 +33,10 @@ impl DrawUi for ArtistDetailsUi { let Route::Lidarr(active_lidarr_block, _) = route else { return false; }; - ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) + DeleteAlbumUi::accepts(route) || ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = app.get_current_route(); if let Route::Lidarr(active_lidarr_block, _) = route { let draw_artist_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| { @@ -65,6 +66,7 @@ impl DrawUi for ArtistDetailsUi { draw_artist_details(f, app, content_area); match active_lidarr_block { + _ if DeleteAlbumUi::accepts(route) => DeleteAlbumUi::draw(f, app, area), ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { let prompt = format!( "Do you want to trigger an automatic search of your indexers for all monitored album(s) for the artist: {}?", diff --git a/src/ui/lidarr_ui/library/artist_details_ui_tests.rs b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs index 865c2ea..56e4740 100644 --- a/src/ui/lidarr_ui/library/artist_details_ui_tests.rs +++ b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs @@ -3,7 +3,7 @@ mod tests { use strum::IntoEnumIterator; use crate::models::servarr_data::lidarr::lidarr_data::{ - ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, + ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, }; use crate::ui::DrawUi; use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; @@ -11,6 +11,8 @@ mod tests { #[test] fn test_artist_details_ui_accepts() { let mut blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec(); + blocks.extend(DELETE_ALBUM_BLOCKS); + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { if blocks.contains(&active_lidarr_block) { assert!(ArtistDetailsUi::accepts(active_lidarr_block.into())); @@ -24,7 +26,10 @@ mod tests { use rstest::rstest; use crate::app::App; - use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + use crate::models::BlockSelectionState; + use crate::models::servarr_data::lidarr::lidarr_data::{ + ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS, + }; use crate::models::stateful_table::StatefulTable; use crate::ui::DrawUi; use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; @@ -96,6 +101,20 @@ mod tests { ); } + #[test] + fn test_artist_details_ui_renders_delete_album_prompt_over_artist_details() { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ArtistDetailsUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + #[test] fn test_artist_details_ui_renders_update_and_scan_prompt_over_artist_details() { let mut app = App::test_default_fully_populated(); diff --git a/src/ui/lidarr_ui/library/delete_album_ui.rs b/src/ui/lidarr_ui/library/delete_album_ui.rs new file mode 100644 index 0000000..cf8fab2 --- /dev/null +++ b/src/ui/lidarr_ui/library/delete_album_ui.rs @@ -0,0 +1,57 @@ +use ratatui::Frame; +use ratatui::layout::Rect; + +use crate::app::App; +use crate::models::Route; +use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ALBUM_BLOCKS}; +use crate::ui::DrawUi; +use crate::ui::widgets::checkbox::Checkbox; +use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; +use crate::ui::widgets::popup::{Popup, Size}; + +#[cfg(test)] +#[path = "delete_album_ui_tests.rs"] +mod delete_album_ui_tests; + +pub(in crate::ui::lidarr_ui) struct DeleteAlbumUi; + +impl DrawUi for DeleteAlbumUi { + fn accepts(route: Route) -> bool { + let Route::Lidarr(active_lidarr_block, _) = route else { + return false; + }; + DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block) + } + + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) { + if matches!( + app.get_current_route(), + Route::Lidarr(ActiveLidarrBlock::DeleteAlbumPrompt, _) + ) { + let selected_block = app.data.lidarr_data.selected_block.get_active_block(); + let prompt = format!( + "Do you really want to delete the album: \n{}?", + app.data.lidarr_data.albums.current_selection().title.text + ); + let checkboxes = vec![ + Checkbox::new("Delete Album Files") + .checked(app.data.lidarr_data.delete_files) + .highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumToggleDeleteFile), + Checkbox::new("Add List Exclusion") + .checked(app.data.lidarr_data.add_import_list_exclusion) + .highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion), + ]; + let confirmation_prompt = ConfirmationPrompt::new() + .title("Delete Album") + .prompt(&prompt) + .checkboxes(checkboxes) + .yes_no_highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumConfirmPrompt) + .yes_no_value(app.data.lidarr_data.prompt_confirm); + + f.render_widget( + Popup::new(confirmation_prompt).size(Size::MediumPrompt), + f.area(), + ); + } + } +} diff --git a/src/ui/lidarr_ui/library/delete_album_ui_tests.rs b/src/ui/lidarr_ui/library/delete_album_ui_tests.rs new file mode 100644 index 0000000..81adf0f --- /dev/null +++ b/src/ui/lidarr_ui/library/delete_album_ui_tests.rs @@ -0,0 +1,43 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::app::App; + use crate::models::BlockSelectionState; + use crate::models::servarr_data::lidarr::lidarr_data::{ + ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, DELETE_ALBUM_SELECTION_BLOCKS, + }; + use crate::ui::DrawUi; + use crate::ui::lidarr_ui::library::delete_album_ui::DeleteAlbumUi; + use crate::ui::ui_test_utils::test_utils::render_to_string_with_app; + + #[test] + fn test_delete_album_ui_accepts() { + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { + if DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block) { + assert!(DeleteAlbumUi::accepts(active_lidarr_block.into())); + } else { + assert!(!DeleteAlbumUi::accepts(active_lidarr_block.into())); + } + }); + } + + mod snapshot_tests { + use crate::ui::ui_test_utils::test_utils::TerminalSize; + + use super::*; + + #[test] + fn test_delete_album_ui_renders_delete_album() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into()); + app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + DeleteAlbumUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + } +} diff --git a/src/ui/lidarr_ui/library/delete_artist_ui.rs b/src/ui/lidarr_ui/library/delete_artist_ui.rs index 3dd690e..a271844 100644 --- a/src/ui/lidarr_ui/library/delete_artist_ui.rs +++ b/src/ui/lidarr_ui/library/delete_artist_ui.rs @@ -41,7 +41,7 @@ impl DrawUi for DeleteArtistUi { ); let checkboxes = vec![ Checkbox::new("Delete Artist Files") - .checked(app.data.lidarr_data.delete_artist_files) + .checked(app.data.lidarr_data.delete_files) .highlighted(selected_block == ActiveLidarrBlock::DeleteArtistToggleDeleteFile), Checkbox::new("Add List Exclusion") .checked(app.data.lidarr_data.add_import_list_exclusion) diff --git a/src/ui/lidarr_ui/library/library_ui_tests.rs b/src/ui/lidarr_ui/library/library_ui_tests.rs index 85dea67..fb8854a 100644 --- a/src/ui/lidarr_ui/library/library_ui_tests.rs +++ b/src/ui/lidarr_ui/library/library_ui_tests.rs @@ -4,8 +4,8 @@ mod tests { use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus}; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, - EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS, + ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, + DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS, }; use crate::ui::DrawUi; use crate::ui::lidarr_ui::library::{LibraryUi, decorate_artist_row_with_style}; @@ -18,6 +18,7 @@ mod tests { let mut library_ui_blocks = Vec::new(); library_ui_blocks.extend(LIBRARY_BLOCKS); library_ui_blocks.extend(DELETE_ARTIST_BLOCKS); + library_ui_blocks.extend(DELETE_ALBUM_BLOCKS); library_ui_blocks.extend(EDIT_ARTIST_BLOCKS); library_ui_blocks.extend(ADD_ARTIST_BLOCKS); library_ui_blocks.extend(ARTIST_DETAILS_BLOCKS); diff --git a/src/ui/lidarr_ui/library/mod.rs b/src/ui/lidarr_ui/library/mod.rs index 997b868..dcce7c5 100644 --- a/src/ui/lidarr_ui/library/mod.rs +++ b/src/ui/lidarr_ui/library/mod.rs @@ -33,6 +33,7 @@ mod artist_details_ui; mod delete_artist_ui; mod edit_artist_ui; +mod delete_album_ui; #[cfg(test)] #[path = "library_ui_tests.rs"] mod library_ui_tests; @@ -59,8 +60,8 @@ impl DrawUi for LibraryUi { match route { _ if AddArtistUi::accepts(route) => AddArtistUi::draw(f, app, area), _ if DeleteArtistUi::accepts(route) => DeleteArtistUi::draw(f, app, area), - _ if EditArtistUi::accepts(route) => EditArtistUi::draw(f, app, area), _ if ArtistDetailsUi::accepts(route) => ArtistDetailsUi::draw(f, app, area), + _ if EditArtistUi::accepts(route) => EditArtistUi::draw(f, app, area), Route::Lidarr(ActiveLidarrBlock::UpdateAllArtistsPrompt, _) => { let confirmation_prompt = ConfirmationPrompt::new() .title("Update All Artists") diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__artist_details_ui_renders_delete_album_prompt_over_artist_details.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__artist_details_ui_renders_delete_album_prompt_over_artist_details.snap new file mode 100644 index 0000000..0f32060 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__artist_details_ui_renders_delete_album_prompt_over_artist_details.snap @@ -0,0 +1,52 @@ +--- +source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs +expression: output +--- + + + + ╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │Artist: Alex │ + │Overview: some interesting description of the artist │ + │Disambiguation: American pianist │ + │Type: Person │ + │Status: Continuing │ + │Genres: soundtrack │ + │Rating: 84% │ + │Path: /nfs/music/test-artist │ + │Quality Profile: Lossless │ + │Metadata Profile: Standard │ + │Monitored: Yes │ + │Albums: 1 │ + │Tracks: 15/15 ╭───────────────────── Delete Album ──────────────────────╮ │ + │Size on Disk: 0.00 GB │ Do you really want to delete the album: │ │ + │ │ Test Album? │ │ + │ │ │ │ + │╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│ + ││ Albums │ ╭───╮ │ ││ + ││─────────────────────────────────────────│ Delete Album Files: │ ✔ │ │───────────────────────────────────────────││ + ││ Monitored Title │ ╰───╯ │ Release Date Size ││ + ││=> 🏷 Test Album │ ╭───╮ │ 2023-01-01 0.00 GB ││ + ││ │ Add List Exclusion: │ │ │ ││ + ││ │ ╰───╯ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │╭────────────────────────────╮╭───────────────────────────╮│ ││ + ││ ││ Yes ││ No ││ ││ + ││ │╰────────────────────────────╯╰───────────────────────────╯│ ││ + ││ ╰───────────────────────────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_album_ui__delete_album_ui_tests__tests__snapshot_tests__delete_album_ui_renders_delete_album.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_album_ui__delete_album_ui_tests__tests__snapshot_tests__delete_album_ui_renders_delete_album.snap new file mode 100644 index 0000000..d7f91f4 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_album_ui__delete_album_ui_tests__tests__snapshot_tests__delete_album_ui_renders_delete_album.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/lidarr_ui/library/delete_album_ui_tests.rs +expression: output +--- + + + + + + + + + + + + + + + + + ╭───────────────────── Delete Album ──────────────────────╮ + │ Do you really want to delete the album: │ + │ Test Album? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Album Files: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯