feat(handler): Full handler support for the Season details UI in Sonarr

This commit is contained in:
2024-12-11 23:18:37 -07:00
parent ed2211586e
commit a84324d3bc
21 changed files with 1727 additions and 115 deletions
+4
View File
@@ -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]
}
}