feat(handler): Full handler support for the Season details UI in Sonarr
This commit is contained in:
@@ -55,10 +55,15 @@ impl<'a> App<'a> {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::ManualSeasonSearch => {
|
ActiveSonarrBlock::ManualSeasonSearch => {
|
||||||
|
match self.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
|
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into())
|
.dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
|
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeDetails(None).into())
|
.dispatch_network_event(SonarrEvent::GetEpisodeDetails(None).into())
|
||||||
@@ -70,10 +75,16 @@ impl<'a> App<'a> {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::ManualEpisodeSearch => {
|
ActiveSonarrBlock::ManualEpisodeSearch => {
|
||||||
|
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
|
if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() {
|
||||||
|
if episode_details_modal.episode_releases.is_empty() {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into())
|
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ActiveSonarrBlock::Downloads => {
|
ActiveSonarrBlock::Downloads => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
models::{
|
models::{
|
||||||
servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
servarr_data::sonarr::{
|
||||||
sonarr_models::{Season, Series},
|
modals::{EpisodeDetailsModal, SeasonDetailsModal},
|
||||||
|
sonarr_data::ActiveSonarrBlock,
|
||||||
|
},
|
||||||
|
sonarr_models::{Season, Series, SonarrRelease},
|
||||||
},
|
},
|
||||||
network::{sonarr_network::SonarrEvent, NetworkEvent},
|
network::{sonarr_network::SonarrEvent, NetworkEvent},
|
||||||
};
|
};
|
||||||
@@ -115,6 +118,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_season_search_block() {
|
async fn test_dispatch_by_manual_season_search_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
||||||
@@ -129,6 +133,40 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_season_search_block_is_loading() {
|
||||||
|
let mut app = App {
|
||||||
|
is_loading: true,
|
||||||
|
..App::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_season_search_block_season_releases_non_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal
|
||||||
|
.season_releases
|
||||||
|
.set_items(vec![SonarrRelease::default()]);
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_episode_details_block() {
|
async fn test_dispatch_by_episode_details_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
@@ -183,6 +221,9 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_episode_search_block() {
|
async fn test_dispatch_by_manual_episode_search_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal.episode_details_modal = Some(EpisodeDetailsModal::default());
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
|
||||||
@@ -197,6 +238,42 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_episode_search_block_is_loading() {
|
||||||
|
let mut app = App {
|
||||||
|
is_loading: true,
|
||||||
|
..App::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_episode_search_block_episode_releases_non_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
let mut episode_details_modal = EpisodeDetailsModal::default();
|
||||||
|
episode_details_modal
|
||||||
|
.episode_releases
|
||||||
|
.set_items(vec![SonarrRelease::default()]);
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal.episode_details_modal = Some(episode_details_modal);
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_history_block() {
|
async fn test_dispatch_by_history_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
|||||||
@@ -351,6 +351,10 @@ mod test_utils {
|
|||||||
movie_details_modal.movie_crew.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()]);
|
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);
|
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();
|
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
|
||||||
series_history.set_items(vec![
|
series_history.set_items(vec![
|
||||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
||||||
|
|||||||
@@ -328,32 +328,27 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt => {
|
ActiveRadarrBlock::AutomaticallySearchMoviePrompt
|
||||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||||
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action =
|
self.app.data.radarr_data.prompt_confirm_action =
|
||||||
Some(RadarrEvent::TriggerAutomaticSearch(None));
|
Some(RadarrEvent::TriggerAutomaticSearch(None));
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
}
|
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||||
ActiveRadarrBlock::UpdateAndScanPrompt => {
|
|
||||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None));
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None));
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
}
|
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||||
ActiveRadarrBlock::ManualSearchConfirmPrompt => {
|
|
||||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action =
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(None));
|
||||||
Some(RadarrEvent::DownloadRelease(None));
|
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ mod tests {
|
|||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler};
|
use crate::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler};
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, LIBRARY_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS};
|
||||||
ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, LIBRARY_BLOCKS,
|
|
||||||
SERIES_DETAILS_BLOCKS,
|
|
||||||
};
|
|
||||||
use crate::models::sonarr_models::{Series, SeriesStatus, SeriesType};
|
use crate::models::sonarr_models::{Series, SeriesStatus, SeriesType};
|
||||||
use crate::test_handler_delegation;
|
use crate::test_handler_delegation;
|
||||||
|
|
||||||
@@ -543,6 +540,33 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_season_details_blocks_to_season_details_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
|
ActiveSonarrBlock::SearchEpisodes,
|
||||||
|
ActiveSonarrBlock::SearchEpisodesError,
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
ActiveSonarrBlock::SearchSeasonHistory,
|
||||||
|
ActiveSonarrBlock::SearchSeasonHistoryError,
|
||||||
|
ActiveSonarrBlock::FilterSeasonHistory,
|
||||||
|
ActiveSonarrBlock::FilterSeasonHistoryError,
|
||||||
|
ActiveSonarrBlock::SeasonHistorySortPrompt,
|
||||||
|
ActiveSonarrBlock::SeasonHistoryDetails,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LibraryHandler,
|
||||||
|
ActiveSonarrBlock::Series,
|
||||||
|
active_sonarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_delegates_edit_series_blocks_to_edit_series_handler(
|
fn test_delegates_edit_series_blocks_to_edit_series_handler(
|
||||||
#[values(
|
#[values(
|
||||||
@@ -768,6 +792,7 @@ mod tests {
|
|||||||
library_handler_blocks.extend(DELETE_SERIES_BLOCKS);
|
library_handler_blocks.extend(DELETE_SERIES_BLOCKS);
|
||||||
library_handler_blocks.extend(EDIT_SERIES_BLOCKS);
|
library_handler_blocks.extend(EDIT_SERIES_BLOCKS);
|
||||||
library_handler_blocks.extend(SERIES_DETAILS_BLOCKS);
|
library_handler_blocks.extend(SERIES_DETAILS_BLOCKS);
|
||||||
|
library_handler_blocks.extend(SEASON_DETAILS_BLOCKS);
|
||||||
|
|
||||||
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
|
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
|
||||||
if library_handler_blocks.contains(&active_sonarr_block) {
|
if library_handler_blocks.contains(&active_sonarr_block) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::handle_change_tab_left_right_keys;
|
use super::handle_change_tab_left_right_keys;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler;
|
||||||
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
|
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
|
||||||
use crate::handlers::table_handler::TableHandlingConfig;
|
use crate::handlers::table_handler::TableHandlingConfig;
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ mod delete_series_handler;
|
|||||||
#[path = "library_handler_tests.rs"]
|
#[path = "library_handler_tests.rs"]
|
||||||
mod library_handler_tests;
|
mod library_handler_tests;
|
||||||
mod series_details_handler;
|
mod series_details_handler;
|
||||||
|
mod season_details_handler;
|
||||||
|
|
||||||
pub(super) struct LibraryHandler<'a, 'b> {
|
pub(super) struct LibraryHandler<'a, 'b> {
|
||||||
key: Key,
|
key: Key,
|
||||||
@@ -75,6 +77,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
|||||||
SeriesDetailsHandler::with(self.key, self.app, self.active_sonarr_block, self.context)
|
SeriesDetailsHandler::with(self.key, self.app, self.active_sonarr_block, self.context)
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
|
_ if SeasonDetailsHandler::accepts(self.active_sonarr_block) => {
|
||||||
|
SeasonDetailsHandler::with(self.key, self.app, self.active_sonarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,6 +91,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
|||||||
|| DeleteSeriesHandler::accepts(active_block)
|
|| DeleteSeriesHandler::accepts(active_block)
|
||||||
|| EditSeriesHandler::accepts(active_block)
|
|| EditSeriesHandler::accepts(active_block)
|
||||||
|| SeriesDetailsHandler::accepts(active_block)
|
|| SeriesDetailsHandler::accepts(active_block)
|
||||||
|
|| SeasonDetailsHandler::accepts(active_block)
|
||||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handle_table_events;
|
||||||
|
use crate::handlers::sonarr_handlers::history::history_sorting_options;
|
||||||
|
use crate::handlers::table_handler::TableHandlingConfig;
|
||||||
|
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS};
|
||||||
|
use crate::models::servarr_models::Language;
|
||||||
|
use crate::models::sonarr_models::{
|
||||||
|
Episode, SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody,
|
||||||
|
};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::sonarr_network::SonarrEvent;
|
||||||
|
use serde_json::Number;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "season_details_handler_tests.rs"]
|
||||||
|
mod season_details_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct SeasonDetailsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
_context: Option<ActiveSonarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> SeasonDetailsHandler<'a, 'b> {
|
||||||
|
handle_table_events!(
|
||||||
|
self,
|
||||||
|
episodes,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Season details modal is undefined")
|
||||||
|
.episodes,
|
||||||
|
Episode
|
||||||
|
);
|
||||||
|
handle_table_events!(
|
||||||
|
self,
|
||||||
|
season_history,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Season details modal is undefined")
|
||||||
|
.season_history,
|
||||||
|
SonarrHistoryItem
|
||||||
|
);
|
||||||
|
handle_table_events!(
|
||||||
|
self,
|
||||||
|
season_releases,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Season details modal is undefined")
|
||||||
|
.season_releases,
|
||||||
|
SonarrRelease
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let episodes_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveSonarrBlock::SeasonDetails.into())
|
||||||
|
.searching_block(ActiveSonarrBlock::SearchEpisodes.into())
|
||||||
|
.search_error_block(ActiveSonarrBlock::SearchEpisodesError.into())
|
||||||
|
.search_field_fn(|episode: &Episode| &episode.title);
|
||||||
|
let season_history_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveSonarrBlock::SeasonHistory.into())
|
||||||
|
.sorting_block(ActiveSonarrBlock::SeasonHistorySortPrompt.into())
|
||||||
|
.sort_options(history_sorting_options())
|
||||||
|
.sort_by_fn(|a: &SonarrHistoryItem, b: &SonarrHistoryItem| a.id.cmp(&b.id))
|
||||||
|
.searching_block(ActiveSonarrBlock::SearchSeasonHistory.into())
|
||||||
|
.search_error_block(ActiveSonarrBlock::SearchSeasonHistoryError.into())
|
||||||
|
.search_field_fn(|history_item: &SonarrHistoryItem| &history_item.source_title.text)
|
||||||
|
.filtering_block(ActiveSonarrBlock::FilterSeasonHistory.into())
|
||||||
|
.filter_error_block(ActiveSonarrBlock::FilterSeasonHistoryError.into())
|
||||||
|
.filter_field_fn(|history_item: &SonarrHistoryItem| &history_item.source_title.text);
|
||||||
|
let season_releases_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveSonarrBlock::ManualSeasonSearch.into())
|
||||||
|
.sorting_block(ActiveSonarrBlock::ManualSeasonSearchSortPrompt.into())
|
||||||
|
.sort_options(releases_sorting_options());
|
||||||
|
|
||||||
|
if !self.handle_episodes_table_events(episodes_table_handling_config)
|
||||||
|
&& !self.handle_season_history_table_events(season_history_table_handling_config)
|
||||||
|
&& !self.handle_season_releases_table_events(season_releases_table_handling_config)
|
||||||
|
{
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveSonarrBlock) -> bool {
|
||||||
|
SEASON_DETAILS_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveSonarrBlock,
|
||||||
|
context: Option<ActiveSonarrBlock>,
|
||||||
|
) -> SeasonDetailsHandler<'a, 'b> {
|
||||||
|
SeasonDetailsHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_sonarr_block: active_block,
|
||||||
|
_context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading
|
||||||
|
&& if let Some(season_details_modal) = &self.app.data.sonarr_data.season_details_modal {
|
||||||
|
match self.active_sonarr_block {
|
||||||
|
ActiveSonarrBlock::SeasonDetails => !season_details_modal.episodes.is_empty(),
|
||||||
|
ActiveSonarrBlock::SeasonHistory => !season_details_modal.season_history.is_empty(),
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch => !season_details_modal.season_releases.is_empty(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_sonarr_block == ActiveSonarrBlock::SeasonDetails {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveSonarrBlock::DeleteEpisodeFilePrompt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_sonarr_block {
|
||||||
|
ActiveSonarrBlock::SeasonDetails
|
||||||
|
| ActiveSonarrBlock::SeasonHistory
|
||||||
|
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
|
||||||
|
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.previous();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.next();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt
|
||||||
|
| ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||||
|
| ActiveSonarrBlock::DeleteEpisodeFilePrompt => {
|
||||||
|
handle_prompt_toggle(self.app, self.key);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_sonarr_block {
|
||||||
|
ActiveSonarrBlock::SeasonHistory => self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveSonarrBlock::SeasonHistoryDetails.into()),
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt => {
|
||||||
|
if self.app.data.sonarr_data.prompt_confirm {
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::DeleteEpisodeFile(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
||||||
|
if self.app.data.sonarr_data.prompt_confirm {
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::TriggerAutomaticSeasonSearch(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt.into());
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt => {
|
||||||
|
if self.app.data.sonarr_data.prompt_confirm {
|
||||||
|
let SonarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_releases
|
||||||
|
.current_selection();
|
||||||
|
let series_id = self.app.data.sonarr_data.series.current_selection().id;
|
||||||
|
let season_number = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.seasons
|
||||||
|
.current_selection()
|
||||||
|
.season_number;
|
||||||
|
let params = SonarrReleaseDownloadBody {
|
||||||
|
guid: guid.clone(),
|
||||||
|
indexer_id: *indexer_id,
|
||||||
|
series_id: Some(series_id),
|
||||||
|
season_number: Some(season_number),
|
||||||
|
..SonarrReleaseDownloadBody::default()
|
||||||
|
};
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::DownloadRelease(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_sonarr_block {
|
||||||
|
ActiveSonarrBlock::SeasonDetails | ActiveSonarrBlock::ManualSeasonSearch => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.sonarr_data.season_details_modal = None;
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::SeasonHistoryDetails => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::SeasonHistory => {
|
||||||
|
if self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_history
|
||||||
|
.filtered_items
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.season_history
|
||||||
|
.filtered_items = None;
|
||||||
|
} else {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.sonarr_data.season_details_modal = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt
|
||||||
|
| ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||||
|
| ActiveSonarrBlock::DeleteEpisodeFilePrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.sonarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_sonarr_block {
|
||||||
|
ActiveSonarrBlock::SeasonDetails
|
||||||
|
| ActiveSonarrBlock::SeasonHistory
|
||||||
|
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
|
||||||
|
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_sonarr_block.into());
|
||||||
|
}
|
||||||
|
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt
|
||||||
|
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||||
|
{
|
||||||
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::TriggerAutomaticSeasonSearch(None));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||||
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::DeleteEpisodeFile(None));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||||
|
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||||
|
{
|
||||||
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
let SonarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_releases
|
||||||
|
.current_selection();
|
||||||
|
let series_id = self.app.data.sonarr_data.series.current_selection().id;
|
||||||
|
let season_number = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.seasons
|
||||||
|
.current_selection()
|
||||||
|
.season_number;
|
||||||
|
let params = SonarrReleaseDownloadBody {
|
||||||
|
guid: guid.clone(),
|
||||||
|
indexer_id: *indexer_id,
|
||||||
|
series_id: Some(series_id),
|
||||||
|
season_number: Some(season_number),
|
||||||
|
..SonarrReleaseDownloadBody::default()
|
||||||
|
};
|
||||||
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
|
Some(SonarrEvent::DownloadRelease(params));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn releases_sorting_options() -> Vec<SortOption<SonarrRelease>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Source",
|
||||||
|
cmp_fn: Some(|a, b| a.protocol.cmp(&b.protocol)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Age",
|
||||||
|
cmp_fn: Some(|a, b| a.age.cmp(&b.age)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Rejected",
|
||||||
|
cmp_fn: Some(|a, b| a.rejected.cmp(&b.rejected)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.title.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Indexer",
|
||||||
|
cmp_fn: Some(|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase())),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Size",
|
||||||
|
cmp_fn: Some(|a, b| a.size.cmp(&b.size)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Peers",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let default_number = Number::from(i64::MAX);
|
||||||
|
let seeder_a = a
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let seeder_b = b
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
seeder_a.cmp(&seeder_b)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Language",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let default_language_vec = vec![Language {
|
||||||
|
id: 1,
|
||||||
|
name: "_".to_owned(),
|
||||||
|
}];
|
||||||
|
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0];
|
||||||
|
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0];
|
||||||
|
|
||||||
|
language_a.cmp(language_b)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| a.quality.cmp(&b.quality)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,982 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::handlers::sonarr_handlers::library::season_details_handler::{
|
||||||
|
releases_sorting_options, SeasonDetailsHandler,
|
||||||
|
};
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::models::servarr_data::sonarr::modals::SeasonDetailsModal;
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data;
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||||
|
ActiveSonarrBlock, SEASON_DETAILS_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::{Language, Quality, QualityWrapper};
|
||||||
|
use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody};
|
||||||
|
use crate::models::HorizontallyScrollableText;
|
||||||
|
use pretty_assertions::assert_str_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use serde_json::Number;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_episode_prompt() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(DELETE_KEY, &mut app, ActiveSonarrBlock::SeasonDetails, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_episode_prompt_no_op_when_not_ready() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(DELETE_KEY, &mut app, ActiveSonarrBlock::SeasonDetails, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonDetails.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_actions {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
#[values(Key::Left, Key::Right)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(key, &mut app, active_sonarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.sonarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(key, &mut app, active_sonarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory)]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
|
ActiveSonarrBlock::SeasonDetails
|
||||||
|
)]
|
||||||
|
fn test_season_details_tabs_left_right_action(
|
||||||
|
#[case] left_block: ActiveSonarrBlock,
|
||||||
|
#[case] right_block: ActiveSonarrBlock,
|
||||||
|
#[values(true, false)] is_ready: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.push_navigation_stack(right_block.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.index = app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.position(|tab_route| tab_route.route == right_block.into())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(DEFAULT_KEYBINDINGS.left.key, &mut app, right_block, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.get_active_route()
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), left_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(DEFAULT_KEYBINDINGS.right.key, &mut app, left_block, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.get_active_route()
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), right_block.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use crate::network::sonarr_network::SonarrEvent;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_history_submit() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SeasonHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonHistoryDetails.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_history_submit_no_op_when_season_history_is_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.season_history = StatefulTable::default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistory.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SeasonHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_history_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistory.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SeasonHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
SonarrEvent::TriggerAutomaticSeasonSearch(None)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
SonarrEvent::DeleteEpisodeFile(None)
|
||||||
|
)]
|
||||||
|
fn test_season_details_prompt_confirm_submit(
|
||||||
|
#[case] prompt_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_action: SonarrEvent,
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.get_current_route(), active_sonarr_block.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.sonarr_data.prompt_confirm_action,
|
||||||
|
Some(expected_action)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_manual_search_confirm_prompt_confirm_submit() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearch.into());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.sonarr_data.prompt_confirm_action,
|
||||||
|
Some(SonarrEvent::DownloadRelease(SonarrReleaseDownloadBody {
|
||||||
|
guid: String::new(),
|
||||||
|
indexer_id: 0,
|
||||||
|
series_id: Some(0),
|
||||||
|
season_number: Some(0),
|
||||||
|
..SonarrReleaseDownloadBody::default()
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_season_details_prompt_decline_submit(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonDetails.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_season_search_submit() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearch.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_season_search_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearch.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::models::sonarr_models::SonarrHistoryItem;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use ratatui::widgets::TableState;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_history_details_block_esc() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistory.into());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistoryDetails.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::SeasonHistoryDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_season_details_prompts_esc(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveSonarrBlock,
|
||||||
|
#[values(true, false)] is_ready: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonDetails.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_history_esc_resets_filter_if_one_is_set_instead_of_closing_the_window() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
let mut season_history = StatefulTable {
|
||||||
|
filter: Some("Test".into()),
|
||||||
|
filtered_items: Some(vec![SonarrHistoryItem::default()]),
|
||||||
|
filtered_state: Some(TableState::default()),
|
||||||
|
..StatefulTable::default()
|
||||||
|
};
|
||||||
|
season_history.set_items(vec![SonarrHistoryItem::default()]);
|
||||||
|
app.data.sonarr_data.season_details_modal.as_mut().unwrap().season_history = season_history;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistory.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::SeasonHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeasonHistory.into()
|
||||||
|
);
|
||||||
|
assert!(app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_history
|
||||||
|
.filter
|
||||||
|
.is_none());
|
||||||
|
assert!(app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_history
|
||||||
|
.filtered_items
|
||||||
|
.is_none());
|
||||||
|
assert!(app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_history
|
||||||
|
.filtered_state
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_season_details_tabs_esc(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch
|
||||||
|
)] active_sonarr_block: ActiveSonarrBlock
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(ESC_KEY, &mut app, active_sonarr_block, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::SeriesDetails.into()
|
||||||
|
);
|
||||||
|
assert!(app.data.sonarr_data.season_details_modal.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use super::*;
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data;
|
||||||
|
use crate::network::sonarr_network::SonarrEvent;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_auto_search_key(
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory, ActiveSonarrBlock::ManualSeasonSearch)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.key,
|
||||||
|
&mut app,
|
||||||
|
active_sonarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_auto_search_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory, ActiveSonarrBlock::ManualSeasonSearch)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.key,
|
||||||
|
&mut app,
|
||||||
|
active_sonarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_sonarr_block.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_refresh_key(
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory, ActiveSonarrBlock::ManualSeasonSearch)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
app.is_routing = false;
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_sonarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_sonarr_block.into());
|
||||||
|
assert!(app.is_routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_refresh_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory, ActiveSonarrBlock::ManualSeasonSearch)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
app.is_routing = false;
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_sonarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_sonarr_block.into());
|
||||||
|
assert!(!app.is_routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
|
SonarrEvent::TriggerAutomaticSeasonSearch(None)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
|
SonarrEvent::DeleteEpisodeFile(None)
|
||||||
|
)]
|
||||||
|
fn test_season_details_prompt_confirm_confirm_key(
|
||||||
|
#[case] prompt_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_action: SonarrEvent,
|
||||||
|
#[values(ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonHistory)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
prompt_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.get_current_route(), active_sonarr_block.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.sonarr_data.prompt_confirm_action,
|
||||||
|
Some(expected_action)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_manual_search_confirm_prompt_confirm_confirm_key() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearch.into());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt.into());
|
||||||
|
|
||||||
|
SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.sonarr_data.prompt_confirm_action,
|
||||||
|
Some(SonarrEvent::DownloadRelease(SonarrReleaseDownloadBody {
|
||||||
|
guid: String::new(),
|
||||||
|
indexer_id: 0,
|
||||||
|
series_id: Some(0),
|
||||||
|
season_number: Some(0),
|
||||||
|
..SonarrReleaseDownloadBody::default()
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_accepts() {
|
||||||
|
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
|
||||||
|
if SEASON_DETAILS_BLOCKS.contains(&active_sonarr_block) {
|
||||||
|
assert!(SeasonDetailsHandler::accepts(active_sonarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!SeasonDetailsHandler::accepts(active_sonarr_block));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_is_not_ready_when_loading() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_is_not_ready_when_not_loading_and_season_details_is_none() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_is_not_ready_when_not_loading_and_episodes_table_is_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_is_not_ready_when_not_loading_and_history_table_is_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeasonHistory.into());
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_season_details_handler_is_not_ready_when_not_loading_and_releases_table_is_empty() {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::ManualSeasonSearch.into());
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_season_details_handler_is_ready_when_not_loading_and_season_details_modal_is_populated(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
|
ActiveSonarrBlock::ManualSeasonSearch
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::default();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let handler = SeasonDetailsHandler::with(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
active_sonarr_block,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_source() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering =
|
||||||
|
|a, b| a.protocol.cmp(&b.protocol);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[0].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_age() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| a.age.cmp(&b.age);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[1].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Age");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_rejected() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering =
|
||||||
|
|a, b| a.rejected.cmp(&b.rejected);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[2].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_title() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| {
|
||||||
|
a.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[3].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_indexer() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering =
|
||||||
|
|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase());
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[4].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Indexer");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_size() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering =
|
||||||
|
|a, b| a.size.cmp(&b.size);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[5].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Size");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_peers() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| {
|
||||||
|
let default_number = Number::from(i64::MAX);
|
||||||
|
let seeder_a = a
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let seeder_b = b
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
seeder_a.cmp(&seeder_b)
|
||||||
|
};
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[6].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Peers");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_language() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering = |a, b| {
|
||||||
|
let default_language_vec = vec![Language {
|
||||||
|
id: 1,
|
||||||
|
name: "_".to_owned(),
|
||||||
|
}];
|
||||||
|
let language_a = &a.languages.as_ref().unwrap_or(&default_language_vec)[0];
|
||||||
|
let language_b = &b.languages.as_ref().unwrap_or(&default_language_vec)[0];
|
||||||
|
|
||||||
|
language_a.cmp(language_b)
|
||||||
|
};
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[7].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Language");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&SonarrRelease, &SonarrRelease) -> Ordering =
|
||||||
|
|a, b| a.quality.cmp(&b.quality);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[8].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_vec() -> Vec<SonarrRelease> {
|
||||||
|
let release_a = SonarrRelease {
|
||||||
|
protocol: "Protocol A".to_owned(),
|
||||||
|
age: 1,
|
||||||
|
title: HorizontallyScrollableText::from("Title A"),
|
||||||
|
indexer: "Indexer A".to_owned(),
|
||||||
|
size: 1,
|
||||||
|
rejected: true,
|
||||||
|
seeders: Some(Number::from(1)),
|
||||||
|
languages: Some(vec![Language {
|
||||||
|
id: 1,
|
||||||
|
name: "Language A".to_owned(),
|
||||||
|
}]),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality A".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..SonarrRelease::default()
|
||||||
|
};
|
||||||
|
let release_b = SonarrRelease {
|
||||||
|
protocol: "Protocol B".to_owned(),
|
||||||
|
age: 2,
|
||||||
|
title: HorizontallyScrollableText::from("title B"),
|
||||||
|
indexer: "indexer B".to_owned(),
|
||||||
|
size: 2,
|
||||||
|
rejected: false,
|
||||||
|
seeders: Some(Number::from(2)),
|
||||||
|
languages: Some(vec![Language {
|
||||||
|
id: 2,
|
||||||
|
name: "Language B".to_owned(),
|
||||||
|
}]),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality B".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..SonarrRelease::default()
|
||||||
|
};
|
||||||
|
let release_c = SonarrRelease {
|
||||||
|
protocol: "Protocol C".to_owned(),
|
||||||
|
age: 3,
|
||||||
|
title: HorizontallyScrollableText::from("Title C"),
|
||||||
|
indexer: "Indexer C".to_owned(),
|
||||||
|
size: 3,
|
||||||
|
rejected: false,
|
||||||
|
seeders: None,
|
||||||
|
languages: None,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality C".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..SonarrRelease::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![release_a, release_b, release_c]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -329,22 +329,22 @@ impl Default for SeasonDetailsModal {
|
|||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Episodes",
|
title: "Episodes",
|
||||||
route: ActiveSonarrBlock::SeasonDetails.into(),
|
route: ActiveSonarrBlock::SeasonDetails.into(),
|
||||||
help: build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
help: build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History",
|
||||||
route: ActiveSonarrBlock::SeasonHistory.into(),
|
route: ActiveSonarrBlock::SeasonHistory.into(),
|
||||||
help: build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
help: build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Manual Search",
|
title: "Manual Search",
|
||||||
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
|
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
|
||||||
help: build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
help: build_context_clue_string(
|
||||||
contextual_help: Some(build_context_clue_string(
|
|
||||||
&MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
&MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
||||||
)),
|
),
|
||||||
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,11 +338,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_str_eq!(
|
assert_str_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[0].help,
|
season_details_modal.season_details_tabs.tabs[0].help,
|
||||||
build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES)
|
build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[0].contextual_help,
|
season_details_modal.season_details_tabs.tabs[0].contextual_help,
|
||||||
Some(build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES))
|
Some(build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(
|
assert_str_eq!(
|
||||||
@@ -355,11 +355,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_str_eq!(
|
assert_str_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[1].help,
|
season_details_modal.season_details_tabs.tabs[1].help,
|
||||||
build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)
|
build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[1].contextual_help,
|
season_details_modal.season_details_tabs.tabs[1].contextual_help,
|
||||||
Some(build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES))
|
Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_str_eq!(
|
assert_str_eq!(
|
||||||
@@ -372,12 +372,12 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_str_eq!(
|
assert_str_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[2].help,
|
season_details_modal.season_details_tabs.tabs[2].help,
|
||||||
build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)
|
build_context_clue_string(&MANUAL_SEASON_SEARCH_CONTEXT_CLUES)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
season_details_modal.season_details_tabs.tabs[2].contextual_help,
|
season_details_modal.season_details_tabs.tabs[2].contextual_help,
|
||||||
Some(build_context_clue_string(
|
Some(build_context_clue_string(
|
||||||
&MANUAL_SEASON_SEARCH_CONTEXT_CLUES
|
&DETAILS_CONTEXTUAL_CONTEXT_CLUES
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,11 +333,11 @@ pub static SERIES_DETAILS_BLOCKS: [ActiveSonarrBlock; 12] = [
|
|||||||
ActiveSonarrBlock::SeriesHistoryDetails,
|
ActiveSonarrBlock::SeriesHistoryDetails,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 14] = [
|
pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 15] = [
|
||||||
ActiveSonarrBlock::SeasonDetails,
|
ActiveSonarrBlock::SeasonDetails,
|
||||||
ActiveSonarrBlock::SeasonHistory,
|
ActiveSonarrBlock::SeasonHistory,
|
||||||
ActiveSonarrBlock::SearchSeason,
|
ActiveSonarrBlock::SearchEpisodes,
|
||||||
ActiveSonarrBlock::SearchSeasonError,
|
ActiveSonarrBlock::SearchEpisodesError,
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||||
ActiveSonarrBlock::SearchSeasonHistory,
|
ActiveSonarrBlock::SearchSeasonHistory,
|
||||||
ActiveSonarrBlock::SearchSeasonHistoryError,
|
ActiveSonarrBlock::SearchSeasonHistoryError,
|
||||||
@@ -348,6 +348,7 @@ pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 14] = [
|
|||||||
ActiveSonarrBlock::ManualSeasonSearch,
|
ActiveSonarrBlock::ManualSeasonSearch,
|
||||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||||
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
||||||
|
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static ADD_SERIES_BLOCKS: [ActiveSonarrBlock; 13] = [
|
pub static ADD_SERIES_BLOCKS: [ActiveSonarrBlock; 13] = [
|
||||||
|
|||||||
@@ -608,11 +608,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_season_details_blocks_contents() {
|
fn test_season_details_blocks_contents() {
|
||||||
assert_eq!(SEASON_DETAILS_BLOCKS.len(), 14);
|
assert_eq!(SEASON_DETAILS_BLOCKS.len(), 15);
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeasonDetails));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeasonDetails));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeasonHistory));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeasonHistory));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeason));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchEpisodes));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonError));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchEpisodesError));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::AutomaticallySearchSeasonPrompt));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::AutomaticallySearchSeasonPrompt));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonHistory));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonHistory));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonHistoryError));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonHistoryError));
|
||||||
@@ -623,6 +623,7 @@ mod tests {
|
|||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearch));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearch));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt));
|
||||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearchSortPrompt));
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearchSortPrompt));
|
||||||
|
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::DeleteEpisodeFilePrompt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub mod utils {
|
|||||||
season_details_modal
|
season_details_modal
|
||||||
.episodes
|
.episodes
|
||||||
.set_items(vec![Episode::default()]);
|
.set_items(vec![Episode::default()]);
|
||||||
|
season_details_modal.season_history.set_items(vec![SonarrHistoryItem::default()]);
|
||||||
season_details_modal
|
season_details_modal
|
||||||
.season_releases
|
.season_releases
|
||||||
.set_items(vec![SonarrRelease::default()]);
|
.set_items(vec![SonarrRelease::default()]);
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ pub struct Episode {
|
|||||||
pub season_number: i64,
|
pub season_number: i64,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
pub episode_number: i64,
|
pub episode_number: i64,
|
||||||
pub title: Option<String>,
|
pub title: String,
|
||||||
pub air_date_utc: Option<DateTime<Utc>>,
|
pub air_date_utc: Option<DateTime<Utc>>,
|
||||||
pub overview: Option<String>,
|
pub overview: Option<String>,
|
||||||
pub has_file: bool,
|
pub has_file: bool,
|
||||||
@@ -220,7 +220,7 @@ pub struct Episode {
|
|||||||
|
|
||||||
impl Display for Episode {
|
impl Display for Episode {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.title.as_ref().unwrap_or(&String::new()))
|
write!(f, "{}", self.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_episode_display() {
|
fn test_episode_display() {
|
||||||
let episode = Episode {
|
let episode = Episode {
|
||||||
title: Some("Test Title".to_owned()),
|
title: "Test Title".to_owned(),
|
||||||
..Episode::default()
|
..Episode::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use log::{debug, info, warn};
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
|
|
||||||
|
use super::{Network, NetworkEvent, NetworkResource};
|
||||||
|
use crate::models::sonarr_models::DownloadStatus;
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{
|
models::{
|
||||||
radarr_models::IndexerTestResult,
|
radarr_models::IndexerTestResult,
|
||||||
@@ -31,8 +33,6 @@ use crate::{
|
|||||||
network::RequestMethod,
|
network::RequestMethod,
|
||||||
utils::convert_to_gb,
|
utils::convert_to_gb,
|
||||||
};
|
};
|
||||||
use crate::models::sonarr_models::DownloadStatus;
|
|
||||||
use super::{Network, NetworkEvent, NetworkResource};
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "sonarr_network_tests.rs"]
|
#[path = "sonarr_network_tests.rs"]
|
||||||
mod sonarr_network_tests;
|
mod sonarr_network_tests;
|
||||||
@@ -1568,7 +1568,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
Air Date: {air_date}
|
Air Date: {air_date}
|
||||||
Status: {status}
|
Status: {status}
|
||||||
Description: {}",
|
Description: {}",
|
||||||
title.unwrap_or_default(),
|
title,
|
||||||
overview.unwrap_or_default(),
|
overview.unwrap_or_default(),
|
||||||
)),
|
)),
|
||||||
..EpisodeDetailsModal::default()
|
..EpisodeDetailsModal::default()
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ mod test {
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, AddSeriesSearchResultStatistics, DownloadStatus, EditSeriesParams, IndexerSettings, SeriesMonitor, SonarrHistoryEventType};
|
use crate::models::sonarr_models::{
|
||||||
|
AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, AddSeriesSearchResultStatistics,
|
||||||
|
DownloadStatus, EditSeriesParams, IndexerSettings, SeriesMonitor, SonarrHistoryEventType,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::{App, ServarrConfig};
|
use crate::app::{App, ServarrConfig};
|
||||||
use crate::models::radarr_models::IndexerTestResult;
|
use crate::models::radarr_models::IndexerTestResult;
|
||||||
@@ -2234,13 +2237,13 @@ mod test {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_episodes_event(#[values(true, false)] use_custom_sorting: bool) {
|
async fn test_handle_get_episodes_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||||
let episode_1 = Episode {
|
let episode_1 = Episode {
|
||||||
title: Some("z test".to_owned()),
|
title: "z test".to_owned(),
|
||||||
episode_file: None,
|
episode_file: None,
|
||||||
..episode()
|
..episode()
|
||||||
};
|
};
|
||||||
let episode_2 = Episode {
|
let episode_2 = Episode {
|
||||||
id: 2,
|
id: 2,
|
||||||
title: Some("A test".to_owned()),
|
title: "A test".to_owned(),
|
||||||
episode_file_id: 2,
|
episode_file_id: 2,
|
||||||
season_number: 2,
|
season_number: 2,
|
||||||
episode_number: 2,
|
episode_number: 2,
|
||||||
@@ -2249,7 +2252,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let episode_3 = Episode {
|
let episode_3 = Episode {
|
||||||
id: 3,
|
id: 3,
|
||||||
title: Some("A test".to_owned()),
|
title: "A test".to_owned(),
|
||||||
episode_file_id: 3,
|
episode_file_id: 3,
|
||||||
season_number: 1,
|
season_number: 1,
|
||||||
episode_number: 2,
|
episode_number: 2,
|
||||||
@@ -2271,13 +2274,7 @@ mod test {
|
|||||||
let mut season_details_modal = SeasonDetailsModal::default();
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
season_details_modal.episodes.sort_asc = true;
|
season_details_modal.episodes.sort_asc = true;
|
||||||
if use_custom_sorting {
|
if use_custom_sorting {
|
||||||
let cmp_fn = |a: &Episode, b: &Episode| {
|
let cmp_fn = |a: &Episode, b: &Episode| a.title.to_lowercase().cmp(&b.title.to_lowercase());
|
||||||
a.title
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.to_lowercase()
|
|
||||||
.cmp(&b.title.as_ref().unwrap().to_lowercase())
|
|
||||||
};
|
|
||||||
expected_sorted_episodes.sort_by(cmp_fn);
|
expected_sorted_episodes.sort_by(cmp_fn);
|
||||||
let title_sort_option = SortOption {
|
let title_sort_option = SortOption {
|
||||||
name: "Title",
|
name: "Title",
|
||||||
@@ -2348,13 +2345,13 @@ mod test {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_episodes_event_empty_seasons_table_returns_all_episodes_by_default() {
|
async fn test_handle_get_episodes_event_empty_seasons_table_returns_all_episodes_by_default() {
|
||||||
let episode_1 = Episode {
|
let episode_1 = Episode {
|
||||||
title: Some("z test".to_owned()),
|
title: "z test".to_owned(),
|
||||||
episode_file: None,
|
episode_file: None,
|
||||||
..episode()
|
..episode()
|
||||||
};
|
};
|
||||||
let episode_2 = Episode {
|
let episode_2 = Episode {
|
||||||
id: 2,
|
id: 2,
|
||||||
title: Some("A test".to_owned()),
|
title: "A test".to_owned(),
|
||||||
episode_file_id: 2,
|
episode_file_id: 2,
|
||||||
season_number: 2,
|
season_number: 2,
|
||||||
episode_number: 2,
|
episode_number: 2,
|
||||||
@@ -2363,7 +2360,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let episode_3 = Episode {
|
let episode_3 = Episode {
|
||||||
id: 3,
|
id: 3,
|
||||||
title: Some("A test".to_owned()),
|
title: "A test".to_owned(),
|
||||||
episode_file_id: 3,
|
episode_file_id: 3,
|
||||||
season_number: 1,
|
season_number: 1,
|
||||||
episode_number: 2,
|
episode_number: 2,
|
||||||
@@ -2537,13 +2534,7 @@ mod test {
|
|||||||
.push_navigation_stack(ActiveSonarrBlock::EpisodesSortPrompt.into());
|
.push_navigation_stack(ActiveSonarrBlock::EpisodesSortPrompt.into());
|
||||||
let mut season_details_modal = SeasonDetailsModal::default();
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
season_details_modal.episodes.sort_asc = true;
|
season_details_modal.episodes.sort_asc = true;
|
||||||
let cmp_fn = |a: &Episode, b: &Episode| {
|
let cmp_fn = |a: &Episode, b: &Episode| a.title.to_lowercase().cmp(&b.title.to_lowercase());
|
||||||
a.title
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.to_lowercase()
|
|
||||||
.cmp(&b.title.as_ref().unwrap().to_lowercase())
|
|
||||||
};
|
|
||||||
expected_episodes.sort_by(cmp_fn);
|
expected_episodes.sort_by(cmp_fn);
|
||||||
let title_sort_option = SortOption {
|
let title_sort_option = SortOption {
|
||||||
name: "Title",
|
name: "Title",
|
||||||
@@ -7255,7 +7246,7 @@ mod test {
|
|||||||
episode_file_id: 1,
|
episode_file_id: 1,
|
||||||
season_number: 1,
|
season_number: 1,
|
||||||
episode_number: 1,
|
episode_number: 1,
|
||||||
title: Some("Something cool".to_owned()),
|
title: "Something cool".to_owned(),
|
||||||
air_date_utc: Some(DateTime::from(
|
air_date_utc: Some(DateTime::from(
|
||||||
DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap(),
|
DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap(),
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS};
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS};
|
||||||
use crate::models::sonarr_models::{
|
use crate::models::sonarr_models::{
|
||||||
DownloadRecord, DownloadStatus, Episode, SonarrHistoryItem, SonarrRelease,
|
DownloadRecord, DownloadStatus, Episode, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease,
|
||||||
};
|
};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
use crate::ui::sonarr_ui::sonarr_ui_utils::{
|
||||||
|
create_download_failed_history_event_details,
|
||||||
|
create_download_folder_imported_history_event_details,
|
||||||
|
create_episode_file_deleted_history_event_details,
|
||||||
|
create_episode_file_renamed_history_event_details, create_grabbed_history_event_details,
|
||||||
|
create_no_data_history_event_details,
|
||||||
|
};
|
||||||
use crate::ui::styles::ManagarrStyle;
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, decorate_peer_style, get_width_from_percentage, layout_block_top_border,
|
borderless_block, decorate_peer_style, get_width_from_percentage, layout_block_top_border,
|
||||||
@@ -11,12 +18,13 @@ use crate::ui::utils::{
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{draw_popup, draw_tabs, DrawUi};
|
use crate::ui::{draw_popup, draw_tabs, DrawUi};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::convert_to_gb;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use ratatui::layout::{Constraint, Rect};
|
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||||
use ratatui::prelude::{Line, Stylize, Text};
|
use ratatui::prelude::{Line, Style, Stylize, Text};
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
|
||||||
@@ -37,20 +45,12 @@ impl DrawUi for SeasonDetailsUi {
|
|||||||
|
|
||||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||||
if app.data.sonarr_data.season_details_modal.is_some() {
|
if app.data.sonarr_data.season_details_modal.is_some() {
|
||||||
if let Route::Sonarr(active_sonarr_block, _) = app
|
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.season_details_modal
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.season_details_tabs
|
|
||||||
.get_active_route()
|
|
||||||
{
|
|
||||||
let draw_season_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
|
let draw_season_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
|
||||||
let content_area = draw_tabs(
|
let content_area = draw_tabs(
|
||||||
f,
|
f,
|
||||||
popup_area,
|
popup_area,
|
||||||
"Season Details",
|
&format!("Season {} Details", app.data.sonarr_data.seasons.current_selection().season_number),
|
||||||
&app
|
&app
|
||||||
.data
|
.data
|
||||||
.sonarr_data
|
.sonarr_data
|
||||||
@@ -64,8 +64,8 @@ impl DrawUi for SeasonDetailsUi {
|
|||||||
match active_sonarr_block {
|
match active_sonarr_block {
|
||||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
||||||
let prompt = format!(
|
let prompt = format!(
|
||||||
"Do you want to trigger an automatic search of your indexers for season packs for the season: Season {}",
|
"Do you want to trigger an automatic search of your indexers for season packs for: {}",
|
||||||
app.data.sonarr_data.seasons.current_selection().season_number
|
app.data.sonarr_data.seasons.current_selection().title.as_ref().unwrap()
|
||||||
);
|
);
|
||||||
let confirmation_prompt = ConfirmationPrompt::new()
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
.title("Automatic Season Search")
|
.title("Automatic Season Search")
|
||||||
@@ -89,8 +89,6 @@ impl DrawUi for SeasonDetailsUi {
|
|||||||
.episodes
|
.episodes
|
||||||
.current_selection()
|
.current_selection()
|
||||||
.title
|
.title
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&String::new())
|
|
||||||
);
|
);
|
||||||
let confirmation_prompt = ConfirmationPrompt::new()
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
.title("Delete Episode")
|
.title("Delete Episode")
|
||||||
@@ -105,11 +103,14 @@ impl DrawUi for SeasonDetailsUi {
|
|||||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt => {
|
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt => {
|
||||||
draw_manual_season_search_confirm_prompt(f, app);
|
draw_manual_season_search_confirm_prompt(f, app);
|
||||||
}
|
}
|
||||||
|
ActiveSonarrBlock::SeasonHistoryDetails => {
|
||||||
|
draw_history_item_details_popup(f, app, popup_area);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
draw_popup(f, app, draw_season_details_popup, Size::Large);
|
draw_popup(f, app, draw_season_details_popup, Size::XLarge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,20 +195,20 @@ fn draw_episodes_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(episode_monitored.to_owned()),
|
Cell::from(episode_monitored.to_owned()),
|
||||||
Cell::from(episode_number.to_string()),
|
Cell::from(episode_number.to_string()),
|
||||||
Cell::from(title.clone().unwrap_or_default()),
|
Cell::from(title.clone()),
|
||||||
Cell::from(air_date),
|
Cell::from(air_date),
|
||||||
Cell::from(format!("{size:.2} GB")),
|
Cell::from(format!("{size:.2} GB")),
|
||||||
Cell::from(quality_profile),
|
Cell::from(quality_profile),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchSeason;
|
let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchEpisodes;
|
||||||
let season_table = ManagarrTable::new(content, episode_row_mapping)
|
let season_table = ManagarrTable::new(content, episode_row_mapping)
|
||||||
.block(layout_block_top_border())
|
.block(layout_block_top_border())
|
||||||
.loading(app.is_loading)
|
.loading(app.is_loading)
|
||||||
.footer(help_footer)
|
.footer(help_footer)
|
||||||
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeason)
|
.searching(is_searching)
|
||||||
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeasonError)
|
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchEpisodesError)
|
||||||
.headers([
|
.headers([
|
||||||
"🏷",
|
"🏷",
|
||||||
"#",
|
"#",
|
||||||
@@ -329,13 +330,14 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
|||||||
fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
match app.data.sonarr_data.season_details_modal.as_ref() {
|
match app.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
Some(season_details_modal) if !app.is_loading => {
|
Some(season_details_modal) if !app.is_loading => {
|
||||||
let current_selection = if season_details_modal.season_releases.is_empty() {
|
let (current_selection, is_empty) = if season_details_modal.season_releases.is_empty() {
|
||||||
SonarrRelease::default()
|
(SonarrRelease::default(), true)
|
||||||
} else {
|
} else {
|
||||||
season_details_modal
|
(season_details_modal
|
||||||
.season_releases
|
.season_releases
|
||||||
.current_selection()
|
.current_selection()
|
||||||
.clone()
|
.clone(),
|
||||||
|
season_details_modal.season_releases.is_empty())
|
||||||
};
|
};
|
||||||
let season_release_table_footer = season_details_modal
|
let season_release_table_footer = season_details_modal
|
||||||
.season_details_tabs
|
.season_details_tabs
|
||||||
@@ -409,16 +411,22 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
let release_table =
|
let release_table =
|
||||||
ManagarrTable::new(Some(&mut season_release_table), season_release_row_mapping)
|
ManagarrTable::new(Some(&mut season_release_table), season_release_row_mapping)
|
||||||
.block(layout_block_top_border())
|
.block(layout_block_top_border())
|
||||||
.loading(app.is_loading)
|
.loading(app.is_loading || is_empty)
|
||||||
.footer(season_release_table_footer)
|
.footer(season_release_table_footer)
|
||||||
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
|
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
|
||||||
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
|
.headers([
|
||||||
|
"Source", "Age", "⛔", "Title", "Indexer", "Size", "Peers", "Language", "Quality",
|
||||||
|
])
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Percentage(40),
|
Constraint::Length(9),
|
||||||
Constraint::Percentage(15),
|
Constraint::Length(10),
|
||||||
Constraint::Percentage(12),
|
Constraint::Length(5),
|
||||||
Constraint::Percentage(13),
|
Constraint::Percentage(30),
|
||||||
Constraint::Percentage(20),
|
Constraint::Percentage(18),
|
||||||
|
Constraint::Length(12),
|
||||||
|
Constraint::Length(12),
|
||||||
|
Constraint::Percentage(7),
|
||||||
|
Constraint::Percentage(10),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
f.render_widget(release_table, area);
|
f.render_widget(release_table, area);
|
||||||
@@ -479,14 +487,14 @@ fn draw_manual_season_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
|||||||
.title(title)
|
.title(title)
|
||||||
.prompt(&prompt)
|
.prompt(&prompt)
|
||||||
.content(content_paragraph)
|
.content(content_paragraph)
|
||||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
.yes_no_value(app.data.sonarr_data.prompt_confirm);
|
||||||
|
|
||||||
f.render_widget(Popup::new(confirmation_prompt).size(Size::Small), f.area());
|
f.render_widget(Popup::new(confirmation_prompt).size(Size::Small), f.area());
|
||||||
} else {
|
} else {
|
||||||
let confirmation_prompt = ConfirmationPrompt::new()
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
.title(title)
|
.title(title)
|
||||||
.prompt(&prompt)
|
.prompt(&prompt)
|
||||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
.yes_no_value(app.data.sonarr_data.prompt_confirm);
|
||||||
|
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
@@ -495,6 +503,47 @@ fn draw_manual_season_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let current_selection =
|
||||||
|
if let Some(season_details_modal) = app.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
|
if season_details_modal.season_history.is_empty() {
|
||||||
|
SonarrHistoryItem::default()
|
||||||
|
} else {
|
||||||
|
season_details_modal
|
||||||
|
.season_history
|
||||||
|
.current_selection()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SonarrHistoryItem::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_vec = match current_selection.event_type {
|
||||||
|
SonarrHistoryEventType::Grabbed => create_grabbed_history_event_details(current_selection),
|
||||||
|
SonarrHistoryEventType::DownloadFolderImported => {
|
||||||
|
create_download_folder_imported_history_event_details(current_selection)
|
||||||
|
}
|
||||||
|
SonarrHistoryEventType::DownloadFailed => {
|
||||||
|
create_download_failed_history_event_details(current_selection)
|
||||||
|
}
|
||||||
|
SonarrHistoryEventType::EpisodeFileDeleted => {
|
||||||
|
create_episode_file_deleted_history_event_details(current_selection)
|
||||||
|
}
|
||||||
|
SonarrHistoryEventType::EpisodeFileRenamed => {
|
||||||
|
create_episode_file_renamed_history_event_details(current_selection)
|
||||||
|
}
|
||||||
|
_ => create_no_data_history_event_details(current_selection),
|
||||||
|
};
|
||||||
|
let text = Text::from(line_vec);
|
||||||
|
|
||||||
|
let message = Message::new(text)
|
||||||
|
.title("Details")
|
||||||
|
.style(Style::new().secondary())
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
f.render_widget(Popup::new(message).size(Size::NarrowMessage), area);
|
||||||
|
}
|
||||||
|
|
||||||
fn decorate_with_row_style<'a>(
|
fn decorate_with_row_style<'a>(
|
||||||
downloads_vec: &[DownloadRecord],
|
downloads_vec: &[DownloadRecord],
|
||||||
episode: &Episode,
|
episode: &Episode,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub enum Size {
|
|||||||
Small,
|
Small,
|
||||||
Medium,
|
Medium,
|
||||||
Large,
|
Large,
|
||||||
|
XLarge,
|
||||||
XXLarge,
|
XXLarge,
|
||||||
Long,
|
Long,
|
||||||
}
|
}
|
||||||
@@ -41,6 +42,7 @@ impl Size {
|
|||||||
Size::Small => (40, 40),
|
Size::Small => (40, 40),
|
||||||
Size::Medium => (60, 60),
|
Size::Medium => (60, 60),
|
||||||
Size::Large => (75, 75),
|
Size::Large => (75, 75),
|
||||||
|
Size::XLarge => (83, 83),
|
||||||
Size::XXLarge => (90, 90),
|
Size::XXLarge => (90, 90),
|
||||||
Size::Long => (65, 75),
|
Size::Long => (65, 75),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod tests {
|
|||||||
assert_eq!(Size::Small.to_percent(), (40, 40));
|
assert_eq!(Size::Small.to_percent(), (40, 40));
|
||||||
assert_eq!(Size::Medium.to_percent(), (60, 60));
|
assert_eq!(Size::Medium.to_percent(), (60, 60));
|
||||||
assert_eq!(Size::Large.to_percent(), (75, 75));
|
assert_eq!(Size::Large.to_percent(), (75, 75));
|
||||||
|
assert_eq!(Size::XLarge.to_percent(), (83, 83));
|
||||||
assert_eq!(Size::XXLarge.to_percent(), (90, 90));
|
assert_eq!(Size::XXLarge.to_percent(), (90, 90));
|
||||||
assert_eq!(Size::Long.to_percent(), (65, 75));
|
assert_eq!(Size::Long.to_percent(), (65, 75));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user