feat: Downloads tab support in Lidarr
This commit is contained in:
@@ -12,6 +12,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_lidarr_block_artists() {
|
async fn test_dispatch_by_lidarr_block_artists() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -37,6 +38,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_lidarr_block_artist_details() {
|
async fn test_dispatch_by_lidarr_block_artist_details() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
@@ -50,10 +52,31 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_downloads_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Downloads)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetDownloads(500).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
app.data.lidarr_data.add_artist_search = Some("test artist".into());
|
app.data.lidarr_data.add_artist_search = Some("test artist".into());
|
||||||
|
|
||||||
@@ -74,6 +97,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_history_block() {
|
async fn test_dispatch_by_history_block() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
app
|
app
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ impl App<'_> {
|
|||||||
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::Downloads => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveLidarrBlock::ArtistDetails => {
|
ActiveLidarrBlock::ArtistDetails => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ pub enum LidarrDeleteCommand {
|
|||||||
#[arg(long, help = "Add a list exclusion for this artist")]
|
#[arg(long, help = "Add a list exclusion for this artist")]
|
||||||
add_list_exclusion: bool,
|
add_list_exclusion: bool,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Delete the specified download")]
|
||||||
|
Download {
|
||||||
|
#[arg(long, help = "The ID of the download to delete", required = true)]
|
||||||
|
download_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Delete the tag with the specified ID")]
|
#[command(about = "Delete the tag with the specified ID")]
|
||||||
Tag {
|
Tag {
|
||||||
#[arg(long, help = "The ID of the tag to delete", required = true)]
|
#[arg(long, help = "The ID of the tag to delete", required = true)]
|
||||||
@@ -103,6 +108,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrDeleteCommand::Download { download_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteDownload(download_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrDeleteCommand::Tag { tag_id } => {
|
LidarrDeleteCommand::Tag { tag_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -145,6 +145,40 @@ mod tests {
|
|||||||
assert_eq!(delete_command, expected_args);
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "download"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"download",
|
||||||
|
"--download-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_tag_requires_arguments() {
|
fn test_delete_tag_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
|
||||||
@@ -260,6 +294,32 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_download_command() {
|
||||||
|
let expected_download_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteDownload(expected_download_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_download_command = LidarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_download_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_tag_command() {
|
async fn test_handle_delete_tag_command() {
|
||||||
let expected_tag_id = 1;
|
let expected_tag_id = 1;
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ pub enum LidarrListCommand {
|
|||||||
},
|
},
|
||||||
#[command(about = "List all artists in your Lidarr library")]
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
Artists,
|
Artists,
|
||||||
|
#[command(about = "List all active downloads in Lidarr")]
|
||||||
|
Downloads {
|
||||||
|
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
||||||
|
count: u64,
|
||||||
|
},
|
||||||
#[command(about = "Fetch all Lidarr history events")]
|
#[command(about = "Fetch all Lidarr history events")]
|
||||||
History {
|
History {
|
||||||
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
|
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
|
||||||
@@ -83,6 +88,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrListCommand::Downloads { count } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetDownloads(count).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrListCommand::History { events: items } => {
|
LidarrListCommand::History { events: items } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -58,6 +58,29 @@ mod tests {
|
|||||||
assert_eq!(album_command, expected_args);
|
assert_eq!(album_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_downloads_count_flag_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "downloads", "--count"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_downloads_default_values() {
|
||||||
|
let expected_args = LidarrListCommand::Downloads { count: 500 };
|
||||||
|
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "downloads"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(downloads_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(downloads_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_history_events_flag_requires_arguments() {
|
fn test_list_history_events_flag_requires_arguments() {
|
||||||
let result =
|
let result =
|
||||||
@@ -151,6 +174,32 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_downloads_command() {
|
||||||
|
let expected_count = 1000;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetDownloads(expected_count).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_downloads_command = LidarrListCommand::Downloads { count: 1000 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrListCommandHandler::with(&app_arc, list_downloads_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_list_history_command() {
|
async fn test_handle_list_history_command() {
|
||||||
let expected_events = 1000;
|
let expected_events = 1000;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ pub enum LidarrRefreshCommand {
|
|||||||
)]
|
)]
|
||||||
artist_id: i64,
|
artist_id: i64,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Refresh all downloads in Lidarr")]
|
||||||
|
Downloads,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LidarrRefreshCommand> for Command {
|
impl From<LidarrRefreshCommand> for Command {
|
||||||
@@ -73,6 +75,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand>
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrRefreshCommand::Downloads => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::UpdateDownloads.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use clap::{Parser, error::ErrorKind};
|
use clap::{Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[rstest]
|
||||||
fn test_refresh_all_artists_has_no_arg_requirements() {
|
fn test_refresh_commands_have_no_arg_requirements(
|
||||||
|
#[values("all-artists", "downloads")] subcommand: &str,
|
||||||
|
) {
|
||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "all-artists"]);
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", subcommand]);
|
||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,7 @@ mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
|
use rstest::rstest;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -80,12 +84,18 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(LidarrRefreshCommand::AllArtists, LidarrEvent::UpdateAllArtists)]
|
||||||
|
#[case(LidarrRefreshCommand::Downloads, LidarrEvent::UpdateDownloads)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_refresh_all_artists_command() {
|
async fn test_handle_refresh_command(
|
||||||
|
#[case] refresh_command: LidarrRefreshCommand,
|
||||||
|
#[case] expected_sonarr_event: LidarrEvent,
|
||||||
|
) {
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(LidarrEvent::UpdateAllArtists.into()))
|
.with(eq::<NetworkEvent>(expected_sonarr_event.into()))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
@@ -93,7 +103,6 @@ mod tests {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_command = LidarrRefreshCommand::AllArtists;
|
|
||||||
|
|
||||||
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
|
|||||||
@@ -330,93 +330,7 @@ mod test_utils {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! test_handler_delegation {
|
macro_rules! test_handler_delegation {
|
||||||
($handler:ident, $base:expr, $active_block:expr) => {
|
($handler:ident, $base:expr, $active_block:expr) => {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.data.sonarr_data.history.set_items(vec![
|
|
||||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
|
||||||
]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.root_folders
|
|
||||||
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.indexers
|
|
||||||
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.blocklist
|
|
||||||
.set_items(vec![$crate::models::sonarr_models::BlocklistItem::default()]);
|
|
||||||
app.data.sonarr_data.add_searched_series =
|
|
||||||
Some($crate::models::stateful_table::StatefulTable::default());
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.movies
|
|
||||||
.set_items(vec![$crate::models::radarr_models::Movie::default()]);
|
|
||||||
app.data.radarr_data.history.set_items(vec![
|
|
||||||
$crate::models::radarr_models::RadarrHistoryItem::default(),
|
|
||||||
]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.collections
|
|
||||||
.set_items(vec![$crate::models::radarr_models::Collection::default()]);
|
|
||||||
app.data.radarr_data.collection_movies.set_items(vec![
|
|
||||||
$crate::models::radarr_models::CollectionMovie::default(),
|
|
||||||
]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.indexers
|
|
||||||
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.root_folders
|
|
||||||
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.blocklist
|
|
||||||
.set_items(vec![$crate::models::radarr_models::BlocklistItem::default()]);
|
|
||||||
app.data.radarr_data.add_searched_movies =
|
|
||||||
Some($crate::models::stateful_table::StatefulTable::default());
|
|
||||||
let mut movie_details_modal =
|
|
||||||
$crate::models::servarr_data::radarr::modals::MovieDetailsModal::default();
|
|
||||||
movie_details_modal.movie_history.set_items(vec![
|
|
||||||
$crate::models::radarr_models::MovieHistoryItem::default(),
|
|
||||||
]);
|
|
||||||
movie_details_modal
|
|
||||||
.movie_cast
|
|
||||||
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
|
|
||||||
movie_details_modal
|
|
||||||
.movie_crew
|
|
||||||
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
|
|
||||||
movie_details_modal
|
|
||||||
.movie_releases
|
|
||||||
.set_items(vec![$crate::models::radarr_models::RadarrRelease::default()]);
|
|
||||||
app.data.radarr_data.movie_details_modal = Some(movie_details_modal);
|
|
||||||
let mut season_details_modal =
|
|
||||||
$crate::models::servarr_data::sonarr::modals::SeasonDetailsModal::default();
|
|
||||||
season_details_modal.season_history.set_items(vec![
|
|
||||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
|
||||||
]);
|
|
||||||
season_details_modal.episode_details_modal =
|
|
||||||
Some($crate::models::servarr_data::sonarr::modals::EpisodeDetailsModal::default());
|
|
||||||
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
|
||||||
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
|
|
||||||
series_history.set_items(vec![
|
|
||||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
|
||||||
]);
|
|
||||||
app.data.sonarr_data.series_history = Some(series_history);
|
|
||||||
app
|
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.series
|
|
||||||
.set_items(vec![$crate::models::sonarr_models::Series::default()]);
|
|
||||||
app.push_navigation_stack($base.into());
|
app.push_navigation_stack($base.into());
|
||||||
app.push_navigation_stack($active_block.into());
|
app.push_navigation_stack($active_block.into());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,481 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
|
||||||
|
use crate::models::lidarr_models::DownloadRecord;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_prompt() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
|
||||||
|
DownloadsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteDownloadPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_prompt_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
|
||||||
|
DownloadsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_downloads_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Artists.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_downloads_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::History.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_downloads_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
DownloadsHandler::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::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
LidarrEvent::DeleteDownload(1)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt,
|
||||||
|
LidarrEvent::UpdateDownloads
|
||||||
|
)]
|
||||||
|
fn test_downloads_prompt_confirm_submit(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![download_record()]);
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, base_route.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::DeleteDownloadPrompt)]
|
||||||
|
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::UpdateDownloadsPrompt)]
|
||||||
|
fn test_downloads_prompt_decline_submit(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
assert_navigation_popped!(app, base_route.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::DeleteDownloadPrompt)]
|
||||||
|
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::UpdateDownloadsPrompt)]
|
||||||
|
fn test_downloads_prompt_blocks_esc(
|
||||||
|
#[case] base_block: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(base_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
|
||||||
|
DownloadsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, base_block.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_default_esc(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.error = "test error".to_owned().into();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Downloads.into());
|
||||||
|
assert_is_empty!(app.error.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_downloads_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::UpdateDownloadsPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_downloads_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_downloads_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_downloads_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
LidarrEvent::DeleteDownload(1)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt,
|
||||||
|
LidarrEvent::UpdateDownloads
|
||||||
|
)]
|
||||||
|
fn test_downloads_prompt_confirm_submit(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![download_record()]);
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
prompt_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, base_route.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if DOWNLOADS_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(DownloadsHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!DownloadsHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_downloads_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 = DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handler.ignore_special_keys(),
|
||||||
|
ignore_special_keys_for_textbox_input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_download_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![download_record()]);
|
||||||
|
|
||||||
|
let download_id = DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_download_id();
|
||||||
|
|
||||||
|
assert_eq!(download_id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_handler_not_ready_when_downloads_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_handler_ready_when_not_loading_and_downloads_is_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![DownloadRecord::default()]);
|
||||||
|
let handler = DownloadsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "downloads_handler_tests.rs"]
|
||||||
|
mod downloads_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct DownloadsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DownloadsHandler<'_, '_> {
|
||||||
|
fn extract_download_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.downloads.current_selection().id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DownloadsHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let download_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.downloads,
|
||||||
|
download_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
DOWNLOADS_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<ActiveLidarrBlock>,
|
||||||
|
) -> DownloadsHandler<'a, 'b> {
|
||||||
|
DownloadsHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
_context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading && !self.app.data.lidarr_data.downloads.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::Downloads {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteDownloadPrompt.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Downloads => handle_change_tab_left_right_keys(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt | ActiveLidarrBlock::UpdateDownloadsPrompt => {
|
||||||
|
handle_prompt_toggle(self.app, self.key)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteDownload(self.extract_download_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateDownloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt | ActiveLidarrBlock::UpdateDownloadsPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
_ => handle_clear_errors(self.app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Downloads => match self.key {
|
||||||
|
_ if matches_key!(update, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::UpdateDownloadsPrompt.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteDownload(self.extract_download_id()));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateDownloads);
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(1);
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -41,9 +41,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.main_tabs.get_active_route(),
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
ActiveLidarrBlock::Artists.into()
|
ActiveLidarrBlock::Downloads.into()
|
||||||
);
|
);
|
||||||
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -51,7 +51,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(1);
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::assert_navigation_pushed;
|
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::{LidarrHandler, handle_change_tab_left_right_keys};
|
use crate::handlers::lidarr_handlers::{LidarrHandler, handle_change_tab_left_right_keys};
|
||||||
use crate::models::lidarr_models::Artist;
|
|
||||||
use crate::models::lidarr_models::LidarrHistoryItem;
|
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::{assert_navigation_pushed, test_handler_delegation};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
@@ -55,8 +52,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::History)]
|
||||||
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys(
|
fn test_lidarr_handler_change_tab_left_right_keys(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -85,8 +83,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::History)]
|
||||||
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -116,7 +115,8 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::Artists)]
|
#[case(0, ActiveLidarrBlock::Artists)]
|
||||||
#[case(1, ActiveLidarrBlock::History)]
|
#[case(1, ActiveLidarrBlock::Downloads)]
|
||||||
|
#[case(2, ActiveLidarrBlock::History)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] block: ActiveLidarrBlock,
|
#[case] block: ActiveLidarrBlock,
|
||||||
@@ -165,25 +165,27 @@ mod tests {
|
|||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
test_handler_delegation!(
|
||||||
app
|
LidarrHandler,
|
||||||
.data
|
ActiveLidarrBlock::Artists,
|
||||||
.lidarr_data
|
active_lidarr_block
|
||||||
.artists
|
);
|
||||||
.set_items(vec![Artist::default()]);
|
}
|
||||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
|
||||||
|
|
||||||
LidarrHandler::new(
|
#[rstest]
|
||||||
DEFAULT_KEYBINDINGS.esc.key,
|
fn test_delegates_downloads_blocks_to_downloads_handler(
|
||||||
&mut app,
|
#[values(
|
||||||
active_lidarr_block,
|
ActiveLidarrBlock::Downloads,
|
||||||
None,
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
)
|
ActiveLidarrBlock::UpdateDownloadsPrompt
|
||||||
.handle();
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -199,23 +201,10 @@ mod tests {
|
|||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
test_handler_delegation!(
|
||||||
app
|
LidarrHandler,
|
||||||
.data
|
ActiveLidarrBlock::History,
|
||||||
.lidarr_data
|
active_lidarr_block
|
||||||
.history
|
);
|
||||||
.set_items(vec![LidarrHistoryItem::default()]);
|
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
|
||||||
|
|
||||||
LidarrHandler::new(
|
|
||||||
DEFAULT_KEYBINDINGS.esc.key,
|
|
||||||
&mut app,
|
|
||||||
active_lidarr_block,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.handle();
|
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use history::HistoryHandler;
|
|||||||
use library::LibraryHandler;
|
use library::LibraryHandler;
|
||||||
|
|
||||||
use super::KeyEventHandler;
|
use super::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
@@ -10,6 +11,7 @@ use crate::{
|
|||||||
mod history;
|
mod history;
|
||||||
mod library;
|
mod library;
|
||||||
|
|
||||||
|
mod downloads;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_handler_tests.rs"]
|
#[path = "lidarr_handler_tests.rs"]
|
||||||
mod lidarr_handler_tests;
|
mod lidarr_handler_tests;
|
||||||
@@ -27,6 +29,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
||||||
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
_ if DownloadsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
DownloadsHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
||||||
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
|
|
||||||
use super::modals::{AddArtistModal, EditArtistModal};
|
use super::modals::{AddArtistModal, EditArtistModal};
|
||||||
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
use crate::app::context_clues::{DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES};
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
@@ -131,6 +131,12 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
||||||
config: None,
|
config: None,
|
||||||
},
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Downloads".to_string(),
|
||||||
|
route: ActiveLidarrBlock::Downloads.into(),
|
||||||
|
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
|
||||||
|
config: None,
|
||||||
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History".to_string(),
|
title: "History".to_string(),
|
||||||
route: ActiveLidarrBlock::History.into(),
|
route: ActiveLidarrBlock::History.into(),
|
||||||
@@ -252,6 +258,8 @@ pub enum ActiveLidarrBlock {
|
|||||||
DeleteArtistConfirmPrompt,
|
DeleteArtistConfirmPrompt,
|
||||||
DeleteArtistToggleDeleteFile,
|
DeleteArtistToggleDeleteFile,
|
||||||
DeleteArtistToggleAddListExclusion,
|
DeleteArtistToggleAddListExclusion,
|
||||||
|
DeleteDownloadPrompt,
|
||||||
|
Downloads,
|
||||||
EditArtistPrompt,
|
EditArtistPrompt,
|
||||||
EditArtistConfirmPrompt,
|
EditArtistConfirmPrompt,
|
||||||
EditArtistPathInput,
|
EditArtistPathInput,
|
||||||
@@ -275,6 +283,7 @@ pub enum ActiveLidarrBlock {
|
|||||||
SearchHistoryError,
|
SearchHistoryError,
|
||||||
UpdateAllArtistsPrompt,
|
UpdateAllArtistsPrompt,
|
||||||
UpdateAndScanArtistPrompt,
|
UpdateAndScanArtistPrompt,
|
||||||
|
UpdateDownloadsPrompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||||
@@ -295,6 +304,12 @@ pub static ARTIST_DETAILS_BLOCKS: [ActiveLidarrBlock; 5] = [
|
|||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static DOWNLOADS_BLOCKS: [ActiveLidarrBlock; 3] = [
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt,
|
||||||
|
];
|
||||||
|
|
||||||
pub static HISTORY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
pub static HISTORY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||||
ActiveLidarrBlock::History,
|
ActiveLidarrBlock::History,
|
||||||
ActiveLidarrBlock::HistoryItemDetails,
|
ActiveLidarrBlock::HistoryItemDetails,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
use crate::app::context_clues::{DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES};
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
@@ -8,7 +8,7 @@ mod tests {
|
|||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
||||||
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
||||||
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, HISTORY_BLOCKS,
|
DOWNLOADS_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, HISTORY_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BlockSelectionState, Route,
|
BlockSelectionState, Route,
|
||||||
@@ -146,7 +146,7 @@ mod tests {
|
|||||||
assert_is_empty!(lidarr_data.tags_map);
|
assert_is_empty!(lidarr_data.tags_map);
|
||||||
assert_is_empty!(lidarr_data.version);
|
assert_is_empty!(lidarr_data.version);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.main_tabs.tabs.len(), 2);
|
assert_eq!(lidarr_data.main_tabs.tabs.len(), 3);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -159,17 +159,28 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[1].title, "History");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[1].title, "Downloads");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lidarr_data.main_tabs.tabs[1].route,
|
lidarr_data.main_tabs.tabs[1].route,
|
||||||
ActiveLidarrBlock::History.into()
|
ActiveLidarrBlock::Downloads.into()
|
||||||
);
|
);
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(
|
||||||
&lidarr_data.main_tabs.tabs[1].contextual_help,
|
&lidarr_data.main_tabs.tabs[1].contextual_help,
|
||||||
&HISTORY_CONTEXT_CLUES
|
&DOWNLOADS_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
||||||
|
|
||||||
|
assert_str_eq!(lidarr_data.main_tabs.tabs[2].title, "History");
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_data.main_tabs.tabs[2].route,
|
||||||
|
ActiveLidarrBlock::History.into()
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&lidarr_data.main_tabs.tabs[2].contextual_help,
|
||||||
|
&HISTORY_CONTEXT_CLUES
|
||||||
|
);
|
||||||
|
assert_none!(lidarr_data.main_tabs.tabs[2].config);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
|
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
|
||||||
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -205,6 +216,14 @@ mod tests {
|
|||||||
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_blocks_contains_expected_blocks() {
|
||||||
|
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);
|
||||||
|
assert!(DOWNLOADS_BLOCKS.contains(&ActiveLidarrBlock::Downloads));
|
||||||
|
assert!(DOWNLOADS_BLOCKS.contains(&ActiveLidarrBlock::DeleteDownloadPrompt));
|
||||||
|
assert!(DOWNLOADS_BLOCKS.contains(&ActiveLidarrBlock::UpdateDownloadsPrompt));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_history_blocks_contains_expected_blocks() {
|
fn test_history_blocks_contains_expected_blocks() {
|
||||||
assert_eq!(HISTORY_BLOCKS.len(), 7);
|
assert_eq!(HISTORY_BLOCKS.len(), 7);
|
||||||
|
|||||||
@@ -2,10 +2,37 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::lidarr_models::{DownloadsResponse, LidarrSerdeable};
|
use crate::models::lidarr_models::{DownloadsResponse, LidarrSerdeable};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
|
||||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_lidarr_download_event() {
|
||||||
|
let (mock, app, _server) = MockServarrApi::delete()
|
||||||
|
.path("/1")
|
||||||
|
.build_for(LidarrEvent::DeleteDownload(1))
|
||||||
|
.await;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.downloads
|
||||||
|
.set_items(vec![download_record()]);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::DeleteDownload(1))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_downloads_event() {
|
async fn test_handle_get_downloads_event() {
|
||||||
let downloads_json = json!({
|
let downloads_json = json!({
|
||||||
@@ -40,4 +67,26 @@ mod tests {
|
|||||||
assert_eq!(downloads_response, response);
|
assert_eq!(downloads_response, response);
|
||||||
assert!(!app.lock().await.data.lidarr_data.downloads.is_empty());
|
assert!(!app.lock().await.data.lidarr_data.downloads.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_update_lidarr_downloads_event() {
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(json!({
|
||||||
|
"name": "RefreshMonitoredDownloads"
|
||||||
|
}))
|
||||||
|
.returns(json!({}))
|
||||||
|
.build_for(LidarrEvent::UpdateDownloads)
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::UpdateDownloads)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
use crate::models::lidarr_models::DownloadsResponse;
|
use crate::models::lidarr_models::DownloadsResponse;
|
||||||
|
use crate::models::servarr_models::CommandBody;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_downloads_network_tests.rs"]
|
#[path = "lidarr_downloads_network_tests.rs"]
|
||||||
mod lidarr_downloads_network_tests;
|
mod lidarr_downloads_network_tests;
|
||||||
|
|
||||||
impl Network<'_, '_> {
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn delete_lidarr_download(
|
||||||
|
&mut self,
|
||||||
|
download_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::DeleteDownload(download_id);
|
||||||
|
info!("Deleting Lidarr download for download with id: {download_id}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Delete,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{download_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn get_lidarr_downloads(
|
pub(in crate::network::lidarr_network) async fn get_lidarr_downloads(
|
||||||
&mut self,
|
&mut self,
|
||||||
count: u64,
|
count: u64,
|
||||||
@@ -37,4 +60,22 @@ impl Network<'_, '_> {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn update_lidarr_downloads(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Value> {
|
||||||
|
info!("Updating Lidarr downloads");
|
||||||
|
let event = LidarrEvent::UpdateDownloads;
|
||||||
|
let body = CommandBody {
|
||||||
|
name: "RefreshMonitoredDownloads".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Post, Some(body), None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<CommandBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ mod tests {
|
|||||||
assert_str_eq!(event.resource(), "/artist");
|
assert_str_eq!(event.resource(), "/artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_resource_downloads(
|
||||||
|
#[values(LidarrEvent::GetDownloads(0), LidarrEvent::DeleteDownload(0))] event: LidarrEvent,
|
||||||
|
) {
|
||||||
|
assert_str_eq!(event.resource(), "/queue");
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_history(#[values(LidarrEvent::GetHistory(0))] event: LidarrEvent) {
|
fn test_resource_history(#[values(LidarrEvent::GetHistory(0))] event: LidarrEvent) {
|
||||||
assert_str_eq!(event.resource(), "/history");
|
assert_str_eq!(event.resource(), "/history");
|
||||||
@@ -59,7 +66,8 @@ mod tests {
|
|||||||
#[values(
|
#[values(
|
||||||
LidarrEvent::UpdateAllArtists,
|
LidarrEvent::UpdateAllArtists,
|
||||||
LidarrEvent::TriggerAutomaticArtistSearch(0),
|
LidarrEvent::TriggerAutomaticArtistSearch(0),
|
||||||
LidarrEvent::UpdateAndScanArtist(0)
|
LidarrEvent::UpdateAndScanArtist(0),
|
||||||
|
LidarrEvent::UpdateDownloads
|
||||||
)]
|
)]
|
||||||
event: LidarrEvent,
|
event: LidarrEvent,
|
||||||
) {
|
) {
|
||||||
@@ -81,7 +89,6 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
||||||
#[case(LidarrEvent::GetDownloads(500), "/queue")]
|
|
||||||
#[case(LidarrEvent::GetMetadataProfiles, "/metadataprofile")]
|
#[case(LidarrEvent::GetMetadataProfiles, "/metadataprofile")]
|
||||||
#[case(LidarrEvent::GetQualityProfiles, "/qualityprofile")]
|
#[case(LidarrEvent::GetQualityProfiles, "/qualityprofile")]
|
||||||
#[case(LidarrEvent::GetRootFolders, "/rootfolder")]
|
#[case(LidarrEvent::GetRootFolders, "/rootfolder")]
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ pub enum LidarrEvent {
|
|||||||
AddTag(String),
|
AddTag(String),
|
||||||
DeleteAlbum(DeleteParams),
|
DeleteAlbum(DeleteParams),
|
||||||
DeleteArtist(DeleteParams),
|
DeleteArtist(DeleteParams),
|
||||||
|
DeleteDownload(i64),
|
||||||
DeleteTag(i64),
|
DeleteTag(i64),
|
||||||
EditArtist(EditArtistParams),
|
EditArtist(EditArtistParams),
|
||||||
GetAlbums(i64),
|
GetAlbums(i64),
|
||||||
@@ -52,6 +53,7 @@ pub enum LidarrEvent {
|
|||||||
TriggerAutomaticArtistSearch(i64),
|
TriggerAutomaticArtistSearch(i64),
|
||||||
UpdateAllArtists,
|
UpdateAllArtists,
|
||||||
UpdateAndScanArtist(i64),
|
UpdateAndScanArtist(i64),
|
||||||
|
UpdateDownloads,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkResource for LidarrEvent {
|
impl NetworkResource for LidarrEvent {
|
||||||
@@ -69,13 +71,14 @@ impl NetworkResource for LidarrEvent {
|
|||||||
| LidarrEvent::GetAlbumDetails(_)
|
| LidarrEvent::GetAlbumDetails(_)
|
||||||
| LidarrEvent::DeleteAlbum(_) => "/album",
|
| LidarrEvent::DeleteAlbum(_) => "/album",
|
||||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||||
LidarrEvent::GetDownloads(_) => "/queue",
|
LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue",
|
||||||
LidarrEvent::GetHistory(_) => "/history",
|
LidarrEvent::GetHistory(_) => "/history",
|
||||||
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||||
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
||||||
| LidarrEvent::UpdateAllArtists
|
| LidarrEvent::UpdateAllArtists
|
||||||
| LidarrEvent::UpdateAndScanArtist(_) => "/command",
|
| LidarrEvent::UpdateAndScanArtist(_)
|
||||||
|
| LidarrEvent::UpdateDownloads => "/command",
|
||||||
LidarrEvent::GetMetadataProfiles => "/metadataprofile",
|
LidarrEvent::GetMetadataProfiles => "/metadataprofile",
|
||||||
LidarrEvent::GetQualityProfiles => "/qualityprofile",
|
LidarrEvent::GetQualityProfiles => "/qualityprofile",
|
||||||
LidarrEvent::GetRootFolders => "/rootfolder",
|
LidarrEvent::GetRootFolders => "/rootfolder",
|
||||||
@@ -105,6 +108,10 @@ impl Network<'_, '_> {
|
|||||||
LidarrEvent::DeleteArtist(params) => {
|
LidarrEvent::DeleteArtist(params) => {
|
||||||
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
|
LidarrEvent::DeleteDownload(download_id) => self
|
||||||
|
.delete_lidarr_download(download_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::DeleteTag(tag_id) => self
|
LidarrEvent::DeleteTag(tag_id) => self
|
||||||
.delete_lidarr_tag(tag_id)
|
.delete_lidarr_tag(tag_id)
|
||||||
.await
|
.await
|
||||||
@@ -182,6 +189,10 @@ impl Network<'_, '_> {
|
|||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::EditArtist(params) => self.edit_artist(params).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),
|
LidarrEvent::AddArtist(body) => self.add_artist(body).await.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::UpdateDownloads => self
|
||||||
|
.update_lidarr_downloads()
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::downloads::DownloadsUi;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_ui_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if DOWNLOADS_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(DownloadsUi::accepts(active_lidarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!DownloadsUi::accepts(active_lidarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_ui_renders_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
DownloadsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downloads_ui_renders_empty_downloads() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
DownloadsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_downloads_ui_renders(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
DownloadsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("downloads_ui_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
use ratatui::Frame;
|
||||||
|
use ratatui::layout::{Constraint, Rect};
|
||||||
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::lidarr_models::DownloadRecord;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
|
||||||
|
use crate::models::{HorizontallyScrollableText, Route};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
||||||
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use crate::utils::convert_f64_to_gb;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "downloads_ui_tests.rs"]
|
||||||
|
mod downloads_ui_tests;
|
||||||
|
|
||||||
|
pub(super) struct DownloadsUi;
|
||||||
|
|
||||||
|
impl DrawUi for DownloadsUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = route {
|
||||||
|
return DOWNLOADS_BLOCKS.contains(&active_lidarr_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
draw_downloads(f, app, area);
|
||||||
|
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteDownloadPrompt => {
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you really want to delete this download: \n{}?",
|
||||||
|
app.data.lidarr_data.downloads.current_selection().title
|
||||||
|
);
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Cancel Download")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateDownloadsPrompt => {
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Update Downloads")
|
||||||
|
.prompt("Do you want to update your downloads?")
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let current_selection = if app.data.lidarr_data.downloads.items.is_empty() {
|
||||||
|
DownloadRecord::default()
|
||||||
|
} else {
|
||||||
|
app.data.lidarr_data.downloads.current_selection().clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let downloads_row_mapping = |download_record: &DownloadRecord| {
|
||||||
|
let DownloadRecord {
|
||||||
|
title,
|
||||||
|
size,
|
||||||
|
sizeleft,
|
||||||
|
download_client,
|
||||||
|
indexer,
|
||||||
|
output_path,
|
||||||
|
..
|
||||||
|
} = download_record;
|
||||||
|
|
||||||
|
if output_path.is_some() {
|
||||||
|
output_path.as_ref().unwrap().scroll_left_or_reset(
|
||||||
|
get_width_from_percentage(area, 18),
|
||||||
|
current_selection == *download_record,
|
||||||
|
app.ui_scroll_tick_count == 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let percent = if *size == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
1f64 - (*sizeleft / *size)
|
||||||
|
};
|
||||||
|
let file_size: f64 = convert_f64_to_gb(*size);
|
||||||
|
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(title.to_owned()),
|
||||||
|
Cell::from(format!("{:.0}%", percent * 100.0)),
|
||||||
|
Cell::from(format!("{file_size:.2} GB")),
|
||||||
|
Cell::from(
|
||||||
|
output_path
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&HorizontallyScrollableText::default())
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
Cell::from(indexer.to_owned()),
|
||||||
|
Cell::from(
|
||||||
|
download_client
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&String::new())
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.primary()
|
||||||
|
};
|
||||||
|
let downloads_table = ManagarrTable::new(
|
||||||
|
Some(&mut app.data.lidarr_data.downloads),
|
||||||
|
downloads_row_mapping,
|
||||||
|
)
|
||||||
|
.block(layout_block_top_border())
|
||||||
|
.loading(app.is_loading)
|
||||||
|
.headers([
|
||||||
|
"Title",
|
||||||
|
"Percent Complete",
|
||||||
|
"Size",
|
||||||
|
"Output Path",
|
||||||
|
"Indexer",
|
||||||
|
"Download Client",
|
||||||
|
])
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(30),
|
||||||
|
Constraint::Percentage(11),
|
||||||
|
Constraint::Percentage(11),
|
||||||
|
Constraint::Percentage(18),
|
||||||
|
Constraint::Percentage(17),
|
||||||
|
Constraint::Percentage(13),
|
||||||
|
]);
|
||||||
|
|
||||||
|
f.render_widget(downloads_table, area);
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Title Percent Complete Size Output Path Indexer Download Client
|
||||||
|
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────── Cancel Download ────────────────────╮
|
||||||
|
│ Do you really want to delete this download: │
|
||||||
|
│ Test download title? │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭────────────────────────────╮╭───────────────────────────╮│
|
||||||
|
││ Yes ││ No ││
|
||||||
|
│╰────────────────────────────╯╰───────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────╯
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Title Percent Complete Size Output Path Indexer Download Client
|
||||||
|
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Title Percent Complete Size Output Path Indexer Download Client
|
||||||
|
=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────────── Update Downloads ────────────────────╮
|
||||||
|
│ Do you want to update your downloads? │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭────────────────────────────╮╭───────────────────────────╮│
|
||||||
|
││ Yes ││ No ││
|
||||||
|
│╰────────────────────────────╯╰───────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────╯
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/downloads/downloads_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
Loading ...
|
||||||
@@ -13,4 +13,30 @@ mod tests {
|
|||||||
assert!(LidarrUi::accepts(Route::Lidarr(lidarr_block, None)));
|
assert!(LidarrUi::accepts(Route::Lidarr(lidarr_block, None)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::Artists, 0)]
|
||||||
|
#[case(ActiveLidarrBlock::Downloads, 1)]
|
||||||
|
#[case(ActiveLidarrBlock::History, 2)]
|
||||||
|
fn test_lidarr_ui_renders_lidarr_tabs(
|
||||||
|
#[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.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
LidarrUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("lidarr_tabs_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -15,6 +15,15 @@ use ratatui::{
|
|||||||
widgets::Paragraph,
|
widgets::Paragraph,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
DrawUi, draw_tabs,
|
||||||
|
styles::ManagarrStyle,
|
||||||
|
utils::{
|
||||||
|
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
|
||||||
|
},
|
||||||
|
widgets::loading_block::LoadingBlock,
|
||||||
|
};
|
||||||
|
use crate::ui::lidarr_ui::downloads::DownloadsUi;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
logos::LIDARR_LOGO,
|
logos::LIDARR_LOGO,
|
||||||
@@ -27,15 +36,7 @@ use crate::{
|
|||||||
utils::convert_to_gb,
|
utils::convert_to_gb,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
mod downloads;
|
||||||
DrawUi, draw_tabs,
|
|
||||||
styles::ManagarrStyle,
|
|
||||||
utils::{
|
|
||||||
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
|
|
||||||
},
|
|
||||||
widgets::loading_block::LoadingBlock,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod history;
|
mod history;
|
||||||
mod library;
|
mod library;
|
||||||
mod lidarr_ui_utils;
|
mod lidarr_ui_utils;
|
||||||
@@ -57,6 +58,7 @@ impl DrawUi for LidarrUi {
|
|||||||
|
|
||||||
match route {
|
match route {
|
||||||
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
||||||
|
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
|
||||||
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ Library │ Downloads │ History │
|
||||||
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
||||||
|
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ Library │ Downloads │ History │
|
||||||
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ Title Percent Complete Size Output Path Indexer Download Client │
|
||||||
|
│=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ Library │ Downloads │ History │
|
||||||
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ Source Title ▼ Event Type Quality Date │
|
||||||
|
│=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
Reference in New Issue
Block a user