diff --git a/src/app/lidarr/lidarr_context_clues.rs b/src/app/lidarr/lidarr_context_clues.rs index 4c631f6..f980b6a 100644 --- a/src/app/lidarr/lidarr_context_clues.rs +++ b/src/app/lidarr/lidarr_context_clues.rs @@ -92,15 +92,11 @@ pub static MANUAL_ARTIST_SEARCH_CONTEXT_CLUES: [ContextClue; 7] = [ (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), ]; -pub static ALBUM_DETAILS_CONTEXT_CLUES: [ContextClue; 7] = [ +pub static ALBUM_DETAILS_CONTEXT_CLUES: [ContextClue; 6] = [ ( DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh.desc, ), - ( - DEFAULT_KEYBINDINGS.toggle_monitoring, - DEFAULT_KEYBINDINGS.toggle_monitoring.desc, - ), (DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc), ( DEFAULT_KEYBINDINGS.auto_search, diff --git a/src/app/lidarr/lidarr_context_clues_tests.rs b/src/app/lidarr/lidarr_context_clues_tests.rs index 313f053..5a0f1e6 100644 --- a/src/app/lidarr/lidarr_context_clues_tests.rs +++ b/src/app/lidarr/lidarr_context_clues_tests.rs @@ -249,13 +249,6 @@ mod tests { DEFAULT_KEYBINDINGS.refresh.desc ) ); - assert_some_eq_x!( - album_details_context_clues_iter.next(), - &( - DEFAULT_KEYBINDINGS.toggle_monitoring, - DEFAULT_KEYBINDINGS.toggle_monitoring.desc - ) - ); assert_some_eq_x!( album_details_context_clues_iter.next(), &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc) diff --git a/src/app/lidarr/lidarr_tests.rs b/src/app/lidarr/lidarr_tests.rs index 7be1432..b595595 100644 --- a/src/app/lidarr/lidarr_tests.rs +++ b/src/app/lidarr/lidarr_tests.rs @@ -9,6 +9,7 @@ mod tests { use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist; use pretty_assertions::{assert_eq, assert_str_eq}; use tokio::sync::mpsc; + use crate::models::servarr_data::lidarr::modals::AlbumDetailsModal; #[tokio::test] async fn test_dispatch_by_lidarr_block_artists() { @@ -156,6 +157,117 @@ mod tests { assert_eq!(app.tick_count, 0); } + #[tokio::test] + async fn test_dispatch_by_album_history_block() { + let (tx, mut rx) = mpsc::channel::(500); + let mut app = App::test_default(); + app.data.lidarr_data.prompt_confirm = true; + app.network_tx = Some(tx); + app.data.lidarr_data.artists.set_items(vec![Artist { + id: 1, + ..Artist::default() + }]); + app.data.lidarr_data.albums.set_items(vec![Album { + id: 1, + ..Album::default() + }]); + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::AlbumHistory) + .await; + + assert!(app.is_loading); + assert_eq!( + rx.recv().await.unwrap(), + LidarrEvent::GetAlbumHistory(1, 1).into() + ); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_album_history_block_no_op_when_albums_table_is_empty() { + let (tx, _) = mpsc::channel::(500); + let mut app = App::test_default(); + app.data.lidarr_data.prompt_confirm = true; + app.network_tx = Some(tx); + app.data.lidarr_data.artists.set_items(vec![Artist { + id: 1, + ..Artist::default() + }]); + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::AlbumHistory) + .await; + + assert!(!app.is_loading); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_album_search_block() { + let (tx, mut rx) = mpsc::channel::(500); + let mut app = App::test_default(); + app.data.lidarr_data.prompt_confirm = true; + app.network_tx = Some(tx); + app.data.lidarr_data.artists.set_items(vec![Artist { + id: 1, + ..Artist::default() + }]); + app.data.lidarr_data.albums.set_items(vec![Album { + id: 1, + ..Album::default() + }]); + app.data.lidarr_data.album_details_modal = Some(AlbumDetailsModal::default()); + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch) + .await; + + assert!(app.is_loading); + assert_eq!( + rx.recv().await.unwrap(), + LidarrEvent::GetAlbumReleases(1, 1).into() + ); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_album_search_block_is_loading() { + let mut app = App { + is_loading: true, + ..App::test_default() + }; + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch) + .await; + + assert!(app.is_loading); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_album_search_block_album_releases_non_empty() { + let mut app = App::test_default(); + let mut album_details_modal = AlbumDetailsModal::default(); + album_details_modal + .album_releases + .set_items(vec![LidarrRelease::default()]); + app.data.lidarr_data.album_details_modal = Some(album_details_modal); + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch) + .await; + + assert!(!app.is_loading); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + #[tokio::test] async fn test_dispatch_by_downloads_block() { let (tx, mut rx) = mpsc::channel::(500); diff --git a/src/app/lidarr/mod.rs b/src/app/lidarr/mod.rs index dc4b1ea..ef7f57b 100644 --- a/src/app/lidarr/mod.rs +++ b/src/app/lidarr/mod.rs @@ -2,7 +2,6 @@ use crate::{ models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock, network::lidarr_network::LidarrEvent, }; - use super::App; pub mod lidarr_context_clues; @@ -67,6 +66,29 @@ impl App<'_> { .dispatch_network_event(LidarrEvent::GetDownloads(500).into()) .await; } + ActiveLidarrBlock::AlbumHistory => { + if !self.data.lidarr_data.albums.is_empty() { + self + .dispatch_network_event( + LidarrEvent::GetAlbumHistory(self.extract_artist_id().await, self.extract_album_id().await) + .into(), + ) + .await; + } + } + ActiveLidarrBlock::ManualAlbumSearch => { + match self.data.lidarr_data.album_details_modal.as_ref() { + Some(album_details_modal) if album_details_modal.album_releases.is_empty() => { + self + .dispatch_network_event( + LidarrEvent::GetAlbumReleases(self.extract_artist_id().await, self.extract_album_id().await) + .into(), + ) + .await; + } + _ => (), + } + } ActiveLidarrBlock::AddArtistSearchResults => { self .dispatch_network_event( diff --git a/src/handlers/lidarr_handlers/library/album_details_handler.rs b/src/handlers/lidarr_handlers/library/album_details_handler.rs new file mode 100644 index 0000000..34e96f8 --- /dev/null +++ b/src/handlers/lidarr_handlers/library/album_details_handler.rs @@ -0,0 +1,476 @@ +use crate::app::App; +use crate::event::Key; +use crate::handlers::lidarr_handlers::history::history_sorting_options; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; +use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; +use crate::matches_key; +use crate::models::Route; +use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, ALBUM_DETAILS_BLOCKS}; +use crate::models::lidarr_models::{ + Track, LidarrHistoryItem, LidarrRelease, LidarrReleaseDownloadBody, +}; +use crate::models::stateful_table::SortOption; +use crate::network::lidarr_network::LidarrEvent; +use serde_json::Number; + +#[cfg(test)] +#[path = "album_details_handler_tests.rs"] +mod album_details_handler_tests; + +pub(in crate::handlers::lidarr_handlers) struct AlbumDetailsHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_lidarr_block: ActiveLidarrBlock, + _context: Option, +} + +impl AlbumDetailsHandler<'_, '_> { + fn extract_track_file_id(&self) -> i64 { + self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .expect("Album details have not been loaded") + .tracks + .current_selection() + .track_file_id + } + + fn extract_album_id(&self) -> i64 { + self + .app + .data + .lidarr_data + .albums + .current_selection() + .id + } +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AlbumDetailsHandler<'a, 'b> { + fn handle(&mut self) { + let tracks_table_handling_config = + TableHandlingConfig::new(ActiveLidarrBlock::AlbumDetails.into()) + .searching_block(ActiveLidarrBlock::SearchTracks.into()) + .search_error_block(ActiveLidarrBlock::SearchTracksError.into()) + .search_field_fn(|track: &Track| &track.title); + let album_history_table_handling_config = + TableHandlingConfig::new(ActiveLidarrBlock::AlbumHistory.into()) + .sorting_block(ActiveLidarrBlock::AlbumHistorySortPrompt.into()) + .sort_options(history_sorting_options()) + .searching_block(ActiveLidarrBlock::SearchAlbumHistory.into()) + .search_error_block(ActiveLidarrBlock::SearchAlbumHistoryError.into()) + .search_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text) + .filtering_block(ActiveLidarrBlock::FilterAlbumHistory.into()) + .filter_error_block(ActiveLidarrBlock::FilterAlbumHistoryError.into()) + .filter_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text); + let album_releases_table_handling_config = + TableHandlingConfig::new(ActiveLidarrBlock::ManualAlbumSearch.into()) + .sorting_block(ActiveLidarrBlock::ManualAlbumSearchSortPrompt.into()) + .sort_options(releases_sorting_options()); + + if !handle_table( + self, + |app| { + &mut app + .data + .lidarr_data + .album_details_modal + .as_mut() + .expect("Album details modal is undefined") + .tracks + }, + tracks_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .lidarr_data + .album_details_modal + .as_mut() + .expect("Album details modal is undefined") + .album_history + }, + album_history_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .lidarr_data + .album_details_modal + .as_mut() + .expect("Album details modal is undefined") + .album_releases + }, + album_releases_table_handling_config, + ) { + self.handle_key_event(); + } + } + + fn accepts(active_block: ActiveLidarrBlock) -> bool { + ALBUM_DETAILS_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, + ) -> AlbumDetailsHandler<'a, 'b> { + AlbumDetailsHandler { + key, + app, + active_lidarr_block: active_block, + _context: context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + if self.app.is_loading { + return false; + } + + let Some(album_details_modal) = &self.app.data.lidarr_data.album_details_modal else { + return false; + }; + + match self.active_lidarr_block { + ActiveLidarrBlock::AlbumDetails => !album_details_modal.tracks.is_empty(), + ActiveLidarrBlock::AlbumHistory => !album_details_modal.album_history.is_empty(), + ActiveLidarrBlock::ManualAlbumSearch => !album_details_modal.album_releases.is_empty(), + _ => true, + } + } + + fn handle_scroll_up(&mut self) {} + + fn handle_scroll_down(&mut self) {} + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) { + if self.active_lidarr_block == ActiveLidarrBlock::AlbumDetails { + self + .app + .push_navigation_stack(ActiveLidarrBlock::DeleteTrackFilePrompt.into()); + } + } + + fn handle_left_right_action(&mut self) { + match self.active_lidarr_block { + ActiveLidarrBlock::AlbumDetails + | ActiveLidarrBlock::AlbumHistory + | ActiveLidarrBlock::ManualAlbumSearch => match self.key { + _ if matches_key!(left, self.key) => { + self + .app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_details_tabs + .previous(); + self.app.pop_and_push_navigation_stack( + self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_details_tabs + .get_active_route(), + ); + } + _ if matches_key!(right, self.key) => { + self + .app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_details_tabs + .next(); + self.app.pop_and_push_navigation_stack( + self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_details_tabs + .get_active_route(), + ); + } + _ => (), + }, + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt + | ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt + | ActiveLidarrBlock::DeleteTrackFilePrompt => { + handle_prompt_toggle(self.app, self.key); + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_lidarr_block { + // ActiveLidarrBlock::AlbumDetails + // if self.app.data.lidarr_data.album_details_modal.is_some() + // && !self + // .app + // .data + // .lidarr_data + // .album_details_modal + // .as_ref() + // .unwrap() + // .tracks + // .is_empty() => + // { + // self + // .app + // .push_navigation_stack(ActiveLidarrBlock::TrackDetails.into()) + // } + ActiveLidarrBlock::AlbumHistory => self + .app + .push_navigation_stack(ActiveLidarrBlock::AlbumHistoryDetails.into()), + ActiveLidarrBlock::DeleteTrackFilePrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteTrackFile( + self.extract_track_file_id(), + )); + } + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = Some( + LidarrEvent::TriggerAutomaticAlbumSearch(self.extract_album_id()), + ); + } + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::ManualAlbumSearch => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt.into()); + } + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + let LidarrRelease { + guid, indexer_id, .. + } = self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_releases + .current_selection(); + let params = LidarrReleaseDownloadBody { + guid: guid.clone(), + indexer_id: *indexer_id, + }; + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::DownloadRelease(params)); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_lidarr_block { + ActiveLidarrBlock::AlbumDetails | ActiveLidarrBlock::ManualAlbumSearch => { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.album_details_modal = None; + } + ActiveLidarrBlock::AlbumHistoryDetails => { + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::AlbumHistory => { + if self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_history + .filtered_items + .is_some() + { + self + .app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_history + .filtered_items = None; + } else { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.album_details_modal = None; + } + } + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt + | ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt + | ActiveLidarrBlock::DeleteTrackFilePrompt => { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.prompt_confirm = false; + } + _ => (), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_lidarr_block { + ActiveLidarrBlock::AlbumDetails + | ActiveLidarrBlock::AlbumHistory + | ActiveLidarrBlock::ManualAlbumSearch => match self.key { + _ if matches_key!(refresh, self.key) => { + self + .app + .pop_and_push_navigation_stack(self.active_lidarr_block.into()); + } + _ if matches_key!(auto_search, self.key) => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchAlbumPrompt.into()); + } + _ => (), + }, + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt if matches_key!(confirm, key) => { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = Some( + LidarrEvent::TriggerAutomaticAlbumSearch(self.extract_album_id()), + ); + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::DeleteTrackFilePrompt if matches_key!(confirm, key) => { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteTrackFile( + self.extract_track_file_id(), + )); + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt if matches_key!(confirm, key) => { + self.app.data.lidarr_data.prompt_confirm = true; + let LidarrRelease { + guid, indexer_id, .. + } = self + .app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_releases + .current_selection(); + let params = LidarrReleaseDownloadBody { + guid: guid.clone(), + indexer_id: *indexer_id, + }; + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::DownloadRelease(params)); + + 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() + } +} + +pub(in crate::handlers::lidarr_handlers::library) fn releases_sorting_options() + -> Vec> { + vec![ + SortOption { + name: "Source", + cmp_fn: Some(|a, b| a.protocol.cmp(&b.protocol)), + }, + SortOption { + name: "Age", + cmp_fn: Some(|a, b| a.age.cmp(&b.age)), + }, + SortOption { + name: "Rejected", + cmp_fn: Some(|a, b| a.rejected.cmp(&b.rejected)), + }, + SortOption { + name: "Title", + cmp_fn: Some(|a, b| { + a.title + .text + .to_lowercase() + .cmp(&b.title.text.to_lowercase()) + }), + }, + SortOption { + name: "Indexer", + cmp_fn: Some(|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase())), + }, + SortOption { + name: "Size", + cmp_fn: Some(|a, b| a.size.cmp(&b.size)), + }, + SortOption { + name: "Peers", + cmp_fn: Some(|a, b| { + let default_number = Number::from(i64::MAX); + let seeder_a = a + .seeders + .as_ref() + .unwrap_or(&default_number) + .as_u64() + .unwrap(); + let seeder_b = b + .seeders + .as_ref() + .unwrap_or(&default_number) + .as_u64() + .unwrap(); + + seeder_a.cmp(&seeder_b) + }), + }, + SortOption { + name: "Quality", + cmp_fn: Some(|a, b| a.quality.cmp(&b.quality)), + }, + ] +} diff --git a/src/handlers/lidarr_handlers/library/album_details_handler_tests.rs b/src/handlers/lidarr_handlers/library/album_details_handler_tests.rs new file mode 100644 index 0000000..3d1b8ca --- /dev/null +++ b/src/handlers/lidarr_handlers/library/album_details_handler_tests.rs @@ -0,0 +1,1034 @@ +#[cfg(test)] +mod tests { + use crate::app::App; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::assert_modal_absent; + use crate::assert_navigation_pushed; + use crate::handlers::KeyEventHandler; + use crate::handlers::lidarr_handlers::library::album_details_handler::{ + AlbumDetailsHandler, releases_sorting_options, + }; + use crate::models::HorizontallyScrollableText; + use crate::models::servarr_data::lidarr::modals::AlbumDetailsModal; + use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, ALBUM_DETAILS_BLOCKS}; + use crate::models::servarr_models::{Quality, QualityWrapper}; + use crate::models::lidarr_models::{LidarrRelease, LidarrReleaseDownloadBody}; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use serde_json::Number; + use std::cmp::Ordering; + use strum::IntoEnumIterator; + + mod test_handle_delete { + use super::*; + use crate::event::Key; + use pretty_assertions::assert_eq; + + const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key; + + #[test] + fn test_delete_track_prompt() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + + AlbumDetailsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::AlbumDetails, None) + .handle(); + + assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteTrackFilePrompt.into()); + } + + #[test] + fn test_delete_track_prompt_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + app.is_loading = true; + + AlbumDetailsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::AlbumDetails, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::AlbumDetails.into() + ); + } + } + + mod test_handle_left_right_actions { + use super::*; + use crate::event::Key; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + fn test_left_right_prompt_toggle( + #[values( + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt, + ActiveLidarrBlock::DeleteTrackFilePrompt + )] + active_lidarr_block: ActiveLidarrBlock, + #[values(Key::Left, Key::Right)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + + AlbumDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + + AlbumDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + } + + #[rstest] + #[case(ActiveLidarrBlock::AlbumDetails, ActiveLidarrBlock::AlbumHistory)] + #[case( + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + #[case( + ActiveLidarrBlock::ManualAlbumSearch, + ActiveLidarrBlock::AlbumDetails + )] + fn test_album_details_tabs_left_right_action( + #[case] left_block: ActiveLidarrBlock, + #[case] right_block: ActiveLidarrBlock, + #[values(true, false)] is_ready: bool, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.is_loading = is_ready; + app.push_navigation_stack(right_block.into()); + app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_details_tabs + .index = app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_details_tabs + .tabs + .iter() + .position(|tab_route| tab_route.route == right_block.into()) + .unwrap_or_default(); + + AlbumDetailsHandler::new(DEFAULT_KEYBINDINGS.left.key, &mut app, right_block, None).handle(); + + assert_eq!( + app.get_current_route(), + app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_details_tabs + .get_active_route() + ); + assert_navigation_pushed!(app, left_block.into()); + + AlbumDetailsHandler::new(DEFAULT_KEYBINDINGS.right.key, &mut app, left_block, None).handle(); + + assert_eq!( + app.get_current_route(), + app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_details_tabs + .get_active_route() + ); + assert_navigation_pushed!(app, right_block.into()); + } + } + + mod test_handle_submit { + use super::*; + use crate::assert_navigation_popped; + use crate::event::Key; + use crate::models::stateful_table::StatefulTable; + use crate::network::lidarr_network::LidarrEvent; + use pretty_assertions::assert_eq; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + // #[test] + // fn test_album_details_submit() { + // let mut app = App::test_default_fully_populated(); + // app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + // + // AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumDetails, None) + // .handle(); + // + // assert_navigation_pushed!(app, ActiveLidarrBlock::TrackDetails.into()); + // } + + // #[test] + // fn test_album_details_submit_no_op_on_empty_tracks_table() { + // let mut app = App::test_default_fully_populated(); + // app + // .data + // .lidarr_data + // .album_details_modal + // .as_mut() + // .unwrap() + // .tracks = StatefulTable::default(); + // app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + // + // AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumDetails, None) + // .handle(); + // + // assert_eq!( + // app.get_current_route(), + // ActiveLidarrBlock::AlbumDetails.into() + // ); + // } + + #[test] + fn test_album_details_submit_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumDetails, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::AlbumDetails.into() + ); + } + + #[test] + fn test_album_history_submit() { + let mut app = App::test_default_fully_populated(); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumHistory, None) + .handle(); + + assert_navigation_pushed!(app, ActiveLidarrBlock::AlbumHistoryDetails.into()); + } + + #[test] + fn test_album_history_submit_no_op_when_album_history_is_empty() { + let mut app = App::test_default_fully_populated(); + app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_history = StatefulTable::default(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistory.into()); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumHistory, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::AlbumHistory.into() + ); + } + + #[test] + fn test_album_history_submit_no_op_when_not_ready() { + let mut app = App::test_default_fully_populated(); + app.is_loading = true; + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistory.into()); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::AlbumHistory, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::AlbumHistory.into() + ); + } + + #[rstest] + #[case( + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + LidarrEvent::TriggerAutomaticAlbumSearch(1) + )] + #[case( + ActiveLidarrBlock::DeleteTrackFilePrompt, + LidarrEvent::DeleteTrackFile(1) + )] + fn test_album_details_prompt_confirm_submit( + #[case] prompt_block: ActiveLidarrBlock, + #[case] expected_action: LidarrEvent, + #[values(ActiveLidarrBlock::AlbumDetails, ActiveLidarrBlock::AlbumHistory)] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(active_lidarr_block.into()); + app.push_navigation_stack(prompt_block.into()); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, active_lidarr_block.into()); + assert_some_eq_x!( + &app.data.lidarr_data.prompt_confirm_action, + &expected_action + ); + } + + #[test] + fn test_album_details_manual_search_confirm_prompt_confirm_submit() { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearch.into()); + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt.into()); + + AlbumDetailsHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::ManualAlbumSearch.into()); + assert_some_eq_x!( + &app.data.lidarr_data.prompt_confirm_action, + &LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + }) + ); + } + + #[rstest] + fn test_album_details_prompt_decline_submit( + #[values( + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + ActiveLidarrBlock::DeleteTrackFilePrompt, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt + )] + prompt_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + AlbumDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::AlbumDetails.into()); + assert_none!(app.data.lidarr_data.prompt_confirm_action); + } + + #[test] + fn test_manual_album_search_submit() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearch.into()); + + AlbumDetailsHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::ManualAlbumSearch, + None, + ) + .handle(); + + assert_navigation_pushed!( + app, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt.into() + ); + } + + #[test] + fn test_manual_album_search_submit_no_op_when_not_ready() { + let mut app = App::test_default_fully_populated(); + app.is_loading = true; + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearch.into()); + + AlbumDetailsHandler::new( + SUBMIT_KEY, + &mut app, + ActiveLidarrBlock::ManualAlbumSearch, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::ManualAlbumSearch.into() + ); + } + } + + mod test_handle_esc { + use super::*; + use crate::assert_navigation_popped; + use crate::event::Key; + use crate::models::lidarr_models::LidarrHistoryItem; + use crate::models::stateful_table::StatefulTable; + use pretty_assertions::assert_eq; + use ratatui::widgets::TableState; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[test] + fn test_album_history_details_block_esc() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistory.into()); + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistoryDetails.into()); + + AlbumDetailsHandler::new( + ESC_KEY, + &mut app, + ActiveLidarrBlock::AlbumHistoryDetails, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::AlbumHistory.into()); + } + + #[rstest] + fn test_album_details_prompts_esc( + #[values( + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + ActiveLidarrBlock::DeleteTrackFilePrompt, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt + )] + prompt_block: ActiveLidarrBlock, + #[values(true, false)] is_ready: bool, + ) { + let mut app = App::test_default_fully_populated(); + app.is_loading = is_ready; + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + AlbumDetailsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::AlbumDetails.into()); + } + + #[test] + fn test_album_history_esc_resets_filter_if_one_is_set_instead_of_closing_the_window() { + let mut app = App::test_default_fully_populated(); + let mut album_history = StatefulTable { + filter: Some("Test".into()), + filtered_items: Some(vec![LidarrHistoryItem::default()]), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; + album_history.set_items(vec![LidarrHistoryItem::default()]); + app + .data + .lidarr_data + .album_details_modal + .as_mut() + .unwrap() + .album_history = album_history; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistory.into()); + + AlbumDetailsHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::AlbumHistory, None).handle(); + + assert_eq!( + app.get_current_route(), + ActiveLidarrBlock::AlbumHistory.into() + ); + assert_none!( + app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_history + .filter + ); + assert_none!( + app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_history + .filtered_items + ); + assert_none!( + app + .data + .lidarr_data + .album_details_modal + .as_ref() + .unwrap() + .album_history + .filtered_state + ); + } + + #[rstest] + fn test_album_details_tabs_esc( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + AlbumDetailsHandler::new(ESC_KEY, &mut app, active_lidarr_block, None).handle(); + + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_modal_absent!(app.data.lidarr_data.album_details_modal); + } + } + + mod test_handle_key_char { + use super::*; + use crate::assert_navigation_popped; + use crate::network::lidarr_network::LidarrEvent; + use pretty_assertions::assert_eq; + + #[rstest] + fn test_auto_search_key( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.auto_search.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!( + app, + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt.into() + ); + } + + #[rstest] + fn test_auto_search_key_no_op_when_not_ready( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(active_lidarr_block.into()); + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.auto_search.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_lidarr_block.into()); + } + + #[rstest] + fn test_refresh_key( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + app.is_routing = false; + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!(app, active_lidarr_block.into()); + assert!(app.is_routing); + } + + #[rstest] + fn test_refresh_key_no_op_when_not_ready( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.is_loading = true; + app.push_navigation_stack(active_lidarr_block.into()); + app.is_routing = false; + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_lidarr_block.into()); + assert!(!app.is_routing); + } + + #[rstest] + #[case( + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + LidarrEvent::TriggerAutomaticAlbumSearch(1) + )] + #[case( + ActiveLidarrBlock::DeleteTrackFilePrompt, + LidarrEvent::DeleteTrackFile(1) + )] + fn test_album_details_prompt_confirm_confirm_key( + #[case] prompt_block: ActiveLidarrBlock, + #[case] expected_action: LidarrEvent, + #[values(ActiveLidarrBlock::AlbumDetails, ActiveLidarrBlock::AlbumHistory)] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(active_lidarr_block.into()); + app.push_navigation_stack(prompt_block.into()); + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + prompt_block, + None, + ) + .handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, active_lidarr_block.into()); + assert_some_eq_x!( + &app.data.lidarr_data.prompt_confirm_action, + &expected_action + ); + } + + #[test] + fn test_album_details_manual_search_confirm_prompt_confirm_confirm_key() { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearch.into()); + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt.into()); + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::ManualAlbumSearch.into()); + assert_some_eq_x!( + &app.data.lidarr_data.prompt_confirm_action, + &LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + }) + ); + } + } + + #[test] + fn test_album_details_handler_accepts() { + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { + if ALBUM_DETAILS_BLOCKS.contains(&active_lidarr_block) { + assert!(AlbumDetailsHandler::accepts(active_lidarr_block)); + } else { + assert!(!AlbumDetailsHandler::accepts(active_lidarr_block)); + } + }); + } + + #[rstest] + fn test_album_details_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 = AlbumDetailsHandler::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_extract_track_file_id() { + let mut app = App::test_default_fully_populated(); + + let track_file_id = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ) + .extract_track_file_id(); + + assert_eq!(track_file_id, 1); + } + + #[test] + #[should_panic(expected = "Album details have not been loaded")] + fn test_extract_track_file_id_empty_album_details_modal_panics() { + let mut app = App::test_default(); + + AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ) + .extract_track_file_id(); + } + + #[test] + fn test_extract_album_id() { + let mut app = App::test_default_fully_populated(); + + let track_file_id = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ) + .extract_album_id(); + + assert_eq!(track_file_id, 1); + } + + #[test] + fn test_album_details_handler_is_not_ready_when_loading() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + app.is_loading = true; + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_album_details_handler_is_not_ready_when_not_loading_and_album_details_is_none() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_album_details_handler_is_not_ready_when_not_loading_and_tracks_table_is_empty() { + let mut app = App::test_default(); + app.data.lidarr_data.album_details_modal = Some(AlbumDetailsModal::default()); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumDetails, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_album_details_handler_is_not_ready_when_not_loading_and_history_table_is_empty() { + let mut app = App::test_default(); + app.data.lidarr_data.album_details_modal = Some(AlbumDetailsModal::default()); + app.push_navigation_stack(ActiveLidarrBlock::AlbumHistory.into()); + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::AlbumHistory, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_album_details_handler_is_not_ready_when_not_loading_and_releases_table_is_empty() { + let mut app = App::test_default(); + app.data.lidarr_data.album_details_modal = Some(AlbumDetailsModal::default()); + app.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearch.into()); + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::ManualAlbumSearch, + None, + ); + + assert!(!handler.is_ready()); + } + + #[rstest] + fn test_album_details_handler_is_ready_when_not_loading_and_album_details_modal_is_populated( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::ManualAlbumSearch + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + let handler = AlbumDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + active_lidarr_block, + None, + ); + + assert!(handler.is_ready()); + } + + #[test] + fn test_releases_sorting_options_source() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = + |a, b| a.protocol.cmp(&b.protocol); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[0].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Source"); + } + + #[test] + fn test_releases_sorting_options_age() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| a.age.cmp(&b.age); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[1].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Age"); + } + + #[test] + fn test_releases_sorting_options_rejected() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = + |a, b| a.rejected.cmp(&b.rejected); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[2].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Rejected"); + } + + #[test] + fn test_releases_sorting_options_title() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| { + a.title + .text + .to_lowercase() + .cmp(&b.title.text.to_lowercase()) + }; + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[3].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Title"); + } + + #[test] + fn test_releases_sorting_options_indexer() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = + |a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase()); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[4].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Indexer"); + } + + #[test] + fn test_releases_sorting_options_size() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = + |a, b| a.size.cmp(&b.size); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[5].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Size"); + } + + #[test] + fn test_releases_sorting_options_peers() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| { + let default_number = Number::from(i64::MAX); + let seeder_a = a + .seeders + .as_ref() + .unwrap_or(&default_number) + .as_u64() + .unwrap(); + let seeder_b = b + .seeders + .as_ref() + .unwrap_or(&default_number) + .as_u64() + .unwrap(); + + seeder_a.cmp(&seeder_b) + }; + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[6].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Peers"); + } + + #[test] + fn test_releases_sorting_options_quality() { + let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = + |a, b| a.quality.cmp(&b.quality); + let mut expected_releases_vec = release_vec(); + expected_releases_vec.sort_by(expected_cmp_fn); + + let sort_option = releases_sorting_options()[7].clone(); + let mut sorted_releases_vec = release_vec(); + sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap()); + + assert_eq!(sorted_releases_vec, expected_releases_vec); + assert_str_eq!(sort_option.name, "Quality"); + } + + fn release_vec() -> Vec { + let release_a = LidarrRelease { + protocol: "Protocol A".to_owned(), + age: 1, + title: HorizontallyScrollableText::from("Title A"), + indexer: "Indexer A".to_owned(), + size: 1, + rejected: true, + seeders: Some(Number::from(1)), + quality: QualityWrapper { + quality: Quality { + name: "Quality A".to_owned(), + }, + }, + ..LidarrRelease::default() + }; + let release_b = LidarrRelease { + protocol: "Protocol B".to_owned(), + age: 2, + title: HorizontallyScrollableText::from("title B"), + indexer: "indexer B".to_owned(), + size: 2, + rejected: false, + seeders: Some(Number::from(2)), + quality: QualityWrapper { + quality: Quality { + name: "Quality B".to_owned(), + }, + }, + ..LidarrRelease::default() + }; + let release_c = LidarrRelease { + protocol: "Protocol C".to_owned(), + age: 3, + title: HorizontallyScrollableText::from("Title C"), + indexer: "Indexer C".to_owned(), + size: 3, + rejected: false, + seeders: None, + quality: QualityWrapper { + quality: Quality { + name: "Quality C".to_owned(), + }, + }, + ..LidarrRelease::default() + }; + + vec![release_a, release_b, release_c] + } +} diff --git a/src/handlers/lidarr_handlers/library/artist_details_handler.rs b/src/handlers/lidarr_handlers/library/artist_details_handler.rs index 9d1bdea..37c909b 100644 --- a/src/handlers/lidarr_handlers/library/artist_details_handler.rs +++ b/src/handlers/lidarr_handlers/library/artist_details_handler.rs @@ -16,6 +16,7 @@ use crate::models::stateful_table::SortOption; use crate::models::{BlockSelectionState, Route}; use crate::network::lidarr_network::LidarrEvent; use serde_json::Number; +use crate::handlers::lidarr_handlers::library::album_details_handler::AlbumDetailsHandler; #[cfg(test)] #[path = "artist_details_handler_tests.rs"] @@ -80,13 +81,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler DeleteAlbumHandler::new(self.key, self.app, self.active_lidarr_block, self.context) .handle(); } + _ if AlbumDetailsHandler::accepts(self.active_lidarr_block) => { + AlbumDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context) + .handle(); + } _ => self.handle_key_event(), }; } } fn accepts(active_block: ActiveLidarrBlock) -> bool { - DeleteAlbumHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block) + DeleteAlbumHandler::accepts(active_block) || AlbumDetailsHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block) } fn ignore_special_keys(&self) -> bool { @@ -183,6 +188,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler fn handle_submit(&mut self) { match self.active_lidarr_block { + ActiveLidarrBlock::ArtistDetails if !self.app.data.lidarr_data.albums.is_empty() => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + } ActiveLidarrBlock::ArtistHistory if !self.app.data.lidarr_data.artist_history.is_empty() => { self .app 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 720d1db..b130db0 100644 --- a/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs @@ -14,12 +14,11 @@ mod tests { }; use crate::models::HorizontallyScrollableText; use crate::models::lidarr_models::{Album, LidarrHistoryItem, LidarrRelease}; - use crate::models::servarr_data::lidarr::lidarr_data::{ - ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, - }; + use crate::models::servarr_data::lidarr::lidarr_data::{ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, ALBUM_DETAILS_BLOCKS}; use crate::models::servarr_models::{Quality, QualityWrapper}; + use crate::test_handler_delegation; - mod test_handle_delete { + mod test_handle_delete { use super::*; use crate::assert_delete_prompt; use crate::event::Key; @@ -813,6 +812,7 @@ mod tests { fn test_artist_details_handler_accepts() { let mut artist_details_blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec(); artist_details_blocks.extend(DELETE_ALBUM_BLOCKS); + artist_details_blocks.extend(ALBUM_DETAILS_BLOCKS); ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { if artist_details_blocks.contains(&active_lidarr_block) { @@ -1000,6 +1000,33 @@ mod tests { ); } + #[rstest] + fn test_delegates_album_details_blocks_to_album_details_handler( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::SearchTracks, + ActiveLidarrBlock::SearchTracksError, + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + ActiveLidarrBlock::SearchAlbumHistory, + ActiveLidarrBlock::SearchAlbumHistoryError, + ActiveLidarrBlock::FilterAlbumHistory, + ActiveLidarrBlock::FilterAlbumHistoryError, + ActiveLidarrBlock::AlbumHistorySortPrompt, + ActiveLidarrBlock::AlbumHistoryDetails, + ActiveLidarrBlock::ManualAlbumSearch, + ActiveLidarrBlock::ManualAlbumSearchSortPrompt, + ActiveLidarrBlock::DeleteTrackFilePrompt + )] + active_sonarr_block: ActiveLidarrBlock, + ) { + test_handler_delegation!( + ArtistDetailsHandler, + ActiveLidarrBlock::Artists, + active_sonarr_block + ); + } + #[test] fn test_releases_sorting_options_source() { let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = diff --git a/src/handlers/lidarr_handlers/library/library_handler_tests.rs b/src/handlers/lidarr_handlers/library/library_handler_tests.rs index b88c5a4..89f2bdf 100644 --- a/src/handlers/lidarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/library_handler_tests.rs @@ -12,15 +12,10 @@ mod tests { use crate::handlers::KeyEventHandler; use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options}; 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_ALBUM_BLOCKS, - DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, - }; + use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, ALBUM_DETAILS_BLOCKS}; use crate::models::servarr_data::lidarr::modals::EditArtistModal; use crate::network::lidarr_network::LidarrEvent; - use crate::{ - assert_modal_absent, assert_modal_present, assert_navigation_popped, assert_navigation_pushed, - }; + use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped, assert_navigation_pushed, test_handler_delegation}; #[test] fn test_library_handler_accepts() { @@ -31,6 +26,7 @@ mod tests { library_handler_blocks.extend(DELETE_ALBUM_BLOCKS); library_handler_blocks.extend(EDIT_ARTIST_BLOCKS); library_handler_blocks.extend(ADD_ARTIST_BLOCKS); + library_handler_blocks.extend(ALBUM_DETAILS_BLOCKS); ActiveLidarrBlock::iter().for_each(|lidarr_block| { if library_handler_blocks.contains(&lidarr_block) { @@ -640,6 +636,33 @@ mod tests { assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into()); } + #[rstest] + fn test_delegates_album_details_blocks_to_album_details_handler( + #[values( + ActiveLidarrBlock::AlbumDetails, + ActiveLidarrBlock::AlbumHistory, + ActiveLidarrBlock::SearchTracks, + ActiveLidarrBlock::SearchTracksError, + ActiveLidarrBlock::AutomaticallySearchAlbumPrompt, + ActiveLidarrBlock::SearchAlbumHistory, + ActiveLidarrBlock::SearchAlbumHistoryError, + ActiveLidarrBlock::FilterAlbumHistory, + ActiveLidarrBlock::FilterAlbumHistoryError, + ActiveLidarrBlock::AlbumHistorySortPrompt, + ActiveLidarrBlock::AlbumHistoryDetails, + ActiveLidarrBlock::ManualAlbumSearch, + ActiveLidarrBlock::ManualAlbumSearchSortPrompt, + ActiveLidarrBlock::DeleteTrackFilePrompt + )] + active_sonarr_block: ActiveLidarrBlock, + ) { + test_handler_delegation!( + LibraryHandler, + ActiveLidarrBlock::Artists, + active_sonarr_block + ); + } + #[test] fn test_edit_key() { 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 9a0d23e..68a6c9c 100644 --- a/src/handlers/lidarr_handlers/library/mod.rs +++ b/src/handlers/lidarr_handlers/library/mod.rs @@ -1,22 +1,22 @@ use crate::{ - app::App, - event::Key, - handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}, - matches_key, - models::{ - BlockSelectionState, HorizontallyScrollableText, - lidarr_models::Artist, - servarr_data::lidarr::lidarr_data::{ - ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, - LIBRARY_BLOCKS, - }, - stateful_table::SortOption, - }, - network::lidarr_network::LidarrEvent, + app::App, + event::Key, + handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}, + matches_key, + models::{ + lidarr_models::Artist, servarr_data::lidarr::lidarr_data::{ + ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, + LIBRARY_BLOCKS, + }, + stateful_table::SortOption, + BlockSelectionState, + HorizontallyScrollableText, + }, + network::lidarr_network::LidarrEvent, }; use super::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; +use crate::handlers::table_handler::{handle_table, TableHandlingConfig}; mod add_artist_handler; mod artist_details_handler; @@ -33,6 +33,7 @@ pub(in crate::handlers::lidarr_handlers) use edit_artist_handler::EditArtistHand #[cfg(test)] #[path = "library_handler_tests.rs"] mod library_handler_tests; +mod album_details_handler; pub(super) struct LibraryHandler<'a, 'b> { key: Key, diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab.snap new file mode 100644 index 0000000..c81afcd --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │ +│Storage: ││ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │ +│Root Folders: ││ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │ +│/nfs: 204800.00 GB free ││ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │ +│ ││ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │ +│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │ +╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯ +╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │ +│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │ +│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_error_popup.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_error_popup.snap new file mode 100644 index 0000000..df4e759 --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_error_popup.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Lidarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 │ Key Alt Key Description │━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │ +│Storage: │=> a add │ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │ +│Root Folders: │ m toggle monitoring │ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │ +│/nfs: 204800.00 GB free │ o sort │ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │ +│ │ del delete │ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │ +│ │ s search │ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │ +╰───────────────────────────────────│ f filter │─────────────────╯╰──────────────────╯ +╭ Artists ────────────────────────│ ctrl-r refresh │─────────────────────────────────────╮ +│ Library │ Downloads │ History │ Ro│ u update all │ │ +│───────────────────────────────────│ enter details │─────────────────────────────────────│ +│ Name ▼ Typ│ esc cancel filter │e Monitored Tags │ +│=> Alex Per│ ↑ k scroll up │0 GB 🏷 alex │ +│ │ ↓ j scroll down │ │ +│ │ ← h previous tab │ │ +│ │ → l next tab │ │ +│ │ pgUp ctrl-u page up │ │ +│ │ pgDown ctrl-d page down │ │ +│ │ tab next servarr │ │ +│ │ shift-tab previous servarr │ │ +│ │ q quit │ │ +│ │ ? show/hide keybindings │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_with_error.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_with_error.snap new file mode 100644 index 0000000..7cc82ac --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__lidarr_ui_renders_library_tab_with_error.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Error | to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│Some error │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │ +│Storage: ││ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │ +│Root Folders: ││ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │ +│/nfs: 204800.00 GB free ││ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │ +│ ││ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │ +│ ││ ││ ⠀⠀⠀⠉⠻⠿⢿⡆⡾⠿⠟⠉⠀⠀⠀ │ +╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯ +╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Library │ Downloads │ History │ Root Folders │ Indexers │ System │ +│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │ +│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab.snap new file mode 100644 index 0000000..daf2d86 --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │ +│Storage: ││ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │ +│Root Folders: ││ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │ +│/nfs: 204800.00 GB free ││ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │ +│ ││ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │ +│ ││ ││ ⠀⠀⠀⠘⠻⠿⣿⣿⣿⣿⠿⠟⠋⠀⠀⠀ │ +╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯ +╭ Series ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │ +│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ Title ▼ Year Network Status Rating Type Quality Profile Language Size Monitored Tags │ +│=> Test 2022 HBO Continuin TV-MA Standard Bluray-1080p English 59.51 GB 🏷 │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_error_popup.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_error_popup.snap new file mode 100644 index 0000000..24ea1ef --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_error_popup.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Sonarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 │ Key Alt Key Description │━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │ +│Storage: │=> a add │ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │ +│Root Folders: │ m toggle monitoring │ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │ +│/nfs: 204800.00 GB free │ o sort │ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │ +│ │ del delete │ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │ +│ │ s search │ ││ ⠀⠀⠀⠘⠻⠿⣿⣿⣿⣿⠿⠟⠋⠀⠀⠀ │ +╰───────────────────────────────────│ f filter │─────────────────╯╰──────────────────╯ +╭ Series ─────────────────────────│ ctrl-r refresh │─────────────────────────────────────╮ +│ Library │ Downloads │ Blocklist │ │ u update all │ │ +│───────────────────────────────────│ enter details │─────────────────────────────────────│ +│ Title ▼ │ esc cancel filter │ Monitored Tags │ +│=> Test │ ↑ k scroll up │ GB 🏷 │ +│ │ ↓ j scroll down │ │ +│ │ ← h previous tab │ │ +│ │ → l next tab │ │ +│ │ pgUp ctrl-u page up │ │ +│ │ pgDown ctrl-d page down │ │ +│ │ tab next servarr │ │ +│ │ shift-tab previous servarr │ │ +│ │ q quit │ │ +│ │ ? show/hide keybindings │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_with_error.snap b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_with_error.snap new file mode 100644 index 0000000..730ecd4 --- /dev/null +++ b/src/ui/snapshots/managarr__ui__ui_tests__snapshot_tests__sonarr_ui_renders_library_tab_with_error.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/ui_tests.rs +expression: output +--- +╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Radarr │ Sonarr │ Lidarr to open help│ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Error | to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│Some error │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮ +│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │ +│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │ +│Storage: ││ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │ +│Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │ +│Root Folders: ││ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │ +│/nfs: 204800.00 GB free ││ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │ +│ ││ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │ +│ ││ ││ ⠀⠀⠀⠘⠻⠿⣿⣿⣿⣿⠿⠟⠋⠀⠀⠀ │ +╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯ +╭ Series ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │ +│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ Title ▼ Year Network Status Rating Type Quality Profile Language Size Monitored Tags │ +│=> Test 2022 HBO Continuin TV-MA Standard Bluray-1080p English 59.51 GB 🏷 │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/ui_tests.rs b/src/ui/ui_tests.rs index 765305e..4ba188a 100644 --- a/src/ui/ui_tests.rs +++ b/src/ui/ui_tests.rs @@ -2,7 +2,9 @@ mod snapshot_tests { use crate::app::App; use crate::handlers::populate_keymapping_table; - use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::ui; use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app}; @@ -44,42 +46,79 @@ mod snapshot_tests { insta::assert_snapshot!(output); } - // TODO after adding fully populated Sonarr data - // #[test] - // fn test_sonarr_ui_renders_library_tab() { - // let mut app = App::test_default_fully_populated(); - // app.push_navigation_stack(ActiveSonarrBlock::default().into()); - // - // let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { - // ui(f, app); - // }); - // - // insta::assert_snapshot!(output); - // } - // - // #[test] - // fn test_sonarr_ui_renders_library_tab_with_error() { - // let mut app = App::test_default_fully_populated(); - // app.push_navigation_stack(ActiveSonarrBlock::default().into()); - // app.error = "Some error".into(); - // - // let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { - // ui(f, app); - // }); - // - // insta::assert_snapshot!(output); - // } - // - // #[test] - // fn test_sonarr_ui_renders_library_tab_error_popup() { - // let mut app = App::test_default_fully_populated(); - // populate_keymapping_table(&mut app); - // app.push_navigation_stack(ActiveSonarrBlock::default().into()); - // - // let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { - // ui(f, app); - // }); - // - // insta::assert_snapshot!(output); - // } + #[test] + fn test_sonarr_ui_renders_library_tab() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveSonarrBlock::default().into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_sonarr_ui_renders_library_tab_with_error() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveSonarrBlock::default().into()); + app.error = "Some error".into(); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_sonarr_ui_renders_library_tab_error_popup() { + let mut app = App::test_default_fully_populated(); + populate_keymapping_table(&mut app); + app.push_navigation_stack(ActiveSonarrBlock::default().into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_lidarr_ui_renders_library_tab() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::default().into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_lidarr_ui_renders_library_tab_with_error() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::default().into()); + app.error = "Some error".into(); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_lidarr_ui_renders_library_tab_error_popup() { + let mut app = App::test_default_fully_populated(); + populate_keymapping_table(&mut app); + app.push_navigation_stack(ActiveLidarrBlock::default().into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ui(f, app); + }); + + insta::assert_snapshot!(output); + } }