From b2814371f0b17f71a2774e184cee6d9cc68095fe Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 9 Jan 2026 16:22:03 -0700 Subject: [PATCH] feat: Completed support for viewing Lidarr artist details --- src/app/lidarr/lidarr_context_clues.rs | 26 +- src/app/lidarr/lidarr_context_clues_tests.rs | 70 ++- src/app/lidarr/lidarr_tests.rs | 26 + src/app/lidarr/mod.rs | 9 + src/cli/lidarr/get_command_handler.rs | 16 + src/cli/lidarr/get_command_handler_tests.rs | 52 ++ src/cli/lidarr/lidarr_command_tests.rs | 58 ++ src/cli/lidarr/list_command_handler.rs | 18 +- src/cli/lidarr/list_command_handler_tests.rs | 50 ++ src/cli/lidarr/mod.rs | 36 ++ src/cli/lidarr/refresh_command_handler.rs | 16 + .../lidarr/refresh_command_handler_tests.rs | 57 ++ ...rigger_automatic_search_command_handler.rs | 72 +++ ..._automatic_search_command_handler_tests.rs | 107 ++++ .../library/artist_details_handler.rs | 211 +++++++ .../library/artist_details_handler_tests.rs | 533 ++++++++++++++++++ .../library/library_handler_tests.rs | 31 +- src/handlers/lidarr_handlers/library/mod.rs | 23 +- .../library/series_details_handler_tests.rs | 2 +- src/models/lidarr_models.rs | 48 ++ src/models/lidarr_models_tests.rs | 56 +- src/models/servarr_data/lidarr/lidarr_data.rs | 41 +- .../servarr_data/lidarr/lidarr_data_tests.rs | 42 +- .../albums/lidarr_albums_network_tests.rs | 106 ++++ .../lidarr_network/library/albums/mod.rs | 121 ++++ .../lidarr_artists_network_tests.rs} | 49 +- .../lidarr_network/library/artists/mod.rs | 399 +++++++++++++ src/network/lidarr_network/library/mod.rs | 358 +----------- .../lidarr_network_test_utils.rs | 56 +- .../lidarr_network/lidarr_network_tests.rs | 16 +- src/network/lidarr_network/mod.rs | 26 +- src/ui/lidarr_ui/library/artist_details_ui.rs | 327 +++++++++++ .../library/artist_details_ui_tests.rs | 125 ++++ src/ui/lidarr_ui/library/edit_artist_ui.rs | 13 +- src/ui/lidarr_ui/library/library_ui_tests.rs | 50 +- src/ui/lidarr_ui/library/mod.rs | 4 + ...ot_tests__AddArtistEmptySearchResults.snap | 47 -- ..._snapshot_tests__AddArtistSearchInput.snap | 47 -- ...napshot_tests__AddArtistSearchResults.snap | 47 -- ..._artist_already_in_library_ui_renders.snap | 2 +- ...sts__add_artist_modal_AddArtistPrompt.snap | 2 +- ..._modal_AddArtistSelectMetadataProfile.snap | 2 +- ...d_artist_modal_AddArtistSelectMonitor.snap | 2 +- ..._modal_AddArtistSelectMonitorNewItems.snap | 2 +- ...t_modal_AddArtistSelectQualityProfile.snap | 2 +- ...rtist_modal_AddArtistSelectRootFolder.snap | 2 +- ...__add_artist_modal_AddArtistTagsInput.snap | 2 +- ..._add_artist_ui_AddArtistSearchResults.snap | 2 +- ...tests__artist_details_ArtistDetails_0.snap | 52 ++ ...ils_AutomaticallySearchArtistPrompt_0.snap | 52 ++ ...s__artist_details_SearchAlbumsError_0.snap | 52 ++ ..._tests__artist_details_SearchAlbums_0.snap | 52 ++ ...t_details_UpdateAndScanArtistPrompt_0.snap | 52 ++ ...tic_search_prompt_over_artist_details.snap | 52 ++ ...e_and_scan_prompt_over_artist_details.snap | 52 ++ ...s__empty_artist_details_ArtistDetails.snap | 52 ++ ..._loading_artist_details_ArtistDetails.snap | 52 ++ ...elete_artist_ui_renders_delete_artist.snap | 2 +- ...__edit_artist_EditArtistConfirmPrompt.snap | 2 +- ...t_tests__edit_artist_EditArtistPrompt.snap | 2 +- ...rtist_EditArtistSelectMetadataProfile.snap | 2 +- ...rtist_EditArtistSelectMonitorNewItems.snap | 2 +- ...artist_EditArtistSelectQualityProfile.snap | 2 +- ...i_renders_artist_details_over_library.snap | 52 ++ ...ui_renders_delete_artist_over_library.snap | 4 +- ..._over_edit_artist_over_artist_details.snap | 48 ++ ...nders_edit_artist_over_artist_details.snap | 48 ++ ..._ui_renders_update_all_artists_prompt.snap | 2 +- ...napshot_tests__lidarr_library_Artists.snap | 2 +- ...sts__lidarr_library_ArtistsSortPrompt.snap | 2 +- ...t_tests__lidarr_library_FilterArtists.snap | 2 +- ...ts__lidarr_library_FilterArtistsError.snap | 2 +- ...t_tests__lidarr_library_SearchArtists.snap | 2 +- ...ts__lidarr_library_SearchArtistsError.snap | 2 +- 74 files changed, 3488 insertions(+), 567 deletions(-) create mode 100644 src/cli/lidarr/trigger_automatic_search_command_handler.rs create mode 100644 src/cli/lidarr/trigger_automatic_search_command_handler_tests.rs create mode 100644 src/handlers/lidarr_handlers/library/artist_details_handler.rs create mode 100644 src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs create mode 100644 src/network/lidarr_network/library/albums/lidarr_albums_network_tests.rs create mode 100644 src/network/lidarr_network/library/albums/mod.rs rename src/network/lidarr_network/library/{lidarr_library_network_tests.rs => artists/lidarr_artists_network_tests.rs} (93%) create mode 100644 src/network/lidarr_network/library/artists/mod.rs create mode 100644 src/ui/lidarr_ui/library/artist_details_ui.rs create mode 100644 src/ui/lidarr_ui/library/artist_details_ui_tests.rs delete mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistEmptySearchResults.snap delete mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchInput.snap delete mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchResults.snap 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_ArtistDetails_0.snap 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_AutomaticallySearchArtistPrompt_0.snap 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_SearchAlbumsError_0.snap 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_SearchAlbums_0.snap 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_UpdateAndScanArtistPrompt_0.snap 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_automatic_search_prompt_over_artist_details.snap 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_update_and_scan_prompt_over_artist_details.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__empty_artist_details_ArtistDetails.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__loading_artist_details_ArtistDetails.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_artist_details_over_library.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_dropdown_over_edit_artist_over_artist_details.snap create mode 100644 src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_edit_artist_over_artist_details.snap diff --git a/src/app/lidarr/lidarr_context_clues.rs b/src/app/lidarr/lidarr_context_clues.rs index 7262e93..830c33f 100644 --- a/src/app/lidarr/lidarr_context_clues.rs +++ b/src/app/lidarr/lidarr_context_clues.rs @@ -5,7 +5,7 @@ use crate::app::context_clues::{ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::models::Route; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, + ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, }; #[cfg(test)] @@ -36,6 +36,25 @@ pub static ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [ (DEFAULT_KEYBINDINGS.esc, "edit search"), ]; +pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 7] = [ + ( + DEFAULT_KEYBINDINGS.refresh, + DEFAULT_KEYBINDINGS.refresh.desc, + ), + (DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc), + ( + DEFAULT_KEYBINDINGS.toggle_monitoring, + DEFAULT_KEYBINDINGS.toggle_monitoring.desc, + ), + (DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc), + (DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc), + ( + DEFAULT_KEYBINDINGS.auto_search, + DEFAULT_KEYBINDINGS.auto_search.desc, + ), + (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), +]; + pub(in crate::app) struct LidarrContextClueProvider; impl ContextClueProvider for LidarrContextClueProvider { @@ -45,6 +64,11 @@ impl ContextClueProvider for LidarrContextClueProvider { }; match active_lidarr_block { + _ if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) => app + .data + .lidarr_data + .artist_info_tabs + .get_active_route_contextual_help(), ActiveLidarrBlock::AddArtistSearchInput | ActiveLidarrBlock::AddArtistEmptySearchResults => { Some(&BARE_POPUP_CONTEXT_CLUES) } diff --git a/src/app/lidarr/lidarr_context_clues_tests.rs b/src/app/lidarr/lidarr_context_clues_tests.rs index a088b58..b698bf0 100644 --- a/src/app/lidarr/lidarr_context_clues_tests.rs +++ b/src/app/lidarr/lidarr_context_clues_tests.rs @@ -2,13 +2,16 @@ mod tests { use crate::app::App; use crate::app::context_clues::{ - BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider, + BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider, }; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::lidarr::lidarr_context_clues::{ - ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, LidarrContextClueProvider, + ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, + LidarrContextClueProvider, + }; + use crate::models::servarr_data::lidarr::lidarr_data::{ + ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, LidarrData, }; - use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use rstest::rstest; @@ -65,6 +68,50 @@ mod tests { assert_none!(artists_context_clues_iter.next()); } + #[test] + fn test_artist_details_context_clues() { + let mut artist_details_context_clues_iter = ARTIST_DETAILS_CONTEXT_CLUES.iter(); + + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &( + DEFAULT_KEYBINDINGS.refresh, + DEFAULT_KEYBINDINGS.refresh.desc, + ) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &( + DEFAULT_KEYBINDINGS.toggle_monitoring, + DEFAULT_KEYBINDINGS.toggle_monitoring.desc, + ) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &( + DEFAULT_KEYBINDINGS.auto_search, + DEFAULT_KEYBINDINGS.auto_search.desc, + ) + ); + assert_some_eq_x!( + artist_details_context_clues_iter.next(), + &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc) + ); + assert_none!(artist_details_context_clues_iter.next()); + } + #[test] fn test_add_artist_search_results_context_clues() { let mut add_artist_search_results_context_clues_iter = @@ -92,6 +139,23 @@ mod tests { LidarrContextClueProvider::get_context_clues(&mut app); } + #[rstest] + #[case(0, ActiveLidarrBlock::ArtistDetails, &ARTIST_DETAILS_CONTEXT_CLUES)] + fn test_lidarr_context_clue_provider_artist_info_tabs( + #[case] index: usize, + #[case] active_lidarr_block: ActiveLidarrBlock, + #[case] expected_context_clues: &[ContextClue], + ) { + let mut app = App::test_default(); + app.data.lidarr_data = LidarrData::default(); + app.data.lidarr_data.artist_info_tabs.set_index(index); + app.push_navigation_stack(active_lidarr_block.into()); + + let context_clues = LidarrContextClueProvider::get_context_clues(&mut app); + + assert_some_eq_x!(context_clues, expected_context_clues); + } + #[test] fn test_lidarr_context_clue_provider_artists_block() { let mut app = App::test_default(); diff --git a/src/app/lidarr/lidarr_tests.rs b/src/app/lidarr/lidarr_tests.rs index 9007ac4..7edcee3 100644 --- a/src/app/lidarr/lidarr_tests.rs +++ b/src/app/lidarr/lidarr_tests.rs @@ -4,6 +4,7 @@ mod tests { use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; use crate::network::NetworkEvent; use crate::network::lidarr_network::LidarrEvent; + use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist; use pretty_assertions::{assert_eq, assert_str_eq}; use tokio::sync::mpsc; @@ -32,6 +33,23 @@ mod tests { assert_eq!(app.tick_count, 0); } + #[tokio::test] + async fn test_dispatch_by_lidarr_block_artist_details() { + let (tx, mut rx) = mpsc::channel::(500); + let mut app = App::test_default(); + app.data.lidarr_data.artists.set_items(vec![artist()]); + app.network_tx = Some(tx); + + app + .dispatch_by_lidarr_block(&ActiveLidarrBlock::ArtistDetails) + .await; + + assert!(app.is_loading); + assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetAlbums(1).into()); + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + #[tokio::test] async fn test_dispatch_by_lidarr_block_add_artist_search_results() { let (tx, mut rx) = mpsc::channel::(500); @@ -60,4 +78,12 @@ mod tests { assert_str_eq!(query, "Test Artist"); } + + #[tokio::test] + async fn test_extract_artist_id() { + let mut app = App::test_default(); + app.data.lidarr_data.artists.set_items(vec![artist()]); + + assert_eq!(app.extract_artist_id().await, 1); + } } diff --git a/src/app/lidarr/mod.rs b/src/app/lidarr/mod.rs index 7c5b118..fbe7c5b 100644 --- a/src/app/lidarr/mod.rs +++ b/src/app/lidarr/mod.rs @@ -28,6 +28,11 @@ impl App<'_> { .dispatch_network_event(LidarrEvent::ListArtists.into()) .await; } + ActiveLidarrBlock::ArtistDetails => { + self + .dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into()) + .await; + } ActiveLidarrBlock::AddArtistSearchResults => { self .dispatch_network_event( @@ -53,6 +58,10 @@ impl App<'_> { .clone() } + async fn extract_artist_id(&self) -> i64 { + self.data.lidarr_data.artists.current_selection().id + } + async fn check_for_lidarr_prompt_action(&mut self) { if self.data.lidarr_data.prompt_confirm { self.data.lidarr_data.prompt_confirm = false; diff --git a/src/cli/lidarr/get_command_handler.rs b/src/cli/lidarr/get_command_handler.rs index 64063b8..b33ce37 100644 --- a/src/cli/lidarr/get_command_handler.rs +++ b/src/cli/lidarr/get_command_handler.rs @@ -18,6 +18,15 @@ mod get_command_handler_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum LidarrGetCommand { + #[command(about = "Get detailed information for the album with the given ID")] + AlbumDetails { + #[arg( + long, + help = "The Lidarr ID of the album whose details you wish to fetch", + required = true + )] + album_id: i64, + }, #[command(about = "Get detailed information for the artist with the given ID")] ArtistDetails { #[arg( @@ -62,6 +71,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHan async fn handle(self) -> Result { let result = match self.command { + LidarrGetCommand::AlbumDetails { album_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetAlbumDetails(album_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } LidarrGetCommand::ArtistDetails { artist_id } => { let resp = self .network diff --git a/src/cli/lidarr/get_command_handler_tests.rs b/src/cli/lidarr/get_command_handler_tests.rs index 4ada9a7..6c6e6a5 100644 --- a/src/cli/lidarr/get_command_handler_tests.rs +++ b/src/cli/lidarr/get_command_handler_tests.rs @@ -23,6 +23,32 @@ mod tests { use super::*; + #[test] + fn test_album_details_requires_album_id() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "album-details"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_album_details_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "get", + "album-details", + "--album-id", + "1", + ]); + + assert_ok!(&result); + } + #[test] fn test_artist_details_requires_artist_id() { let result = @@ -91,6 +117,32 @@ mod tests { network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent}, }; + #[tokio::test] + async fn test_handle_get_album_details_command() { + let expected_album_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::GetAlbumDetails(expected_album_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_album_details_command = LidarrGetCommand::AlbumDetails { album_id: 1 }; + + let result = + LidarrGetCommandHandler::with(&app_arc, get_album_details_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + #[tokio::test] async fn test_handle_get_artist_details_command() { let expected_artist_id = 1; diff --git a/src/cli/lidarr/lidarr_command_tests.rs b/src/cli/lidarr/lidarr_command_tests.rs index 59c37d9..f814eec 100644 --- a/src/cli/lidarr/lidarr_command_tests.rs +++ b/src/cli/lidarr/lidarr_command_tests.rs @@ -75,6 +75,31 @@ mod tests { assert_ok!(&result); } + #[test] + fn test_toggle_album_monitoring_requires_album_id() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-album-monitoring"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_toggle_album_monitoring_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "toggle-album-monitoring", + "--album-id", + "1", + ]); + + assert_ok!(&result); + } + #[test] fn test_search_new_artist_requires_query() { let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "search-new-artist"]); @@ -110,6 +135,7 @@ mod tests { use crate::cli::lidarr::add_command_handler::LidarrAddCommand; use crate::cli::lidarr::get_command_handler::LidarrGetCommand; use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand; + use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand; use crate::{ app::App, cli::{ @@ -306,5 +332,37 @@ mod tests { assert_ok!(&result); } + + #[tokio::test] + async fn test_lidarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler() + { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::TriggerAutomaticArtistSearch(1).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let trigger_automatic_search_command = + LidarrCommand::TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand::Artist { + artist_id: 1, + }); + + let result = LidarrCliHandler::with( + &app_arc, + trigger_automatic_search_command, + &mut mock_network, + ) + .handle() + .await; + + assert_ok!(&result); + } } } diff --git a/src/cli/lidarr/list_command_handler.rs b/src/cli/lidarr/list_command_handler.rs index 9d0b176..2799e29 100644 --- a/src/cli/lidarr/list_command_handler.rs +++ b/src/cli/lidarr/list_command_handler.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::Result; -use clap::Subcommand; +use clap::{Subcommand, arg}; use tokio::sync::Mutex; use crate::{ @@ -18,6 +18,15 @@ mod list_command_handler_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum LidarrListCommand { + #[command(about = "List all albums for the artist with the given ID")] + Albums { + #[arg( + long, + help = "The Lidarr ID of the artist whose albums you want to list", + required = true + )] + artist_id: i64, + }, #[command(about = "List all artists in your Lidarr library")] Artists, #[command(about = "List all Lidarr metadata profiles")] @@ -55,6 +64,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH async fn handle(self) -> Result { let result = match self.command { + LidarrListCommand::Albums { artist_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetAlbums(artist_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } LidarrListCommand::Artists => { let resp = self .network diff --git a/src/cli/lidarr/list_command_handler_tests.rs b/src/cli/lidarr/list_command_handler_tests.rs index 3933cb6..f9c6d03 100644 --- a/src/cli/lidarr/list_command_handler_tests.rs +++ b/src/cli/lidarr/list_command_handler_tests.rs @@ -19,6 +19,8 @@ mod tests { mod cli { use super::*; + use clap::{Parser, error::ErrorKind}; + use pretty_assertions::assert_eq; use rstest::rstest; #[rstest] @@ -29,6 +31,32 @@ mod tests { assert_ok!(&result); } + + #[test] + fn test_list_albums_requires_artist_id() { + let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "albums"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_list_albums_with_artist_id() { + let expected_args = LidarrListCommand::Albums { artist_id: 1 }; + let result = + Cli::try_parse_from(["managarr", "lidarr", "list", "albums", "--artist-id", "1"]); + + assert_ok!(&result); + + let Some(Command::Lidarr(LidarrCommand::List(album_command))) = result.unwrap().command + else { + panic!("Unexpected command type"); + }; + assert_eq!(album_command, expected_args); + } } mod handler { @@ -77,5 +105,27 @@ mod tests { assert_ok!(&result); } + + #[tokio::test] + async fn test_handle_list_albums_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(LidarrEvent::GetAlbums(1).into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let list_command = LidarrListCommand::Albums { artist_id: 1 }; + + let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } } } diff --git a/src/cli/lidarr/mod.rs b/src/cli/lidarr/mod.rs index 29bc93f..9ddcd08 100644 --- a/src/cli/lidarr/mod.rs +++ b/src/cli/lidarr/mod.rs @@ -9,6 +9,9 @@ use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler}; use list_command_handler::{LidarrListCommand, LidarrListCommandHandler}; use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler}; use tokio::sync::Mutex; +use trigger_automatic_search_command_handler::{ + LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler, +}; use crate::network::lidarr_network::LidarrEvent; use crate::{app::App, network::NetworkTrait}; @@ -21,6 +24,7 @@ mod edit_command_handler; mod get_command_handler; mod list_command_handler; mod refresh_command_handler; +mod trigger_automatic_search_command_handler; #[cfg(test)] #[path = "lidarr_command_tests.rs"] @@ -58,6 +62,11 @@ pub enum LidarrCommand { about = "Commands to refresh the data in your Lidarr instance" )] Refresh(LidarrRefreshCommand), + #[command( + subcommand, + about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance" + )] + TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand), #[command(about = "Search for a new artist to add to Lidarr")] SearchNewArtist { #[arg( @@ -67,6 +76,17 @@ pub enum LidarrCommand { )] query: String, }, + #[command( + about = "Toggle monitoring for the specified album corresponding to the given album ID" + )] + ToggleAlbumMonitoring { + #[arg( + long, + help = "The Lidarr ID of the album to toggle monitoring on", + required = true + )] + album_id: i64, + }, #[command( about = "Toggle monitoring for the specified artist corresponding to the given artist ID" )] @@ -137,6 +157,15 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, ' .handle() .await? } + LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => { + LidarrTriggerAutomaticSearchCommandHandler::with( + self.app, + trigger_automatic_search_command, + self.network, + ) + .handle() + .await? + } LidarrCommand::SearchNewArtist { query } => { let resp = self .network @@ -144,6 +173,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, ' .await?; serde_json::to_string_pretty(&resp)? } + LidarrCommand::ToggleAlbumMonitoring { album_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::ToggleAlbumMonitoring(album_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } LidarrCommand::ToggleArtistMonitoring { artist_id } => { let resp = self .network diff --git a/src/cli/lidarr/refresh_command_handler.rs b/src/cli/lidarr/refresh_command_handler.rs index 60c23cf..ee89aee 100644 --- a/src/cli/lidarr/refresh_command_handler.rs +++ b/src/cli/lidarr/refresh_command_handler.rs @@ -19,6 +19,15 @@ mod refresh_command_handler_tests; pub enum LidarrRefreshCommand { #[command(about = "Refresh all artist data for all artists in your Lidarr library")] AllArtists, + #[command(about = "Refresh artist data and scan disk for the artist with the given ID")] + Artist { + #[arg( + long, + help = "The ID of the artist to refresh information on and to scan the disk for", + required = true + )] + artist_id: i64, + }, } impl From for Command { @@ -57,6 +66,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand> .await?; serde_json::to_string_pretty(&resp)? } + LidarrRefreshCommand::Artist { artist_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::UpdateAndScanArtist(artist_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/lidarr/refresh_command_handler_tests.rs b/src/cli/lidarr/refresh_command_handler_tests.rs index 5efdde8..dc9e63d 100644 --- a/src/cli/lidarr/refresh_command_handler_tests.rs +++ b/src/cli/lidarr/refresh_command_handler_tests.rs @@ -20,6 +20,8 @@ mod tests { mod cli { use super::*; + use clap::{Parser, error::ErrorKind}; + use pretty_assertions::assert_eq; #[test] fn test_refresh_all_artists_has_no_arg_requirements() { @@ -28,6 +30,37 @@ mod tests { assert_ok!(&result); } + + #[test] + fn test_refresh_artist_requires_artist_id() { + let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "artist"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_refresh_artist_with_artist_id() { + let expected_args = LidarrRefreshCommand::Artist { artist_id: 1 }; + let result = Cli::try_parse_from([ + "managarr", + "lidarr", + "refresh", + "artist", + "--artist-id", + "1", + ]); + + assert_ok!(&result); + let Some(Command::Lidarr(LidarrCommand::Refresh(refresh_command))) = result.unwrap().command + else { + panic!("Unexpected command type"); + }; + assert_eq!(refresh_command, expected_args); + } } mod handler { @@ -68,5 +101,29 @@ mod tests { assert_ok!(&result); } + + #[tokio::test] + async fn test_handle_refresh_artist_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::UpdateAndScanArtist(1).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let refresh_command = LidarrRefreshCommand::Artist { artist_id: 1 }; + + let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } } } diff --git a/src/cli/lidarr/trigger_automatic_search_command_handler.rs b/src/cli/lidarr/trigger_automatic_search_command_handler.rs new file mode 100644 index 0000000..ae19b82 --- /dev/null +++ b/src/cli/lidarr/trigger_automatic_search_command_handler.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use anyhow::Result; +use clap::Subcommand; +use tokio::sync::Mutex; + +use crate::{ + app::App, + cli::{CliCommandHandler, Command}, + network::{NetworkTrait, lidarr_network::LidarrEvent}, +}; + +use super::LidarrCommand; + +#[cfg(test)] +#[path = "trigger_automatic_search_command_handler_tests.rs"] +mod trigger_automatic_search_command_handler_tests; + +#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] +pub enum LidarrTriggerAutomaticSearchCommand { + #[command(about = "Trigger an automatic search for the artist with the specified ID")] + Artist { + #[arg( + long, + help = "The ID of the artist you want to trigger an automatic search for", + required = true + )] + artist_id: i64, + }, +} + +impl From for Command { + fn from(value: LidarrTriggerAutomaticSearchCommand) -> Self { + Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(value)) + } +} + +pub(super) struct LidarrTriggerAutomaticSearchCommandHandler<'a, 'b> { + _app: &'a Arc>>, + command: LidarrTriggerAutomaticSearchCommand, + network: &'a mut dyn NetworkTrait, +} + +impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrTriggerAutomaticSearchCommand> + for LidarrTriggerAutomaticSearchCommandHandler<'a, 'b> +{ + fn with( + _app: &'a Arc>>, + command: LidarrTriggerAutomaticSearchCommand, + network: &'a mut dyn NetworkTrait, + ) -> Self { + LidarrTriggerAutomaticSearchCommandHandler { + _app, + command, + network, + } + } + + async fn handle(self) -> Result { + let result = match self.command { + LidarrTriggerAutomaticSearchCommand::Artist { artist_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::TriggerAutomaticArtistSearch(artist_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + }; + + Ok(result) + } +} diff --git a/src/cli/lidarr/trigger_automatic_search_command_handler_tests.rs b/src/cli/lidarr/trigger_automatic_search_command_handler_tests.rs new file mode 100644 index 0000000..35a537a --- /dev/null +++ b/src/cli/lidarr/trigger_automatic_search_command_handler_tests.rs @@ -0,0 +1,107 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use crate::Cli; + use crate::cli::{ + Command, + lidarr::{ + LidarrCommand, trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand, + }, + }; + use clap::CommandFactory; + + #[test] + fn test_lidarr_trigger_automatic_search_command_from() { + let command = LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 }; + + let result = Command::from(command.clone()); + + assert_eq!( + result, + Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(command)) + ); + } + + mod cli { + use super::*; + use clap::error::ErrorKind; + use pretty_assertions::assert_eq; + + #[test] + fn test_trigger_automatic_artist_search_requires_artist_id() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "trigger-automatic-search", + "artist", + ]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_trigger_automatic_artist_search_with_artist_id() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "trigger-automatic-search", + "artist", + "--artist-id", + "1", + ]); + + assert_ok!(&result); + } + } + + mod handler { + use std::sync::Arc; + + use mockall::predicate::eq; + use serde_json::json; + use tokio::sync::Mutex; + + use crate::cli::lidarr::trigger_automatic_search_command_handler::{ + LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler, + }; + use crate::{app::App, cli::CliCommandHandler}; + use crate::{ + models::{Serdeable, lidarr_models::LidarrSerdeable}, + network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent}, + }; + + #[tokio::test] + async fn test_handle_trigger_automatic_artist_search_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::TriggerAutomaticArtistSearch(1).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let trigger_automatic_search_command = + LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 }; + + let result = LidarrTriggerAutomaticSearchCommandHandler::with( + &app_arc, + trigger_automatic_search_command, + &mut mock_network, + ) + .handle() + .await; + + assert_ok!(&result); + } + } +} diff --git a/src/handlers/lidarr_handlers/library/artist_details_handler.rs b/src/handlers/lidarr_handlers/library/artist_details_handler.rs new file mode 100644 index 0000000..174615c --- /dev/null +++ b/src/handlers/lidarr_handlers/library/artist_details_handler.rs @@ -0,0 +1,211 @@ +use crate::app::App; +use crate::event::Key; +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, +}; +use crate::models::{BlockSelectionState, Route}; +use crate::network::lidarr_network::LidarrEvent; + +#[cfg(test)] +#[path = "artist_details_handler_tests.rs"] +mod artist_details_handler_tests; + +pub struct ArtistDetailsHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_lidarr_block: ActiveLidarrBlock, + _context: Option, +} + +impl ArtistDetailsHandler<'_, '_> { + fn extract_artist_id(&self) -> i64 { + self.app.data.lidarr_data.artists.current_selection().id + } + + fn extract_album_id(&self) -> i64 { + self.app.data.lidarr_data.albums.current_selection().id + } +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler<'a, 'b> { + fn handle(&mut self) { + let albums_table_handling_config = + TableHandlingConfig::new(ActiveLidarrBlock::ArtistDetails.into()) + .searching_block(ActiveLidarrBlock::SearchAlbums.into()) + .search_error_block(ActiveLidarrBlock::SearchAlbumsError.into()) + .search_field_fn(|album: &Album| &album.title.text); + + if !handle_table( + self, + |app| &mut app.data.lidarr_data.albums, + albums_table_handling_config, + ) { + self.handle_key_event(); + } + } + + fn accepts(active_block: ActiveLidarrBlock) -> bool { + ARTIST_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, + ) -> ArtistDetailsHandler<'a, 'b> { + ArtistDetailsHandler { + 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) {} + + fn handle_scroll_down(&mut self) {} + + fn handle_home(&mut self) {} + + fn handle_end(&mut self) {} + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + match self.active_lidarr_block { + ActiveLidarrBlock::UpdateAndScanArtistPrompt + | ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { + handle_prompt_toggle(self.app, self.key); + } + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_lidarr_block { + ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = Some( + LidarrEvent::TriggerAutomaticArtistSearch(self.extract_artist_id()), + ); + } + + self.app.pop_navigation_stack(); + } + ActiveLidarrBlock::UpdateAndScanArtistPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::UpdateAndScanArtist(self.extract_artist_id())); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_lidarr_block { + ActiveLidarrBlock::UpdateAndScanArtistPrompt + | ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.prompt_confirm = false; + } + ActiveLidarrBlock::ArtistDetails => { + self.app.pop_navigation_stack(); + self.app.data.lidarr_data.reset_artist_info_tabs(); + } + _ => (), + } + } + + fn handle_char_key_event(&mut self) { + let key = self.key; + match self.active_lidarr_block { + ActiveLidarrBlock::ArtistDetails => match self.key { + _ if matches_key!(refresh, key) => self + .app + .pop_and_push_navigation_stack(self.active_lidarr_block.into()), + _ if matches_key!(auto_search, key) => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into()); + } + _ if matches_key!(update, key) => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::UpdateAndScanArtistPrompt.into()); + } + _ if matches_key!(edit, key) => { + self.app.push_navigation_stack( + ( + ActiveLidarrBlock::EditArtistPrompt, + Some(self.active_lidarr_block), + ) + .into(), + ); + self.app.data.lidarr_data.edit_artist_modal = Some((&self.app.data.lidarr_data).into()); + self.app.data.lidarr_data.selected_block = + BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS); + } + _ if matches_key!(toggle_monitoring, key) => { + if !self.app.data.lidarr_data.albums.is_empty() { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::ToggleAlbumMonitoring(self.extract_album_id())); + + self + .app + .pop_and_push_navigation_stack(self.active_lidarr_block.into()); + } + } + _ => (), + }, + ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { + if matches_key!(confirm, key) { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = Some( + LidarrEvent::TriggerAutomaticArtistSearch(self.extract_artist_id()), + ); + + self.app.pop_navigation_stack(); + } + } + ActiveLidarrBlock::UpdateAndScanArtistPrompt => { + if matches_key!(confirm, key) { + self.app.data.lidarr_data.prompt_confirm = true; + self.app.data.lidarr_data.prompt_confirm_action = + Some(LidarrEvent::UpdateAndScanArtist(self.extract_artist_id())); + + 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/artist_details_handler_tests.rs b/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs new file mode 100644 index 0000000..2bd82d0 --- /dev/null +++ b/src/handlers/lidarr_handlers/library/artist_details_handler_tests.rs @@ -0,0 +1,533 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::lidarr::lidarr_data::{ + ActiveLidarrBlock, ARTIST_DETAILS_BLOCKS, + }; + + mod test_handle_left_right_action { + use rstest::rstest; + + use crate::app::App; + use crate::event::Key; + use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + + #[rstest] + fn test_left_right_prompt_toggle( + #[values( + ActiveLidarrBlock::UpdateAndScanArtistPrompt, + ActiveLidarrBlock::AutomaticallySearchArtistPrompt, + )] active_lidarr_block: ActiveLidarrBlock, + #[values(Key::Left, Key::Right)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::new( + key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + + ArtistDetailsHandler::new( + key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use rstest::rstest; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::assert_navigation_popped; + use crate::event::Key; + use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist; + use crate::network::lidarr_network::LidarrEvent; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[rstest] + #[case(ActiveLidarrBlock::AutomaticallySearchArtistPrompt, LidarrEvent::TriggerAutomaticArtistSearch(1))] + #[case(ActiveLidarrBlock::UpdateAndScanArtistPrompt, LidarrEvent::UpdateAndScanArtist(1))] + fn test_artist_details_prompt_confirm_submit( + #[case] prompt_block: ActiveLidarrBlock, + #[case] expected_action: LidarrEvent + ) { + let mut app = App::test_default(); + app.data.lidarr_data.prompt_confirm = true; + app.data.lidarr_data.artists.set_items(vec![artist()]); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + ArtistDetailsHandler::new( + SUBMIT_KEY, + &mut app, + prompt_block, + None, + ) + .handle(); + + assert!(app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_some_eq_x!( + &app.data.lidarr_data.prompt_confirm_action, + &expected_action + ); + } + + #[rstest] + fn test_artist_details_prompt_decline_submit( + #[values( + ActiveLidarrBlock::AutomaticallySearchArtistPrompt, + ActiveLidarrBlock::UpdateAndScanArtistPrompt, + )] prompt_block: ActiveLidarrBlock + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + ArtistDetailsHandler::new( + SUBMIT_KEY, + &mut app, + prompt_block, + None, + ) + .handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + assert_none!(app.data.lidarr_data.prompt_confirm_action); + } + } + + mod test_handle_esc { + use rstest::rstest; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::assert_navigation_popped; + use crate::event::Key; + use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_artist_details_esc( + #[values( + ActiveLidarrBlock::AutomaticallySearchArtistPrompt, + ActiveLidarrBlock::UpdateAndScanArtistPrompt + )] prompt_block: ActiveLidarrBlock, + #[values(true, false)] is_ready: bool + ) { + let mut app = App::test_default(); + app.is_loading = is_ready; + app.data.lidarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(prompt_block.into()); + + ArtistDetailsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into()); + } + } + + mod test_handle_char_key_event { + use pretty_assertions::assert_eq; + use rstest::rstest; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped}; + use crate::assert_navigation_pushed; + use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::lidarr_models::{Artist}; + use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS}; + use crate::network::lidarr_network::LidarrEvent; + + #[rstest] + fn test_artist_details_edit_key( + #[values(ActiveLidarrBlock::ArtistDetails)] 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()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.edit.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!( + app, + ( + ActiveLidarrBlock::EditArtistPrompt, + Some(ActiveLidarrBlock::ArtistDetails) + ) + .into() + ); + assert_modal_present!(app.data.lidarr_data.edit_artist_modal); + assert!(app.data.lidarr_data.edit_artist_modal.is_some()); + assert_eq!( + app.data.lidarr_data.selected_block.blocks, + EDIT_ARTIST_SELECTION_BLOCKS + ); + } + + #[rstest] + fn test_artist_details_edit_key_no_op_when_not_ready( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.edit.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_lidarr_block.into()); + assert_modal_absent!(app.data.lidarr_data.edit_artist_modal); + } + + #[test] + fn test_artist_details_toggle_monitoring_key() { + let mut app = App::test_default_fully_populated(); + app.is_routing = false; + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.toggle_monitoring.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveLidarrBlock::ArtistDetails.into()); + assert!(app.data.lidarr_data.prompt_confirm); + assert!(app.is_routing); + assert_eq!( + app.data.lidarr_data.prompt_confirm_action, + Some(LidarrEvent::ToggleAlbumMonitoring(1)) + ); + } + + #[test] + fn test_artist_details_toggle_monitoring_key_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.data.lidarr_data.prompt_confirm = false; + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.toggle_monitoring.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveLidarrBlock::ArtistDetails.into()); + assert!(!app.data.lidarr_data.prompt_confirm); + assert_none!(app.data.lidarr_data.prompt_confirm_action); + } + + #[test] + fn test_artist_details_toggle_monitoring_key_no_op_when_albums_empty() { + let mut app = App::test_default(); + app.data.lidarr_data.artists.set_items(vec![Artist { + id: 1, + ..Artist::default() + }]); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.toggle_monitoring.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ) + .handle(); + + assert!(!app.data.lidarr_data.prompt_confirm); + assert!(app.data.lidarr_data.prompt_confirm_action.is_none()); + } + + #[rstest] + fn test_artist_details_auto_search_key( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.auto_search.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!( + app, + ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into() + ); + } + + #[rstest] + fn test_artist_details_auto_search_key_no_op_when_not_ready( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default_fully_populated(); + app.is_loading = true; + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::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_artist_details_update_key( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveLidarrBlock::UpdateAndScanArtistPrompt.into()); + } + + #[rstest] + fn test_artist_details_update_key_no_op_when_not_ready( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default_fully_populated(); + app.is_loading = true; + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_lidarr_block.into()); + } + + #[rstest] + fn test_artist_details_refresh_key( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default_fully_populated(); + app.is_routing = false; + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + ArtistDetailsHandler::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_artist_details_refresh_key_no_op_when_not_ready( + #[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock + ) { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(active_lidarr_block.into()); + app.is_routing = false; + + ArtistDetailsHandler::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::AutomaticallySearchArtistPrompt, + LidarrEvent::TriggerAutomaticArtistSearch(1) + )] + #[case( + ActiveLidarrBlock::UpdateAndScanArtistPrompt, + LidarrEvent::UpdateAndScanArtist(1) + )] + fn test_artist_details_prompt_confirm_key( + #[case] prompt_block: ActiveLidarrBlock, + #[case] expected_action: LidarrEvent, + #[values(ActiveLidarrBlock::ArtistDetails)] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + app.push_navigation_stack(prompt_block.into()); + + ArtistDetailsHandler::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_artist_details_handler_accepts() { + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { + if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) { + assert!(ArtistDetailsHandler::accepts(active_lidarr_block)); + } else { + assert!(!ArtistDetailsHandler::accepts(active_lidarr_block)); + } + }); + } + + #[test] + fn test_extract_artist_id() { + let mut app = App::test_default_fully_populated(); + + let artist_id = ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.esc.key, + &mut app, ActiveLidarrBlock::ArtistDetails, None).extract_artist_id(); + + assert_eq!(artist_id, 1); + } + + #[test] + fn test_extract_album_id() { + let mut app = App::test_default_fully_populated(); + + let album_id = ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.esc.key, + &mut app, ActiveLidarrBlock::ArtistDetails, None).extract_album_id(); + + assert_eq!(album_id, 1); + } + + #[rstest] + fn test_artist_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 = ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ); + + assert_eq!( + handler.ignore_special_keys(), + ignore_special_keys_for_textbox_input + ); + } + + #[test] + fn test_artist_details_handler_is_not_ready_when_loading() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.is_loading = true; + + let handler = ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_artist_details_handler_is_ready_when_not_loading() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.is_loading = false; + + let handler = ArtistDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveLidarrBlock::ArtistDetails, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/lidarr_handlers/library/library_handler_tests.rs b/src/handlers/lidarr_handlers/library/library_handler_tests.rs index 485ee90..ea4f5bd 100644 --- a/src/handlers/lidarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/library_handler_tests.rs @@ -13,8 +13,8 @@ mod tests { use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options}; use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus}; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, - EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, + ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, 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; @@ -26,6 +26,7 @@ mod tests { fn test_library_handler_accepts() { let mut library_handler_blocks = Vec::new(); library_handler_blocks.extend(LIBRARY_BLOCKS); + library_handler_blocks.extend(ARTIST_DETAILS_BLOCKS); library_handler_blocks.extend(DELETE_ARTIST_BLOCKS); library_handler_blocks.extend(EDIT_ARTIST_BLOCKS); library_handler_blocks.extend(ADD_ARTIST_BLOCKS); @@ -587,6 +588,32 @@ mod tests { assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into()); } + #[rstest] + fn test_delegates_artist_details_blocks_to_artist_details_handler( + #[values( + ActiveLidarrBlock::ArtistDetails, + ActiveLidarrBlock::AutomaticallySearchArtistPrompt, + ActiveLidarrBlock::SearchAlbums, + ActiveLidarrBlock::SearchAlbumsError, + ActiveLidarrBlock::UpdateAndScanArtistPrompt + )] + active_lidarr_block: ActiveLidarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(active_lidarr_block.into()); + + LibraryHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + active_lidarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into()); + } + #[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 9bee4f3..137f3d8 100644 --- a/src/handlers/lidarr_handlers/library/mod.rs +++ b/src/handlers/lidarr_handlers/library/mod.rs @@ -19,11 +19,13 @@ use super::handle_change_tab_left_right_keys; use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; mod add_artist_handler; +mod artist_details_handler; mod delete_artist_handler; mod edit_artist_handler; use crate::models::Route; pub(in crate::handlers::lidarr_handlers) use add_artist_handler::AddArtistHandler; +pub(in crate::handlers::lidarr_handlers) use artist_details_handler::ArtistDetailsHandler; pub(in crate::handlers::lidarr_handlers) use delete_artist_handler::DeleteArtistHandler; pub(in crate::handlers::lidarr_handlers) use edit_artist_handler::EditArtistHandler; @@ -74,6 +76,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, ' EditArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context) .handle(); } + _ if ArtistDetailsHandler::accepts(self.active_lidarr_block) => { + ArtistDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context) + .handle(); + } _ => self.handle_key_event(), } } @@ -83,6 +89,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, ' AddArtistHandler::accepts(active_block) || DeleteArtistHandler::accepts(active_block) || EditArtistHandler::accepts(active_block) + || ArtistDetailsHandler::accepts(active_block) || LIBRARY_BLOCKS.contains(&active_block) } @@ -139,12 +146,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, ' } fn handle_submit(&mut self) { - if self.active_lidarr_block == ActiveLidarrBlock::UpdateAllArtistsPrompt { - if self.app.data.lidarr_data.prompt_confirm { - self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateAllArtists); + match self.active_lidarr_block { + ActiveLidarrBlock::Artists => { + self + .app + .push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); } + ActiveLidarrBlock::UpdateAllArtistsPrompt => { + if self.app.data.lidarr_data.prompt_confirm { + self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateAllArtists); + } - self.app.pop_navigation_stack(); + self.app.pop_navigation_stack(); + } + _ => (), } } diff --git a/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs b/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs index 8144e57..d5cf919 100644 --- a/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs @@ -402,7 +402,7 @@ mod tests { ActiveSonarrBlock::SeriesDetails.into() ); assert!(!app.data.sonarr_data.prompt_confirm); - assert_modal_absent!(app.data.sonarr_data.prompt_confirm_action); + assert_none!(app.data.sonarr_data.prompt_confirm_action); assert!(!app.is_routing); } diff --git a/src/models/lidarr_models.rs b/src/models/lidarr_models.rs index 5724406..848ef16 100644 --- a/src/models/lidarr_models.rs +++ b/src/models/lidarr_models.rs @@ -245,6 +245,14 @@ pub struct AddArtistSearchResult { pub ratings: Option, } +#[derive(Default, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct LidarrCommandBody { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub artist_id: Option, +} + #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub struct DeleteArtistParams { @@ -291,6 +299,44 @@ pub struct EditArtistParams { pub clear_tags: bool, } +#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Album { + #[serde(deserialize_with = "super::from_i64")] + pub id: i64, + pub title: HorizontallyScrollableText, + pub foreign_album_id: String, + pub monitored: bool, + #[serde(default)] + pub any_release_ok: bool, + #[serde(deserialize_with = "super::from_i64")] + pub profile_id: i64, + #[serde(deserialize_with = "super::from_i64")] + pub duration: i64, + pub album_type: Option, + pub genres: Vec, + pub ratings: Option, + pub release_date: Option>, + pub statistics: Option, +} + +#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AlbumStatistics { + #[serde(deserialize_with = "super::from_i64")] + pub track_file_count: i64, + #[serde(deserialize_with = "super::from_i64")] + pub track_count: i64, + #[serde(deserialize_with = "super::from_i64")] + pub total_track_count: i64, + #[serde(deserialize_with = "super::from_i64")] + pub size_on_disk: i64, + #[serde(deserialize_with = "super::from_f64")] + pub percent_of_tracks: f64, +} + +impl Eq for AlbumStatistics {} + impl From for Serdeable { fn from(value: LidarrSerdeable) -> Serdeable { Serdeable::Lidarr(value) @@ -300,6 +346,8 @@ impl From for Serdeable { serde_enum_from!( LidarrSerdeable { AddArtistSearchResults(Vec), + Albums(Vec), + Album(Album), Artist(Artist), Artists(Vec), DiskSpaces(Vec), diff --git a/src/models/lidarr_models_tests.rs b/src/models/lidarr_models_tests.rs index 8108a6a..dbc8722 100644 --- a/src/models/lidarr_models_tests.rs +++ b/src/models/lidarr_models_tests.rs @@ -5,7 +5,7 @@ mod tests { use serde_json::json; use crate::models::lidarr_models::{ - AddArtistSearchResult, DownloadRecord, DownloadStatus, DownloadsResponse, Member, + AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse, Member, MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus, }; use crate::models::servarr_models::{ @@ -401,6 +401,45 @@ mod tests { assert_eq!(lidarr_serdeable, LidarrSerdeable::Tags(tags)); } + #[test] + fn test_lidarr_serdeable_from_add_artist_search_results() { + let search_results = vec![AddArtistSearchResult { + foreign_artist_id: "test-id".to_owned(), + ..AddArtistSearchResult::default() + }]; + + let lidarr_serdeable: LidarrSerdeable = search_results.clone().into(); + + assert_eq!( + lidarr_serdeable, + LidarrSerdeable::AddArtistSearchResults(search_results) + ); + } + + #[test] + fn test_lidarr_serdeable_from_albums() { + let albums = vec![Album { + id: 1, + ..Album::default() + }]; + + let lidarr_serdeable: LidarrSerdeable = albums.clone().into(); + + assert_eq!(lidarr_serdeable, LidarrSerdeable::Albums(albums)); + } + + #[test] + fn test_lidarr_serdeable_from_album() { + let album = Album { + id: 1, + ..Album::default() + }; + + let lidarr_serdeable: LidarrSerdeable = album.clone().into(); + + assert_eq!(lidarr_serdeable, LidarrSerdeable::Album(album)); + } + #[test] fn test_artist_status_display() { assert_str_eq!(ArtistStatus::Continuing.to_string(), "continuing"); @@ -501,19 +540,4 @@ mod tests { assert!(search_result.genres.is_empty()); assert_none!(&search_result.ratings); } - - #[test] - fn test_lidarr_serdeable_from_add_artist_search_results() { - let search_results = vec![AddArtistSearchResult { - foreign_artist_id: "test-id".to_owned(), - ..AddArtistSearchResult::default() - }]; - - let lidarr_serdeable: LidarrSerdeable = search_results.clone().into(); - - assert_eq!( - lidarr_serdeable, - LidarrSerdeable::AddArtistSearchResults(search_results) - ); - } } diff --git a/src/models/servarr_data/lidarr/lidarr_data.rs b/src/models/servarr_data/lidarr/lidarr_data.rs index 5387aa3..46c4c8e 100644 --- a/src/models/servarr_data/lidarr/lidarr_data.rs +++ b/src/models/servarr_data/lidarr/lidarr_data.rs @@ -1,10 +1,12 @@ use serde_json::Number; use super::modals::{AddArtistModal, EditArtistModal}; -use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES; +use crate::app::lidarr::lidarr_context_clues::{ + ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, +}; use crate::models::{ BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState, - lidarr_models::{AddArtistSearchResult, Artist, DownloadRecord}, + lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord}, servarr_models::{DiskSpace, RootFolder}, stateful_table::StatefulTable, }; @@ -19,8 +21,8 @@ use { crate::models::stateful_table::SortOption, crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map, crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{ - add_artist_search_result, download_record, metadata_profile, metadata_profile_map, - quality_profile, root_folder, tags_map, + add_artist_search_result, album, artist, download_record, metadata_profile, + metadata_profile_map, quality_profile, root_folder, tags_map, }, crate::network::servarr_test_utils::diskspace, strum::{Display, EnumString, IntoEnumIterator}, @@ -35,6 +37,8 @@ pub struct LidarrData<'a> { pub add_artist_search: Option, pub add_import_list_exclusion: bool, pub add_searched_artists: Option>, + pub albums: StatefulTable, + pub artist_info_tabs: TabState, pub artists: StatefulTable, pub delete_artist_files: bool, pub disk_space_vec: Vec, @@ -58,6 +62,11 @@ impl LidarrData<'_> { self.add_import_list_exclusion = false; } + pub fn reset_artist_info_tabs(&mut self) { + self.albums = StatefulTable::default(); + self.artist_info_tabs.index = 0; + } + pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String { tag_ids .iter() @@ -97,6 +106,7 @@ impl<'a> Default for LidarrData<'a> { add_artist_search: None, add_import_list_exclusion: false, add_searched_artists: None, + albums: StatefulTable::default(), artists: StatefulTable::default(), delete_artist_files: false, disk_space_vec: Vec::new(), @@ -117,6 +127,12 @@ impl<'a> Default for LidarrData<'a> { contextual_help: Some(&ARTISTS_CONTEXT_CLUES), config: None, }]), + artist_info_tabs: TabState::new(vec![TabRoute { + title: "Albums".to_string(), + route: ActiveLidarrBlock::ArtistDetails.into(), + contextual_help: Some(&ARTIST_DETAILS_CONTEXT_CLUES), + config: None, + }]), } } } @@ -169,7 +185,9 @@ impl LidarrData<'_> { tags_map: tags_map(), ..LidarrData::default() }; - lidarr_data.artists.set_items(vec![Artist::default()]); + lidarr_data.albums.set_items(vec![album()]); + lidarr_data.albums.search = Some("album search".into()); + lidarr_data.artists.set_items(vec![artist()]); lidarr_data.artists.sorting(vec![SortOption { name: "Name", cmp_fn: Some(|a: &Artist, b: &Artist| a.artist_name.text.cmp(&b.artist_name.text)), @@ -193,6 +211,7 @@ impl LidarrData<'_> { pub enum ActiveLidarrBlock { #[default] Artists, + ArtistDetails, ArtistsSortPrompt, AddArtistAlreadyInLibrary, AddArtistConfirmPrompt, @@ -206,6 +225,7 @@ pub enum ActiveLidarrBlock { AddArtistSelectQualityProfile, AddArtistSelectRootFolder, AddArtistTagsInput, + AutomaticallySearchArtistPrompt, DeleteArtistPrompt, DeleteArtistConfirmPrompt, DeleteArtistToggleDeleteFile, @@ -220,9 +240,12 @@ pub enum ActiveLidarrBlock { EditArtistToggleMonitored, FilterArtists, FilterArtistsError, + SearchAlbums, + SearchAlbumsError, SearchArtists, SearchArtistsError, UpdateAllArtistsPrompt, + UpdateAndScanArtistPrompt, } pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [ @@ -235,6 +258,14 @@ pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [ ActiveLidarrBlock::UpdateAllArtistsPrompt, ]; +pub static ARTIST_DETAILS_BLOCKS: [ActiveLidarrBlock; 5] = [ + ActiveLidarrBlock::ArtistDetails, + ActiveLidarrBlock::AutomaticallySearchArtistPrompt, + ActiveLidarrBlock::SearchAlbums, + ActiveLidarrBlock::SearchAlbumsError, + ActiveLidarrBlock::UpdateAndScanArtistPrompt, +]; + pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [ ActiveLidarrBlock::AddArtistAlreadyInLibrary, ActiveLidarrBlock::AddArtistConfirmPrompt, diff --git a/src/models/servarr_data/lidarr/lidarr_data_tests.rs b/src/models/servarr_data/lidarr/lidarr_data_tests.rs index ada2a60..55ad5c1 100644 --- a/src/models/servarr_data/lidarr/lidarr_data_tests.rs +++ b/src/models/servarr_data/lidarr/lidarr_data_tests.rs @@ -1,8 +1,11 @@ #[cfg(test)] mod tests { - use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES; + use crate::app::lidarr::lidarr_context_clues::{ + ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, + }; + use crate::models::lidarr_models::Album; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, + ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, }; use crate::models::{ @@ -44,6 +47,18 @@ mod tests { assert!(!lidarr_data.add_import_list_exclusion); } + #[test] + fn test_reset_artist_info_tabs() { + let mut lidarr_data = LidarrData::default(); + lidarr_data.albums.set_items(vec![Album::default()]); + lidarr_data.artist_info_tabs.index = 1; + + lidarr_data.reset_artist_info_tabs(); + + assert_is_empty!(lidarr_data.albums); + assert_eq!(lidarr_data.artist_info_tabs.index, 0); + } + #[test] fn test_tag_ids_to_display() { let mut tags_map = BiMap::new(); @@ -112,6 +127,7 @@ mod tests { assert_none!(lidarr_data.add_artist_search); assert!(!lidarr_data.add_import_list_exclusion); 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_is_empty!(lidarr_data.disk_space_vec); @@ -139,6 +155,18 @@ mod tests { &ARTISTS_CONTEXT_CLUES ); assert_none!(lidarr_data.main_tabs.tabs[0].config); + + assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1); + assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums"); + assert_eq!( + lidarr_data.artist_info_tabs.tabs[0].route, + ActiveLidarrBlock::ArtistDetails.into() + ); + assert_some_eq_x!( + &lidarr_data.artist_info_tabs.tabs[0].contextual_help, + &ARTIST_DETAILS_CONTEXT_CLUES + ); + assert_none!(lidarr_data.artist_info_tabs.tabs[0].config); } #[test] @@ -153,6 +181,16 @@ mod tests { assert!(LIBRARY_BLOCKS.contains(&ActiveLidarrBlock::UpdateAllArtistsPrompt)); } + #[test] + fn test_artist_details_blocks_contains_expected_blocks() { + assert_eq!(ARTIST_DETAILS_BLOCKS.len(), 5); + assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::ArtistDetails)); + assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::AutomaticallySearchArtistPrompt)); + assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::SearchAlbums)); + assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::SearchAlbumsError)); + assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt)); + } + #[test] fn test_add_artist_blocks_contents() { assert_eq!(ADD_ARTIST_BLOCKS.len(), 12); diff --git a/src/network/lidarr_network/library/albums/lidarr_albums_network_tests.rs b/src/network/lidarr_network/library/albums/lidarr_albums_network_tests.rs new file mode 100644 index 0000000..61a8652 --- /dev/null +++ b/src/network/lidarr_network/library/albums/lidarr_albums_network_tests.rs @@ -0,0 +1,106 @@ +#[cfg(test)] +mod tests { + use mockito::Matcher; + use pretty_assertions::assert_eq; + use serde_json::{json, Value}; + use crate::models::lidarr_models::{Album, LidarrSerdeable}; + use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{ALBUM_JSON}; + use crate::network::lidarr_network::LidarrEvent; + use crate::network::network_tests::test_utils::{test_network, MockServarrApi}; + + #[tokio::test] + async fn test_handle_get_albums_event() { + let albums_json = json!([{ + "id": 1, + "title": "Test Album", + "foreignAlbumId": "test-foreign-album-id", + "monitored": true, + "anyReleaseOk": true, + "profileId": 1, + "duration": 180, + "albumType": "Album", + "genres": ["Classical"], + "ratings": {"votes": 15, "value": 8.4}, + "releaseDate": "2023-01-01T00:00:00Z", + "statistics": { + "trackFileCount": 10, + "trackCount": 10, + "totalTrackCount": 10, + "sizeOnDisk": 1024, + "percentOfTracks": 99.9 + } + }]); + let response: Vec = serde_json::from_value(albums_json.clone()).unwrap(); + let (mock, app, _server) = MockServarrApi::get() + .returns(albums_json) + .query("artistId=1") + .build_for(LidarrEvent::GetAlbums(1)) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let result = network.handle_lidarr_event(LidarrEvent::GetAlbums(1)).await; + + mock.assert_async().await; + + let LidarrSerdeable::Albums(albums) = result.unwrap() else { + panic!("Expected Albums"); + }; + + assert_eq!(albums, response); + assert!(!app.lock().await.data.lidarr_data.albums.is_empty()); + } + + #[tokio::test] + async fn test_handle_toggle_album_monitoring_event() { + let mut expected_body: Value = serde_json::from_str(ALBUM_JSON).unwrap(); + *expected_body.get_mut("monitored").unwrap() = json!(false); + let (get_mock, app, mut server) = MockServarrApi::get() + .returns(serde_json::from_str(ALBUM_JSON).unwrap()) + .path("/1") + .build_for(LidarrEvent::GetAlbums(1)) + .await; + let put_mock = server + .mock("PUT", "/api/v1/album/1") + .match_body(Matcher::Json(expected_body)) + .match_header("X-Api-Key", "test1234") + .with_status(202) + .create_async() + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + assert_ok!( + network + .handle_lidarr_event(LidarrEvent::ToggleAlbumMonitoring(1)) + .await + ); + + get_mock.assert_async().await; + put_mock.assert_async().await; + } + + #[tokio::test] + async fn test_handle_get_album_details_event() { + let expected_album: Album = serde_json::from_str(ALBUM_JSON).unwrap(); + let (mock, app, _server) = MockServarrApi::get() + .returns(serde_json::from_str(ALBUM_JSON).unwrap()) + .path("/1") + .build_for(LidarrEvent::GetAlbumDetails(1)) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let result = network + .handle_lidarr_event(LidarrEvent::GetAlbumDetails(1)) + .await; + + mock.assert_async().await; + + let LidarrSerdeable::Album(album) = result.unwrap() else { + panic!("Expected Album"); + }; + + assert_eq!(album, expected_album); + } +} diff --git a/src/network/lidarr_network/library/albums/mod.rs b/src/network/lidarr_network/library/albums/mod.rs new file mode 100644 index 0000000..63c41fa --- /dev/null +++ b/src/network/lidarr_network/library/albums/mod.rs @@ -0,0 +1,121 @@ +use crate::models::lidarr_models::{Album}; +use crate::network::lidarr_network::LidarrEvent; +use crate::network::{Network, RequestMethod}; +use anyhow::Result; +use log::{debug, info, warn}; +use serde_json::{Value, json}; + +#[cfg(test)] +#[path = "lidarr_albums_network_tests.rs"] +mod lidarr_albums_network_tests; + +impl Network<'_, '_> { + pub(in crate::network::lidarr_network) async fn get_albums( + &mut self, + artist_id: i64, + ) -> Result> { + info!("Fetching albums for Lidarr artist with ID: {artist_id}"); + let event = LidarrEvent::GetAlbums(artist_id); + + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + None, + Some(format!("artistId={artist_id}")), + ) + .await; + + self + .handle_request::<(), Vec>(request_props, |mut albums_vec, mut app| { + albums_vec.sort_by(|a, b| a.id.cmp(&b.id)); + app.data.lidarr_data.albums.set_items(albums_vec); + }) + .await + } + + pub(in crate::network::lidarr_network) async fn get_album_details( + &mut self, + album_id: i64, + ) -> Result { + info!("Fetching details for Lidarr album with ID: {album_id}"); + let event = LidarrEvent::GetAlbumDetails(album_id); + + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + Some(format!("/{album_id}")), + None, + ) + .await; + + self + .handle_request::<(), Album>(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn toggle_album_monitoring( + &mut self, + album_id: i64, + ) -> Result<()> { + let event = LidarrEvent::ToggleAlbumMonitoring(album_id); + info!("Toggling album monitoring for album with ID: {album_id}"); + info!("Fetching album details for album with ID: {album_id}"); + + let detail_event = LidarrEvent::GetAlbums(0); + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{album_id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_album_body, _| { + response = detailed_album_body.to_string() + }) + .await?; + + info!("Constructing toggle album monitoring body"); + + match serde_json::from_str::(&response) { + Ok(mut detailed_album_body) => { + let monitored = detailed_album_body + .get("monitored") + .unwrap() + .as_bool() + .unwrap(); + + *detailed_album_body.get_mut("monitored").unwrap() = json!(!monitored); + + debug!("Toggle album monitoring body: {detailed_album_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_album_body), + Some(format!("/{album_id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + Err(_) => { + warn!("Request for detailed album body was interrupted"); + Ok(()) + } + } + } +} diff --git a/src/network/lidarr_network/library/lidarr_library_network_tests.rs b/src/network/lidarr_network/library/artists/lidarr_artists_network_tests.rs similarity index 93% rename from src/network/lidarr_network/library/lidarr_library_network_tests.rs rename to src/network/lidarr_network/library/artists/lidarr_artists_network_tests.rs index 781ae1c..5b3e45f 100644 --- a/src/network/lidarr_network/library/lidarr_library_network_tests.rs +++ b/src/network/lidarr_network/library/artists/lidarr_artists_network_tests.rs @@ -134,11 +134,10 @@ mod tests { app.lock().await.server_tabs.set_index(2); let mut network = test_network(&app); - assert!( + assert_ok!( network .handle_lidarr_event(LidarrEvent::ToggleArtistMonitoring(1)) .await - .is_ok() ); get_mock.assert_async().await; @@ -167,6 +166,52 @@ mod tests { mock.assert_async().await; } + #[tokio::test] + async fn test_handle_update_and_scan_artist_event() { + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "RefreshArtist", + "artistId": 1 + })) + .returns(json!({})) + .build_for(LidarrEvent::UpdateAndScanArtist(1)) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + assert!( + network + .handle_lidarr_event(LidarrEvent::UpdateAndScanArtist(1)) + .await + .is_ok() + ); + + mock.assert_async().await; + } + + #[tokio::test] + async fn test_handle_trigger_automatic_artist_search_event() { + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "ArtistSearch", + "artistId": 1 + })) + .returns(json!({})) + .build_for(LidarrEvent::TriggerAutomaticArtistSearch(1)) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + assert!( + network + .handle_lidarr_event(LidarrEvent::TriggerAutomaticArtistSearch(1)) + .await + .is_ok() + ); + + mock.assert_async().await; + } + #[tokio::test] async fn test_handle_edit_artist_event() { let mut expected_body: Value = serde_json::from_str(ARTIST_JSON).unwrap(); diff --git a/src/network/lidarr_network/library/artists/mod.rs b/src/network/lidarr_network/library/artists/mod.rs new file mode 100644 index 0000000..f7bc5ec --- /dev/null +++ b/src/network/lidarr_network/library/artists/mod.rs @@ -0,0 +1,399 @@ +use anyhow::Result; +use log::{debug, info, warn}; +use serde_json::{Value, json}; + +use crate::models::Route; +use crate::models::lidarr_models::{ + AddArtistBody, AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams, + LidarrCommandBody, +}; +use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; +use crate::models::stateful_table::StatefulTable; +use crate::network::lidarr_network::LidarrEvent; +use crate::network::{Network, RequestMethod}; +use urlencoding::encode; + +#[cfg(test)] +#[path = "lidarr_artists_network_tests.rs"] +mod lidarr_artists_network_tests; + +impl Network<'_, '_> { + pub(in crate::network::lidarr_network) async fn delete_artist( + &mut self, + delete_artist_params: DeleteArtistParams, + ) -> Result<()> { + let event = LidarrEvent::DeleteArtist(DeleteArtistParams::default()); + let DeleteArtistParams { + id, + delete_files, + add_import_list_exclusion, + } = delete_artist_params; + + info!( + "Deleting Lidarr artist with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}" + ); + + let request_props = self + .request_props_from( + event, + RequestMethod::Delete, + None::<()>, + Some(format!("/{id}")), + Some(format!( + "deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}" + )), + ) + .await; + + self + .handle_request::<(), ()>(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn list_artists(&mut self) -> Result> { + info!("Fetching Lidarr artists"); + let event = LidarrEvent::ListArtists; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), Vec>(request_props, |mut artists_vec, mut app| { + if !matches!( + app.get_current_route(), + Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _) + ) { + artists_vec.sort_by(|a, b| a.id.cmp(&b.id)); + app.data.lidarr_data.artists.set_items(artists_vec); + app.data.lidarr_data.artists.apply_sorting_toggle(false); + } + }) + .await + } + + pub(in crate::network::lidarr_network) async fn get_artist_details( + &mut self, + artist_id: i64, + ) -> Result { + info!("Fetching details for Lidarr artist with ID: {artist_id}"); + let event = LidarrEvent::GetArtistDetails(artist_id); + + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + Some(format!("/{artist_id}")), + None, + ) + .await; + + self + .handle_request::<(), Artist>(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn toggle_artist_monitoring( + &mut self, + artist_id: i64, + ) -> Result<()> { + let event = LidarrEvent::ToggleArtistMonitoring(artist_id); + + let detail_event = LidarrEvent::GetArtistDetails(artist_id); + info!("Toggling artist monitoring for artist with ID: {artist_id}"); + info!("Fetching artist details for artist with ID: {artist_id}"); + + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{artist_id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_artist_body, _| { + response = detailed_artist_body.to_string() + }) + .await?; + + info!("Constructing toggle artist monitoring body"); + + match serde_json::from_str::(&response) { + Ok(mut detailed_artist_body) => { + let monitored = detailed_artist_body + .get("monitored") + .unwrap() + .as_bool() + .unwrap(); + + *detailed_artist_body.get_mut("monitored").unwrap() = json!(!monitored); + + debug!("Toggle artist monitoring body: {detailed_artist_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_artist_body), + Some(format!("/{artist_id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + Err(_) => { + warn!("Request for detailed artist body was interrupted"); + Ok(()) + } + } + } + + pub(in crate::network::lidarr_network) async fn update_all_artists(&mut self) -> Result { + info!("Updating all artists"); + let event = LidarrEvent::UpdateAllArtists; + let body = LidarrCommandBody { + name: "RefreshArtist".to_owned(), + ..LidarrCommandBody::default() + }; + + let request_props = self + .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn update_and_scan_artist( + &mut self, + artist_id: i64, + ) -> Result { + let event = LidarrEvent::UpdateAndScanArtist(artist_id); + info!("Updating and scanning artist with ID: {artist_id}"); + let body = LidarrCommandBody { + name: "RefreshArtist".to_owned(), + artist_id: Some(artist_id), + ..LidarrCommandBody::default() + }; + + let request_props = self + .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn trigger_automatic_artist_search( + &mut self, + artist_id: i64, + ) -> Result { + let event = LidarrEvent::TriggerAutomaticArtistSearch(artist_id); + info!("Searching indexers for artist with ID: {artist_id}"); + let body = LidarrCommandBody { + name: "ArtistSearch".to_owned(), + artist_id: Some(artist_id), + ..LidarrCommandBody::default() + }; + + let request_props = self + .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn search_artist( + &mut self, + query: String, + ) -> Result> { + info!("Searching for artist: {query}"); + let event = LidarrEvent::SearchNewArtist(String::new()); + + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + None, + Some(format!("term={}", encode(&query))), + ) + .await; + + let result = self + .handle_request::<(), Vec>(request_props, |artist_vec, mut app| { + if artist_vec.is_empty() { + app.pop_and_push_navigation_stack(ActiveLidarrBlock::AddArtistEmptySearchResults.into()); + } else if let Some(add_searched_artists) = + app.data.lidarr_data.add_searched_artists.as_mut() + { + add_searched_artists.set_items(artist_vec); + } else { + let mut add_searched_artists = StatefulTable::default(); + add_searched_artists.set_items(artist_vec); + app.data.lidarr_data.add_searched_artists = Some(add_searched_artists); + } + }) + .await; + + if result.is_err() { + self.app.lock().await.data.lidarr_data.add_searched_artists = Some(StatefulTable::default()); + } + + result + } + + pub(in crate::network::lidarr_network) async fn add_artist( + &mut self, + mut add_artist_body: AddArtistBody, + ) -> Result { + info!("Adding Lidarr artist: {}", add_artist_body.artist_name); + if let Some(tag_input_str) = add_artist_body.tag_input_string.as_ref() { + let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await; + add_artist_body.tags = tag_ids_vec; + } + let event = LidarrEvent::AddArtist(AddArtistBody::default()); + + let request_props = self + .request_props_from( + event, + RequestMethod::Post, + Some(add_artist_body), + None, + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn edit_artist( + &mut self, + mut edit_artist_params: EditArtistParams, + ) -> Result<()> { + info!("Editing Lidarr artist"); + if let Some(tag_input_str) = edit_artist_params.tag_input_string.as_ref() { + let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await; + edit_artist_params.tags = Some(tag_ids_vec); + } + let artist_id = edit_artist_params.artist_id; + let detail_event = LidarrEvent::GetArtistDetails(artist_id); + let event = LidarrEvent::EditArtist(EditArtistParams::default()); + info!("Fetching artist details for artist with ID: {artist_id}"); + + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{artist_id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_artist_body, _| { + response = detailed_artist_body.to_string() + }) + .await?; + + info!("Constructing edit artist body"); + + let mut detailed_artist_body: Value = serde_json::from_str(&response)?; + let ( + monitored, + monitor_new_items, + quality_profile_id, + metadata_profile_id, + root_folder_path, + tags, + ) = { + let monitored = edit_artist_params.monitored.unwrap_or( + detailed_artist_body["monitored"] + .as_bool() + .expect("Unable to deserialize 'monitored'"), + ); + let monitor_new_items = edit_artist_params.monitor_new_items.unwrap_or_else(|| { + serde_json::from_value(detailed_artist_body["monitorNewItems"].clone()) + .expect("Unable to deserialize 'monitorNewItems'") + }); + let quality_profile_id = edit_artist_params.quality_profile_id.unwrap_or_else(|| { + detailed_artist_body["qualityProfileId"] + .as_i64() + .expect("Unable to deserialize 'qualityProfileId'") + }); + let metadata_profile_id = edit_artist_params.metadata_profile_id.unwrap_or_else(|| { + detailed_artist_body["metadataProfileId"] + .as_i64() + .expect("Unable to deserialize 'metadataProfileId'") + }); + let root_folder_path = edit_artist_params.root_folder_path.unwrap_or_else(|| { + detailed_artist_body["path"] + .as_str() + .expect("Unable to deserialize 'path'") + .to_owned() + }); + let tags = if edit_artist_params.clear_tags { + vec![] + } else { + edit_artist_params.tags.unwrap_or( + detailed_artist_body["tags"] + .as_array() + .expect("Unable to deserialize 'tags'") + .iter() + .map(|item| item.as_i64().expect("Unable to deserialize tag ID")) + .collect(), + ) + }; + + ( + monitored, + monitor_new_items, + quality_profile_id, + metadata_profile_id, + root_folder_path, + tags, + ) + }; + + *detailed_artist_body.get_mut("monitored").unwrap() = json!(monitored); + *detailed_artist_body.get_mut("monitorNewItems").unwrap() = json!(monitor_new_items); + *detailed_artist_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id); + *detailed_artist_body.get_mut("metadataProfileId").unwrap() = json!(metadata_profile_id); + *detailed_artist_body.get_mut("path").unwrap() = json!(root_folder_path); + *detailed_artist_body.get_mut("tags").unwrap() = json!(tags); + + debug!("Edit artist body: {detailed_artist_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_artist_body), + Some(format!("/{artist_id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } +} diff --git a/src/network/lidarr_network/library/mod.rs b/src/network/lidarr_network/library/mod.rs index 4a89cd9..3b6eaff 100644 --- a/src/network/lidarr_network/library/mod.rs +++ b/src/network/lidarr_network/library/mod.rs @@ -1,356 +1,2 @@ -use anyhow::Result; -use log::{debug, info, warn}; -use serde_json::{Value, json}; - -use crate::models::Route; -use crate::models::lidarr_models::{ - AddArtistBody, AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams, -}; -use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; -use crate::models::servarr_models::CommandBody; -use crate::models::stateful_table::StatefulTable; -use crate::network::lidarr_network::LidarrEvent; -use crate::network::{Network, RequestMethod}; -use urlencoding::encode; - -#[cfg(test)] -#[path = "lidarr_library_network_tests.rs"] -mod lidarr_library_network_tests; - -impl Network<'_, '_> { - pub(in crate::network::lidarr_network) async fn delete_artist( - &mut self, - delete_artist_params: DeleteArtistParams, - ) -> Result<()> { - let event = LidarrEvent::DeleteArtist(DeleteArtistParams::default()); - let DeleteArtistParams { - id, - delete_files, - add_import_list_exclusion, - } = delete_artist_params; - - info!( - "Deleting Lidarr artist with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}" - ); - - let request_props = self - .request_props_from( - event, - RequestMethod::Delete, - None::<()>, - Some(format!("/{id}")), - Some(format!( - "deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}" - )), - ) - .await; - - self - .handle_request::<(), ()>(request_props, |_, _| ()) - .await - } - - pub(in crate::network::lidarr_network) async fn list_artists(&mut self) -> Result> { - info!("Fetching Lidarr artists"); - let event = LidarrEvent::ListArtists; - - let request_props = self - .request_props_from(event, RequestMethod::Get, None::<()>, None, None) - .await; - - self - .handle_request::<(), Vec>(request_props, |mut artists_vec, mut app| { - if !matches!( - app.get_current_route(), - Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _) - ) { - artists_vec.sort_by(|a, b| a.id.cmp(&b.id)); - app.data.lidarr_data.artists.set_items(artists_vec); - app.data.lidarr_data.artists.apply_sorting_toggle(false); - } - }) - .await - } - - pub(in crate::network::lidarr_network) async fn get_artist_details( - &mut self, - artist_id: i64, - ) -> Result { - info!("Fetching details for Lidarr artist with ID: {artist_id}"); - let event = LidarrEvent::GetArtistDetails(artist_id); - - let request_props = self - .request_props_from( - event, - RequestMethod::Get, - None::<()>, - Some(format!("/{artist_id}")), - None, - ) - .await; - - self - .handle_request::<(), Artist>(request_props, |_, _| ()) - .await - } - - pub(in crate::network::lidarr_network) async fn toggle_artist_monitoring( - &mut self, - artist_id: i64, - ) -> Result<()> { - let event = LidarrEvent::ToggleArtistMonitoring(artist_id); - - let detail_event = LidarrEvent::GetArtistDetails(artist_id); - info!("Toggling artist monitoring for artist with ID: {artist_id}"); - info!("Fetching artist details for artist with ID: {artist_id}"); - - let request_props = self - .request_props_from( - detail_event, - RequestMethod::Get, - None::<()>, - Some(format!("/{artist_id}")), - None, - ) - .await; - - let mut response = String::new(); - - self - .handle_request::<(), Value>(request_props, |detailed_artist_body, _| { - response = detailed_artist_body.to_string() - }) - .await?; - - info!("Constructing toggle artist monitoring body"); - - match serde_json::from_str::(&response) { - Ok(mut detailed_artist_body) => { - let monitored = detailed_artist_body - .get("monitored") - .unwrap() - .as_bool() - .unwrap(); - - *detailed_artist_body.get_mut("monitored").unwrap() = json!(!monitored); - - debug!("Toggle artist monitoring body: {detailed_artist_body:?}"); - - let request_props = self - .request_props_from( - event, - RequestMethod::Put, - Some(detailed_artist_body), - Some(format!("/{artist_id}")), - None, - ) - .await; - - self - .handle_request::(request_props, |_, _| ()) - .await - } - Err(_) => { - warn!("Request for detailed artist body was interrupted"); - Ok(()) - } - } - } - - pub(in crate::network::lidarr_network) async fn update_all_artists(&mut self) -> Result { - info!("Updating all artists"); - let event = LidarrEvent::UpdateAllArtists; - let body = CommandBody { - name: "RefreshArtist".to_owned(), - }; - - let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) - .await; - - self - .handle_request::(request_props, |_, _| ()) - .await - } - - pub(in crate::network::lidarr_network) async fn search_artist( - &mut self, - query: String, - ) -> Result> { - info!("Searching for artist: {query}"); - let event = LidarrEvent::SearchNewArtist(String::new()); - - let request_props = self - .request_props_from( - event, - RequestMethod::Get, - None::<()>, - None, - Some(format!("term={}", encode(&query))), - ) - .await; - - let result = self - .handle_request::<(), Vec>(request_props, |artist_vec, mut app| { - if artist_vec.is_empty() { - app.pop_and_push_navigation_stack(ActiveLidarrBlock::AddArtistEmptySearchResults.into()); - } else if let Some(add_searched_artists) = - app.data.lidarr_data.add_searched_artists.as_mut() - { - add_searched_artists.set_items(artist_vec); - } else { - let mut add_searched_artists = StatefulTable::default(); - add_searched_artists.set_items(artist_vec); - app.data.lidarr_data.add_searched_artists = Some(add_searched_artists); - } - }) - .await; - - if result.is_err() { - self.app.lock().await.data.lidarr_data.add_searched_artists = Some(StatefulTable::default()); - } - - result - } - - pub(in crate::network::lidarr_network) async fn add_artist( - &mut self, - mut add_artist_body: AddArtistBody, - ) -> Result { - info!("Adding Lidarr artist: {}", add_artist_body.artist_name); - if let Some(tag_input_str) = add_artist_body.tag_input_string.as_ref() { - let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await; - add_artist_body.tags = tag_ids_vec; - } - let event = LidarrEvent::AddArtist(AddArtistBody::default()); - - let request_props = self - .request_props_from( - event, - RequestMethod::Post, - Some(add_artist_body), - None, - None, - ) - .await; - - self - .handle_request::(request_props, |_, _| ()) - .await - } - - pub(in crate::network::lidarr_network) async fn edit_artist( - &mut self, - mut edit_artist_params: EditArtistParams, - ) -> Result<()> { - info!("Editing Lidarr artist"); - if let Some(tag_input_str) = edit_artist_params.tag_input_string.as_ref() { - let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await; - edit_artist_params.tags = Some(tag_ids_vec); - } - let artist_id = edit_artist_params.artist_id; - let detail_event = LidarrEvent::GetArtistDetails(artist_id); - let event = LidarrEvent::EditArtist(EditArtistParams::default()); - info!("Fetching artist details for artist with ID: {artist_id}"); - - let request_props = self - .request_props_from( - detail_event, - RequestMethod::Get, - None::<()>, - Some(format!("/{artist_id}")), - None, - ) - .await; - - let mut response = String::new(); - - self - .handle_request::<(), Value>(request_props, |detailed_artist_body, _| { - response = detailed_artist_body.to_string() - }) - .await?; - - info!("Constructing edit artist body"); - - let mut detailed_artist_body: Value = serde_json::from_str(&response)?; - let ( - monitored, - monitor_new_items, - quality_profile_id, - metadata_profile_id, - root_folder_path, - tags, - ) = { - let monitored = edit_artist_params.monitored.unwrap_or( - detailed_artist_body["monitored"] - .as_bool() - .expect("Unable to deserialize 'monitored'"), - ); - let monitor_new_items = edit_artist_params.monitor_new_items.unwrap_or_else(|| { - serde_json::from_value(detailed_artist_body["monitorNewItems"].clone()) - .expect("Unable to deserialize 'monitorNewItems'") - }); - let quality_profile_id = edit_artist_params.quality_profile_id.unwrap_or_else(|| { - detailed_artist_body["qualityProfileId"] - .as_i64() - .expect("Unable to deserialize 'qualityProfileId'") - }); - let metadata_profile_id = edit_artist_params.metadata_profile_id.unwrap_or_else(|| { - detailed_artist_body["metadataProfileId"] - .as_i64() - .expect("Unable to deserialize 'metadataProfileId'") - }); - let root_folder_path = edit_artist_params.root_folder_path.unwrap_or_else(|| { - detailed_artist_body["path"] - .as_str() - .expect("Unable to deserialize 'path'") - .to_owned() - }); - let tags = if edit_artist_params.clear_tags { - vec![] - } else { - edit_artist_params.tags.unwrap_or( - detailed_artist_body["tags"] - .as_array() - .expect("Unable to deserialize 'tags'") - .iter() - .map(|item| item.as_i64().expect("Unable to deserialize tag ID")) - .collect(), - ) - }; - - ( - monitored, - monitor_new_items, - quality_profile_id, - metadata_profile_id, - root_folder_path, - tags, - ) - }; - - *detailed_artist_body.get_mut("monitored").unwrap() = json!(monitored); - *detailed_artist_body.get_mut("monitorNewItems").unwrap() = json!(monitor_new_items); - *detailed_artist_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id); - *detailed_artist_body.get_mut("metadataProfileId").unwrap() = json!(metadata_profile_id); - *detailed_artist_body.get_mut("path").unwrap() = json!(root_folder_path); - *detailed_artist_body.get_mut("tags").unwrap() = json!(tags); - - debug!("Edit artist body: {detailed_artist_body:?}"); - - let request_props = self - .request_props_from( - event, - RequestMethod::Put, - Some(detailed_artist_body), - Some(format!("/{artist_id}")), - None, - ) - .await; - - self - .handle_request::(request_props, |_, _| ()) - .await - } -} +mod albums; +mod artists; diff --git a/src/network/lidarr_network/lidarr_network_test_utils.rs b/src/network/lidarr_network/lidarr_network_test_utils.rs index 2b05c7f..a5e514d 100644 --- a/src/network/lidarr_network/lidarr_network_test_utils.rs +++ b/src/network/lidarr_network/lidarr_network_test_utils.rs @@ -3,9 +3,9 @@ pub mod test_utils { use crate::models::HorizontallyScrollableText; use crate::models::lidarr_models::{ - AddArtistSearchResult, Artist, ArtistStatistics, ArtistStatus, DownloadRecord, DownloadStatus, - DownloadsResponse, EditArtistParams, Member, MetadataProfile, NewItemMonitorType, Ratings, - SystemStatus, + AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus, + DownloadRecord, DownloadStatus, DownloadsResponse, EditArtistParams, Member, MetadataProfile, + NewItemMonitorType, Ratings, SystemStatus, }; use crate::models::servarr_models::{QualityProfile, RootFolder, Tag}; use bimap::BiMap; @@ -50,6 +50,27 @@ pub mod test_utils { "percentOfTracks": 99.9 } }"#; + + pub const ALBUM_JSON: &str = r#"{ + "id": 1, + "title": "Test Album", + "foreignAlbumId": "test-foreign-album-id", + "monitored": true, + "anyReleaseOk": true, + "profileId": 1, + "duration": 180, + "albumType": "Album", + "genres": ["Classical"], + "ratings": {"votes": 15, "value": 8.4}, + "releaseDate": "2023-01-01T00:00:00Z", + "statistics": { + "trackFileCount": 10, + "trackCount": 10, + "totalTrackCount": 10, + "sizeOnDisk": 1024, + "percentOfTracks": 99.9 + } + }"#; pub fn member() -> Member { Member { @@ -199,4 +220,33 @@ pub mod test_utils { ratings: Some(ratings()), } } + + pub fn album_statistics() -> AlbumStatistics { + AlbumStatistics { + track_file_count: 10, + track_count: 10, + total_track_count: 10, + size_on_disk: 1024, + percent_of_tracks: 99.9, + } + } + + pub fn album() -> Album { + Album { + id: 1, + title: "Test Album".into(), + foreign_album_id: "test-foreign-album-id".to_string(), + monitored: true, + any_release_ok: true, + profile_id: 1, + duration: 180, + album_type: Some("Album".to_owned()), + genres: vec!["Classical".to_owned()], + ratings: Some(ratings()), + release_date: Some(DateTime::from( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z").unwrap(), + )), + statistics: Some(album_statistics()), + } + } } diff --git a/src/network/lidarr_network/lidarr_network_tests.rs b/src/network/lidarr_network/lidarr_network_tests.rs index 35f66a4..c9f1e7a 100644 --- a/src/network/lidarr_network/lidarr_network_tests.rs +++ b/src/network/lidarr_network/lidarr_network_tests.rs @@ -46,10 +46,24 @@ mod tests { } #[rstest] - fn test_resource_command(#[values(LidarrEvent::UpdateAllArtists)] event: LidarrEvent) { + fn test_resource_command( + #[values( + LidarrEvent::UpdateAllArtists, + LidarrEvent::TriggerAutomaticArtistSearch(0), + LidarrEvent::UpdateAndScanArtist(0) + )] + event: LidarrEvent, + ) { assert_str_eq!(event.resource(), "/command"); } + #[rstest] + fn test_resource_albums( + #[values(LidarrEvent::GetAlbums(0), LidarrEvent::ToggleAlbumMonitoring(0), LidarrEvent::GetAlbumDetails(0))] event: LidarrEvent, + ) { + assert_str_eq!(event.resource(), "/album"); + } + #[rstest] #[case(LidarrEvent::GetDiskSpace, "/diskspace")] #[case(LidarrEvent::GetDownloads(500), "/queue")] diff --git a/src/network/lidarr_network/mod.rs b/src/network/lidarr_network/mod.rs index de9576e..518c357 100644 --- a/src/network/lidarr_network/mod.rs +++ b/src/network/lidarr_network/mod.rs @@ -28,6 +28,8 @@ pub enum LidarrEvent { DeleteArtist(DeleteArtistParams), DeleteTag(i64), EditArtist(EditArtistParams), + GetAlbums(i64), + GetAlbumDetails(i64), GetArtistDetails(i64), GetDiskSpace, GetDownloads(u64), @@ -41,8 +43,11 @@ pub enum LidarrEvent { HealthCheck, ListArtists, SearchNewArtist(String), + ToggleAlbumMonitoring(i64), ToggleArtistMonitoring(i64), + TriggerAutomaticArtistSearch(i64), UpdateAllArtists, + UpdateAndScanArtist(i64), } impl NetworkResource for LidarrEvent { @@ -55,10 +60,13 @@ impl NetworkResource for LidarrEvent { | LidarrEvent::ListArtists | LidarrEvent::AddArtist(_) | LidarrEvent::ToggleArtistMonitoring(_) => "/artist", + LidarrEvent::GetAlbums(_) | LidarrEvent::ToggleAlbumMonitoring(_) | LidarrEvent::GetAlbumDetails(_) => "/album", LidarrEvent::GetDiskSpace => "/diskspace", LidarrEvent::GetDownloads(_) => "/queue", LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host", - LidarrEvent::UpdateAllArtists => "/command", + LidarrEvent::TriggerAutomaticArtistSearch(_) + | LidarrEvent::UpdateAllArtists + | LidarrEvent::UpdateAndScanArtist(_) => "/command", LidarrEvent::GetMetadataProfiles => "/metadataprofile", LidarrEvent::GetQualityProfiles => "/qualityprofile", LidarrEvent::GetRootFolders => "/rootfolder", @@ -89,10 +97,14 @@ impl Network<'_, '_> { .delete_lidarr_tag(tag_id) .await .map(LidarrSerdeable::from), + LidarrEvent::GetAlbums(artist_id) => { + self.get_albums(artist_id).await.map(LidarrSerdeable::from) + } LidarrEvent::GetArtistDetails(artist_id) => self .get_artist_details(artist_id) .await .map(LidarrSerdeable::from), + LidarrEvent::GetAlbumDetails(album_id) => self.get_album_details(album_id).await.map(LidarrSerdeable::from), LidarrEvent::GetDiskSpace => self.get_lidarr_diskspace().await.map(LidarrSerdeable::from), LidarrEvent::GetDownloads(count) => self .get_lidarr_downloads(count) @@ -128,11 +140,23 @@ impl Network<'_, '_> { LidarrEvent::SearchNewArtist(query) => { self.search_artist(query).await.map(LidarrSerdeable::from) } + LidarrEvent::ToggleAlbumMonitoring(album_id) => self + .toggle_album_monitoring(album_id) + .await + .map(LidarrSerdeable::from), LidarrEvent::ToggleArtistMonitoring(artist_id) => self .toggle_artist_monitoring(artist_id) .await .map(LidarrSerdeable::from), + LidarrEvent::TriggerAutomaticArtistSearch(artist_id) => self + .trigger_automatic_artist_search(artist_id) + .await + .map(LidarrSerdeable::from), LidarrEvent::UpdateAllArtists => self.update_all_artists().await.map(LidarrSerdeable::from), + LidarrEvent::UpdateAndScanArtist(artist_id) => self + .update_and_scan_artist(artist_id) + .await + .map(LidarrSerdeable::from), LidarrEvent::EditArtist(params) => self.edit_artist(params).await.map(LidarrSerdeable::from), LidarrEvent::AddArtist(body) => self.add_artist(body).await.map(LidarrSerdeable::from), } diff --git a/src/ui/lidarr_ui/library/artist_details_ui.rs b/src/ui/lidarr_ui/library/artist_details_ui.rs new file mode 100644 index 0000000..faf70ac --- /dev/null +++ b/src/ui/lidarr_ui/library/artist_details_ui.rs @@ -0,0 +1,327 @@ +use chrono::Utc; +use deunicode::deunicode; +use ratatui::Frame; +use ratatui::layout::{Constraint, Layout, Rect}; +use ratatui::style::Stylize; +use ratatui::text::Line; +use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; +use regex::Regex; + +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::styles::ManagarrStyle; +use crate::ui::utils::{ + borderless_block, get_width_from_percentage, layout_block_top_border, title_block, +}; +use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; +use crate::ui::widgets::managarr_table::ManagarrTable; +use crate::ui::widgets::popup::{Popup, Size}; +use crate::ui::{DrawUi, draw_popup, draw_tabs}; +use crate::utils::convert_to_gb; + +#[cfg(test)] +#[path = "artist_details_ui_tests.rs"] +mod artist_details_ui_tests; + +pub(super) struct ArtistDetailsUi; + +impl DrawUi for ArtistDetailsUi { + fn accepts(route: Route) -> bool { + let Route::Lidarr(active_lidarr_block, _) = route else { + return false; + }; + ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) + } + + 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| { + f.render_widget( + title_block( + &app + .data + .lidarr_data + .artists + .current_selection() + .artist_name + .text, + ), + popup_area, + ); + let [description_area, detail_area] = + Layout::vertical([Constraint::Percentage(37), Constraint::Fill(0)]) + .margin(1) + .areas(popup_area); + draw_artist_description(f, app, description_area); + let content_area = draw_tabs( + f, + detail_area, + "Artist Details", + &app.data.lidarr_data.artist_info_tabs, + ); + draw_artist_details(f, app, content_area); + + match active_lidarr_block { + ActiveLidarrBlock::AutomaticallySearchArtistPrompt => { + let prompt = format!( + "Do you want to trigger an automatic search of your indexers for all monitored album(s) for the artist: {}?", + app.data.lidarr_data.artists.current_selection().artist_name + ); + let confirmation_prompt = ConfirmationPrompt::new() + .title("Automatic Artist Search") + .prompt(&prompt) + .yes_no_value(app.data.lidarr_data.prompt_confirm); + + f.render_widget( + Popup::new(confirmation_prompt).size(Size::MediumPrompt), + f.area(), + ); + } + ActiveLidarrBlock::UpdateAndScanArtistPrompt => { + let prompt = format!( + "Do you want to trigger an update and disk scan for the artist: {}?", + app.data.lidarr_data.artists.current_selection().artist_name + ); + let confirmation_prompt = ConfirmationPrompt::new() + .title("Update and Scan") + .prompt(&prompt) + .yes_no_value(app.data.lidarr_data.prompt_confirm); + + f.render_widget( + Popup::new(confirmation_prompt).size(Size::MediumPrompt), + f.area(), + ); + } + _ => (), + } + }; + + draw_popup(f, app, draw_artist_details_popup, Size::XXLarge); + } + } +} + +fn draw_artist_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + let current_selection = app.data.lidarr_data.artists.current_selection(); + let monitored = if current_selection.monitored { + "Yes" + } else { + "No" + }; + let quality_profile = app + .data + .lidarr_data + .quality_profile_map + .get_by_left(¤t_selection.quality_profile_id) + .cloned() + .unwrap_or_default(); + let metadata_profile = app + .data + .lidarr_data + .metadata_profile_map + .get_by_left(¤t_selection.metadata_profile_id) + .cloned() + .unwrap_or_default(); + let overview = Regex::new(r"[\r\n\t]") + .unwrap() + .replace_all( + &deunicode( + current_selection + .overview + .as_ref() + .unwrap_or(&String::new()), + ), + "", + ) + .to_string(); + + let mut artist_description = vec![ + Line::from(vec![ + "Artist: ".primary().bold(), + current_selection.artist_name.text.clone().primary().bold(), + ]), + Line::from(vec![ + "Overview: ".primary().bold(), + overview.default_color(), + ]), + Line::from(vec![ + "Disambiguation: ".primary().bold(), + current_selection + .disambiguation + .clone() + .unwrap_or_default() + .default_color(), + ]), + Line::from(vec![ + "Type: ".primary().bold(), + current_selection + .artist_type + .clone() + .unwrap_or_default() + .default_color(), + ]), + Line::from(vec![ + "Status: ".primary().bold(), + current_selection.status.to_display_str().default_color(), + ]), + Line::from(vec![ + "Genres: ".primary().bold(), + current_selection.genres.join(", ").default_color(), + ]), + Line::from(vec![ + "Rating: ".primary().bold(), + current_selection + .ratings + .as_ref() + .map_or_else( + || "N/A".to_owned(), + |r| format!("{}%", (r.value * 10.0) as i32), + ) + .default_color(), + ]), + Line::from(vec![ + "Path: ".primary().bold(), + current_selection.path.clone().default_color(), + ]), + Line::from(vec![ + "Quality Profile: ".primary().bold(), + quality_profile.default_color(), + ]), + Line::from(vec![ + "Metadata Profile: ".primary().bold(), + metadata_profile.default_color(), + ]), + Line::from(vec![ + "Monitored: ".primary().bold(), + monitored.default_color(), + ]), + ]; + + if let Some(stats) = current_selection.statistics.as_ref() { + let size = convert_to_gb(stats.size_on_disk); + artist_description.extend(vec![ + Line::from(vec![ + "Albums: ".primary().bold(), + stats.album_count.to_string().default_color(), + ]), + Line::from(vec![ + "Tracks: ".primary().bold(), + format!("{}/{}", stats.track_file_count, stats.total_track_count).default_color(), + ]), + Line::from(vec![ + "Size on Disk: ".primary().bold(), + format!("{size:.2} GB").default_color(), + ]), + ]); + } + + let description_paragraph = Paragraph::new(artist_description) + .block(borderless_block()) + .wrap(Wrap { trim: true }); + f.render_widget(description_paragraph, area); +} + +fn draw_artist_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + if let Route::Lidarr(active_lidarr_block, _) = + app.data.lidarr_data.artist_info_tabs.get_active_route() + { + match active_lidarr_block { + ActiveLidarrBlock::ArtistDetails => draw_albums_table(f, app, area), + _ => (), + } + } +} + +fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() { + let current_selection = if app.data.lidarr_data.albums.is_empty() { + Album::default() + } else { + app.data.lidarr_data.albums.current_selection().clone() + }; + let content = Some(&mut app.data.lidarr_data.albums); + let album_row_mapping = |album: &Album| { + album.title.scroll_left_or_reset( + get_width_from_percentage(area, 33), + *album == current_selection, + app.ui_scroll_tick_count == 0, + ); + let monitored = if album.monitored { "🏷" } else { "" }; + let album_type = album.album_type.clone().unwrap_or_default(); + let release_date = album + .release_date + .map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string()); + let track_count = album.statistics.as_ref().map_or_else( + || "0/0".to_owned(), + |s| format!("{}/{}", s.track_file_count, s.total_track_count), + ); + let size = album + .statistics + .as_ref() + .map_or(0f64, |s| convert_to_gb(s.size_on_disk)); + let duration_mins = album.duration / 60000; + + let row = Row::new(vec![ + Cell::from(monitored.to_owned()), + Cell::from(album.title.to_string()), + Cell::from(album_type), + Cell::from(track_count), + Cell::from(format!("{duration_mins} min")), + Cell::from(release_date), + Cell::from(format!("{size:.2} GB")), + ]); + + if !album.monitored { + row.unmonitored() + } else if let Some(stats) = album.statistics.as_ref() { + if stats.track_file_count == stats.total_track_count && stats.total_track_count > 0 { + row.downloaded() + } else if let Some(release_date) = album.release_date.as_ref() { + if release_date > &Utc::now() { + row.unreleased() + } else { + row.missing() + } + } else { + row.missing() + } + } else { + row.indeterminate() + } + }; + + let is_searching = active_lidarr_block == ActiveLidarrBlock::SearchAlbums; + let album_table = ManagarrTable::new(content, album_row_mapping) + .block(layout_block_top_border()) + .loading(app.is_loading) + .searching(is_searching) + .search_produced_empty_results(active_lidarr_block == ActiveLidarrBlock::SearchAlbumsError) + .headers([ + "Monitored", + "Title", + "Type", + "Tracks", + "Duration", + "Release Date", + "Size", + ]) + .constraints([ + Constraint::Percentage(7), + Constraint::Percentage(35), + Constraint::Percentage(10), + Constraint::Percentage(10), + Constraint::Percentage(10), + Constraint::Percentage(13), + Constraint::Percentage(15), + ]); + + if is_searching { + album_table.show_cursor(f, area); + } + + f.render_widget(album_table, area); + } +} diff --git a/src/ui/lidarr_ui/library/artist_details_ui_tests.rs b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs new file mode 100644 index 0000000..865c2ea --- /dev/null +++ b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs @@ -0,0 +1,125 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::models::servarr_data::lidarr::lidarr_data::{ + ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, + }; + use crate::ui::DrawUi; + use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; + + #[test] + fn test_artist_details_ui_accepts() { + let mut blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec(); + ActiveLidarrBlock::iter().for_each(|active_lidarr_block| { + if blocks.contains(&active_lidarr_block) { + assert!(ArtistDetailsUi::accepts(active_lidarr_block.into())); + } else { + assert!(!ArtistDetailsUi::accepts(active_lidarr_block.into())); + } + }); + } + + mod snapshot_tests { + use rstest::rstest; + + use crate::app::App; + use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock; + use crate::models::stateful_table::StatefulTable; + use crate::ui::DrawUi; + use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app}; + + #[rstest] + #[case(ActiveLidarrBlock::ArtistDetails, 0)] + #[case(ActiveLidarrBlock::SearchAlbums, 0)] + #[case(ActiveLidarrBlock::SearchAlbumsError, 0)] + #[case(ActiveLidarrBlock::UpdateAndScanArtistPrompt, 0)] + #[case(ActiveLidarrBlock::AutomaticallySearchArtistPrompt, 0)] + fn test_artist_details_ui_renders( + #[case] active_lidarr_block: ActiveLidarrBlock, + #[case] index: usize, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + app.data.lidarr_data.artist_info_tabs.set_index(index); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ArtistDetailsUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!( + format!("artist_details_{active_lidarr_block}_{index}"), + output + ); + } + + #[rstest] + #[case(ActiveLidarrBlock::ArtistDetails, 0)] + fn test_artist_details_ui_renders_artist_details_loading( + #[case] active_lidarr_block: ActiveLidarrBlock, + #[case] index: usize, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_lidarr_block.into()); + app.data.lidarr_data.artist_info_tabs.set_index(index); + app.is_loading = true; + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ArtistDetailsUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!( + format!("loading_artist_details_{active_lidarr_block}"), + output + ); + } + + #[rstest] + #[case(ActiveLidarrBlock::ArtistDetails, 0)] + fn test_artist_details_ui_renders_artist_details_empty( + #[case] active_lidarr_block: ActiveLidarrBlock, + #[case] index: usize, + ) { + let mut app = App::test_default_fully_populated(); + app.data.lidarr_data.albums = StatefulTable::default(); + app.push_navigation_stack(active_lidarr_block.into()); + app.data.lidarr_data.artist_info_tabs.set_index(index); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ArtistDetailsUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!( + format!("empty_artist_details_{active_lidarr_block}"), + output + ); + } + + #[test] + fn test_artist_details_ui_renders_update_and_scan_prompt_over_artist_details() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::UpdateAndScanArtistPrompt.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_automatic_search_prompt_over_artist_details() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + ArtistDetailsUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + } +} diff --git a/src/ui/lidarr_ui/library/edit_artist_ui.rs b/src/ui/lidarr_ui/library/edit_artist_ui.rs index 8f33ad3..304c822 100644 --- a/src/ui/lidarr_ui/library/edit_artist_ui.rs +++ b/src/ui/lidarr_ui/library/edit_artist_ui.rs @@ -7,10 +7,13 @@ use ratatui::widgets::ListItem; use crate::app::App; use crate::models::Route; -use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS}; +use crate::models::servarr_data::lidarr::lidarr_data::{ + ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, +}; use crate::models::servarr_data::lidarr::modals::EditArtistModal; use crate::render_selectable_input_box; +use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; use crate::ui::utils::title_block_centered; use crate::ui::widgets::button::Button; use crate::ui::widgets::checkbox::Checkbox; @@ -34,7 +37,13 @@ impl DrawUi for EditArtistUi { } fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) { - if let Route::Lidarr(active_lidarr_block, _context_option) = app.get_current_route() { + if let Route::Lidarr(active_lidarr_block, context_option) = app.get_current_route() { + if let Some(context) = context_option + && ARTIST_DETAILS_BLOCKS.contains(&context) + { + draw_popup(f, app, ArtistDetailsUi::draw, Size::Large); + } + let draw_edit_artist_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| { draw_edit_artist_confirmation_prompt(f, app, prompt_area); diff --git a/src/ui/lidarr_ui/library/library_ui_tests.rs b/src/ui/lidarr_ui/library/library_ui_tests.rs index 040d96e..85dea67 100644 --- a/src/ui/lidarr_ui/library/library_ui_tests.rs +++ b/src/ui/lidarr_ui/library/library_ui_tests.rs @@ -4,7 +4,8 @@ mod tests { use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus}; use crate::models::servarr_data::lidarr::lidarr_data::{ - ADD_ARTIST_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS, + ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, + EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS, }; use crate::ui::DrawUi; use crate::ui::lidarr_ui::library::{LibraryUi, decorate_artist_row_with_style}; @@ -19,6 +20,7 @@ mod tests { library_ui_blocks.extend(DELETE_ARTIST_BLOCKS); library_ui_blocks.extend(EDIT_ARTIST_BLOCKS); library_ui_blocks.extend(ADD_ARTIST_BLOCKS); + library_ui_blocks.extend(ARTIST_DETAILS_BLOCKS); for active_lidarr_block in ActiveLidarrBlock::iter() { if library_ui_blocks.contains(&active_lidarr_block) { @@ -182,7 +184,7 @@ mod tests { use crate::app::App; use crate::models::BlockSelectionState; use crate::models::servarr_data::lidarr::lidarr_data::{ - ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, + ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, }; use rstest::rstest; @@ -263,5 +265,49 @@ mod tests { insta::assert_snapshot!(output); } + + #[test] + fn test_library_ui_renders_artist_details_over_library() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + LibraryUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_library_ui_renders_edit_artist_over_artist_details() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into()); + app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + LibraryUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + + #[test] + fn test_library_ui_renders_dropdown_over_edit_artist_over_artist_details() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::Artists.into()); + app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into()); + app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into()); + app.push_navigation_stack(ActiveLidarrBlock::EditArtistSelectMetadataProfile.into()); + app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + LibraryUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } } } diff --git a/src/ui/lidarr_ui/library/mod.rs b/src/ui/lidarr_ui/library/mod.rs index 115c048..997b868 100644 --- a/src/ui/lidarr_ui/library/mod.rs +++ b/src/ui/lidarr_ui/library/mod.rs @@ -1,4 +1,5 @@ use add_artist_ui::AddArtistUi; +use artist_details_ui::ArtistDetailsUi; use delete_artist_ui::DeleteArtistUi; use edit_artist_ui::EditArtistUi; use ratatui::{ @@ -28,6 +29,7 @@ use crate::{ }; mod add_artist_ui; +mod artist_details_ui; mod delete_artist_ui; mod edit_artist_ui; @@ -43,6 +45,7 @@ impl DrawUi for LibraryUi { return AddArtistUi::accepts(route) || DeleteArtistUi::accepts(route) || EditArtistUi::accepts(route) + || ArtistDetailsUi::accepts(route) || LIBRARY_BLOCKS.contains(&active_lidarr_block); } @@ -57,6 +60,7 @@ impl DrawUi for LibraryUi { _ 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), 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__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistEmptySearchResults.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistEmptySearchResults.snap deleted file mode 100644 index 68d5e7f..0000000 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistEmptySearchResults.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs -expression: output ---- - - - - - - - - ╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮ - │Test Artist │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ ╭─────────────── Error ───────────────╮ │ - │ │ No artists found matching your query! │ │ - │ │ │ │ - │ ╰───────────────────────────────────────╯ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchInput.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchInput.snap deleted file mode 100644 index 1f81b1d..0000000 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchInput.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs -expression: output ---- - - - - - - - - ╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮ - │Test Artist │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchResults.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchResults.snap deleted file mode 100644 index 007dda8..0000000 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__AddArtistSearchResults.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs -expression: output ---- - - - - - - - - ╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮ - │Test Artist │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ ✔ Name Type Status Rating Genres │ - │=> Test Artist Person Continuing 8.4 soundtrack │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_already_in_library_ui_renders.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_already_in_library_ui_renders.snap index 0476b2f..1d69061 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_already_in_library_ui_renders.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_already_in_library_ui_renders.snap @@ -14,7 +14,7 @@ expression: output ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ ✔ Name Type Status Rating Genres │ - │=> Test Artist Person Continuing 8.4 soundtrack │ + │=> ✔ Test Artist Person Continuing 8.4 soundtrack │ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistPrompt.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistPrompt.snap index 2eaecd9..3b97587 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistPrompt.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistPrompt.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMetadataProfile.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMetadataProfile.snap index 41195bd..78471ed 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMetadataProfile.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMetadataProfile.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitor.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitor.snap index cef8638..a11a32c 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitor.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitor.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitorNewItems.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitorNewItems.snap index 0cb823c..269177c 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitorNewItems.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectMonitorNewItems.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectQualityProfile.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectQualityProfile.snap index 2a74c48..3f94c56 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectQualityProfile.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectQualityProfile.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectRootFolder.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectRootFolder.snap index 90a69df..a947912 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectRootFolder.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistSelectRootFolder.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistTagsInput.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistTagsInput.snap index 2eaecd9..3b97587 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistTagsInput.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_modal_AddArtistTagsInput.snap @@ -14,7 +14,7 @@ expression: output ╰──────│ │───────╯ ╭──────│ │───────╮ │ ✔ │ │ │ - │=> │ │ │ + │=> ✔ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭─────────────────────────────────────────────────╮ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_ui_AddArtistSearchResults.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_ui_AddArtistSearchResults.snap index 007dda8..890828a 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_ui_AddArtistSearchResults.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__add_artist_ui__add_artist_ui_tests__tests__snapshot_tests__add_artist_ui_AddArtistSearchResults.snap @@ -14,7 +14,7 @@ expression: output ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ ✔ Name Type Status Rating Genres │ - │=> Test Artist Person Continuing 8.4 soundtrack │ + │=> ✔ Test Artist Person Continuing 8.4 soundtrack │ │ │ │ │ │ │ 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_ArtistDetails_0.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_ArtistDetails_0.snap new file mode 100644 index 0000000..bc3c8d1 --- /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_ArtistDetails_0.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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ Monitored Title Type Tracks Duration Release Date Size ││ + ││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_AutomaticallySearchArtistPrompt_0.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_AutomaticallySearchArtistPrompt_0.snap new file mode 100644 index 0000000..320efb0 --- /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_AutomaticallySearchArtistPrompt_0.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 ╭──────────────── Automatic Artist Search ────────────────╮ │ + │Size on Disk: 0.00 GB │Do you want to trigger an automatic search of your indexers│ │ + │ │ for all monitored album(s) for the artist: Alex? │ │ + │ │ │ │ + │╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│ + ││ Albums │ │ ││ + ││─────────────────────────────────────────│ │───────────────────────────────────────────││ + ││ Monitored Title │ │ Release Date Size ││ + ││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │╭────────────────────────────╮╭───────────────────────────╮│ ││ + ││ ││ Yes ││ No ││ ││ + ││ │╰────────────────────────────╯╰───────────────────────────╯│ ││ + ││ ╰───────────────────────────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_SearchAlbumsError_0.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_SearchAlbumsError_0.snap new file mode 100644 index 0000000..29b51e9 --- /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_SearchAlbumsError_0.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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ Monitored Title Type Tracks Duration Release Date Size ││ + ││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ╭──────────── Error ─────────────╮ ││ + ││ │ No items found matching search │ ││ + ││ ╰──────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_SearchAlbums_0.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_SearchAlbums_0.snap new file mode 100644 index 0000000..d85a44f --- /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_SearchAlbums_0.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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ Monitored Title Type Tracks Duration Release Date Size ││ + ││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ╭────────────── Search ───────────────╮ ││ + ││ │album search │ ││ + ││ ╰───────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_UpdateAndScanArtistPrompt_0.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_UpdateAndScanArtistPrompt_0.snap new file mode 100644 index 0000000..86b5f16 --- /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_UpdateAndScanArtistPrompt_0.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 ╭──────────────────── Update and Scan ────────────────────╮ │ + │Size on Disk: 0.00 GB │ Do you want to trigger an update and disk scan for the │ │ + │ │ artist: Alex? │ │ + │ │ │ │ + │╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│ + ││ Albums │ │ ││ + ││─────────────────────────────────────────│ │───────────────────────────────────────────││ + ││ Monitored Title │ │ Release Date Size ││ + ││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │╭────────────────────────────╮╭───────────────────────────╮│ ││ + ││ ││ Yes ││ No ││ ││ + ││ │╰────────────────────────────╯╰───────────────────────────╯│ ││ + ││ ╰───────────────────────────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_automatic_search_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_automatic_search_prompt_over_artist_details.snap new file mode 100644 index 0000000..320efb0 --- /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_automatic_search_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 ╭──────────────── Automatic Artist Search ────────────────╮ │ + │Size on Disk: 0.00 GB │Do you want to trigger an automatic search of your indexers│ │ + │ │ for all monitored album(s) for the artist: Alex? │ │ + │ │ │ │ + │╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│ + ││ Albums │ │ ││ + ││─────────────────────────────────────────│ │───────────────────────────────────────────││ + ││ Monitored Title │ │ Release Date Size ││ + ││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │╭────────────────────────────╮╭───────────────────────────╮│ ││ + ││ ││ Yes ││ No ││ ││ + ││ │╰────────────────────────────╯╰───────────────────────────╯│ ││ + ││ ╰───────────────────────────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 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_update_and_scan_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_update_and_scan_prompt_over_artist_details.snap new file mode 100644 index 0000000..86b5f16 --- /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_update_and_scan_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 ╭──────────────────── Update and Scan ────────────────────╮ │ + │Size on Disk: 0.00 GB │ Do you want to trigger an update and disk scan for the │ │ + │ │ artist: Alex? │ │ + │ │ │ │ + │╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│ + ││ Albums │ │ ││ + ││─────────────────────────────────────────│ │───────────────────────────────────────────││ + ││ Monitored Title │ │ Release Date Size ││ + ││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │ │ ││ + ││ │╭────────────────────────────╮╭───────────────────────────╮│ ││ + ││ ││ Yes ││ No ││ ││ + ││ │╰────────────────────────────╯╰───────────────────────────╯│ ││ + ││ ╰───────────────────────────────────────────────────────────╯ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__empty_artist_details_ArtistDetails.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__empty_artist_details_ArtistDetails.snap new file mode 100644 index 0000000..fddabc4 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__empty_artist_details_ArtistDetails.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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__loading_artist_details_ArtistDetails.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__loading_artist_details_ArtistDetails.snap new file mode 100644 index 0000000..2951df4 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__artist_details_ui__artist_details_ui_tests__tests__snapshot_tests__loading_artist_details_ArtistDetails.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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ ││ + ││ ││ + ││ Loading ... ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_artist_ui__delete_artist_ui_tests__tests__snapshot_tests__delete_artist_ui_renders_delete_artist.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_artist_ui__delete_artist_ui_tests__tests__snapshot_tests__delete_artist_ui_renders_delete_artist.snap index 7c770ca..98bd800 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_artist_ui__delete_artist_ui_tests__tests__snapshot_tests__delete_artist_ui_renders_delete_artist.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__delete_artist_ui__delete_artist_ui_tests__tests__snapshot_tests__delete_artist_ui_renders_delete_artist.snap @@ -20,7 +20,7 @@ expression: output ╭───────────────────── Delete Artist ─────────────────────╮ │ Do you really want to delete the artist: │ - │ ? │ + │ Alex? │ │ │ │ │ │ ╭───╮ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistConfirmPrompt.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistConfirmPrompt.snap index 0518e01..460dad4 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistConfirmPrompt.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistConfirmPrompt.snap @@ -8,7 +8,7 @@ expression: output - ╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮ + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistPrompt.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistPrompt.snap index 0518e01..460dad4 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistPrompt.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistPrompt.snap @@ -8,7 +8,7 @@ expression: output - ╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮ + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMetadataProfile.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMetadataProfile.snap index e07f1c6..709d31c 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMetadataProfile.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMetadataProfile.snap @@ -8,7 +8,7 @@ expression: output - ╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮ + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMonitorNewItems.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMonitorNewItems.snap index b8d5af3..9b232a2 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMonitorNewItems.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectMonitorNewItems.snap @@ -8,7 +8,7 @@ expression: output - ╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮ + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectQualityProfile.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectQualityProfile.snap index fdb719d..45a5e12 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectQualityProfile.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__edit_artist_ui__edit_artist_ui_tests__tests__snapshot_tests__edit_artist_EditArtistSelectQualityProfile.snap @@ -8,7 +8,7 @@ expression: output - ╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮ + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ │ │ │ │ │ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_artist_details_over_library.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_artist_details_over_library.snap new file mode 100644 index 0000000..80b2830 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_artist_details_over_library.snap @@ -0,0 +1,52 @@ +--- +source: src/ui/lidarr_ui/library/library_ui_tests.rs +expression: output +--- +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex + ╭ 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 │ + │Size on Disk: 0.00 GB │ + │ │ + │ │ + │╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ + ││ Albums ││ + ││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││ + ││ Monitored Title Type Tracks Duration Release Date Size ││ + ││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + ││ ││ + │╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_artist_over_library.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_artist_over_library.snap index a85e622..c4602f8 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_artist_over_library.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_artist_over_library.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex @@ -20,7 +20,7 @@ expression: output ╭───────────────────── Delete Artist ─────────────────────╮ │ Do you really want to delete the artist: │ - │ ? │ + │ Alex? │ │ │ │ │ │ ╭───╮ │ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_dropdown_over_edit_artist_over_artist_details.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_dropdown_over_edit_artist_over_artist_details.snap new file mode 100644 index 0000000..311a053 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_dropdown_over_edit_artist_over_artist_details.snap @@ -0,0 +1,48 @@ +--- +source: src/ui/lidarr_ui/library/library_ui_tests.rs +expression: output +--- +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex + + + + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ ╭───╮ │ + │ Monitored: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───────────────────────────────╮──────────────────────────────╮ │ + │ Monitor│Standard │ ▼ │ │ + │ │ │──────────────────────────────╯ │ + │ │ │──────────────────────────────╮ │ + │ Qual│ │ ▼ │ │ + │ │ │──────────────────────────────╯ │ + │ │ │──────────────────────────────╮ │ + │ Metad│ │ ▼ │ │ + │ │ │──────────────────────────────╯ │ + │ │ │──────────────────────────────╮ │ + │ │ │ │ │ + │ │ │──────────────────────────────╯ │ + │ │ │──────────────────────────────╮ │ + │ │ │ │ │ + │ ╰───────────────────────────────╯──────────────────────────────╯ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ + ││ Save ││ Cancel ││ + │╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│ + ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_edit_artist_over_artist_details.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_edit_artist_over_artist_details.snap new file mode 100644 index 0000000..8539203 --- /dev/null +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_edit_artist_over_artist_details.snap @@ -0,0 +1,48 @@ +--- +source: src/ui/lidarr_ui/library/library_ui_tests.rs +expression: output +--- +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex + + + + ╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ ╭───╮ │ + │ Monitored: │ ✔ │ │ + │ ╰───╯ │ + │ ╭─────────────────────────────────────────────────╮ │ + │ Monitor New Albums: │All Albums ▼ │ │ + │ ╰─────────────────────────────────────────────────╯ │ + │ ╭─────────────────────────────────────────────────╮ │ + │ Quality Profile: │Lossless ▼ │ │ + │ ╰─────────────────────────────────────────────────╯ │ + │ ╭─────────────────────────────────────────────────╮ │ + │ Metadata Profile: │Standard ▼ │ │ + │ ╰─────────────────────────────────────────────────╯ │ + │ ╭─────────────────────────────────────────────────╮ │ + │ Path: │/nfs/music │ │ + │ ╰─────────────────────────────────────────────────╯ │ + │ ╭─────────────────────────────────────────────────╮ │ + │ Tags: │alex │ │ + │ ╰─────────────────────────────────────────────────╯ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ + ││ Save ││ Cancel ││ + │╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│ + ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_update_all_artists_prompt.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_update_all_artists_prompt.snap index 2f2678c..d20a2fa 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_update_all_artists_prompt.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_update_all_artists_prompt.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_Artists.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_Artists.snap index bf51331..1443a4f 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_Artists.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_Artists.snap @@ -4,4 +4,4 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_ArtistsSortPrompt.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_ArtistsSortPrompt.snap index 1204b85..c2d2087 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_ArtistsSortPrompt.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_ArtistsSortPrompt.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtists.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtists.snap index 3421078..24b5f60 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtists.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtists.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtistsError.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtistsError.snap index 913cbea..2b5b359 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtistsError.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_FilterArtistsError.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtists.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtists.snap index 42c7af9..1c21242 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtists.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtists.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex diff --git a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtistsError.snap b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtistsError.snap index e9ee85e..6af1685 100644 --- a/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtistsError.snap +++ b/src/ui/lidarr_ui/library/snapshots/managarr__ui__lidarr_ui__library__library_ui_tests__tests__snapshot_tests__lidarr_library_SearchArtistsError.snap @@ -4,7 +4,7 @@ expression: output --- ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags -=> Continuing 0 0.00 GB +=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex