From e9981a4bc1f4f7d744941148bdc1d62569478483 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 23 Apr 2023 21:01:04 +0200 Subject: [PATCH 1/7] Version 1 of solution --- src/main.rs | 12 +- src/tui/app.rs | 1316 ++++++++++++++++---------------------------- src/tui/handler.rs | 32 +- src/tui/mod.rs | 277 +++++----- src/tui/ui.rs | 510 +++++++++++++---- 5 files changed, 1040 insertions(+), 1107 deletions(-) diff --git a/src/main.rs b/src/main.rs index cc74a41..c4c140d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,10 +20,8 @@ use musichoard::{ }; mod tui; -use tui::{ - app::TuiApp, event::EventChannel, handler::TuiEventHandler, listener::TuiEventListener, ui::Ui, - Tui, -}; +use tui::ui::MhUi; +use tui::{event::EventChannel, handler::TuiEventHandler, listener::TuiEventListener, Tui}; #[derive(StructOpt)] struct Opt { @@ -52,12 +50,10 @@ fn with(lib: LIB, db: DB) { let listener = TuiEventListener::new(channel.sender()); let handler = TuiEventHandler::new(channel.receiver()); - let ui = Ui::new(); - - let app = TuiApp::new(collection_manager).expect("failed to initialise app"); + let ui = MhUi::new(collection_manager).expect("failed to initialise ui"); // Run the TUI application. - Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui"); + Tui::run(terminal, ui, handler, listener).expect("failed to run tui"); } fn main() { diff --git a/src/tui/app.rs b/src/tui/app.rs index e6bc869..1f33535 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,832 +1,484 @@ -use musichoard::{collection::CollectionManager, Album, AlbumId, Artist, ArtistId, Track}; - -use super::Error; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Category { - Artist, - Album, - Track, -} - -struct TrackSelection { - index: usize, -} - -impl TrackSelection { - fn initialise(tracks: &[Track]) -> Option { - if !tracks.is_empty() { - Some(TrackSelection { index: 0 }) - } else { - None - } - } - - fn increment(&mut self, tracks: &[Track]) { - if let Some(result) = self.index.checked_add(1) { - if result < tracks.len() { - self.index = result; - } - } - } - - fn decrement(&mut self, _tracks: &[Track]) { - if let Some(result) = self.index.checked_sub(1) { - self.index = result; - } - } -} - -struct AlbumSelection { - index: usize, - track: Option, -} - -impl AlbumSelection { - fn initialise(albums: &[Album]) -> Option { - if !albums.is_empty() { - Some(AlbumSelection { - index: 0, - track: TrackSelection::initialise(&albums[0].tracks), - }) - } else { - None - } - } - - fn increment(&mut self, albums: &[Album]) { - if let Some(result) = self.index.checked_add(1) { - if result < albums.len() { - self.index = result; - self.track = TrackSelection::initialise(&albums[self.index].tracks); - } - } - } - - fn decrement(&mut self, albums: &[Album]) { - if let Some(result) = self.index.checked_sub(1) { - self.index = result; - self.track = TrackSelection::initialise(&albums[self.index].tracks); - } - } -} - -struct ArtistSelection { - index: usize, - album: Option, -} - -impl ArtistSelection { - fn initialise(artists: &[Artist]) -> Option { - if !artists.is_empty() { - Some(ArtistSelection { - index: 0, - album: AlbumSelection::initialise(&artists[0].albums), - }) - } else { - None - } - } - - fn increment(&mut self, artists: &[Artist]) { - if let Some(result) = self.index.checked_add(1) { - if result < artists.len() { - self.index = result; - self.album = AlbumSelection::initialise(&artists[self.index].albums); - } - } - } - - fn decrement(&mut self, artists: &[Artist]) { - if let Some(result) = self.index.checked_sub(1) { - self.index = result; - self.album = AlbumSelection::initialise(&artists[self.index].albums); - } - } -} - -struct Selection { - active: Category, - artist: Option, -} - -pub trait App { - fn is_running(&self) -> bool; - fn quit(&mut self); - - fn increment_category(&mut self); - fn decrement_category(&mut self); - - fn increment_selection(&mut self); - fn decrement_selection(&mut self); - - fn get_active_category(&self) -> Category; - - fn get_artist_ids(&self) -> Vec<&ArtistId>; - fn get_album_ids(&self) -> Vec<&AlbumId>; - fn get_track_ids(&self) -> Vec<&Track>; - - fn selected_artist(&self) -> Option; - fn selected_album(&self) -> Option; - fn selected_track(&self) -> Option; -} - -trait AppPrivate { - fn increment_artist_selection(&mut self); - fn decrement_artist_selection(&mut self); - - fn increment_album_selection(&mut self); - fn decrement_album_selection(&mut self); - - fn increment_track_selection(&mut self); - - fn decrement_track_selection(&mut self); - - fn get_artists(&self) -> &Vec; - fn get_albums(&self) -> Option<&Vec>; - fn get_tracks(&self) -> Option<&Vec>; -} - -pub struct TuiApp { - collection_manager: CM, - selection: Selection, - running: bool, -} - -impl TuiApp { - pub fn new(mut collection_manager: CM) -> Result { - collection_manager.rescan_library()?; - let selection = Selection { - active: Category::Artist, - artist: ArtistSelection::initialise(collection_manager.get_collection()), - }; - Ok(TuiApp { - collection_manager, - selection, - running: true, - }) - } -} - -impl App for TuiApp { - fn is_running(&self) -> bool { - self.running - } - - fn quit(&mut self) { - self.running = false; - } - - fn increment_category(&mut self) { - self.selection.active = match self.selection.active { - Category::Artist => Category::Album, - Category::Album => Category::Track, - Category::Track => Category::Track, - }; - } - - fn decrement_category(&mut self) { - self.selection.active = match self.selection.active { - Category::Artist => Category::Artist, - Category::Album => Category::Artist, - Category::Track => Category::Album, - }; - } - - fn increment_selection(&mut self) { - match self.selection.active { - Category::Artist => self.increment_artist_selection(), - Category::Album => self.increment_album_selection(), - Category::Track => self.increment_track_selection(), - } - } - - fn decrement_selection(&mut self) { - match self.selection.active { - Category::Artist => self.decrement_artist_selection(), - Category::Album => self.decrement_album_selection(), - Category::Track => self.decrement_track_selection(), - } - } - - fn get_active_category(&self) -> Category { - self.selection.active - } - - fn get_artist_ids(&self) -> Vec<&ArtistId> { - let artists = self.get_artists(); - artists.iter().map(|a| &a.id).collect() - } - - fn get_album_ids(&self) -> Vec<&AlbumId> { - if let Some(albums) = self.get_albums() { - albums.iter().map(|a| &a.id).collect() - } else { - vec![] - } - } - - fn get_track_ids(&self) -> Vec<&Track> { - if let Some(tracks) = self.get_tracks() { - tracks.iter().collect() - } else { - vec![] - } - } - - fn selected_artist(&self) -> Option { - self.selection.artist.as_ref().map(|s| s.index) - } - - fn selected_album(&self) -> Option { - if let Some(ref artist_selection) = self.selection.artist { - artist_selection.album.as_ref().map(|s| s.index) - } else { - None - } - } - - fn selected_track(&self) -> Option { - if let Some(ref artist_selection) = self.selection.artist { - if let Some(ref album_selection) = artist_selection.album { - album_selection.track.as_ref().map(|s| s.index) - } else { - None - } - } else { - None - } - } -} - -impl AppPrivate for TuiApp { - fn increment_artist_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - let artists = &self.collection_manager.get_collection(); - artist_selection.increment(artists); - } - } - - fn decrement_artist_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - let artists = &self.collection_manager.get_collection(); - artist_selection.decrement(artists); - } - } - - fn increment_album_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - if let Some(ref mut album_selection) = artist_selection.album { - let artists = &self.collection_manager.get_collection(); - let albums = &artists[artist_selection.index].albums; - album_selection.increment(albums); - } - } - } - - fn decrement_album_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - if let Some(ref mut album_selection) = artist_selection.album { - let artists = &self.collection_manager.get_collection(); - let albums = &artists[artist_selection.index].albums; - album_selection.decrement(albums); - } - } - } - - fn increment_track_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - if let Some(ref mut album_selection) = artist_selection.album { - if let Some(ref mut track_selection) = album_selection.track { - let artists = &self.collection_manager.get_collection(); - let albums = &artists[artist_selection.index].albums; - let tracks = &albums[album_selection.index].tracks; - track_selection.increment(tracks); - } - } - } - } - - fn decrement_track_selection(&mut self) { - if let Some(ref mut artist_selection) = self.selection.artist { - if let Some(ref mut album_selection) = artist_selection.album { - if let Some(ref mut track_selection) = album_selection.track { - let artists = &self.collection_manager.get_collection(); - let albums = &artists[artist_selection.index].albums; - let tracks = &albums[album_selection.index].tracks; - track_selection.decrement(tracks); - } - } - } - } - - fn get_artists(&self) -> &Vec { - self.collection_manager.get_collection() - } - - fn get_albums(&self) -> Option<&Vec> { - let artists = self.get_artists(); - if let Some(artist_index) = self.selected_artist() { - Some(&artists[artist_index].albums) - } else { - None - } - } - - fn get_tracks(&self) -> Option<&Vec> { - if let Some(albums) = self.get_albums() { - if let Some(album_index) = self.selected_album() { - Some(&albums[album_index].tracks) - } else { - None - } - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use crate::tests::{MockCollectionManager, COLLECTION}; - - use super::*; - - #[test] - fn test_track_selection() { - let tracks = &COLLECTION[0].albums[0].tracks; - assert!(tracks.len() > 1); - - let empty = TrackSelection::initialise(&vec![]); - assert!(empty.is_none()); - - let sel = TrackSelection::initialise(tracks); - assert!(sel.is_some()); - - let mut sel = sel.unwrap(); - assert_eq!(sel.index, 0); - - sel.decrement(tracks); - assert_eq!(sel.index, 0); - - sel.increment(tracks); - assert_eq!(sel.index, 1); - - sel.decrement(tracks); - assert_eq!(sel.index, 0); - - for _ in 0..(tracks.len() + 5) { - sel.increment(tracks); - } - assert_eq!(sel.index, tracks.len() - 1); - - // Artifical test case to verify upper limit. - let mut sel = TrackSelection { - index: std::usize::MAX, - }; - assert_eq!(sel.index, std::usize::MAX); - - sel.increment(&vec![]); - assert_eq!(sel.index, std::usize::MAX); - } - - #[test] - fn test_album_selection() { - let albums = &COLLECTION[0].albums; - assert!(albums.len() > 1); - - let empty = AlbumSelection::initialise(&vec![]); - assert!(empty.is_none()); - - let sel = AlbumSelection::initialise(albums); - assert!(sel.is_some()); - - let mut sel = sel.unwrap(); - assert_eq!(sel.index, 0); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 0); - - sel.track - .as_mut() - .unwrap() - .increment(&albums[sel.index].tracks); - assert_eq!(sel.index, 0); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - - // Verify that decrement that doesn't change index does not reset track. - sel.decrement(albums); - assert_eq!(sel.index, 0); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - - sel.increment(albums); - assert_eq!(sel.index, 1); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 0); - - sel.decrement(albums); - assert_eq!(sel.index, 0); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 0); - - for _ in 0..(albums.len() + 5) { - sel.increment(albums); - } - assert_eq!(sel.index, albums.len() - 1); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 0); - - sel.track - .as_mut() - .unwrap() - .increment(&albums[sel.index].tracks); - assert_eq!(sel.index, albums.len() - 1); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - - // Verify that increment that doesn't change index does not reset track. - sel.increment(albums); - assert_eq!(sel.index, albums.len() - 1); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - - // Artifical test case to verify upper limit. - let mut sel = AlbumSelection { - index: std::usize::MAX, - track: Some(TrackSelection { index: 1 }), - }; - assert_eq!(sel.index, std::usize::MAX); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - - sel.increment(&vec![]); - assert_eq!(sel.index, std::usize::MAX); - assert!(sel.track.is_some()); - assert_eq!(sel.track.as_ref().unwrap().index, 1); - } - - #[test] - fn test_artist_selection() { - let artists = &COLLECTION; - assert!(artists.len() > 1); - - let empty = ArtistSelection::initialise(&vec![]); - assert!(empty.is_none()); - - let sel = ArtistSelection::initialise(artists); - assert!(sel.is_some()); - - let mut sel = sel.unwrap(); - assert_eq!(sel.index, 0); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 0); - - sel.album - .as_mut() - .unwrap() - .increment(&artists[sel.index].albums); - assert_eq!(sel.index, 0); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - - // Verify that decrement that doesn't change index does not reset album. - sel.decrement(artists); - assert_eq!(sel.index, 0); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - - sel.increment(artists); - assert_eq!(sel.index, 1); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 0); - - sel.decrement(artists); - assert_eq!(sel.index, 0); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 0); - - for _ in 0..(artists.len() + 5) { - sel.increment(artists); - } - assert_eq!(sel.index, artists.len() - 1); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 0); - - sel.album - .as_mut() - .unwrap() - .increment(&artists[sel.index].albums); - assert_eq!(sel.index, artists.len() - 1); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - - // Verify that increment that doesn't change index does not reset album. - sel.increment(artists); - assert_eq!(sel.index, artists.len() - 1); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - - // Artifical test case to verify upper limit. - let mut sel = ArtistSelection { - index: std::usize::MAX, - album: Some(AlbumSelection { - index: 1, - track: None, - }), - }; - assert_eq!(sel.index, std::usize::MAX); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - - sel.increment(&vec![]); - assert_eq!(sel.index, std::usize::MAX); - assert!(sel.album.is_some()); - assert_eq!(sel.album.as_ref().unwrap().index, 1); - } - - #[test] - fn app_running() { - let mut collection_manager = MockCollectionManager::new(); - - collection_manager - .expect_rescan_library() - .times(1) - .return_once(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(COLLECTION.to_owned()); - - let mut app = TuiApp::new(collection_manager).unwrap(); - assert!(app.is_running()); - - app.quit(); - assert!(!app.is_running()); - } - - #[test] - fn app_modifiers() { - let mut collection_manager = MockCollectionManager::new(); - - collection_manager - .expect_rescan_library() - .times(1) - .return_once(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(COLLECTION.to_owned()); - - let mut app = TuiApp::new(collection_manager).unwrap(); - assert!(app.is_running()); - - assert!(!app.get_artist_ids().is_empty()); - assert!(!app.get_album_ids().is_empty()); - assert!(!app.get_track_ids().is_empty()); - - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_category(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_category(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(1)); - - app.increment_category(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(1)); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_selection(); - app.decrement_category(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(1)); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_selection(); - app.decrement_category(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(1)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(0)); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), Some(0)); - - app.increment_category(); - app.increment_selection(); - app.decrement_category(); - app.decrement_selection(); - app.decrement_category(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(1)); - assert_eq!(app.selected_track(), Some(0)); - } - - #[test] - fn app_no_tracks() { - let mut collection_manager = MockCollectionManager::new(); - let mut collection = COLLECTION.to_owned(); - collection[0].albums[0].tracks = vec![]; - - collection_manager - .expect_rescan_library() - .times(1) - .return_once(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(collection); - - let mut app = TuiApp::new(collection_manager).unwrap(); - assert!(app.is_running()); - - assert!(!app.get_artist_ids().is_empty()); - assert!(!app.get_album_ids().is_empty()); - assert!(app.get_track_ids().is_empty()); - - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), None); - - app.increment_category(); - app.increment_category(); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), Some(0)); - assert_eq!(app.selected_track(), None); - } - - #[test] - fn app_no_albums() { - let mut collection_manager = MockCollectionManager::new(); - let mut collection = COLLECTION.to_owned(); - collection[0].albums = vec![]; - - collection_manager - .expect_rescan_library() - .times(1) - .return_once(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(collection); - - let mut app = TuiApp::new(collection_manager).unwrap(); - assert!(app.is_running()); - - assert!(!app.get_artist_ids().is_empty()); - assert!(app.get_album_ids().is_empty()); - assert!(app.get_track_ids().is_empty()); - - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.increment_category(); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.increment_category(); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), Some(0)); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - } - - #[test] - fn app_no_artists() { - let mut collection_manager = MockCollectionManager::new(); - let collection = vec![]; - - collection_manager - .expect_rescan_library() - .times(1) - .return_once(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(collection); - - let mut app = TuiApp::new(collection_manager).unwrap(); - assert!(app.is_running()); - - assert!(app.get_artist_ids().is_empty()); - assert!(app.get_album_ids().is_empty()); - assert!(app.get_track_ids().is_empty()); - - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Artist); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.increment_category(); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Album); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.increment_category(); - - app.increment_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - - app.decrement_selection(); - assert_eq!(app.get_active_category(), Category::Track); - assert_eq!(app.selected_artist(), None); - assert_eq!(app.selected_album(), None); - assert_eq!(app.selected_track(), None); - } -} +// #[cfg(test)] +// mod tests { +// use crate::tests::{MockCollectionManager, COLLECTION}; + +// use super::*; + +// #[test] +// fn test_track_selection() { +// let tracks = &COLLECTION[0].albums[0].tracks; +// assert!(tracks.len() > 1); + +// let empty = SelectedTrack::initialise(&vec![]); +// assert!(empty.is_none()); + +// let sel = SelectedTrack::initialise(tracks); +// assert!(sel.is_some()); + +// let mut sel = sel.unwrap(); +// assert_eq!(sel.index, 0); + +// sel.decrement(tracks); +// assert_eq!(sel.index, 0); + +// sel.increment(tracks); +// assert_eq!(sel.index, 1); + +// sel.decrement(tracks); +// assert_eq!(sel.index, 0); + +// for _ in 0..(tracks.len() + 5) { +// sel.increment(tracks); +// } +// assert_eq!(sel.index, tracks.len() - 1); + +// // Artifical test case to verify upper limit. +// let mut sel = SelectedTrack { +// index: std::usize::MAX, +// }; +// assert_eq!(sel.index, std::usize::MAX); + +// sel.increment(&vec![]); +// assert_eq!(sel.index, std::usize::MAX); +// } + +// #[test] +// fn test_album_selection() { +// let albums = &COLLECTION[0].albums; +// assert!(albums.len() > 1); + +// let empty = SelectedAlbum::initialise(&vec![]); +// assert!(empty.is_none()); + +// let sel = SelectedAlbum::initialise(albums); +// assert!(sel.is_some()); + +// let mut sel = sel.unwrap(); +// assert_eq!(sel.index, 0); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 0); + +// sel.track +// .as_mut() +// .unwrap() +// .increment(&albums[sel.index].tracks); +// assert_eq!(sel.index, 0); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); + +// // Verify that decrement that doesn't change index does not reset track. +// sel.decrement(albums); +// assert_eq!(sel.index, 0); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); + +// sel.increment(albums); +// assert_eq!(sel.index, 1); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 0); + +// sel.decrement(albums); +// assert_eq!(sel.index, 0); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 0); + +// for _ in 0..(albums.len() + 5) { +// sel.increment(albums); +// } +// assert_eq!(sel.index, albums.len() - 1); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 0); + +// sel.track +// .as_mut() +// .unwrap() +// .increment(&albums[sel.index].tracks); +// assert_eq!(sel.index, albums.len() - 1); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); + +// // Verify that increment that doesn't change index does not reset track. +// sel.increment(albums); +// assert_eq!(sel.index, albums.len() - 1); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); + +// // Artifical test case to verify upper limit. +// let mut sel = SelectedAlbum { +// index: std::usize::MAX, +// track: Some(SelectedTrack { index: 1 }), +// }; +// assert_eq!(sel.index, std::usize::MAX); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); + +// sel.increment(&vec![]); +// assert_eq!(sel.index, std::usize::MAX); +// assert!(sel.track.is_some()); +// assert_eq!(sel.track.as_ref().unwrap().index, 1); +// } + +// #[test] +// fn test_artist_selection() { +// let artists = &COLLECTION; +// assert!(artists.len() > 1); + +// let empty = SelectedArtist::initialise(&vec![]); +// assert!(empty.is_none()); + +// let sel = SelectedArtist::initialise(artists); +// assert!(sel.is_some()); + +// let mut sel = sel.unwrap(); +// assert_eq!(sel.index, 0); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 0); + +// sel.album +// .as_mut() +// .unwrap() +// .increment(&artists[sel.index].albums); +// assert_eq!(sel.index, 0); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); + +// // Verify that decrement that doesn't change index does not reset album. +// sel.decrement(artists); +// assert_eq!(sel.index, 0); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); + +// sel.increment(artists); +// assert_eq!(sel.index, 1); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 0); + +// sel.decrement(artists); +// assert_eq!(sel.index, 0); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 0); + +// for _ in 0..(artists.len() + 5) { +// sel.increment(artists); +// } +// assert_eq!(sel.index, artists.len() - 1); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 0); + +// sel.album +// .as_mut() +// .unwrap() +// .increment(&artists[sel.index].albums); +// assert_eq!(sel.index, artists.len() - 1); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); + +// // Verify that increment that doesn't change index does not reset album. +// sel.increment(artists); +// assert_eq!(sel.index, artists.len() - 1); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); + +// // Artifical test case to verify upper limit. +// let mut sel = SelectedArtist { +// index: std::usize::MAX, +// album: Some(SelectedAlbum { +// index: 1, +// track: None, +// }), +// }; +// assert_eq!(sel.index, std::usize::MAX); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); + +// sel.increment(&vec![]); +// assert_eq!(sel.index, std::usize::MAX); +// assert!(sel.album.is_some()); +// assert_eq!(sel.album.as_ref().unwrap().index, 1); +// } + +// #[test] +// fn app_running() { +// let mut collection_manager = MockCollectionManager::new(); + +// collection_manager +// .expect_rescan_library() +// .times(1) +// .return_once(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(COLLECTION.to_owned()); + +// let mut app = TuiApp::new(collection_manager).unwrap(); +// assert!(app.is_running()); + +// app.quit(); +// assert!(!app.is_running()); +// } + +// #[test] +// fn app_modifiers() { +// let mut collection_manager = MockCollectionManager::new(); + +// collection_manager +// .expect_rescan_library() +// .times(1) +// .return_once(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(COLLECTION.to_owned()); + +// let mut app = TuiApp::new(collection_manager).unwrap(); +// assert!(app.is_running()); + +// assert!(!app.get_artist_ids().is_empty()); +// assert!(!app.get_album_ids().is_empty()); +// assert!(!app.get_track_ids().is_empty()); + +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_category(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_category(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(1)); + +// app.increment_category(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(1)); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_selection(); +// app.decrement_category(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(1)); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_selection(); +// app.decrement_category(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(1)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), Some(0)); + +// app.increment_category(); +// app.increment_selection(); +// app.decrement_category(); +// app.decrement_selection(); +// app.decrement_category(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(1)); +// assert_eq!(app.selected_track(), Some(0)); +// } + +// #[test] +// fn app_no_tracks() { +// let mut collection_manager = MockCollectionManager::new(); +// let mut collection = COLLECTION.to_owned(); +// collection[0].albums[0].tracks = vec![]; + +// collection_manager +// .expect_rescan_library() +// .times(1) +// .return_once(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(collection); + +// let mut app = TuiApp::new(collection_manager).unwrap(); +// assert!(app.is_running()); + +// assert!(!app.get_artist_ids().is_empty()); +// assert!(!app.get_album_ids().is_empty()); +// assert!(app.get_track_ids().is_empty()); + +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), None); + +// app.increment_category(); +// app.increment_category(); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), Some(0)); +// assert_eq!(app.selected_track(), None); +// } + +// #[test] +// fn app_no_albums() { +// let mut collection_manager = MockCollectionManager::new(); +// let mut collection = COLLECTION.to_owned(); +// collection[0].albums = vec![]; + +// collection_manager +// .expect_rescan_library() +// .times(1) +// .return_once(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(collection); + +// let mut app = TuiApp::new(collection_manager).unwrap(); +// assert!(app.is_running()); + +// assert!(!app.get_artist_ids().is_empty()); +// assert!(app.get_album_ids().is_empty()); +// assert!(app.get_track_ids().is_empty()); + +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.increment_category(); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.increment_category(); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), Some(0)); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); +// } + +// #[test] +// fn app_no_artists() { +// let mut collection_manager = MockCollectionManager::new(); +// let collection = vec![]; + +// collection_manager +// .expect_rescan_library() +// .times(1) +// .return_once(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(collection); + +// let mut app = TuiApp::new(collection_manager).unwrap(); +// assert!(app.is_running()); + +// assert!(app.get_artist_ids().is_empty()); +// assert!(app.get_album_ids().is_empty()); +// assert!(app.get_track_ids().is_empty()); + +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Artist); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.increment_category(); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Album); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.increment_category(); + +// app.increment_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); + +// app.decrement_selection(); +// assert_eq!(app.get_active_category(), Category::Track); +// assert_eq!(app.selected_artist(), None); +// assert_eq!(app.selected_album(), None); +// assert_eq!(app.selected_track(), None); +// } +// } diff --git a/src/tui/handler.rs b/src/tui/handler.rs index fecc501..b9346f6 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -4,17 +4,17 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use mockall::automock; use super::{ - app::App, event::{Event, EventError, EventReceiver}, + ui::Ui, }; #[cfg_attr(test, automock)] -pub trait EventHandler { - fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError>; +pub trait EventHandler { + fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError>; } -trait EventHandlerPrivate { - fn handle_key_event(app: &mut APP, key_event: KeyEvent); +trait EventHandlerPrivate { + fn handle_key_event(ui: &mut UI, key_event: KeyEvent); } pub struct TuiEventHandler { @@ -28,10 +28,10 @@ impl TuiEventHandler { } } -impl EventHandler for TuiEventHandler { - fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError> { +impl EventHandler for TuiEventHandler { + fn handle_next_event(&self, ui: &mut UI) -> Result<(), EventError> { match self.events.recv()? { - Event::Key(key_event) => Self::handle_key_event(app, key_event), + Event::Key(key_event) => Self::handle_key_event(ui, key_event), Event::Mouse(_) => {} Event::Resize(_, _) => {} }; @@ -39,32 +39,32 @@ impl EventHandler for TuiEventHandler { } } -impl EventHandlerPrivate for TuiEventHandler { - fn handle_key_event(app: &mut APP, key_event: KeyEvent) { +impl EventHandlerPrivate for TuiEventHandler { + fn handle_key_event(ui: &mut UI, key_event: KeyEvent) { match key_event.code { // Exit application on `ESC` or `q`. KeyCode::Esc | KeyCode::Char('q') => { - app.quit(); + ui.quit(); } // Exit application on `Ctrl-C`. KeyCode::Char('c') | KeyCode::Char('C') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.quit(); + ui.quit(); } } // Category change. KeyCode::Left => { - app.decrement_category(); + ui.decrement_category(); } KeyCode::Right => { - app.increment_category(); + ui.increment_category(); } // Selection change. KeyCode::Up => { - app.decrement_selection(); + ui.decrement_selection(); } KeyCode::Down => { - app.increment_selection(); + ui.increment_selection(); } // Other keys. _ => {} diff --git a/src/tui/mod.rs b/src/tui/mod.rs index da87d7b..d9af99f 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -6,13 +6,11 @@ use ratatui::Terminal; use std::io; use std::marker::PhantomData; -pub mod app; pub mod event; pub mod handler; pub mod listener; pub mod ui; -use self::app::App; use self::event::EventError; use self::handler::EventHandler; use self::listener::EventListener; @@ -44,12 +42,12 @@ impl From for Error { } } -pub struct Tui { +pub struct Tui { terminal: Terminal, - _phantom: PhantomData, + _phantom: PhantomData, } -impl Tui { +impl Tui { fn init(&mut self) -> Result<(), Error> { self.terminal.hide_cursor()?; self.terminal.clear()?; @@ -66,15 +64,10 @@ impl Tui { self.exit(); } - fn main_loop( - &mut self, - mut app: APP, - ui: Ui, - handler: impl EventHandler, - ) -> Result<(), Error> { - while app.is_running() { - self.terminal.draw(|frame| ui.render(&app, frame))?; - handler.handle_next_event(&mut app)?; + fn main_loop(&mut self, mut ui: UI, handler: impl EventHandler) -> Result<(), Error> { + while ui.is_running() { + self.terminal.draw(|frame| ui.render(frame))?; + handler.handle_next_event(&mut ui)?; } Ok(()) @@ -82,9 +75,8 @@ impl Tui { fn main( term: Terminal, - app: APP, - ui: Ui, - handler: impl EventHandler, + ui: UI, + handler: impl EventHandler, listener: impl EventListener, ) -> Result<(), Error> { let mut tui = Tui { @@ -95,7 +87,7 @@ impl Tui { tui.init()?; let listener_handle = listener.spawn(); - let result = tui.main_loop(app, ui, handler); + let result = tui.main_loop(ui, handler); match result { Ok(_) => { @@ -142,13 +134,12 @@ impl Tui { pub fn run( term: Terminal, - app: APP, - ui: Ui, - handler: impl EventHandler, + ui: UI, + handler: impl EventHandler, listener: impl EventListener, ) -> Result<(), Error> { Self::enable()?; - let result = Self::main(term, app, ui, handler, listener); + let result = Self::main(term, ui, handler, listener); match result { Ok(_) => { Self::disable()?; @@ -165,155 +156,151 @@ impl Tui { // GRCOV_EXCL_STOP } -#[cfg(test)] -mod tests { - use std::{io, thread}; +// #[cfg(test)] +// mod tests { +// use std::{io, thread}; - use musichoard::collection::{self, Collection}; - use ratatui::{backend::TestBackend, Terminal}; +// use musichoard::collection::{self, Collection}; +// use ratatui::{backend::TestBackend, Terminal}; - use crate::tests::{MockCollectionManager, COLLECTION}; +// use crate::tests::{MockCollectionManager, COLLECTION}; - use super::{ - app::{App, TuiApp}, - event::EventError, - handler::MockEventHandler, - listener::MockEventListener, - ui::Ui, - Error, Tui, - }; +// use super::{ +// app::TuiApp, event::EventError, handler::MockEventHandler, listener::MockEventListener, +// ui::Ui, Error, Tui, +// }; - pub fn terminal() -> Terminal { - let backend = TestBackend::new(150, 30); - Terminal::new(backend).unwrap() - } +// pub fn terminal() -> Terminal { +// let backend = TestBackend::new(150, 30); +// Terminal::new(backend).unwrap() +// } - pub fn app(collection: Collection) -> TuiApp { - let mut collection_manager = MockCollectionManager::new(); +// pub fn app(collection: Collection) -> TuiApp { +// let mut collection_manager = MockCollectionManager::new(); - collection_manager - .expect_rescan_library() - .returning(|| Ok(())); - collection_manager - .expect_get_collection() - .return_const(collection); +// collection_manager +// .expect_rescan_library() +// .returning(|| Ok(())); +// collection_manager +// .expect_get_collection() +// .return_const(collection); - TuiApp::new(collection_manager).unwrap() - } +// TuiApp::new(collection_manager).unwrap() +// } - fn listener() -> MockEventListener { - let mut listener = MockEventListener::new(); - listener.expect_spawn().return_once(|| { - thread::spawn(|| { - thread::park(); - return EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")); - }) - }); - listener - } +// fn listener() -> MockEventListener { +// let mut listener = MockEventListener::new(); +// listener.expect_spawn().return_once(|| { +// thread::spawn(|| { +// thread::park(); +// return EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")); +// }) +// }); +// listener +// } - fn handler() -> MockEventHandler> { - let mut handler = MockEventHandler::new(); - handler.expect_handle_next_event().return_once( - |app: &mut TuiApp| { - app.quit(); - Ok(()) - }, - ); - handler - } +// fn handler() -> MockEventHandler> { +// let mut handler = MockEventHandler::new(); +// handler.expect_handle_next_event().return_once( +// |app: &mut TuiApp| { +// app.quit(); +// Ok(()) +// }, +// ); +// handler +// } - #[test] - fn run() { - let terminal = terminal(); - let app = app(COLLECTION.to_owned()); - let ui = Ui::new(); +// #[test] +// fn run() { +// let terminal = terminal(); +// let app = app(COLLECTION.to_owned()); +// let ui = Ui::new(); - let listener = listener(); - let handler = handler(); +// let listener = listener(); +// let handler = handler(); - let result = Tui::main(terminal, app, ui, handler, listener); - assert!(result.is_ok()); - } +// let result = Tui::main(terminal, app, ui, handler, listener); +// assert!(result.is_ok()); +// } - #[test] - fn event_error() { - let terminal = terminal(); - let app = app(COLLECTION.to_owned()); - let ui = Ui::new(); +// #[test] +// fn event_error() { +// let terminal = terminal(); +// let app = app(COLLECTION.to_owned()); +// let ui = Ui::new(); - let listener = listener(); +// let listener = listener(); - let mut handler = MockEventHandler::new(); - handler - .expect_handle_next_event() - .return_once(|_| Err(EventError::Recv)); +// let mut handler = MockEventHandler::new(); +// handler +// .expect_handle_next_event() +// .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, ui, handler, listener); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err(), - Error::Event(EventError::Recv.to_string()) - ); - } +// let result = Tui::main(terminal, app, ui, handler, listener); +// assert!(result.is_err()); +// assert_eq!( +// result.unwrap_err(), +// Error::Event(EventError::Recv.to_string()) +// ); +// } - #[test] - fn listener_error() { - let terminal = terminal(); - let app = app(COLLECTION.to_owned()); - let ui = Ui::new(); +// #[test] +// fn listener_error() { +// let terminal = terminal(); +// let app = app(COLLECTION.to_owned()); +// let ui = Ui::new(); - let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); - let listener_handle: thread::JoinHandle = thread::spawn(|| error); - while !listener_handle.is_finished() {} +// let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); +// let listener_handle: thread::JoinHandle = thread::spawn(|| error); +// while !listener_handle.is_finished() {} - let mut listener = MockEventListener::new(); - listener.expect_spawn().return_once(|| listener_handle); +// let mut listener = MockEventListener::new(); +// listener.expect_spawn().return_once(|| listener_handle); - let mut handler = MockEventHandler::new(); - handler - .expect_handle_next_event() - .return_once(|_| Err(EventError::Recv)); +// let mut handler = MockEventHandler::new(); +// handler +// .expect_handle_next_event() +// .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, ui, handler, listener); - assert!(result.is_err()); +// let result = Tui::main(terminal, app, ui, handler, listener); +// assert!(result.is_err()); - let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); - assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); - } +// let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); +// assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); +// } - #[test] - fn listener_panic() { - let terminal = terminal(); - let app = app(COLLECTION.to_owned()); - let ui = Ui::new(); +// #[test] +// fn listener_panic() { +// let terminal = terminal(); +// let app = app(COLLECTION.to_owned()); +// let ui = Ui::new(); - let listener_handle: thread::JoinHandle = thread::spawn(|| panic!()); - while !listener_handle.is_finished() {} +// let listener_handle: thread::JoinHandle = thread::spawn(|| panic!()); +// while !listener_handle.is_finished() {} - let mut listener = MockEventListener::new(); - listener.expect_spawn().return_once(|| listener_handle); +// let mut listener = MockEventListener::new(); +// listener.expect_spawn().return_once(|| listener_handle); - let mut handler = MockEventHandler::new(); - handler - .expect_handle_next_event() - .return_once(|_| Err(EventError::Recv)); +// let mut handler = MockEventHandler::new(); +// handler +// .expect_handle_next_event() +// .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, ui, handler, listener); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), Error::ListenerPanic); - } +// let result = Tui::main(terminal, app, ui, handler, listener); +// assert!(result.is_err()); +// assert_eq!(result.unwrap_err(), Error::ListenerPanic); +// } - #[test] - fn errors() { - let collection_err: Error = collection::Error::DatabaseError(String::from("")).into(); - let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); - let event_err: Error = EventError::Recv.into(); - let listener_err = Error::ListenerPanic; +// #[test] +// fn errors() { +// let collection_err: Error = collection::Error::DatabaseError(String::from("")).into(); +// let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); +// let event_err: Error = EventError::Recv.into(); +// let listener_err = Error::ListenerPanic; - assert!(!format!("{:?}", collection_err).is_empty()); - assert!(!format!("{:?}", io_err).is_empty()); - assert!(!format!("{:?}", event_err).is_empty()); - assert!(!format!("{:?}", listener_err).is_empty()); - } -} +// assert!(!format!("{:?}", collection_err).is_empty()); +// assert!(!format!("{:?}", io_err).is_empty()); +// assert!(!format!("{:?}", event_err).is_empty()); +// assert!(!format!("{:?}", listener_err).is_empty()); +// } +// } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 47288e3..cfef7b3 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,6 +1,7 @@ -use std::marker::PhantomData; - -use musichoard::TrackFormat; +use musichoard::{ + collection::{Collection, CollectionManager}, + Album, AlbumId, Artist, Track, TrackFormat, +}; use ratatui::{ backend::Backend, layout::{Alignment, Rect}, @@ -9,7 +10,276 @@ use ratatui::{ Frame, }; -use super::app::{App, Category}; +use super::Error; + +struct TrackSelection { + selection: ListState, +} + +struct AlbumSelection { + selection: ListState, + track: TrackSelection, +} + +struct ArtistSelection { + selection: ListState, + album: AlbumSelection, +} + +impl TrackSelection { + fn initialise(tracks: Option<&[Track]>) -> Self { + let mut selection = ListState::default(); + if let Some(tracks) = tracks { + selection.select(if !tracks.is_empty() { Some(0) } else { None }); + } else { + selection.select(None); + }; + TrackSelection { selection } + } + + fn selection(&mut self) -> &mut ListState { + &mut self.selection + } + + fn increment(&mut self, tracks: &[Track]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_add(1) { + if result < tracks.len() { + self.selection.select(Some(result)); + } + } + } + } + + fn decrement(&mut self, _tracks: &[Track]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_sub(1) { + self.selection.select(Some(result)); + } + } + } +} + +impl AlbumSelection { + fn initialise(albums: Option<&[Album]>) -> Self { + let mut selection = ListState::default(); + let track: TrackSelection; + if let Some(albums) = albums { + selection.select(if !albums.is_empty() { Some(0) } else { None }); + track = TrackSelection::initialise(albums.get(0).map(|a| a.tracks.as_slice())); + } else { + selection.select(None); + track = TrackSelection::initialise(None); + } + AlbumSelection { selection, track } + } + + fn selection(&mut self) -> &mut ListState { + &mut self.selection + } + + fn track_selection(&mut self) -> &mut ListState { + self.track.selection() + } + + fn increment(&mut self, albums: &[Album]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_add(1) { + if result < albums.len() { + self.selection.select(Some(result)); + self.track = TrackSelection::initialise(Some(&albums[result].tracks)); + } + } + } + } + + fn increment_track(&mut self, albums: &[Album]) { + if let Some(index) = self.selection.selected() { + self.track.increment(&albums[index].tracks); + } + } + + fn decrement(&mut self, albums: &[Album]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_sub(1) { + self.selection.select(Some(result)); + self.track = TrackSelection::initialise(Some(&albums[result].tracks)); + } + } + } + + fn decrement_track(&mut self, albums: &[Album]) { + if let Some(index) = self.selection.selected() { + self.track.decrement(&albums[index].tracks); + } + } +} + +impl ArtistSelection { + fn initialise(artists: Option<&[Artist]>) -> Self { + let mut selection = ListState::default(); + let album: AlbumSelection; + if let Some(artists) = artists { + selection.select(if !artists.is_empty() { Some(0) } else { None }); + album = AlbumSelection::initialise(artists.get(0).map(|a| a.albums.as_slice())); + } else { + selection.select(None); + album = AlbumSelection::initialise(None); + } + ArtistSelection { selection, album } + } + + fn selection(&mut self) -> &mut ListState { + &mut self.selection + } + + fn album_selection(&mut self) -> &mut ListState { + self.album.selection() + } + + fn track_selection(&mut self) -> &mut ListState { + self.album.track_selection() + } + + fn increment(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_add(1) { + if result < artists.len() { + self.selection.select(Some(result)); + self.album = AlbumSelection::initialise(Some(&artists[result].albums)); + } + } + } + } + + fn increment_album(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + self.album.increment(&artists[index].albums); + } + } + + fn increment_track(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + self.album.increment_track(&artists[index].albums); + } + } + + fn decrement(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + if let Some(result) = index.checked_sub(1) { + self.selection.select(Some(result)); + self.album = AlbumSelection::initialise(Some(&artists[result].albums)); + } + } + } + + fn decrement_album(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + self.album.decrement(&artists[index].albums); + } + } + + fn decrement_track(&mut self, artists: &[Artist]) { + if let Some(index) = self.selection.selected() { + self.album.decrement_track(&artists[index].albums); + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Category { + Artist, + Album, + Track, +} + +struct Selection { + active: Category, + artist: ArtistSelection, +} + +impl Selection { + fn new(artists: Option<&[Artist]>) -> Self { + Selection { + active: Category::Artist, + artist: ArtistSelection::initialise(artists), + } + } + + fn artist_selection(&mut self) -> &mut ListState { + self.artist.selection() + } + + fn album_selection(&mut self) -> &mut ListState { + self.artist.album_selection() + } + + fn track_selection(&mut self) -> &mut ListState { + self.artist.track_selection() + } + + fn increment_category(&mut self) { + self.active = match self.active { + Category::Artist => Category::Album, + Category::Album => Category::Track, + Category::Track => Category::Track, + }; + } + + fn decrement_category(&mut self) { + self.active = match self.active { + Category::Artist => Category::Artist, + Category::Album => Category::Artist, + Category::Track => Category::Album, + }; + } + + fn increment_selection(&mut self, collection: &Collection) { + match self.active { + Category::Artist => self.increment_artist(collection), + Category::Album => self.increment_album(collection), + Category::Track => self.increment_track(collection), + } + } + + fn decrement_selection(&mut self, collection: &Collection) { + match self.active { + Category::Artist => self.decrement_artist(collection), + Category::Album => self.decrement_album(collection), + Category::Track => self.decrement_track(collection), + } + } + + fn increment_artist(&mut self, artists: &[Artist]) { + self.artist.increment(artists); + } + + fn decrement_artist(&mut self, artists: &[Artist]) { + self.artist.decrement(artists); + } + + fn increment_album(&mut self, artists: &[Artist]) { + self.artist.increment_album(artists); + } + + fn decrement_album(&mut self, artists: &[Artist]) { + self.artist.decrement_album(artists); + } + + fn increment_track(&mut self, artists: &[Artist]) { + self.artist.increment_track(artists); + } + + fn decrement_track(&mut self, artists: &[Artist]) { + self.artist.decrement_track(artists); + } +} + +pub struct MhUi { + collection_manager: CM, + selection: Selection, + running: bool, +} struct ArtistArea { list: Rect, @@ -33,7 +303,7 @@ struct FrameAreas { struct SelectionList<'a> { list: List<'a>, - state: ListState, + state: &'a mut ListState, } struct ArtistState<'a> { @@ -53,21 +323,15 @@ struct TrackState<'a> { active: bool, } -struct AppState<'a> { - artists: ArtistState<'a>, - albums: AlbumState<'a>, - tracks: TrackState<'a>, -} - -pub struct Ui { - _phantom: PhantomData, -} - -impl Ui { - pub fn new() -> Self { - Ui { - _phantom: PhantomData, - } +impl MhUi { + pub fn new(mut collection_manager: CM) -> Result { + collection_manager.rescan_library()?; + let selection = Selection::new(Some(collection_manager.get_collection())); + Ok(MhUi { + collection_manager, + selection, + running: true, + }) } fn construct_areas(frame: Rect) -> FrameAreas { @@ -127,21 +391,17 @@ impl Ui { } } - fn construct_artist_list(app: &APP) -> ArtistState { - let artists = app.get_artist_ids(); + fn construct_artist_state(&mut self) -> ArtistState { + let artists = self.collection_manager.get_collection(); let list = List::new( artists .iter() - .map(|id| ListItem::new(id.name.as_str())) + .map(|a| ListItem::new(a.id.name.as_str())) .collect::>(), ); - let selected_artist = app.selected_artist(); - - let mut state = ListState::default(); - state.select(selected_artist); - - let active = app.get_active_category() == Category::Artist; + let active = self.selection.active == Category::Artist; + let state = self.selection.artist_selection(); ArtistState { list: SelectionList { list, state }, @@ -149,8 +409,18 @@ impl Ui { } } - fn construct_album_list(app: &APP) -> AlbumState { - let albums = app.get_album_ids(); + fn construct_album_state(&mut self) -> AlbumState { + let albums: Vec<&AlbumId> = + if let Some(artist_index) = self.selection.artist.selection.selected() { + self.collection_manager.get_collection()[artist_index] + .albums + .iter() + .map(|a| &a.id) + .collect() + } else { + vec![] + }; + let list = List::new( albums .iter() @@ -158,14 +428,10 @@ impl Ui { .collect::>(), ); - let selected_album = app.selected_album(); + let active = self.selection.active == Category::Album; + let state = self.selection.album_selection(); - let mut state = ListState::default(); - state.select(selected_album); - - let active = app.get_active_category() == Category::Album; - - let album = selected_album.map(|i| albums[i]); + let album = state.selected().map(|i| albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ Year: {}", @@ -182,8 +448,21 @@ impl Ui { } } - fn construct_track_list(app: &APP) -> TrackState { - let tracks = app.get_track_ids(); + fn construct_track_state(&mut self) -> TrackState { + let tracks: Vec<&Track> = + if let Some(artist_index) = self.selection.artist.selection.selected() { + if let Some(album_index) = self.selection.artist.album.selection.selected() { + self.collection_manager.get_collection()[artist_index].albums[album_index] + .tracks + .iter() + .collect() + } else { + vec![] + } + } else { + vec![] + }; + let list = List::new( tracks .iter() @@ -191,14 +470,10 @@ impl Ui { .collect::>(), ); - let selected_track = app.selected_track(); + let active = self.selection.active == Category::Track; + let state = self.selection.track_selection(); - let mut state = ListState::default(); - state.select(selected_track); - - let active = app.get_active_category() == Category::Track; - - let track = selected_track.map(|i| tracks[i]); + let track = state.selected().map(|i| tracks[i]); let info = Paragraph::new(format!( "Track: {}\n\ Title: {}\n\ @@ -226,14 +501,6 @@ impl Ui { } } - fn construct_app_state(app: &APP) -> AppState { - AppState { - artists: Self::construct_artist_list(app), - albums: Self::construct_album_list(app), - tracks: Self::construct_track_list(app), - } - } - fn style(_active: bool) -> Style { Style::default().fg(Color::White).bg(Color::Black) } @@ -292,78 +559,109 @@ impl Ui { ); } - fn render_artist_column( - state: ArtistState, - area: ArtistArea, - frame: &mut Frame<'_, B>, - ) { + fn render_artist_column(&mut self, area: ArtistArea, frame: &mut Frame<'_, B>) { + let state = self.construct_artist_state(); Self::render_list_widget("Artists", state.list, state.active, area.list, frame); } - fn render_album_column( - state: AlbumState, - area: AlbumArea, - frame: &mut Frame<'_, B>, - ) { + fn render_album_column(&mut self, area: AlbumArea, frame: &mut Frame<'_, B>) { + let state = self.construct_album_state(); Self::render_list_widget("Albums", state.list, state.active, area.list, frame); Self::render_info_widget("Album info", state.info, state.active, area.info, frame); } - fn render_track_column( - state: TrackState, - area: TrackArea, - frame: &mut Frame<'_, B>, - ) { + fn render_track_column(&mut self, area: TrackArea, frame: &mut Frame<'_, B>) { + let state = self.construct_track_state(); Self::render_list_widget("Tracks", state.list, state.active, area.list, frame); Self::render_info_widget("Track info", state.info, state.active, area.info, frame); } +} - pub fn render(&self, app: &APP, frame: &mut Frame<'_, B>) { +pub trait Ui { + fn is_running(&self) -> bool; + fn quit(&mut self); + + fn increment_category(&mut self); + fn decrement_category(&mut self); + + fn increment_selection(&mut self); + fn decrement_selection(&mut self); + + fn render(&mut self, frame: &mut Frame<'_, B>); +} + +impl Ui for MhUi { + fn is_running(&self) -> bool { + self.running + } + + fn quit(&mut self) { + self.running = false; + } + + fn increment_category(&mut self) { + self.selection.increment_category(); + } + + fn decrement_category(&mut self) { + self.selection.decrement_category(); + } + + fn increment_selection(&mut self) { + self.selection + .increment_selection(self.collection_manager.get_collection()); + } + + fn decrement_selection(&mut self) { + self.selection + .decrement_selection(self.collection_manager.get_collection()); + } + + fn render(&mut self, frame: &mut Frame<'_, B>) { let areas = Self::construct_areas(frame.size()); - let app_state = Self::construct_app_state(app); - Self::render_artist_column(app_state.artists, areas.artists, frame); - Self::render_album_column(app_state.albums, areas.albums, frame); - Self::render_track_column(app_state.tracks, areas.tracks, frame); + self.render_artist_column(areas.artists, frame); + self.render_album_column(areas.albums, frame); + self.render_track_column(areas.tracks, frame); } } -#[cfg(test)] -mod tests { - // This is UI so the only sensible unit test is to run the code through various app states. +// #[cfg(test)] +// mod tests { +// // This is UI so the only sensible unit test is to run the code through various app states. - use crate::{ - tests::COLLECTION, - tui::{ - app::App, - tests::{app, terminal}, - }, - }; +// use crate::{ +// tests::COLLECTION, +// tui::{ +// app::App, +// tests::{app, terminal}, +// }, +// }; - use super::Ui; +// use super::Ui; - #[test] - fn empty() { - let mut terminal = terminal(); - let app = app(vec![]); - let ui = Ui::new(); +// #[test] +// fn empty() { +// let mut terminal = terminal(); +// let app = app(vec![]); +// let ui = Ui::new(); - terminal.draw(|frame| ui.render(&app, frame)).unwrap(); - } +// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); +// } - #[test] - fn collection() { - let mut terminal = terminal(); - let mut app = app(COLLECTION.to_owned()); - let ui = Ui::new(); +// #[test] +// fn collection() { +// let mut terminal = terminal(); +// let mut app = app(COLLECTION.to_owned()); +// let ui = Ui::new(); - terminal.draw(|frame| ui.render(&app, frame)).unwrap(); +// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); - // Change the track (which has a different track format). - app.increment_category(); - app.increment_category(); - app.increment_selection(); +// // Change the track (which has a different track format). +// app.increment_category(); +// app.increment_category(); +// app.increment_selection(); - terminal.draw(|frame| ui.render(&app, frame)).unwrap(); - } -} +// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); +// } +// } -- 2.45.2 From 4ee70bce53ab6c2cacae2314865326b7a9606ab0 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 23 Apr 2023 21:01:20 +0200 Subject: [PATCH 2/7] Version 2 of solution --- src/tui/ui.rs | 261 ++++++++++++++++++++++++-------------------------- 1 file changed, 124 insertions(+), 137 deletions(-) diff --git a/src/tui/ui.rs b/src/tui/ui.rs index cfef7b3..24a1037 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,6 +1,6 @@ use musichoard::{ collection::{Collection, CollectionManager}, - Album, AlbumId, Artist, Track, TrackFormat, + Album, Artist, Track, TrackFormat, }; use ratatui::{ backend::Backend, @@ -37,10 +37,6 @@ impl TrackSelection { TrackSelection { selection } } - fn selection(&mut self) -> &mut ListState { - &mut self.selection - } - fn increment(&mut self, tracks: &[Track]) { if let Some(index) = self.selection.selected() { if let Some(result) = index.checked_add(1) { @@ -74,14 +70,6 @@ impl AlbumSelection { AlbumSelection { selection, track } } - fn selection(&mut self) -> &mut ListState { - &mut self.selection - } - - fn track_selection(&mut self) -> &mut ListState { - self.track.selection() - } - fn increment(&mut self, albums: &[Album]) { if let Some(index) = self.selection.selected() { if let Some(result) = index.checked_add(1) { @@ -129,18 +117,6 @@ impl ArtistSelection { ArtistSelection { selection, album } } - fn selection(&mut self) -> &mut ListState { - &mut self.selection - } - - fn album_selection(&mut self) -> &mut ListState { - self.album.selection() - } - - fn track_selection(&mut self) -> &mut ListState { - self.album.track_selection() - } - fn increment(&mut self, artists: &[Artist]) { if let Some(index) = self.selection.selected() { if let Some(result) = index.checked_add(1) { @@ -206,18 +182,6 @@ impl Selection { } } - fn artist_selection(&mut self) -> &mut ListState { - self.artist.selection() - } - - fn album_selection(&mut self) -> &mut ListState { - self.artist.album_selection() - } - - fn track_selection(&mut self) -> &mut ListState { - self.artist.track_selection() - } - fn increment_category(&mut self) { self.active = match self.active { Category::Artist => Category::Album, @@ -283,11 +247,13 @@ pub struct MhUi { struct ArtistArea { list: Rect, + album: AlbumArea, } struct AlbumArea { list: Rect, info: Rect, + track: TrackArea, } struct TrackArea { @@ -295,32 +261,18 @@ struct TrackArea { info: Rect, } -struct FrameAreas { - artists: ArtistArea, - albums: AlbumArea, - tracks: TrackArea, -} - -struct SelectionList<'a> { - list: List<'a>, - state: &'a mut ListState, -} - struct ArtistState<'a> { - list: SelectionList<'a>, - active: bool, + list: List<'a>, } struct AlbumState<'a> { - list: SelectionList<'a>, + list: List<'a>, info: Paragraph<'a>, - active: bool, } struct TrackState<'a> { - list: SelectionList<'a>, + list: List<'a>, info: Paragraph<'a>, - active: bool, } impl MhUi { @@ -334,7 +286,7 @@ impl MhUi { }) } - fn construct_areas(frame: Rect) -> FrameAreas { + fn construct_areas(frame: Rect) -> ArtistArea { let width_one_third = frame.width / 3; let height_one_third = frame.height / 3; @@ -378,21 +330,20 @@ impl MhUi { height: panel_height_bottom, }; - FrameAreas { - artists: ArtistArea { list: artist_list }, - albums: AlbumArea { + ArtistArea { + list: artist_list, + album: AlbumArea { list: album_list, info: album_info, - }, - tracks: TrackArea { - list: track_list, - info: track_info, + track: TrackArea { + list: track_list, + info: track_info, + }, }, } } - fn construct_artist_state(&mut self) -> ArtistState { - let artists = self.collection_manager.get_collection(); + fn construct_artist_state(artists: &[Artist]) -> ArtistState { let list = List::new( artists .iter() @@ -400,69 +351,31 @@ impl MhUi { .collect::>(), ); - let active = self.selection.active == Category::Artist; - let state = self.selection.artist_selection(); - - ArtistState { - list: SelectionList { list, state }, - active, - } + ArtistState { list } } - fn construct_album_state(&mut self) -> AlbumState { - let albums: Vec<&AlbumId> = - if let Some(artist_index) = self.selection.artist.selection.selected() { - self.collection_manager.get_collection()[artist_index] - .albums - .iter() - .map(|a| &a.id) - .collect() - } else { - vec![] - }; - + fn construct_album_state(albums: &[Album], selected: Option) -> AlbumState { let list = List::new( albums .iter() - .map(|id| ListItem::new(id.title.as_str())) + .map(|a| ListItem::new(a.id.title.as_str())) .collect::>(), ); - let active = self.selection.active == Category::Album; - let state = self.selection.album_selection(); - - let album = state.selected().map(|i| albums[i]); + let album = selected.map(|i| &albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ Year: {}", - album.map(|a| a.title.as_str()).unwrap_or(""), + album.map(|a| a.id.title.as_str()).unwrap_or(""), album - .map(|a| a.year.to_string()) + .map(|a| a.id.year.to_string()) .unwrap_or_else(|| "".to_string()), )); - AlbumState { - list: SelectionList { list, state }, - info, - active, - } + AlbumState { list, info } } - fn construct_track_state(&mut self) -> TrackState { - let tracks: Vec<&Track> = - if let Some(artist_index) = self.selection.artist.selection.selected() { - if let Some(album_index) = self.selection.artist.album.selection.selected() { - self.collection_manager.get_collection()[artist_index].albums[album_index] - .tracks - .iter() - .collect() - } else { - vec![] - } - } else { - vec![] - }; - + fn construct_track_state(tracks: &[Track], selected: Option) -> TrackState { let list = List::new( tracks .iter() @@ -470,10 +383,7 @@ impl MhUi { .collect::>(), ); - let active = self.selection.active == Category::Track; - let state = self.selection.track_selection(); - - let track = state.selected().map(|i| tracks[i]); + let track = selected.map(|i| &tracks[i]); let info = Paragraph::new(format!( "Track: {}\n\ Title: {}\n\ @@ -494,11 +404,7 @@ impl MhUi { .unwrap_or(""), )); - TrackState { - list: SelectionList { list, state }, - info, - active, - } + TrackState { list, info } } fn style(_active: bool) -> Style { @@ -528,19 +434,19 @@ impl MhUi { fn render_list_widget( title: &str, - mut list: SelectionList, + list: List, + list_state: &mut ListState, active: bool, area: Rect, frame: &mut Frame<'_, B>, ) { frame.render_stateful_widget( - list.list - .highlight_style(Self::highlight_style(active)) + list.highlight_style(Self::highlight_style(active)) .highlight_symbol(">> ") .style(Self::style(active)) .block(Self::block(title, active)), area, - &mut list.state, + list_state, ); } @@ -559,21 +465,98 @@ impl MhUi { ); } - fn render_artist_column(&mut self, area: ArtistArea, frame: &mut Frame<'_, B>) { - let state = self.construct_artist_state(); - Self::render_list_widget("Artists", state.list, state.active, area.list, frame); + fn render_artist_column( + artists: &[Artist], + category: Category, + selection: &mut ArtistSelection, + area: ArtistArea, + frame: &mut Frame<'_, B>, + ) { + let state = Self::construct_artist_state(artists); + Self::render_list_widget( + "Artists", + state.list, + &mut selection.selection, + category == Category::Artist, + area.list, + frame, + ); + + let empty_vec: Vec = vec![]; + Self::render_album_column( + if let Some(artist_index) = selection.selection.selected() { + &artists[artist_index].albums + } else { + &empty_vec + }, + category, + &mut selection.album, + area.album, + frame, + ); } - fn render_album_column(&mut self, area: AlbumArea, frame: &mut Frame<'_, B>) { - let state = self.construct_album_state(); - Self::render_list_widget("Albums", state.list, state.active, area.list, frame); - Self::render_info_widget("Album info", state.info, state.active, area.info, frame); + fn render_album_column( + albums: &[Album], + category: Category, + selection: &mut AlbumSelection, + area: AlbumArea, + frame: &mut Frame<'_, B>, + ) { + let state = Self::construct_album_state(albums, selection.selection.selected()); + Self::render_list_widget( + "Albums", + state.list, + &mut selection.selection, + category == Category::Album, + area.list, + frame, + ); + Self::render_info_widget( + "Album info", + state.info, + category == Category::Album, + area.info, + frame, + ); + + let empty_vec: Vec = vec![]; + Self::render_track_column( + if let Some(album_index) = selection.selection.selected() { + &albums[album_index].tracks + } else { + &empty_vec + }, + category, + &mut selection.track, + area.track, + frame, + ); } - fn render_track_column(&mut self, area: TrackArea, frame: &mut Frame<'_, B>) { - let state = self.construct_track_state(); - Self::render_list_widget("Tracks", state.list, state.active, area.list, frame); - Self::render_info_widget("Track info", state.info, state.active, area.info, frame); + fn render_track_column( + tracks: &[Track], + category: Category, + selection: &mut TrackSelection, + area: TrackArea, + frame: &mut Frame<'_, B>, + ) { + let state = Self::construct_track_state(tracks, selection.selection.selected()); + Self::render_list_widget( + "Tracks", + state.list, + &mut selection.selection, + category == Category::Track, + area.list, + frame, + ); + Self::render_info_widget( + "Track info", + state.info, + category == Category::Track, + area.info, + frame, + ); } } @@ -620,9 +603,13 @@ impl Ui for MhUi { fn render(&mut self, frame: &mut Frame<'_, B>) { let areas = Self::construct_areas(frame.size()); - self.render_artist_column(areas.artists, frame); - self.render_album_column(areas.albums, frame); - self.render_track_column(areas.tracks, frame); + Self::render_artist_column( + self.collection_manager.get_collection(), + self.selection.active, + &mut self.selection.artist, + areas, + frame, + ); } } -- 2.45.2 From f9a9c264cc48dbccfc09ef3ffe086248621dbaf5 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 23 Apr 2023 23:26:28 +0200 Subject: [PATCH 3/7] Tidiest solution so far --- src/tui/ui.rs | 327 ++++++++++++++++++++++++-------------------------- 1 file changed, 156 insertions(+), 171 deletions(-) diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 24a1037..1b0ecce 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -13,44 +13,44 @@ use ratatui::{ use super::Error; struct TrackSelection { - selection: ListState, + state: ListState, } struct AlbumSelection { - selection: ListState, + state: ListState, track: TrackSelection, } struct ArtistSelection { - selection: ListState, + state: ListState, album: AlbumSelection, } impl TrackSelection { fn initialise(tracks: Option<&[Track]>) -> Self { - let mut selection = ListState::default(); + let mut state = ListState::default(); if let Some(tracks) = tracks { - selection.select(if !tracks.is_empty() { Some(0) } else { None }); + state.select(if !tracks.is_empty() { Some(0) } else { None }); } else { - selection.select(None); + state.select(None); }; - TrackSelection { selection } + TrackSelection { state } } fn increment(&mut self, tracks: &[Track]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_add(1) { if result < tracks.len() { - self.selection.select(Some(result)); + self.state.select(Some(result)); } } } } fn decrement(&mut self, _tracks: &[Track]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_sub(1) { - self.selection.select(Some(result)); + self.state.select(Some(result)); } } } @@ -58,23 +58,23 @@ impl TrackSelection { impl AlbumSelection { fn initialise(albums: Option<&[Album]>) -> Self { - let mut selection = ListState::default(); + let mut state = ListState::default(); let track: TrackSelection; if let Some(albums) = albums { - selection.select(if !albums.is_empty() { Some(0) } else { None }); + state.select(if !albums.is_empty() { Some(0) } else { None }); track = TrackSelection::initialise(albums.get(0).map(|a| a.tracks.as_slice())); } else { - selection.select(None); + state.select(None); track = TrackSelection::initialise(None); } - AlbumSelection { selection, track } + AlbumSelection { state, track } } fn increment(&mut self, albums: &[Album]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_add(1) { if result < albums.len() { - self.selection.select(Some(result)); + self.state.select(Some(result)); self.track = TrackSelection::initialise(Some(&albums[result].tracks)); } } @@ -82,22 +82,22 @@ impl AlbumSelection { } fn increment_track(&mut self, albums: &[Album]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.track.increment(&albums[index].tracks); } } fn decrement(&mut self, albums: &[Album]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_sub(1) { - self.selection.select(Some(result)); + self.state.select(Some(result)); self.track = TrackSelection::initialise(Some(&albums[result].tracks)); } } } fn decrement_track(&mut self, albums: &[Album]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.track.decrement(&albums[index].tracks); } } @@ -105,23 +105,23 @@ impl AlbumSelection { impl ArtistSelection { fn initialise(artists: Option<&[Artist]>) -> Self { - let mut selection = ListState::default(); + let mut state = ListState::default(); let album: AlbumSelection; if let Some(artists) = artists { - selection.select(if !artists.is_empty() { Some(0) } else { None }); + state.select(if !artists.is_empty() { Some(0) } else { None }); album = AlbumSelection::initialise(artists.get(0).map(|a| a.albums.as_slice())); } else { - selection.select(None); + state.select(None); album = AlbumSelection::initialise(None); } - ArtistSelection { selection, album } + ArtistSelection { state, album } } fn increment(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_add(1) { if result < artists.len() { - self.selection.select(Some(result)); + self.state.select(Some(result)); self.album = AlbumSelection::initialise(Some(&artists[result].albums)); } } @@ -129,34 +129,34 @@ impl ArtistSelection { } fn increment_album(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.album.increment(&artists[index].albums); } } fn increment_track(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.album.increment_track(&artists[index].albums); } } fn decrement(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { if let Some(result) = index.checked_sub(1) { - self.selection.select(Some(result)); + self.state.select(Some(result)); self.album = AlbumSelection::initialise(Some(&artists[result].albums)); } } } fn decrement_album(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.album.decrement(&artists[index].albums); } } fn decrement_track(&mut self, artists: &[Artist]) { - if let Some(index) = self.selection.selected() { + if let Some(index) = self.state.selected() { self.album.decrement_track(&artists[index].albums); } } @@ -247,13 +247,11 @@ pub struct MhUi { struct ArtistArea { list: Rect, - album: AlbumArea, } struct AlbumArea { list: Rect, info: Rect, - track: TrackArea, } struct TrackArea { @@ -261,32 +259,14 @@ struct TrackArea { info: Rect, } -struct ArtistState<'a> { - list: List<'a>, +struct FrameArea { + artist: ArtistArea, + album: AlbumArea, + track: TrackArea, } -struct AlbumState<'a> { - list: List<'a>, - info: Paragraph<'a>, -} - -struct TrackState<'a> { - list: List<'a>, - info: Paragraph<'a>, -} - -impl MhUi { - pub fn new(mut collection_manager: CM) -> Result { - collection_manager.rescan_library()?; - let selection = Selection::new(Some(collection_manager.get_collection())); - Ok(MhUi { - collection_manager, - selection, - running: true, - }) - } - - fn construct_areas(frame: Rect) -> ArtistArea { +impl FrameArea { + fn new(frame: Rect) -> FrameArea { let width_one_third = frame.width / 3; let height_one_third = frame.height / 3; @@ -330,20 +310,28 @@ impl MhUi { height: panel_height_bottom, }; - ArtistArea { - list: artist_list, + FrameArea { + artist: ArtistArea { list: artist_list }, album: AlbumArea { list: album_list, info: album_info, - track: TrackArea { - list: track_list, - info: track_info, - }, + }, + track: TrackArea { + list: track_list, + info: track_info, }, } } +} - fn construct_artist_state(artists: &[Artist]) -> ArtistState { +struct ArtistState<'a, 'b> { + active: bool, + list: List<'a>, + state: &'b mut ListState, +} + +impl<'a, 'b> ArtistState<'a, 'b> { + fn new(active: bool, artists: &'a [Artist], state: &'b mut ListState) -> ArtistState<'a, 'b> { let list = List::new( artists .iter() @@ -351,10 +339,23 @@ impl MhUi { .collect::>(), ); - ArtistState { list } + ArtistState { + active, + list, + state, + } } +} - fn construct_album_state(albums: &[Album], selected: Option) -> AlbumState { +struct AlbumState<'a, 'b> { + active: bool, + list: List<'a>, + state: &'b mut ListState, + info: Paragraph<'a>, +} + +impl<'a, 'b> AlbumState<'a, 'b> { + fn new(active: bool, albums: &'a [Album], state: &'b mut ListState) -> AlbumState<'a, 'b> { let list = List::new( albums .iter() @@ -362,7 +363,7 @@ impl MhUi { .collect::>(), ); - let album = selected.map(|i| &albums[i]); + let album = state.selected().map(|i| &albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ Year: {}", @@ -372,10 +373,24 @@ impl MhUi { .unwrap_or_else(|| "".to_string()), )); - AlbumState { list, info } + AlbumState { + active, + list, + state, + info, + } } +} - fn construct_track_state(tracks: &[Track], selected: Option) -> TrackState { +struct TrackState<'a, 'b> { + active: bool, + list: List<'a>, + state: &'b mut ListState, + info: Paragraph<'a>, +} + +impl<'a, 'b> TrackState<'a, 'b> { + fn new(active: bool, tracks: &'a [Track], state: &'b mut ListState) -> TrackState<'a, 'b> { let list = List::new( tracks .iter() @@ -383,12 +398,12 @@ impl MhUi { .collect::>(), ); - let track = selected.map(|i| &tracks[i]); + let track = state.selected().map(|i| &tracks[i]); let info = Paragraph::new(format!( "Track: {}\n\ - Title: {}\n\ - Artist: {}\n\ - Format: {}", + Title: {}\n\ + Artist: {}\n\ + Format: {}", track .map(|t| t.number.to_string()) .unwrap_or_else(|| "".to_string()), @@ -404,7 +419,24 @@ impl MhUi { .unwrap_or(""), )); - TrackState { list, info } + TrackState { + active, + list, + state, + info, + } + } +} + +impl MhUi { + pub fn new(mut collection_manager: CM) -> Result { + collection_manager.rescan_library()?; + let selection = Selection::new(Some(collection_manager.get_collection())); + Ok(MhUi { + collection_manager, + selection, + running: true, + }) } fn style(_active: bool) -> Style { @@ -465,98 +497,18 @@ impl MhUi { ); } - fn render_artist_column( - artists: &[Artist], - category: Category, - selection: &mut ArtistSelection, - area: ArtistArea, - frame: &mut Frame<'_, B>, - ) { - let state = Self::construct_artist_state(artists); - Self::render_list_widget( - "Artists", - state.list, - &mut selection.selection, - category == Category::Artist, - area.list, - frame, - ); - - let empty_vec: Vec = vec![]; - Self::render_album_column( - if let Some(artist_index) = selection.selection.selected() { - &artists[artist_index].albums - } else { - &empty_vec - }, - category, - &mut selection.album, - area.album, - frame, - ); + fn render_artist_column(st: ArtistState, ar: ArtistArea, fr: &mut Frame<'_, B>) { + Self::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr); } - fn render_album_column( - albums: &[Album], - category: Category, - selection: &mut AlbumSelection, - area: AlbumArea, - frame: &mut Frame<'_, B>, - ) { - let state = Self::construct_album_state(albums, selection.selection.selected()); - Self::render_list_widget( - "Albums", - state.list, - &mut selection.selection, - category == Category::Album, - area.list, - frame, - ); - Self::render_info_widget( - "Album info", - state.info, - category == Category::Album, - area.info, - frame, - ); - - let empty_vec: Vec = vec![]; - Self::render_track_column( - if let Some(album_index) = selection.selection.selected() { - &albums[album_index].tracks - } else { - &empty_vec - }, - category, - &mut selection.track, - area.track, - frame, - ); + fn render_album_column(st: AlbumState, ar: AlbumArea, fr: &mut Frame<'_, B>) { + Self::render_list_widget("Albums", st.list, st.state, st.active, ar.list, fr); + Self::render_info_widget("Album info", st.info, st.active, ar.info, fr); } - fn render_track_column( - tracks: &[Track], - category: Category, - selection: &mut TrackSelection, - area: TrackArea, - frame: &mut Frame<'_, B>, - ) { - let state = Self::construct_track_state(tracks, selection.selection.selected()); - Self::render_list_widget( - "Tracks", - state.list, - &mut selection.selection, - category == Category::Track, - area.list, - frame, - ); - Self::render_info_widget( - "Track info", - state.info, - category == Category::Track, - area.info, - frame, - ); + fn render_track_column(st: TrackState, ar: TrackArea, fr: &mut Frame<'_, B>) { + Self::render_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr); + Self::render_info_widget("Track info", st.info, st.active, ar.info, fr); } } @@ -601,15 +553,48 @@ impl Ui for MhUi { } fn render(&mut self, frame: &mut Frame<'_, B>) { - let areas = Self::construct_areas(frame.size()); + let active = self.selection.active; + let areas = FrameArea::new(frame.size()); - Self::render_artist_column( - self.collection_manager.get_collection(), - self.selection.active, - &mut self.selection.artist, - areas, - frame, + let artists = self.collection_manager.get_collection(); + let artist_selection = &mut self.selection.artist; + let artist_state = ArtistState::new( + active == Category::Artist, + artists, + &mut artist_selection.state, ); + + Self::render_artist_column(artist_state, areas.artist, frame); + + let no_albums: Vec = vec![]; + let albums = artist_selection + .state + .selected() + .map(|i| &artists[i].albums) + .unwrap_or_else(|| &no_albums); + let album_selection = &mut artist_selection.album; + let album_state = AlbumState::new( + active == Category::Album, + albums, + &mut album_selection.state, + ); + + Self::render_album_column(album_state, areas.album, frame); + + let no_tracks: Vec = vec![]; + let tracks = album_selection + .state + .selected() + .map(|i| &albums[i].tracks) + .unwrap_or_else(|| &no_tracks); + let track_selection = &mut album_selection.track; + let track_state = TrackState::new( + active == Category::Track, + tracks, + &mut track_selection.state, + ); + + Self::render_track_column(track_state, areas.track, frame); } } -- 2.45.2 From 9d73a37f1aeb50fa301d1f12c9764293f6c60074 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 27 Apr 2023 18:44:30 +0200 Subject: [PATCH 4/7] Update all unit tests --- src/tui/app.rs | 484 ------------------------------------------------- src/tui/mod.rs | 237 ++++++++++++------------ src/tui/ui.rs | 476 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 563 insertions(+), 634 deletions(-) delete mode 100644 src/tui/app.rs diff --git a/src/tui/app.rs b/src/tui/app.rs deleted file mode 100644 index 1f33535..0000000 --- a/src/tui/app.rs +++ /dev/null @@ -1,484 +0,0 @@ -// #[cfg(test)] -// mod tests { -// use crate::tests::{MockCollectionManager, COLLECTION}; - -// use super::*; - -// #[test] -// fn test_track_selection() { -// let tracks = &COLLECTION[0].albums[0].tracks; -// assert!(tracks.len() > 1); - -// let empty = SelectedTrack::initialise(&vec![]); -// assert!(empty.is_none()); - -// let sel = SelectedTrack::initialise(tracks); -// assert!(sel.is_some()); - -// let mut sel = sel.unwrap(); -// assert_eq!(sel.index, 0); - -// sel.decrement(tracks); -// assert_eq!(sel.index, 0); - -// sel.increment(tracks); -// assert_eq!(sel.index, 1); - -// sel.decrement(tracks); -// assert_eq!(sel.index, 0); - -// for _ in 0..(tracks.len() + 5) { -// sel.increment(tracks); -// } -// assert_eq!(sel.index, tracks.len() - 1); - -// // Artifical test case to verify upper limit. -// let mut sel = SelectedTrack { -// index: std::usize::MAX, -// }; -// assert_eq!(sel.index, std::usize::MAX); - -// sel.increment(&vec![]); -// assert_eq!(sel.index, std::usize::MAX); -// } - -// #[test] -// fn test_album_selection() { -// let albums = &COLLECTION[0].albums; -// assert!(albums.len() > 1); - -// let empty = SelectedAlbum::initialise(&vec![]); -// assert!(empty.is_none()); - -// let sel = SelectedAlbum::initialise(albums); -// assert!(sel.is_some()); - -// let mut sel = sel.unwrap(); -// assert_eq!(sel.index, 0); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 0); - -// sel.track -// .as_mut() -// .unwrap() -// .increment(&albums[sel.index].tracks); -// assert_eq!(sel.index, 0); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); - -// // Verify that decrement that doesn't change index does not reset track. -// sel.decrement(albums); -// assert_eq!(sel.index, 0); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); - -// sel.increment(albums); -// assert_eq!(sel.index, 1); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 0); - -// sel.decrement(albums); -// assert_eq!(sel.index, 0); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 0); - -// for _ in 0..(albums.len() + 5) { -// sel.increment(albums); -// } -// assert_eq!(sel.index, albums.len() - 1); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 0); - -// sel.track -// .as_mut() -// .unwrap() -// .increment(&albums[sel.index].tracks); -// assert_eq!(sel.index, albums.len() - 1); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); - -// // Verify that increment that doesn't change index does not reset track. -// sel.increment(albums); -// assert_eq!(sel.index, albums.len() - 1); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); - -// // Artifical test case to verify upper limit. -// let mut sel = SelectedAlbum { -// index: std::usize::MAX, -// track: Some(SelectedTrack { index: 1 }), -// }; -// assert_eq!(sel.index, std::usize::MAX); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); - -// sel.increment(&vec![]); -// assert_eq!(sel.index, std::usize::MAX); -// assert!(sel.track.is_some()); -// assert_eq!(sel.track.as_ref().unwrap().index, 1); -// } - -// #[test] -// fn test_artist_selection() { -// let artists = &COLLECTION; -// assert!(artists.len() > 1); - -// let empty = SelectedArtist::initialise(&vec![]); -// assert!(empty.is_none()); - -// let sel = SelectedArtist::initialise(artists); -// assert!(sel.is_some()); - -// let mut sel = sel.unwrap(); -// assert_eq!(sel.index, 0); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 0); - -// sel.album -// .as_mut() -// .unwrap() -// .increment(&artists[sel.index].albums); -// assert_eq!(sel.index, 0); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); - -// // Verify that decrement that doesn't change index does not reset album. -// sel.decrement(artists); -// assert_eq!(sel.index, 0); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); - -// sel.increment(artists); -// assert_eq!(sel.index, 1); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 0); - -// sel.decrement(artists); -// assert_eq!(sel.index, 0); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 0); - -// for _ in 0..(artists.len() + 5) { -// sel.increment(artists); -// } -// assert_eq!(sel.index, artists.len() - 1); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 0); - -// sel.album -// .as_mut() -// .unwrap() -// .increment(&artists[sel.index].albums); -// assert_eq!(sel.index, artists.len() - 1); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); - -// // Verify that increment that doesn't change index does not reset album. -// sel.increment(artists); -// assert_eq!(sel.index, artists.len() - 1); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); - -// // Artifical test case to verify upper limit. -// let mut sel = SelectedArtist { -// index: std::usize::MAX, -// album: Some(SelectedAlbum { -// index: 1, -// track: None, -// }), -// }; -// assert_eq!(sel.index, std::usize::MAX); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); - -// sel.increment(&vec![]); -// assert_eq!(sel.index, std::usize::MAX); -// assert!(sel.album.is_some()); -// assert_eq!(sel.album.as_ref().unwrap().index, 1); -// } - -// #[test] -// fn app_running() { -// let mut collection_manager = MockCollectionManager::new(); - -// collection_manager -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(COLLECTION.to_owned()); - -// let mut app = TuiApp::new(collection_manager).unwrap(); -// assert!(app.is_running()); - -// app.quit(); -// assert!(!app.is_running()); -// } - -// #[test] -// fn app_modifiers() { -// let mut collection_manager = MockCollectionManager::new(); - -// collection_manager -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(COLLECTION.to_owned()); - -// let mut app = TuiApp::new(collection_manager).unwrap(); -// assert!(app.is_running()); - -// assert!(!app.get_artist_ids().is_empty()); -// assert!(!app.get_album_ids().is_empty()); -// assert!(!app.get_track_ids().is_empty()); - -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_category(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_category(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(1)); - -// app.increment_category(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(1)); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_selection(); -// app.decrement_category(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(1)); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_selection(); -// app.decrement_category(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(1)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), Some(0)); - -// app.increment_category(); -// app.increment_selection(); -// app.decrement_category(); -// app.decrement_selection(); -// app.decrement_category(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(1)); -// assert_eq!(app.selected_track(), Some(0)); -// } - -// #[test] -// fn app_no_tracks() { -// let mut collection_manager = MockCollectionManager::new(); -// let mut collection = COLLECTION.to_owned(); -// collection[0].albums[0].tracks = vec![]; - -// collection_manager -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(collection); - -// let mut app = TuiApp::new(collection_manager).unwrap(); -// assert!(app.is_running()); - -// assert!(!app.get_artist_ids().is_empty()); -// assert!(!app.get_album_ids().is_empty()); -// assert!(app.get_track_ids().is_empty()); - -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), None); - -// app.increment_category(); -// app.increment_category(); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), Some(0)); -// assert_eq!(app.selected_track(), None); -// } - -// #[test] -// fn app_no_albums() { -// let mut collection_manager = MockCollectionManager::new(); -// let mut collection = COLLECTION.to_owned(); -// collection[0].albums = vec![]; - -// collection_manager -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(collection); - -// let mut app = TuiApp::new(collection_manager).unwrap(); -// assert!(app.is_running()); - -// assert!(!app.get_artist_ids().is_empty()); -// assert!(app.get_album_ids().is_empty()); -// assert!(app.get_track_ids().is_empty()); - -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.increment_category(); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.increment_category(); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), Some(0)); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); -// } - -// #[test] -// fn app_no_artists() { -// let mut collection_manager = MockCollectionManager::new(); -// let collection = vec![]; - -// collection_manager -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(collection); - -// let mut app = TuiApp::new(collection_manager).unwrap(); -// assert!(app.is_running()); - -// assert!(app.get_artist_ids().is_empty()); -// assert!(app.get_album_ids().is_empty()); -// assert!(app.get_track_ids().is_empty()); - -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Artist); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.increment_category(); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Album); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.increment_category(); - -// app.increment_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); - -// app.decrement_selection(); -// assert_eq!(app.get_active_category(), Category::Track); -// assert_eq!(app.selected_artist(), None); -// assert_eq!(app.selected_album(), None); -// assert_eq!(app.selected_track(), None); -// } -// } diff --git a/src/tui/mod.rs b/src/tui/mod.rs index d9af99f..f623a81 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -156,151 +156,150 @@ impl Tui { // GRCOV_EXCL_STOP } -// #[cfg(test)] -// mod tests { -// use std::{io, thread}; +#[cfg(test)] +mod tests { + use std::{io, thread}; -// use musichoard::collection::{self, Collection}; -// use ratatui::{backend::TestBackend, Terminal}; + use musichoard::collection::{self, Collection}; + use ratatui::{backend::TestBackend, Terminal}; -// use crate::tests::{MockCollectionManager, COLLECTION}; + use crate::tests::{MockCollectionManager, COLLECTION}; -// use super::{ -// app::TuiApp, event::EventError, handler::MockEventHandler, listener::MockEventListener, -// ui::Ui, Error, Tui, -// }; + use super::{ + event::EventError, + handler::MockEventHandler, + listener::MockEventListener, + ui::{MhUi, Ui}, + Error, Tui, + }; -// pub fn terminal() -> Terminal { -// let backend = TestBackend::new(150, 30); -// Terminal::new(backend).unwrap() -// } + pub fn terminal() -> Terminal { + let backend = TestBackend::new(150, 30); + Terminal::new(backend).unwrap() + } -// pub fn app(collection: Collection) -> TuiApp { -// let mut collection_manager = MockCollectionManager::new(); + pub fn app(collection: Collection) -> MhUi { + let mut collection_manager = MockCollectionManager::new(); -// collection_manager -// .expect_rescan_library() -// .returning(|| Ok(())); -// collection_manager -// .expect_get_collection() -// .return_const(collection); + collection_manager + .expect_rescan_library() + .returning(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(collection); -// TuiApp::new(collection_manager).unwrap() -// } + MhUi::new(collection_manager).unwrap() + } -// fn listener() -> MockEventListener { -// let mut listener = MockEventListener::new(); -// listener.expect_spawn().return_once(|| { -// thread::spawn(|| { -// thread::park(); -// return EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")); -// }) -// }); -// listener -// } + fn listener() -> MockEventListener { + let mut listener = MockEventListener::new(); + listener.expect_spawn().return_once(|| { + thread::spawn(|| { + thread::park(); + return EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")); + }) + }); + listener + } -// fn handler() -> MockEventHandler> { -// let mut handler = MockEventHandler::new(); -// handler.expect_handle_next_event().return_once( -// |app: &mut TuiApp| { -// app.quit(); -// Ok(()) -// }, -// ); -// handler -// } + fn handler() -> MockEventHandler> { + let mut handler = MockEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|app: &mut MhUi| { + app.quit(); + Ok(()) + }); + handler + } -// #[test] -// fn run() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui::new(); + #[test] + fn run() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); -// let listener = listener(); -// let handler = handler(); + let listener = listener(); + let handler = handler(); -// let result = Tui::main(terminal, app, ui, handler, listener); -// assert!(result.is_ok()); -// } + let result = Tui::main(terminal, app, handler, listener); + assert!(result.is_ok()); + } -// #[test] -// fn event_error() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui::new(); + #[test] + fn event_error() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); -// let listener = listener(); + let listener = listener(); -// let mut handler = MockEventHandler::new(); -// handler -// .expect_handle_next_event() -// .return_once(|_| Err(EventError::Recv)); + let mut handler = MockEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let result = Tui::main(terminal, app, ui, handler, listener); -// assert!(result.is_err()); -// assert_eq!( -// result.unwrap_err(), -// Error::Event(EventError::Recv.to_string()) -// ); -// } + let result = Tui::main(terminal, app, handler, listener); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + Error::Event(EventError::Recv.to_string()) + ); + } -// #[test] -// fn listener_error() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui::new(); + #[test] + fn listener_error() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); -// let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); -// let listener_handle: thread::JoinHandle = thread::spawn(|| error); -// while !listener_handle.is_finished() {} + let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); + let listener_handle: thread::JoinHandle = thread::spawn(|| error); + while !listener_handle.is_finished() {} -// let mut listener = MockEventListener::new(); -// listener.expect_spawn().return_once(|| listener_handle); + let mut listener = MockEventListener::new(); + listener.expect_spawn().return_once(|| listener_handle); -// let mut handler = MockEventHandler::new(); -// handler -// .expect_handle_next_event() -// .return_once(|_| Err(EventError::Recv)); + let mut handler = MockEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let result = Tui::main(terminal, app, ui, handler, listener); -// assert!(result.is_err()); + let result = Tui::main(terminal, app, handler, listener); + assert!(result.is_err()); -// let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); -// assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); -// } + let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); + assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); + } -// #[test] -// fn listener_panic() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui::new(); + #[test] + fn listener_panic() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); -// let listener_handle: thread::JoinHandle = thread::spawn(|| panic!()); -// while !listener_handle.is_finished() {} + let listener_handle: thread::JoinHandle = thread::spawn(|| panic!()); + while !listener_handle.is_finished() {} -// let mut listener = MockEventListener::new(); -// listener.expect_spawn().return_once(|| listener_handle); + let mut listener = MockEventListener::new(); + listener.expect_spawn().return_once(|| listener_handle); -// let mut handler = MockEventHandler::new(); -// handler -// .expect_handle_next_event() -// .return_once(|_| Err(EventError::Recv)); + let mut handler = MockEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let result = Tui::main(terminal, app, ui, handler, listener); -// assert!(result.is_err()); -// assert_eq!(result.unwrap_err(), Error::ListenerPanic); -// } + let result = Tui::main(terminal, app, handler, listener); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), Error::ListenerPanic); + } -// #[test] -// fn errors() { -// let collection_err: Error = collection::Error::DatabaseError(String::from("")).into(); -// let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); -// let event_err: Error = EventError::Recv.into(); -// let listener_err = Error::ListenerPanic; + #[test] + fn errors() { + let collection_err: Error = collection::Error::DatabaseError(String::from("")).into(); + let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); + let event_err: Error = EventError::Recv.into(); + let listener_err = Error::ListenerPanic; -// assert!(!format!("{:?}", collection_err).is_empty()); -// assert!(!format!("{:?}", io_err).is_empty()); -// assert!(!format!("{:?}", event_err).is_empty()); -// assert!(!format!("{:?}", listener_err).is_empty()); -// } -// } + assert!(!format!("{:?}", collection_err).is_empty()); + assert!(!format!("{:?}", io_err).is_empty()); + assert!(!format!("{:?}", event_err).is_empty()); + assert!(!format!("{:?}", listener_err).is_empty()); + } +} diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 1b0ecce..6c1f50e 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -598,42 +598,456 @@ impl Ui for MhUi { } } -// #[cfg(test)] -// mod tests { -// // This is UI so the only sensible unit test is to run the code through various app states. +#[cfg(test)] +mod tests { + use crate::tests::{MockCollectionManager, COLLECTION}; + use crate::tui::tests::{app, terminal}; -// use crate::{ -// tests::COLLECTION, -// tui::{ -// app::App, -// tests::{app, terminal}, -// }, -// }; + use super::*; -// use super::Ui; + #[test] + fn test_track_selection() { + let tracks = &COLLECTION[0].albums[0].tracks; + assert!(tracks.len() > 1); -// #[test] -// fn empty() { -// let mut terminal = terminal(); -// let app = app(vec![]); -// let ui = Ui::new(); + let empty = TrackSelection::initialise(None); + assert_eq!(empty.state.selected(), None); -// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); -// } + let empty = TrackSelection::initialise(Some(&vec![])); + assert_eq!(empty.state.selected(), None); -// #[test] -// fn collection() { -// let mut terminal = terminal(); -// let mut app = app(COLLECTION.to_owned()); -// let ui = Ui::new(); + let mut sel = TrackSelection::initialise(Some(tracks)); + assert_eq!(sel.state.selected(), Some(0)); -// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); + sel.decrement(tracks); + assert_eq!(sel.state.selected(), Some(0)); -// // Change the track (which has a different track format). -// app.increment_category(); -// app.increment_category(); -// app.increment_selection(); + sel.increment(tracks); + assert_eq!(sel.state.selected(), Some(1)); -// terminal.draw(|frame| ui.render(&app, frame)).unwrap(); -// } -// } + sel.decrement(tracks); + assert_eq!(sel.state.selected(), Some(0)); + + for _ in 0..(tracks.len() + 5) { + sel.increment(tracks); + } + assert_eq!(sel.state.selected(), Some(tracks.len() - 1)); + + // Artifical test case to verify upper limit. + sel.state.select(Some(std::usize::MAX)); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + + sel.increment(&vec![]); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + } + + #[test] + fn test_album_selection() { + let albums = &COLLECTION[0].albums; + assert!(albums.len() > 1); + + let empty = AlbumSelection::initialise(None); + assert_eq!(empty.state.selected(), None); + + let empty = AlbumSelection::initialise(Some(&vec![])); + assert_eq!(empty.state.selected(), None); + + let mut sel = AlbumSelection::initialise(Some(albums)); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.track.state.selected(), Some(0)); + + sel.increment_track(albums); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.track.state.selected(), Some(1)); + + // Verify that decrement that doesn't change index does not reset track. + sel.decrement(albums); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.track.state.selected(), Some(1)); + + sel.increment(albums); + assert_eq!(sel.state.selected(), Some(1)); + assert_eq!(sel.track.state.selected(), Some(0)); + + sel.decrement(albums); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.track.state.selected(), Some(0)); + + for _ in 0..(albums.len() + 5) { + sel.increment(albums); + } + assert_eq!(sel.state.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.selected(), Some(0)); + + sel.increment_track(albums); + assert_eq!(sel.state.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.selected(), Some(1)); + + // Verify that increment that doesn't change index does not reset track. + sel.increment(albums); + assert_eq!(sel.state.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.selected(), Some(1)); + + // Artifical test case to verify upper limit. + sel.state.select(Some(std::usize::MAX)); + sel.track.state.select(Some(1)); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + assert_eq!(sel.track.state.selected(), Some(1)); + + sel.increment(&vec![]); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + assert_eq!(sel.track.state.selected(), Some(1)); + } + + #[test] + fn test_artist_selection() { + let artists = &COLLECTION; + assert!(artists.len() > 1); + + let empty = ArtistSelection::initialise(None); + assert_eq!(empty.state.selected(), None); + + let empty = ArtistSelection::initialise(Some(&vec![])); + assert_eq!(empty.state.selected(), None); + + let mut sel = ArtistSelection::initialise(Some(artists)); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.album.state.selected(), Some(0)); + + sel.increment_album(artists); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.album.state.selected(), Some(1)); + + // Verify that decrement that doesn't change index does not reset album. + sel.decrement(artists); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.album.state.selected(), Some(1)); + + sel.increment(artists); + assert_eq!(sel.state.selected(), Some(1)); + assert_eq!(sel.album.state.selected(), Some(0)); + + sel.decrement(artists); + assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.album.state.selected(), Some(0)); + + for _ in 0..(artists.len() + 5) { + sel.increment(artists); + } + assert_eq!(sel.state.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.selected(), Some(0)); + + sel.increment_album(artists); + assert_eq!(sel.state.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.selected(), Some(1)); + + // Verify that increment that doesn't change index does not reset album. + sel.increment(artists); + assert_eq!(sel.state.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.selected(), Some(1)); + + // Artifical test case to verify upper limit. + sel.state.select(Some(std::usize::MAX)); + sel.album.state.select(Some(1)); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + assert_eq!(sel.album.state.selected(), Some(1)); + + sel.increment(&vec![]); + assert_eq!(sel.state.selected(), Some(std::usize::MAX)); + assert_eq!(sel.album.state.selected(), Some(1)); + } + + #[test] + fn app_running() { + let mut collection_manager = MockCollectionManager::new(); + + collection_manager + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(COLLECTION.to_owned()); + + let mut app = MhUi::new(collection_manager).unwrap(); + assert!(app.is_running()); + + app.quit(); + assert!(!app.is_running()); + } + + #[test] + fn app_modifiers() { + let mut collection_manager = MockCollectionManager::new(); + + collection_manager + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(COLLECTION.to_owned()); + + let mut app = MhUi::new(collection_manager).unwrap(); + assert!(app.is_running()); + + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_category(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_category(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + + app.increment_category(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_selection(); + app.decrement_category(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_selection(); + app.decrement_category(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + + app.increment_category(); + app.increment_selection(); + app.decrement_category(); + app.decrement_selection(); + app.decrement_category(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(1)); + assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + } + + #[test] + fn app_no_tracks() { + let mut collection_manager = MockCollectionManager::new(); + let mut collection = COLLECTION.to_owned(); + collection[0].albums[0].tracks = vec![]; + + collection_manager + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(collection); + + let mut app = MhUi::new(collection_manager).unwrap(); + assert!(app.is_running()); + + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_category(); + app.increment_category(); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + } + + #[test] + fn app_no_albums() { + let mut collection_manager = MockCollectionManager::new(); + let mut collection = COLLECTION.to_owned(); + collection[0].albums = vec![]; + + collection_manager + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(collection); + + let mut app = MhUi::new(collection_manager).unwrap(); + assert!(app.is_running()); + + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_category(); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_category(); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + } + + #[test] + fn app_no_artists() { + let mut collection_manager = MockCollectionManager::new(); + let collection = vec![]; + + collection_manager + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + collection_manager + .expect_get_collection() + .return_const(collection); + + let mut app = MhUi::new(collection_manager).unwrap(); + assert!(app.is_running()); + + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Artist); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_category(); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Album); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.increment_category(); + + app.increment_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + + app.decrement_selection(); + assert_eq!(app.selection.active, Category::Track); + assert_eq!(app.selection.artist.state.selected(), None); + assert_eq!(app.selection.artist.album.state.selected(), None); + assert_eq!(app.selection.artist.album.track.state.selected(), None); + } + + // This is UI so the only sensible unit test is to run the code through various app states. + + #[test] + fn empty() { + let mut terminal = terminal(); + let mut app = app(vec![]); + + terminal.draw(|frame| app.render(frame)).unwrap(); + } + + #[test] + fn collection() { + let mut terminal = terminal(); + let mut app = app(COLLECTION.to_owned()); + + terminal.draw(|frame| app.render(frame)).unwrap(); + + // Change the track (which has a different track format). + app.increment_category(); + app.increment_category(); + app.increment_selection(); + + terminal.draw(|frame| app.render(frame)).unwrap(); + } +} -- 2.45.2 From 6d22e430ca47ebc9e8bcdedbbe7d64159bd36af9 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 27 Apr 2023 18:47:42 +0200 Subject: [PATCH 5/7] lint fix --- src/database/json/mod.rs | 3 ++- src/library/beets/mod.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/database/json/mod.rs b/src/database/json/mod.rs index 5caeefa..0398292 100644 --- a/src/database/json/mod.rs +++ b/src/database/json/mod.rs @@ -32,7 +32,8 @@ pub struct JsonDatabase { } impl JsonDatabase { - /// Create a new JSON database with the provided backend, e.g. [`JsonDatabaseFileBackend`]. + /// Create a new JSON database with the provided backend, e.g. + /// [`backend::JsonDatabaseFileBackend`]. pub fn new(backend: JDB) -> Self { JsonDatabase { backend } } diff --git a/src/library/beets/mod.rs b/src/library/beets/mod.rs index 7ce5214..d066ea0 100644 --- a/src/library/beets/mod.rs +++ b/src/library/beets/mod.rs @@ -94,7 +94,8 @@ trait LibraryPrivate { } impl BeetsLibrary { - /// Create a new beets library with the provided executor, e.g. [`BeetsLibraryProcessExecutor`]. + /// Create a new beets library with the provided executor, e.g. + /// [`executor::BeetsLibraryProcessExecutor`]. pub fn new(executor: BLE) -> Self { BeetsLibrary { executor } } -- 2.45.2 From 1be636b648d824157054d07f3f5a54e742f27914 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 27 Apr 2023 18:51:23 +0200 Subject: [PATCH 6/7] Rename app to ui --- src/tui/mod.rs | 22 +++---- src/tui/ui.rs | 174 ++++++++++++++++++++++++------------------------- 2 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/tui/mod.rs b/src/tui/mod.rs index f623a81..50478b6 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -178,7 +178,7 @@ mod tests { Terminal::new(backend).unwrap() } - pub fn app(collection: Collection) -> MhUi { + pub fn ui(collection: Collection) -> MhUi { let mut collection_manager = MockCollectionManager::new(); collection_manager @@ -206,8 +206,8 @@ mod tests { let mut handler = MockEventHandler::new(); handler .expect_handle_next_event() - .return_once(|app: &mut MhUi| { - app.quit(); + .return_once(|ui: &mut MhUi| { + ui.quit(); Ok(()) }); handler @@ -216,19 +216,19 @@ mod tests { #[test] fn run() { let terminal = terminal(); - let app = app(COLLECTION.to_owned()); + let ui = ui(COLLECTION.to_owned()); let listener = listener(); let handler = handler(); - let result = Tui::main(terminal, app, handler, listener); + let result = Tui::main(terminal, ui, handler, listener); assert!(result.is_ok()); } #[test] fn event_error() { let terminal = terminal(); - let app = app(COLLECTION.to_owned()); + let ui = ui(COLLECTION.to_owned()); let listener = listener(); @@ -237,7 +237,7 @@ mod tests { .expect_handle_next_event() .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, handler, listener); + let result = Tui::main(terminal, ui, handler, listener); assert!(result.is_err()); assert_eq!( result.unwrap_err(), @@ -248,7 +248,7 @@ mod tests { #[test] fn listener_error() { let terminal = terminal(); - let app = app(COLLECTION.to_owned()); + let ui = ui(COLLECTION.to_owned()); let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); let listener_handle: thread::JoinHandle = thread::spawn(|| error); @@ -262,7 +262,7 @@ mod tests { .expect_handle_next_event() .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, handler, listener); + let result = Tui::main(terminal, ui, handler, listener); assert!(result.is_err()); let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); @@ -272,7 +272,7 @@ mod tests { #[test] fn listener_panic() { let terminal = terminal(); - let app = app(COLLECTION.to_owned()); + let ui = ui(COLLECTION.to_owned()); let listener_handle: thread::JoinHandle = thread::spawn(|| panic!()); while !listener_handle.is_finished() {} @@ -285,7 +285,7 @@ mod tests { .expect_handle_next_event() .return_once(|_| Err(EventError::Recv)); - let result = Tui::main(terminal, app, handler, listener); + let result = Tui::main(terminal, ui, handler, listener); assert!(result.is_err()); assert_eq!(result.unwrap_err(), Error::ListenerPanic); } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 6c1f50e..59e15d5 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -601,7 +601,7 @@ impl Ui for MhUi { #[cfg(test)] mod tests { use crate::tests::{MockCollectionManager, COLLECTION}; - use crate::tui::tests::{app, terminal}; + use crate::tui::tests::{terminal, ui}; use super::*; @@ -758,7 +758,7 @@ mod tests { } #[test] - fn app_running() { + fn ui_running() { let mut collection_manager = MockCollectionManager::new(); collection_manager @@ -769,15 +769,15 @@ mod tests { .expect_get_collection() .return_const(COLLECTION.to_owned()); - let mut app = MhUi::new(collection_manager).unwrap(); - assert!(app.is_running()); + let mut ui = MhUi::new(collection_manager).unwrap(); + assert!(ui.is_running()); - app.quit(); - assert!(!app.is_running()); + ui.quit(); + assert!(!ui.is_running()); } #[test] - fn app_modifiers() { + fn ui_modifiers() { let mut collection_manager = MockCollectionManager::new(); collection_manager @@ -788,91 +788,91 @@ mod tests { .expect_get_collection() .return_const(COLLECTION.to_owned()); - let mut app = MhUi::new(collection_manager).unwrap(); - assert!(app.is_running()); + let mut ui = MhUi::new(collection_manager).unwrap(); + assert!(ui.is_running()); - assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(ui.selection.active, Category::Artist); + assert_eq!(ui.selection.artist.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_selection(); - assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_selection(); + assert_eq!(ui.selection.active, Category::Artist); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_category(); - assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_category(); + assert_eq!(ui.selection.active, Category::Album); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_selection(); - assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_selection(); + assert_eq!(ui.selection.active, Category::Album); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_category(); - assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_category(); + assert_eq!(ui.selection.active, Category::Track); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_selection(); - assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + ui.increment_selection(); + assert_eq!(ui.selection.active, Category::Track); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1)); - app.increment_category(); - assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + ui.increment_category(); + assert_eq!(ui.selection.active, Category::Track); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1)); - app.decrement_selection(); - assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.decrement_selection(); + assert_eq!(ui.selection.active, Category::Track); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_selection(); - app.decrement_category(); - assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + ui.increment_selection(); + ui.decrement_category(); + assert_eq!(ui.selection.active, Category::Album); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1)); - app.decrement_selection(); - assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.decrement_selection(); + assert_eq!(ui.selection.active, Category::Album); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_selection(); - app.decrement_category(); - assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_selection(); + ui.decrement_category(); + assert_eq!(ui.selection.active, Category::Artist); + assert_eq!(ui.selection.artist.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.decrement_selection(); - assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.decrement_selection(); + assert_eq!(ui.selection.active, Category::Artist); + assert_eq!(ui.selection.artist.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); - app.increment_category(); - app.increment_selection(); - app.decrement_category(); - app.decrement_selection(); - app.decrement_category(); - assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + ui.increment_category(); + ui.increment_selection(); + ui.decrement_category(); + ui.decrement_selection(); + ui.decrement_category(); + assert_eq!(ui.selection.active, Category::Artist); + assert_eq!(ui.selection.artist.state.selected(), Some(0)); + assert_eq!(ui.selection.artist.album.state.selected(), Some(1)); + assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0)); } #[test] @@ -1031,23 +1031,23 @@ mod tests { #[test] fn empty() { let mut terminal = terminal(); - let mut app = app(vec![]); + let mut ui = ui(vec![]); - terminal.draw(|frame| app.render(frame)).unwrap(); + terminal.draw(|frame| ui.render(frame)).unwrap(); } #[test] fn collection() { let mut terminal = terminal(); - let mut app = app(COLLECTION.to_owned()); + let mut ui = ui(COLLECTION.to_owned()); - terminal.draw(|frame| app.render(frame)).unwrap(); + terminal.draw(|frame| ui.render(frame)).unwrap(); // Change the track (which has a different track format). - app.increment_category(); - app.increment_category(); - app.increment_selection(); + ui.increment_category(); + ui.increment_category(); + ui.increment_selection(); - terminal.draw(|frame| app.render(frame)).unwrap(); + terminal.draw(|frame| ui.render(frame)).unwrap(); } } -- 2.45.2 From 4c57df5b9ac1dbe48eae87d0d1f40920ee947744 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 27 Apr 2023 18:52:21 +0200 Subject: [PATCH 7/7] Formatting --- src/tui/ui.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 59e15d5..cb13a98 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -401,9 +401,9 @@ impl<'a, 'b> TrackState<'a, 'b> { let track = state.selected().map(|i| &tracks[i]); let info = Paragraph::new(format!( "Track: {}\n\ - Title: {}\n\ - Artist: {}\n\ - Format: {}", + Title: {}\n\ + Artist: {}\n\ + Format: {}", track .map(|t| t.number.to_string()) .unwrap_or_else(|| "".to_string()), -- 2.45.2