feat(lidarr): add a scrollable overview popup to artist details
An artist's overview renders as one line of a description box that does not scroll, so a long overview overflows with no way to read the rest. Pressing v on the artist details Albums tab now opens a popup holding the full overview. The popup owns its own ScrollableText, built fresh from the artist's overview when it opens and torn down on Esc so a reopened popup starts at the top. Scrolling mutates only that owned state. The background paragraph renders the Artist record's overview, which carries no scroll offset, so it cannot scroll along with the popup. Cite the existing view keybinding from the artist details context clues and give the popup its own clue set so it does not inherit the hints of the view underneath. Enrich the shared artist fixture to a multi-paragraph overview, and the blocklist response fixtures that embed the same artist, so a scrolled popup is distinguishable from an unscrolled one.
This commit is contained in:
59 files changed
+1521
-134
No files matched your search
@@ -1,3 +1,4 @@
|
||||
use super::artist_overview_handler::ArtistOverviewHandler;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::lidarr_handlers::history::history_sorting_options;
|
||||
@@ -9,9 +10,10 @@ use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||
EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::modals::ArtistOverviewModal;
|
||||
use crate::models::servarr_models::ReleaseDownloadBody;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::{BlockSelectionState, Route, ScrollableText};
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use serde_json::Number;
|
||||
|
||||
@@ -34,10 +36,36 @@ impl ArtistDetailsHandler<'_, '_> {
|
||||
fn extract_album_id(&self) -> i64 {
|
||||
self.app.data.lidarr_data.albums.current_selection().id
|
||||
}
|
||||
|
||||
fn build_artist_overview_modal(&mut self) {
|
||||
let overview = self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artists
|
||||
.current_selection()
|
||||
.overview
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
self.app.data.lidarr_data.artist_overview_modal = Some(ArtistOverviewModal {
|
||||
overview: ScrollableText::with_string(overview),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
if ArtistOverviewHandler::accepts(self.active_lidarr_block) {
|
||||
return ArtistOverviewHandler::new(
|
||||
self.key,
|
||||
self.app,
|
||||
self.active_lidarr_block,
|
||||
self._context,
|
||||
)
|
||||
.handle();
|
||||
}
|
||||
|
||||
let albums_table_handling_config =
|
||||
TableHandlingConfig::new(ActiveLidarrBlock::ArtistDetails.into())
|
||||
.searching_block(ActiveLidarrBlock::SearchAlbums.into())
|
||||
@@ -78,7 +106,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
||||
}
|
||||
|
||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||
ARTIST_DETAILS_BLOCKS.contains(&active_block)
|
||||
ArtistOverviewHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
@@ -292,6 +320,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
||||
self.app.data.lidarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if matches_key!(view, key) => {
|
||||
self.build_artist_overview_modal();
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveLidarrBlock::ArtistOverview.into());
|
||||
}
|
||||
_ if matches_key!(toggle_monitoring, key)
|
||||
&& !self.app.data.lidarr_data.albums.is_empty() =>
|
||||
{
|
||||
|
||||
@@ -15,9 +15,10 @@ mod tests {
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrRelease};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
|
||||
ARTIST_DETAILS_BLOCKS, ARTIST_OVERVIEW_BLOCKS, ActiveLidarrBlock,
|
||||
};
|
||||
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||
use crate::test_handler_delegation;
|
||||
|
||||
mod test_handle_delete {
|
||||
use super::*;
|
||||
@@ -450,6 +451,7 @@ mod tests {
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||
use crate::handlers::lidarr_handlers::library::artist_overview_handler::ArtistOverviewHandler;
|
||||
use crate::models::lidarr_models::Artist;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
@@ -458,7 +460,7 @@ mod tests {
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::torrent_release;
|
||||
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
|
||||
use pretty_assertions::assert_eq;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
@@ -524,6 +526,161 @@ mod tests {
|
||||
assert_modal_absent!(app.data.lidarr_data.edit_artist_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_view_key_opens_the_artist_overview() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_pushed!(app, ActiveLidarrBlock::ArtistOverview.into());
|
||||
assert_modal_present!(app.data.lidarr_data.artist_overview_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_view_key_builds_the_overview_from_the_selected_artist() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
let overview = &app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.overview;
|
||||
assert_eq!(overview.offset, 0);
|
||||
assert_eq!(
|
||||
overview.get_text(),
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artists
|
||||
.current_selection()
|
||||
.overview
|
||||
.clone()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_view_key_for_an_artist_with_no_overview() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
let mut artist = app.data.lidarr_data.artists.current_selection().clone();
|
||||
artist.overview = None;
|
||||
app.data.lidarr_data.artists.set_items(vec![artist]);
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_pushed!(app, ActiveLidarrBlock::ArtistOverview.into());
|
||||
assert_str_eq!(
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.overview
|
||||
.get_text(),
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_view_key_reopens_the_artist_overview_at_the_top() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.end.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_pushed!(app, ActiveLidarrBlock::ArtistOverview.into());
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.overview
|
||||
.offset,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_view_key_no_op_when_not_ready() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
app.is_loading = true;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
|
||||
ArtistDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.view.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveLidarrBlock::ArtistDetails.into()
|
||||
);
|
||||
assert_modal_absent!(app.data.lidarr_data.artist_overview_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_toggle_monitoring_key() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
@@ -813,8 +970,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_handler_accepts() {
|
||||
let mut artist_details_blocks = ARTIST_DETAILS_BLOCKS.to_vec();
|
||||
artist_details_blocks.extend(ARTIST_OVERVIEW_BLOCKS);
|
||||
|
||||
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||
if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) {
|
||||
if artist_details_blocks.contains(&active_lidarr_block) {
|
||||
assert!(ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||
} else {
|
||||
assert!(!ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||
@@ -822,6 +982,15 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delegates_artist_overview_blocks_to_artist_overview_handler() {
|
||||
test_handler_delegation!(
|
||||
ArtistDetailsHandler,
|
||||
ActiveLidarrBlock::ArtistDetails,
|
||||
ActiveLidarrBlock::ArtistOverview
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_artist_id() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ARTIST_OVERVIEW_BLOCKS, ActiveLidarrBlock};
|
||||
use crate::models::{Route, Scrollable, ScrollableText};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "artist_overview_handler_tests.rs"]
|
||||
mod artist_overview_handler_tests;
|
||||
|
||||
pub(super) struct ArtistOverviewHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_lidarr_block: ActiveLidarrBlock,
|
||||
_context: Option<ActiveLidarrBlock>,
|
||||
}
|
||||
|
||||
impl ArtistOverviewHandler<'_, '_> {
|
||||
fn overview(&mut self) -> &mut ScrollableText {
|
||||
&mut self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_mut()
|
||||
.expect("Artist overview modal is undefined")
|
||||
.overview
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistOverviewHandler<'a, 'b> {
|
||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||
ARTIST_OVERVIEW_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: ActiveLidarrBlock,
|
||||
_context: Option<ActiveLidarrBlock>,
|
||||
) -> ArtistOverviewHandler<'a, 'b> {
|
||||
ArtistOverviewHandler {
|
||||
key,
|
||||
app,
|
||||
active_lidarr_block: active_block,
|
||||
_context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
if self.app.is_loading {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.app.data.lidarr_data.artist_overview_modal.is_some()
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
self.overview().scroll_up();
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
self.overview().scroll_down();
|
||||
}
|
||||
|
||||
fn handle_home(&mut self) {
|
||||
self.overview().scroll_to_top();
|
||||
}
|
||||
|
||||
fn handle_end(&mut self) {
|
||||
self.overview().scroll_to_bottom();
|
||||
}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {}
|
||||
|
||||
fn handle_submit(&mut self) {}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.lidarr_data.artist_overview_modal = None;
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if matches_key!(refresh, self.key) {
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::library::artist_overview_handler::ArtistOverviewHandler;
|
||||
use crate::models::ScrollableText;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ARTIST_OVERVIEW_BLOCKS, ActiveLidarrBlock,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::modals::ArtistOverviewModal;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_scroll_up() {
|
||||
let mut app = app_with_artist_overview();
|
||||
set_offset(&mut app, 2);
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_scroll_up_no_op_when_not_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
set_offset(&mut app, 2);
|
||||
app.is_loading = true;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_scroll_down() {
|
||||
let mut app = app_with_artist_overview();
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.down.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_scroll_down_no_op_when_not_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_loading = true;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.down.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 0);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_home_end {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_home() {
|
||||
let mut app = app_with_artist_overview();
|
||||
set_offset(&mut app, 3);
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.home.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_end() {
|
||||
let mut app = app_with_artist_overview();
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.end.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_home_no_op_when_not_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
set_offset(&mut app, 3);
|
||||
app.is_loading = true;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.home.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_end_no_op_when_not_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_loading = true;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.end.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 0);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use crate::{assert_modal_absent, assert_navigation_popped};
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_esc_tears_down_the_artist_overview_modal() {
|
||||
let mut app = app_with_artist_overview();
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||
assert_modal_absent!(app.data.lidarr_data.artist_overview_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_esc_tears_down_the_modal_even_when_loading() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_loading = true;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||
assert_modal_absent!(app.data.lidarr_data.artist_overview_modal);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::assert_navigation_pushed;
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_refresh_key() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_routing = false;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.refresh.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_pushed!(app, ActiveLidarrBlock::ArtistOverview.into());
|
||||
assert!(app.is_routing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_refresh_key_no_op_when_not_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_loading = true;
|
||||
app.is_routing = false;
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.refresh.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.is_routing);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scrolling_the_overview_does_not_mutate_the_artist_record() {
|
||||
let mut app = app_with_artist_overview();
|
||||
let overview_before = app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artists
|
||||
.current_selection()
|
||||
.overview
|
||||
.clone();
|
||||
|
||||
ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.down.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(overview_offset(&app), 1);
|
||||
assert_eq!(
|
||||
app.data.lidarr_data.artists.current_selection().overview,
|
||||
overview_before
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_accepts() {
|
||||
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||
if ARTIST_OVERVIEW_BLOCKS.contains(&active_lidarr_block) {
|
||||
assert!(
|
||||
ArtistOverviewHandler::accepts(active_lidarr_block),
|
||||
"{active_lidarr_block} is not accepted by the ArtistOverviewHandler"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
!ArtistOverviewHandler::accepts(active_lidarr_block),
|
||||
"{active_lidarr_block} is wrongly accepted by the ArtistOverviewHandler"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_dispatches_no_network_events() {
|
||||
let mut app = app_with_artist_overview();
|
||||
|
||||
for key in [
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
DEFAULT_KEYBINDINGS.down.key,
|
||||
DEFAULT_KEYBINDINGS.home.key,
|
||||
DEFAULT_KEYBINDINGS.end.key,
|
||||
DEFAULT_KEYBINDINGS.delete.key,
|
||||
DEFAULT_KEYBINDINGS.left.key,
|
||||
DEFAULT_KEYBINDINGS.right.key,
|
||||
DEFAULT_KEYBINDINGS.submit.key,
|
||||
DEFAULT_KEYBINDINGS.refresh.key,
|
||||
] {
|
||||
ArtistOverviewHandler::new(key, &mut app, ActiveLidarrBlock::ArtistOverview, None).handle();
|
||||
|
||||
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_is_not_ready_when_loading() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.is_loading = true;
|
||||
|
||||
let handler = ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_is_not_ready_when_the_modal_is_absent() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.data.lidarr_data.artist_overview_modal = None;
|
||||
|
||||
let handler = ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_is_ready() {
|
||||
let mut app = app_with_artist_overview();
|
||||
|
||||
let handler = ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_overview_handler_ignores_special_keys_for_textbox_input() {
|
||||
let mut app = app_with_artist_overview();
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
|
||||
let handler = ArtistOverviewHandler::new(
|
||||
DEFAULT_KEYBINDINGS.up.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::ArtistOverview,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.ignore_special_keys());
|
||||
}
|
||||
|
||||
fn app_with_artist_overview() -> App<'static> {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.artist_overview_modal = Some(ArtistOverviewModal {
|
||||
overview: ScrollableText::with_string(
|
||||
"some interesting description of the artist\r\n\
|
||||
\r\n\
|
||||
She was born in Madison, Wisconsin: a city she has never really left.\r\n\
|
||||
\r\n\
|
||||
Her first album took her seven years to finish.\r\n"
|
||||
.to_owned(),
|
||||
),
|
||||
});
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistOverview.into());
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
fn overview_offset(app: &App<'_>) -> u16 {
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.overview
|
||||
.offset
|
||||
}
|
||||
|
||||
fn set_offset(app: &mut App<'_>, offset: u16) {
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artist_overview_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.overview
|
||||
.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ mod tests {
|
||||
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
||||
use crate::models::lidarr_models::{Album, Artist, ArtistStatistics, ArtistStatus};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ADD_ARTIST_BLOCKS, ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
|
||||
DELETE_ALBUM_BLOCKS, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
LIBRARY_BLOCKS, TRACK_DETAILS_BLOCKS,
|
||||
ADD_ARTIST_BLOCKS, ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS, ARTIST_OVERVIEW_BLOCKS,
|
||||
ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS,
|
||||
EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS, TRACK_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
@@ -29,6 +29,7 @@ mod tests {
|
||||
let mut library_handler_blocks = Vec::new();
|
||||
library_handler_blocks.extend(LIBRARY_BLOCKS);
|
||||
library_handler_blocks.extend(ARTIST_DETAILS_BLOCKS);
|
||||
library_handler_blocks.extend(ARTIST_OVERVIEW_BLOCKS);
|
||||
library_handler_blocks.extend(DELETE_ARTIST_BLOCKS);
|
||||
library_handler_blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
mod add_artist_handler;
|
||||
mod album_details_handler;
|
||||
mod artist_details_handler;
|
||||
mod artist_overview_handler;
|
||||
mod delete_album_handler;
|
||||
mod delete_artist_handler;
|
||||
mod edit_artist_handler;
|
||||
|
||||
Reference in new issue
Block a user