feat(handler): Full handler support for the Season details UI in Sonarr
This commit is contained in:
+17
-6
@@ -55,9 +55,14 @@ impl<'a> App<'a> {
|
||||
.await;
|
||||
}
|
||||
ActiveSonarrBlock::ManualSeasonSearch => {
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into())
|
||||
.await;
|
||||
match self.data.sonarr_data.season_details_modal.as_ref() {
|
||||
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into())
|
||||
.await;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
|
||||
self
|
||||
@@ -70,9 +75,15 @@ impl<'a> App<'a> {
|
||||
.await;
|
||||
}
|
||||
ActiveSonarrBlock::ManualEpisodeSearch => {
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into())
|
||||
.await;
|
||||
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
|
||||
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::Downloads => {
|
||||
self
|
||||
|
||||
@@ -7,8 +7,11 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
models::{
|
||||
servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
||||
sonarr_models::{Season, Series},
|
||||
servarr_data::sonarr::{
|
||||
modals::{EpisodeDetailsModal, SeasonDetailsModal},
|
||||
sonarr_data::ActiveSonarrBlock,
|
||||
},
|
||||
sonarr_models::{Season, Series, SonarrRelease},
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, NetworkEvent},
|
||||
};
|
||||
@@ -115,6 +118,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_manual_season_search_block() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||
|
||||
app
|
||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
||||
@@ -129,6 +133,40 @@ mod tests {
|
||||
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]
|
||||
async fn test_dispatch_by_episode_details_block() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
@@ -183,6 +221,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_manual_episode_search_block() {
|
||||
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
|
||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch)
|
||||
@@ -197,6 +238,42 @@ mod tests {
|
||||
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]
|
||||
async fn test_dispatch_by_history_block() {
|
||||
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_releases.set_items(vec![$crate::models::radarr_models::RadarrRelease::default()]);
|
||||
app.data.radarr_data.movie_details_modal = Some(movie_details_modal);
|
||||
let mut season_details_modal = $crate::models::servarr_data::sonarr::modals::SeasonDetailsModal::default();
|
||||
season_details_modal.season_history.set_items(vec![$crate::models::sonarr_models::SonarrHistoryItem::default()]);
|
||||
season_details_modal.episode_details_modal = Some($crate::models::servarr_data::sonarr::modals::EpisodeDetailsModal::default());
|
||||
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
|
||||
series_history.set_items(vec![
|
||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
||||
|
||||
@@ -328,31 +328,26 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::TriggerAutomaticSearch(None));
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key =>
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::TriggerAutomaticSearch(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None));
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
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 {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DownloadRelease(None));
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = 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::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, LIBRARY_BLOCKS,
|
||||
SERIES_DETAILS_BLOCKS,
|
||||
};
|
||||
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};
|
||||
use crate::models::sonarr_models::{Series, SeriesStatus, SeriesType};
|
||||
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]
|
||||
fn test_delegates_edit_series_blocks_to_edit_series_handler(
|
||||
#[values(
|
||||
@@ -768,6 +792,7 @@ mod tests {
|
||||
library_handler_blocks.extend(DELETE_SERIES_BLOCKS);
|
||||
library_handler_blocks.extend(EDIT_SERIES_BLOCKS);
|
||||
library_handler_blocks.extend(SERIES_DETAILS_BLOCKS);
|
||||
library_handler_blocks.extend(SEASON_DETAILS_BLOCKS);
|
||||
|
||||
ActiveSonarrBlock::iter().for_each(|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 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::table_handler::TableHandlingConfig;
|
||||
|
||||
@@ -32,6 +33,7 @@ mod delete_series_handler;
|
||||
#[path = "library_handler_tests.rs"]
|
||||
mod library_handler_tests;
|
||||
mod series_details_handler;
|
||||
mod season_details_handler;
|
||||
|
||||
pub(super) struct LibraryHandler<'a, 'b> {
|
||||
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)
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
@@ -85,6 +91,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
|| DeleteSeriesHandler::accepts(active_block)
|
||||
|| EditSeriesHandler::accepts(active_block)
|
||||
|| SeriesDetailsHandler::accepts(active_block)
|
||||
|| SeasonDetailsHandler::accepts(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 {
|
||||
title: "Episodes",
|
||||
route: ActiveSonarrBlock::SeasonDetails.into(),
|
||||
help: build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
||||
contextual_help: Some(build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES)),
|
||||
help: build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES),
|
||||
contextual_help: Some(build_context_clue_string(&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "History",
|
||||
route: ActiveSonarrBlock::SeasonHistory.into(),
|
||||
help: build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
||||
contextual_help: Some(build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES)),
|
||||
help: build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES),
|
||||
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Manual Search",
|
||||
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
|
||||
help: build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES),
|
||||
contextual_help: Some(build_context_clue_string(
|
||||
help: build_context_clue_string(
|
||||
&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!(
|
||||
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!(
|
||||
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!(
|
||||
@@ -355,11 +355,11 @@ mod tests {
|
||||
);
|
||||
assert_str_eq!(
|
||||
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!(
|
||||
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!(
|
||||
@@ -372,12 +372,12 @@ mod tests {
|
||||
);
|
||||
assert_str_eq!(
|
||||
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!(
|
||||
season_details_modal.season_details_tabs.tabs[2].contextual_help,
|
||||
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,
|
||||
];
|
||||
|
||||
pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 14] = [
|
||||
pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 15] = [
|
||||
ActiveSonarrBlock::SeasonDetails,
|
||||
ActiveSonarrBlock::SeasonHistory,
|
||||
ActiveSonarrBlock::SearchSeason,
|
||||
ActiveSonarrBlock::SearchSeasonError,
|
||||
ActiveSonarrBlock::SearchEpisodes,
|
||||
ActiveSonarrBlock::SearchEpisodesError,
|
||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
|
||||
ActiveSonarrBlock::SearchSeasonHistory,
|
||||
ActiveSonarrBlock::SearchSeasonHistoryError,
|
||||
@@ -348,6 +348,7 @@ pub static SEASON_DETAILS_BLOCKS: [ActiveSonarrBlock; 14] = [
|
||||
ActiveSonarrBlock::ManualSeasonSearch,
|
||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
|
||||
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
|
||||
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
|
||||
];
|
||||
|
||||
pub static ADD_SERIES_BLOCKS: [ActiveSonarrBlock; 13] = [
|
||||
|
||||
@@ -608,11 +608,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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::SeasonHistory));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeason));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonError));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchEpisodes));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchEpisodesError));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::AutomaticallySearchSeasonPrompt));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonHistory));
|
||||
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::ManualSeasonSearchConfirmPrompt));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::ManualSeasonSearchSortPrompt));
|
||||
assert!(SEASON_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::DeleteEpisodeFilePrompt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pub mod utils {
|
||||
season_details_modal
|
||||
.episodes
|
||||
.set_items(vec![Episode::default()]);
|
||||
season_details_modal.season_history.set_items(vec![SonarrHistoryItem::default()]);
|
||||
season_details_modal
|
||||
.season_releases
|
||||
.set_items(vec![SonarrRelease::default()]);
|
||||
|
||||
@@ -210,7 +210,7 @@ pub struct Episode {
|
||||
pub season_number: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub episode_number: i64,
|
||||
pub title: Option<String>,
|
||||
pub title: String,
|
||||
pub air_date_utc: Option<DateTime<Utc>>,
|
||||
pub overview: Option<String>,
|
||||
pub has_file: bool,
|
||||
@@ -220,7 +220,7 @@ pub struct Episode {
|
||||
|
||||
impl Display for Episode {
|
||||
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]
|
||||
fn test_episode_display() {
|
||||
let episode = Episode {
|
||||
title: Some("Test Title".to_owned()),
|
||||
title: "Test Title".to_owned(),
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ use log::{debug, info, warn};
|
||||
use serde_json::{json, Value};
|
||||
use urlencoding::encode;
|
||||
|
||||
use super::{Network, NetworkEvent, NetworkResource};
|
||||
use crate::models::sonarr_models::DownloadStatus;
|
||||
use crate::{
|
||||
models::{
|
||||
radarr_models::IndexerTestResult,
|
||||
@@ -31,8 +33,6 @@ use crate::{
|
||||
network::RequestMethod,
|
||||
utils::convert_to_gb,
|
||||
};
|
||||
use crate::models::sonarr_models::DownloadStatus;
|
||||
use super::{Network, NetworkEvent, NetworkResource};
|
||||
#[cfg(test)]
|
||||
#[path = "sonarr_network_tests.rs"]
|
||||
mod sonarr_network_tests;
|
||||
@@ -1568,7 +1568,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
Air Date: {air_date}
|
||||
Status: {status}
|
||||
Description: {}",
|
||||
title.unwrap_or_default(),
|
||||
title,
|
||||
overview.unwrap_or_default(),
|
||||
)),
|
||||
..EpisodeDetailsModal::default()
|
||||
|
||||
@@ -15,7 +15,10 @@ mod test {
|
||||
use tokio::sync::Mutex;
|
||||
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::models::radarr_models::IndexerTestResult;
|
||||
@@ -2234,13 +2237,13 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_episodes_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||
let episode_1 = Episode {
|
||||
title: Some("z test".to_owned()),
|
||||
title: "z test".to_owned(),
|
||||
episode_file: None,
|
||||
..episode()
|
||||
};
|
||||
let episode_2 = Episode {
|
||||
id: 2,
|
||||
title: Some("A test".to_owned()),
|
||||
title: "A test".to_owned(),
|
||||
episode_file_id: 2,
|
||||
season_number: 2,
|
||||
episode_number: 2,
|
||||
@@ -2249,7 +2252,7 @@ mod test {
|
||||
};
|
||||
let episode_3 = Episode {
|
||||
id: 3,
|
||||
title: Some("A test".to_owned()),
|
||||
title: "A test".to_owned(),
|
||||
episode_file_id: 3,
|
||||
season_number: 1,
|
||||
episode_number: 2,
|
||||
@@ -2271,13 +2274,7 @@ mod test {
|
||||
let mut season_details_modal = SeasonDetailsModal::default();
|
||||
season_details_modal.episodes.sort_asc = true;
|
||||
if use_custom_sorting {
|
||||
let cmp_fn = |a: &Episode, b: &Episode| {
|
||||
a.title
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.as_ref().unwrap().to_lowercase())
|
||||
};
|
||||
let cmp_fn = |a: &Episode, b: &Episode| a.title.to_lowercase().cmp(&b.title.to_lowercase());
|
||||
expected_sorted_episodes.sort_by(cmp_fn);
|
||||
let title_sort_option = SortOption {
|
||||
name: "Title",
|
||||
@@ -2348,13 +2345,13 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_episodes_event_empty_seasons_table_returns_all_episodes_by_default() {
|
||||
let episode_1 = Episode {
|
||||
title: Some("z test".to_owned()),
|
||||
title: "z test".to_owned(),
|
||||
episode_file: None,
|
||||
..episode()
|
||||
};
|
||||
let episode_2 = Episode {
|
||||
id: 2,
|
||||
title: Some("A test".to_owned()),
|
||||
title: "A test".to_owned(),
|
||||
episode_file_id: 2,
|
||||
season_number: 2,
|
||||
episode_number: 2,
|
||||
@@ -2363,7 +2360,7 @@ mod test {
|
||||
};
|
||||
let episode_3 = Episode {
|
||||
id: 3,
|
||||
title: Some("A test".to_owned()),
|
||||
title: "A test".to_owned(),
|
||||
episode_file_id: 3,
|
||||
season_number: 1,
|
||||
episode_number: 2,
|
||||
@@ -2537,13 +2534,7 @@ mod test {
|
||||
.push_navigation_stack(ActiveSonarrBlock::EpisodesSortPrompt.into());
|
||||
let mut season_details_modal = SeasonDetailsModal::default();
|
||||
season_details_modal.episodes.sort_asc = true;
|
||||
let cmp_fn = |a: &Episode, b: &Episode| {
|
||||
a.title
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.as_ref().unwrap().to_lowercase())
|
||||
};
|
||||
let cmp_fn = |a: &Episode, b: &Episode| a.title.to_lowercase().cmp(&b.title.to_lowercase());
|
||||
expected_episodes.sort_by(cmp_fn);
|
||||
let title_sort_option = SortOption {
|
||||
name: "Title",
|
||||
@@ -7255,7 +7246,7 @@ mod test {
|
||||
episode_file_id: 1,
|
||||
season_number: 1,
|
||||
episode_number: 1,
|
||||
title: Some("Something cool".to_owned()),
|
||||
title: "Something cool".to_owned(),
|
||||
air_date_utc: Some(DateTime::from(
|
||||
DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap(),
|
||||
)),
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::{
|
||||
DownloadRecord, DownloadStatus, Episode, SonarrHistoryItem, SonarrRelease,
|
||||
DownloadRecord, DownloadStatus, Episode, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease,
|
||||
};
|
||||
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::utils::{
|
||||
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::loading_block::LoadingBlock;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::ui::widgets::message::Message;
|
||||
use crate::ui::widgets::popup::{Popup, Size};
|
||||
use crate::ui::{draw_popup, draw_tabs, DrawUi};
|
||||
use crate::utils::convert_to_gb;
|
||||
use chrono::Utc;
|
||||
use ratatui::layout::{Constraint, Rect};
|
||||
use ratatui::prelude::{Line, Stylize, Text};
|
||||
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||
use ratatui::prelude::{Line, Style, Stylize, Text};
|
||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||
use ratatui::Frame;
|
||||
|
||||
@@ -37,20 +45,12 @@ impl DrawUi for SeasonDetailsUi {
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||
if app.data.sonarr_data.season_details_modal.is_some() {
|
||||
if let Route::Sonarr(active_sonarr_block, _) = app
|
||||
.data
|
||||
.sonarr_data
|
||||
.season_details_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.season_details_tabs
|
||||
.get_active_route()
|
||||
{
|
||||
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
|
||||
let draw_season_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
|
||||
let content_area = draw_tabs(
|
||||
f,
|
||||
popup_area,
|
||||
"Season Details",
|
||||
&format!("Season {} Details", app.data.sonarr_data.seasons.current_selection().season_number),
|
||||
&app
|
||||
.data
|
||||
.sonarr_data
|
||||
@@ -64,8 +64,8 @@ impl DrawUi for SeasonDetailsUi {
|
||||
match active_sonarr_block {
|
||||
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
|
||||
let prompt = format!(
|
||||
"Do you want to trigger an automatic search of your indexers for season packs for the season: Season {}",
|
||||
app.data.sonarr_data.seasons.current_selection().season_number
|
||||
"Do you want to trigger an automatic search of your indexers for season packs for: {}",
|
||||
app.data.sonarr_data.seasons.current_selection().title.as_ref().unwrap()
|
||||
);
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Automatic Season Search")
|
||||
@@ -89,8 +89,6 @@ impl DrawUi for SeasonDetailsUi {
|
||||
.episodes
|
||||
.current_selection()
|
||||
.title
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
);
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Delete Episode")
|
||||
@@ -105,11 +103,14 @@ impl DrawUi for SeasonDetailsUi {
|
||||
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt => {
|
||||
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![
|
||||
Cell::from(episode_monitored.to_owned()),
|
||||
Cell::from(episode_number.to_string()),
|
||||
Cell::from(title.clone().unwrap_or_default()),
|
||||
Cell::from(title.clone()),
|
||||
Cell::from(air_date),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
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)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.footer(help_footer)
|
||||
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeason)
|
||||
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeasonError)
|
||||
.searching(is_searching)
|
||||
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchEpisodesError)
|
||||
.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) {
|
||||
match app.data.sonarr_data.season_details_modal.as_ref() {
|
||||
Some(season_details_modal) if !app.is_loading => {
|
||||
let current_selection = if season_details_modal.season_releases.is_empty() {
|
||||
SonarrRelease::default()
|
||||
let (current_selection, is_empty) = if season_details_modal.season_releases.is_empty() {
|
||||
(SonarrRelease::default(), true)
|
||||
} else {
|
||||
season_details_modal
|
||||
(season_details_modal
|
||||
.season_releases
|
||||
.current_selection()
|
||||
.clone()
|
||||
.clone(),
|
||||
season_details_modal.season_releases.is_empty())
|
||||
};
|
||||
let season_release_table_footer = season_details_modal
|
||||
.season_details_tabs
|
||||
@@ -409,16 +411,22 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let release_table =
|
||||
ManagarrTable::new(Some(&mut season_release_table), season_release_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.loading(app.is_loading || is_empty)
|
||||
.footer(season_release_table_footer)
|
||||
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
|
||||
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
|
||||
.headers([
|
||||
"Source", "Age", "⛔", "Title", "Indexer", "Size", "Peers", "Language", "Quality",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(12),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Length(9),
|
||||
Constraint::Length(10),
|
||||
Constraint::Length(5),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(18),
|
||||
Constraint::Length(12),
|
||||
Constraint::Length(12),
|
||||
Constraint::Percentage(7),
|
||||
Constraint::Percentage(10),
|
||||
]);
|
||||
|
||||
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)
|
||||
.prompt(&prompt)
|
||||
.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());
|
||||
} else {
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title(title)
|
||||
.prompt(&prompt)
|
||||
.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::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>(
|
||||
downloads_vec: &[DownloadRecord],
|
||||
episode: &Episode,
|
||||
|
||||
@@ -106,7 +106,7 @@ impl DrawUi for SeriesDetailsUi {
|
||||
};
|
||||
|
||||
draw_popup(f, app, draw_series_details_popup, Size::XXLarge);
|
||||
|
||||
|
||||
if SeasonDetailsUi::accepts(route) {
|
||||
SeasonDetailsUi::draw(f, app, area);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub enum Size {
|
||||
Small,
|
||||
Medium,
|
||||
Large,
|
||||
XLarge,
|
||||
XXLarge,
|
||||
Long,
|
||||
}
|
||||
@@ -41,6 +42,7 @@ impl Size {
|
||||
Size::Small => (40, 40),
|
||||
Size::Medium => (60, 60),
|
||||
Size::Large => (75, 75),
|
||||
Size::XLarge => (83, 83),
|
||||
Size::XXLarge => (90, 90),
|
||||
Size::Long => (65, 75),
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ mod tests {
|
||||
assert_eq!(Size::Small.to_percent(), (40, 40));
|
||||
assert_eq!(Size::Medium.to_percent(), (60, 60));
|
||||
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::Long.to_percent(), (65, 75));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user