diff --git a/src/tui/app/machine/browse_state.rs b/src/tui/app/machine/browse_state.rs index 14fbb4e..7f26314 100644 --- a/src/tui/app/machine/browse_state.rs +++ b/src/tui/app/machine/browse_state.rs @@ -77,105 +77,112 @@ impl IAppInteractBrowse for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use crate::tui::{ -// app::{ -// machine::tests::{inner, inner_with_mb, music_hoard}, -// Category, IApp, IAppAccess, -// }, -// lib::interface::musicbrainz::api::MockIMusicBrainz, -// testmod::COLLECTION, -// }; +#[cfg(test)] +mod tests { + use crate::tui::{ + app::{ + machine::tests::{inner, inner_with_mb, music_hoard}, + Category, IApp, IAppAccess, + }, + lib::interface::musicbrainz::daemon::MockIMbJobSender, + testmod::COLLECTION, + }; -// use super::*; + use super::*; -// #[test] -// fn quit() { -// let music_hoard = music_hoard(vec![]); + #[test] + fn quit() { + let music_hoard = music_hoard(vec![]); -// let browse = AppMachine::browse_state(inner(music_hoard)); + let browse = AppMachine::browse_state(inner(music_hoard)); -// let app = browse.quit(); -// assert!(!app.is_running()); -// app.unwrap_browse(); -// } + let app = browse.quit(); + assert!(!app.is_running()); + app.unwrap_browse(); + } -// #[test] -// fn increment_decrement() { -// let mut browse = AppMachine::browse_state(inner(music_hoard(COLLECTION.to_owned()))); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Artist); -// assert_eq!(sel.selected(), Some(0)); + #[test] + fn increment_decrement() { + let mut browse = AppMachine::browse_state(inner(music_hoard(COLLECTION.to_owned()))); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Artist); + assert_eq!(sel.selected(), Some(0)); -// browse = browse.increment_selection(Delta::Line).unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Artist); -// assert_eq!(sel.selected(), Some(1)); + browse = browse.increment_selection(Delta::Line).unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Artist); + assert_eq!(sel.selected(), Some(1)); -// browse = browse.increment_category().unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Album); -// assert_eq!(sel.selected(), Some(0)); + browse = browse.increment_category().unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Album); + assert_eq!(sel.selected(), Some(0)); -// browse = browse.increment_selection(Delta::Line).unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Album); -// assert_eq!(sel.selected(), Some(1)); + browse = browse.increment_selection(Delta::Line).unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Album); + assert_eq!(sel.selected(), Some(1)); -// browse = browse.decrement_selection(Delta::Line).unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Album); -// assert_eq!(sel.selected(), Some(0)); + browse = browse.decrement_selection(Delta::Line).unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Album); + assert_eq!(sel.selected(), Some(0)); -// browse = browse.decrement_category().unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Artist); -// assert_eq!(sel.selected(), Some(1)); + browse = browse.decrement_category().unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Artist); + assert_eq!(sel.selected(), Some(1)); -// browse = browse.decrement_selection(Delta::Line).unwrap_browse(); -// let sel = &browse.inner.selection; -// assert_eq!(sel.category(), Category::Artist); -// assert_eq!(sel.selected(), Some(0)); -// } + browse = browse.decrement_selection(Delta::Line).unwrap_browse(); + let sel = &browse.inner.selection; + assert_eq!(sel.category(), Category::Artist); + assert_eq!(sel.selected(), Some(0)); + } -// #[test] -// fn show_info_overlay() { -// let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); -// let app = browse.show_info_overlay(); -// app.unwrap_info(); -// } + #[test] + fn show_info_overlay() { + let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); + let app = browse.show_info_overlay(); + app.unwrap_info(); + } -// #[test] -// fn show_reload_menu() { -// let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); -// let app = browse.show_reload_menu(); -// app.unwrap_reload(); -// } + #[test] + fn show_reload_menu() { + let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); + let app = browse.show_reload_menu(); + app.unwrap_reload(); + } -// #[test] -// fn begin_search() { -// let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); -// let app = browse.begin_search(); -// app.unwrap_search(); -// } + #[test] + fn begin_search() { + let browse = AppMachine::browse_state(inner(music_hoard(vec![]))); + let app = browse.begin_search(); + app.unwrap_search(); + } -// #[test] -// fn fetch_musicbrainz() { -// let mb_api = MockIMusicBrainz::new(); -// let browse = -// AppMachine::browse_state(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api)); + #[test] + fn fetch_musicbrainz() { + let mut mb_job_sender = MockIMbJobSender::new(); + mb_job_sender + .expect_submit_background_job() + .times(1) + .returning(|_, _| Ok(())); -// // Use the second artist for this test. -// let browse = browse.increment_selection(Delta::Line).unwrap_browse(); -// let mut app = browse.fetch_musicbrainz(); + let browse = AppMachine::browse_state(inner_with_mb( + music_hoard(COLLECTION.to_owned()), + mb_job_sender, + )); -// let public = app.get(); + // Use the second artist for this test. + let browse = browse.increment_selection(Delta::Line).unwrap_browse(); + let mut app = browse.fetch_musicbrainz(); -// // Because of fetch's threaded behaviour, this unit test cannot expect one or the other. -// assert!( -// matches!(public.state, AppState::Match(_)) -// || matches!(public.state, AppState::Fetch(_)) -// ); -// } -// } + let public = app.get(); + + // Because of fetch's threaded behaviour, this unit test cannot expect one or the other. + assert!( + matches!(public.state, AppState::Match(_)) + || matches!(public.state, AppState::Fetch(_)) + ); + } +} diff --git a/src/tui/app/machine/error_state.rs b/src/tui/app/machine/error_state.rs index 252e6c5..0ff6561 100644 --- a/src/tui/app/machine/error_state.rs +++ b/src/tui/app/machine/error_state.rs @@ -41,16 +41,16 @@ impl IAppInteractError for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use crate::tui::app::machine::tests::{inner, music_hoard}; +#[cfg(test)] +mod tests { + use crate::tui::app::machine::tests::{inner, music_hoard}; -// use super::*; + use super::*; -// #[test] -// fn dismiss_error() { -// let error = AppMachine::error_state(inner(music_hoard(vec![])), "get rekt"); -// let app = error.dismiss_error(); -// app.unwrap_browse(); -// } -// } + #[test] + fn dismiss_error() { + let error = AppMachine::error_state(inner(music_hoard(vec![])), "get rekt"); + let app = error.dismiss_error(); + app.unwrap_browse(); + } +} diff --git a/src/tui/app/machine/fetch_state.rs b/src/tui/app/machine/fetch_state.rs index bc9893d..73e5964 100644 --- a/src/tui/app/machine/fetch_state.rs +++ b/src/tui/app/machine/fetch_state.rs @@ -87,6 +87,7 @@ impl AppMachine { let arid = arid.mbid(); let albums = artist.albums.iter(); albums + .filter(|album| album.meta.musicbrainz.is_none()) .map(|album| MbParams::search_release_group(arid.clone(), album.meta.clone())) .collect() } @@ -124,299 +125,205 @@ impl IAppEventFetch for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use mockall::{predicate, Sequence}; -// use musichoard::collection::artist::ArtistMeta; +#[cfg(test)] +mod tests { + use mockall::predicate; + use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::Mbid}; -// use crate::tui::{ -// app::{ -// machine::tests::{inner, music_hoard}, -// AlbumMatches, ArtistMatches, IApp, -// }, -// event::EventReceiver, -// lib::interface::musicbrainz::{ -// self, -// api::{Match, MockIMusicBrainz}, -// }, -// testmod::COLLECTION, -// EventChannel, -// }; + use crate::tui::{ + app::{ + machine::tests::{inner, music_hoard}, + Delta, IApp, IAppAccess, IAppInteractBrowse, MatchOption, MatchStateInfo, + }, + lib::interface::musicbrainz::{self, api::Match, daemon::MockIMbJobSender}, + testmod::COLLECTION, + }; -// use super::*; + use super::*; -// #[test] -// fn fetch_no_artist() { -// let app = AppMachine::app_fetch_new(inner(music_hoard(vec![]))); -// assert!(matches!(app.state(), AppState::Error(_))); -// } + #[test] + fn fetch_no_artist() { + let app = AppMachine::app_fetch_new(inner(music_hoard(vec![]))); + assert!(matches!(app.state(), AppState::Error(_))); + } -// fn event_channel() -> (EventSender, EventReceiver) { -// let event_channel = EventChannel::new(); -// let events_tx = event_channel.sender(); -// let events_rx = event_channel.receiver(); -// (events_tx, events_rx) -// } + fn search_release_group_expectation( + job_sender: &mut MockIMbJobSender, + arid: &Mbid, + albums: &[AlbumMeta], + ) { + let mut requests = VecDeque::new(); + for album in albums.iter() { + requests.push_back(MbParams::search_release_group(arid.clone(), album.clone())); + } + job_sender + .expect_submit_background_job() + .with(predicate::always(), predicate::eq(requests)) + .times(1) + .return_once(|_, _| Ok(())); + } -// fn album_expectations_1() -> (AlbumMeta, Vec>) { -// let album_1 = COLLECTION[1].albums[0].meta.clone(); -// let album_4 = COLLECTION[1].albums[3].meta.clone(); + #[test] + fn fetch_albums() { + let mut mb_job_sender = MockIMbJobSender::new(); -// let album_match_1_1 = Match::new(100, album_1.clone()); -// let album_match_1_2 = Match::new(50, album_4.clone()); -// let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()]; + let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); -// (album_1, matches_1) -// } + let album_1_meta = COLLECTION[1].albums[0].meta.clone(); + let album_4_meta = COLLECTION[1].albums[3].meta.clone(); -// fn album_expectations_4() -> (AlbumMeta, Vec>) { -// let album_1 = COLLECTION[1].albums[0].meta.clone(); -// let album_4 = COLLECTION[1].albums[3].meta.clone(); + // Other albums have an MBID and so they will be skipped. + search_release_group_expectation(&mut mb_job_sender, &arid, &[album_1_meta, album_4_meta]); -// let album_match_4_1 = Match::new(100, album_4.clone()); -// let album_match_4_2 = Match::new(30, album_1.clone()); -// let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()]; + let music_hoard = music_hoard(COLLECTION.to_owned()); + let inner = AppInner::new(music_hoard, mb_job_sender); -// (album_4, matches_4) -// } + // Use second artist to match the expectation. + let browse = AppMachine::browse_state(inner); + let app = browse.increment_selection(Delta::Line); -// fn search_release_group_expectation( -// api: &mut MockIMusicBrainz, -// seq: &mut Sequence, -// arid: &Mbid, -// album: &AlbumMeta, -// matches: &[Match], -// ) { -// let result = Ok(matches.to_owned()); -// api.expect_search_release_group() -// .with(predicate::eq(arid.clone()), predicate::eq(album.clone())) -// .times(1) -// .in_sequence(seq) -// .return_once(|_, _| result); -// } + let app = app.unwrap_browse().fetch_musicbrainz(); + assert!(matches!(app, AppState::Match(_))); + } -// #[test] -// fn fetch_albums() { -// let mut mb_api = MockIMusicBrainz::new(); + fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) { + let requests = VecDeque::from([MbParams::search_artist(artist.clone())]); + job_sender + .expect_submit_background_job() + .with(predicate::always(), predicate::eq(requests)) + .times(1) + .return_once(|_, _| Ok(())); + } -// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + #[test] + fn fetch_artist() { + let mut mb_job_sender = MockIMbJobSender::new(); -// let (album_1, matches_1) = album_expectations_1(); -// let (album_4, matches_4) = album_expectations_4(); + let artist = COLLECTION[3].meta.clone(); + search_artist_expectation(&mut mb_job_sender, &artist); -// // Other albums have an MBID and so they will be skipped. -// let mut seq = Sequence::new(); + let music_hoard = music_hoard(COLLECTION.to_owned()); + let inner = AppInner::new(music_hoard, mb_job_sender); -// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); -// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4); + // Use fourth artist to match the expectation. + let browse = AppMachine::browse_state(inner); + let browse = browse.increment_selection(Delta::Line).unwrap_browse(); + let browse = browse.increment_selection(Delta::Line).unwrap_browse(); + let app = browse.increment_selection(Delta::Line); -// let music_hoard = music_hoard(COLLECTION.to_owned()); -// let (events_tx, events_rx) = event_channel(); -// let inner = AppInner::new(music_hoard, mb_api, events_tx); + let app = app.unwrap_browse().fetch_musicbrainz(); + assert!(matches!(app, AppState::Match(_))); + } -// let (fetch_tx, fetch_rx) = mpsc::channel(); -// // Use the second artist for this test. -// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); -// handle.join().unwrap(); + #[test] + fn fetch_artist_job_sender_err() { + let mut mb_job_sender = MockIMbJobSender::new(); -// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); -// let result = fetch_rx.try_recv().unwrap(); -// let expected = Ok(MatchStateInfo::Album(AlbumMatches { -// matching: album_1.clone(), -// list: matches_1.iter().cloned().map(Into::into).collect(), -// })); -// assert_eq!(result, expected); + mb_job_sender + .expect_submit_background_job() + .return_once(|_, _| Err(DaemonError::JobChannelDisconnected)); -// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); -// let result = fetch_rx.try_recv().unwrap(); -// let expected = Ok(MatchStateInfo::Album(AlbumMatches { -// matching: album_4.clone(), -// list: matches_4.iter().cloned().map(Into::into).collect(), -// })); -// assert_eq!(result, expected); -// } + let music_hoard = music_hoard(COLLECTION.to_owned()); + let inner = AppInner::new(music_hoard, mb_job_sender); + let browse = AppMachine::browse_state(inner); -// fn artist_expectations() -> (ArtistMeta, Vec>) { -// let artist = COLLECTION[3].meta.clone(); + let app = browse.fetch_musicbrainz(); + assert!(matches!(app, AppState::Error(_))); + } -// let artist_match_1 = Match::new(100, artist.clone()); -// let artist_match_2 = Match::new(50, artist.clone()); -// let matches = vec![artist_match_1.clone(), artist_match_2.clone()]; + #[test] + fn recv_ok_fetch_ok() { + let (tx, rx) = mpsc::channel::(); -// (artist, matches) -// } + let artist = COLLECTION[3].meta.clone(); + let artist_match = Match::new(80, COLLECTION[2].meta.clone()); + let artist_match_info = MatchStateInfo::artist(artist.clone(), vec![artist_match.clone()]); + let fetch_result = Ok(artist_match_info); + tx.send(fetch_result).unwrap(); -// fn search_artist_expectation( -// api: &mut MockIMusicBrainz, -// seq: &mut Sequence, -// artist: &ArtistMeta, -// matches: &[Match], -// ) { -// let result = Ok(matches.to_owned()); -// api.expect_search_artist() -// .with(predicate::eq(artist.clone())) -// .times(1) -// .in_sequence(seq) -// .return_once(|_| result); -// } + let inner = inner(music_hoard(COLLECTION.clone())); + let fetch = FetchState::new(rx); + let mut app = AppMachine::app_fetch(inner, fetch, true); + assert!(matches!(app, AppState::Match(_))); -// #[test] -// fn fetch_artist() { -// let mut mb_api = MockIMusicBrainz::new(); + let public = app.get(); + let match_state = public.state.unwrap_match(); + let match_options = vec![ + artist_match.into(), + MatchOption::CannotHaveMbid, + MatchOption::ManualInputMbid, + ]; + let expected = MatchStateInfo::artist(artist, match_options); + assert_eq!(match_state.info, Some(expected).as_ref()); + } -// let (artist, matches) = artist_expectations(); -// let mut seq = Sequence::new(); -// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + #[test] + fn recv_ok_fetch_err() { + let (tx, rx) = mpsc::channel::(); -// let music_hoard = music_hoard(COLLECTION.to_owned()); -// let (events_tx, events_rx) = event_channel(); -// let inner = AppInner::new(music_hoard, mb_api, events_tx); + let fetch_result = Err(musicbrainz::api::Error::RateLimit); + tx.send(fetch_result).unwrap(); -// let (fetch_tx, fetch_rx) = mpsc::channel(); -// // Use the fourth artist for this test as they have no MBID. -// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); -// handle.join().unwrap(); + let inner = inner(music_hoard(COLLECTION.clone())); + let fetch = FetchState::new(rx); + let app = AppMachine::app_fetch(inner, fetch, true); + assert!(matches!(app, AppState::Error(_))); + } -// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); -// let result = fetch_rx.try_recv().unwrap(); -// let expected = Ok(MatchStateInfo::Artist(ArtistMatches { -// matching: artist.clone(), -// list: matches.iter().cloned().map(Into::into).collect(), -// })); -// assert_eq!(result, expected); -// } + #[test] + fn recv_err_empty() { + let (_tx, rx) = mpsc::channel::(); -// #[test] -// fn fetch_artist_fetch_disconnect() { -// let mut mb_api = MockIMusicBrainz::new(); + let inner = inner(music_hoard(COLLECTION.clone())); + let fetch = FetchState::new(rx); + let app = AppMachine::app_fetch(inner, fetch, true); + assert!(matches!(app, AppState::Fetch(_))); + } -// let (artist, matches) = artist_expectations(); -// let mut seq = Sequence::new(); -// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + #[test] + fn recv_err_disconnected_first() { + let (_, rx) = mpsc::channel::(); -// let music_hoard = music_hoard(COLLECTION.to_owned()); -// let (events_tx, events_rx) = event_channel(); -// let inner = AppInner::new(music_hoard, mb_api, events_tx); + let inner = inner(music_hoard(COLLECTION.clone())); + let fetch = FetchState::new(rx); + let app = AppMachine::app_fetch(inner, fetch, true); + assert!(matches!(app, AppState::Match(_))); + } -// let (fetch_tx, _) = mpsc::channel(); -// // Use the fourth artist for this test as they have no MBID. -// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); -// handle.join().unwrap(); + #[test] + fn recv_err_disconnected_next() { + let (_, rx) = mpsc::channel::(); -// assert!(events_rx.try_recv().is_err()); -// } + let fetch = FetchState::new(rx); + let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch); + assert!(matches!(app, AppState::Browse(_))); + } -// #[test] -// fn fetch_albums_event_disconnect() { -// let mut mb_api = MockIMusicBrainz::new(); + #[test] + fn empty_first_then_ready() { + let (tx, rx) = mpsc::channel::(); -// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + let inner = inner(music_hoard(COLLECTION.clone())); + let fetch = FetchState::new(rx); + let app = AppMachine::app_fetch(inner, fetch, true); + assert!(matches!(app, AppState::Fetch(_))); -// let (album_1, matches_1) = album_expectations_1(); + let artist = COLLECTION[3].meta.clone(); + let fetch_result = Ok(MatchStateInfo::artist::>(artist, vec![])); + tx.send(fetch_result).unwrap(); -// let mut seq = Sequence::new(); -// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); + let app = app.unwrap_fetch().fetch_result_ready(); + assert!(matches!(app, AppState::Match(_))); + } -// let music_hoard = music_hoard(COLLECTION.to_owned()); -// let (events_tx, _) = event_channel(); -// let inner = AppInner::new(music_hoard, mb_api, events_tx); + #[test] + fn abort() { + let (_, rx) = mpsc::channel::(); -// let (fetch_tx, fetch_rx) = mpsc::channel(); -// // Use the second artist for this test. -// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); -// handle.join().unwrap(); + let fetch = FetchState::new(rx); + let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch); -// let result = fetch_rx.try_recv().unwrap(); -// let expected = Ok(MatchStateInfo::Album(AlbumMatches { -// matching: album_1.clone(), -// list: matches_1.iter().cloned().map(Into::into).collect(), -// })); -// assert_eq!(result, expected); - -// assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected); -// } - -// #[test] -// fn recv_ok_fetch_ok() { -// let (tx, rx) = mpsc::channel::(); - -// let artist = COLLECTION[3].meta.clone(); -// let fetch_result = Ok(MatchStateInfo::artist::>(artist, vec![])); -// tx.send(fetch_result).unwrap(); - -// let inner = inner(music_hoard(COLLECTION.clone())); -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch(inner, fetch, true); -// assert!(matches!(app, AppState::Match(_))); -// } - -// #[test] -// fn recv_ok_fetch_err() { -// let (tx, rx) = mpsc::channel::(); - -// let fetch_result = Err(musicbrainz::api::Error::RateLimit); -// tx.send(fetch_result).unwrap(); - -// let inner = inner(music_hoard(COLLECTION.clone())); -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch(inner, fetch, true); -// assert!(matches!(app, AppState::Error(_))); -// } - -// #[test] -// fn recv_err_empty() { -// let (_tx, rx) = mpsc::channel::(); - -// let inner = inner(music_hoard(COLLECTION.clone())); -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch(inner, fetch, true); -// assert!(matches!(app, AppState::Fetch(_))); -// } - -// #[test] -// fn recv_err_disconnected_first() { -// let (_, rx) = mpsc::channel::(); - -// let inner = inner(music_hoard(COLLECTION.clone())); -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch(inner, fetch, true); -// assert!(matches!(app, AppState::Match(_))); -// } - -// #[test] -// fn recv_err_disconnected_next() { -// let (_, rx) = mpsc::channel::(); - -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch); -// assert!(matches!(app, AppState::Browse(_))); -// } - -// #[test] -// fn empty_first_then_ready() { -// let (tx, rx) = mpsc::channel::(); - -// let inner = inner(music_hoard(COLLECTION.clone())); -// let fetch = FetchState::new(rx); -// let app = AppMachine::app_fetch(inner, fetch, true); -// assert!(matches!(app, AppState::Fetch(_))); - -// let artist = COLLECTION[3].meta.clone(); -// let fetch_result = Ok(MatchStateInfo::artist::>(artist, vec![])); -// tx.send(fetch_result).unwrap(); - -// let app = app.unwrap_fetch().fetch_result_ready(); -// assert!(matches!(app, AppState::Match(_))); -// } - -// #[test] -// fn abort() { -// let (_, rx) = mpsc::channel::(); - -// let fetch = FetchState::new(rx); -// let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch); - -// let app = app.abort(); -// assert!(matches!(app, AppState::Browse(_))); -// } -// } + let app = app.abort(); + assert!(matches!(app, AppState::Browse(_))); + } +} diff --git a/src/tui/app/machine/info_state.rs b/src/tui/app/machine/info_state.rs index f4c714d..9dd7ddc 100644 --- a/src/tui/app/machine/info_state.rs +++ b/src/tui/app/machine/info_state.rs @@ -31,16 +31,16 @@ impl IAppInteractInfo for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use crate::tui::app::machine::tests::{inner, music_hoard}; +#[cfg(test)] +mod tests { + use crate::tui::app::machine::tests::{inner, music_hoard}; -// use super::*; + use super::*; -// #[test] -// fn hide_info_overlay() { -// let info = AppMachine::info_state(inner(music_hoard(vec![]))); -// let app = info.hide_info_overlay(); -// app.unwrap_browse(); -// } -// } + #[test] + fn hide_info_overlay() { + let info = AppMachine::info_state(inner(music_hoard(vec![]))); + let app = info.hide_info_overlay(); + app.unwrap_browse(); + } +} diff --git a/src/tui/app/machine/input.rs b/src/tui/app/machine/input.rs index 052e21b..17f22a1 100644 --- a/src/tui/app/machine/input.rs +++ b/src/tui/app/machine/input.rs @@ -55,45 +55,45 @@ impl IAppInput for AppInputMode { } } -// #[cfg(test)] -// mod tests { -// use crate::tui::app::{ -// machine::tests::{events, mb_api, music_hoard_init}, -// IApp, -// }; +#[cfg(test)] +mod tests { + use crate::tui::app::{ + machine::tests::{mb_job_sender, music_hoard_init}, + IApp, + }; -// use super::*; + use super::*; -// fn input_event(c: char) -> InputEvent { -// crossterm::event::KeyEvent::new( -// crossterm::event::KeyCode::Char(c), -// crossterm::event::KeyModifiers::empty(), -// ) -// .into() -// } + fn input_event(c: char) -> InputEvent { + crossterm::event::KeyEvent::new( + crossterm::event::KeyCode::Char(c), + crossterm::event::KeyModifiers::empty(), + ) + .into() + } -// #[test] -// fn handle_input() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// app.input_mut().replace(Input::default()); + #[test] + fn handle_input() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + app.input_mut().replace(Input::default()); -// let input = app.mode().unwrap_input(); -// let app = input.input(input_event('H')); + let input = app.mode().unwrap_input(); + let app = input.input(input_event('H')); -// let input = app.mode().unwrap_input(); -// let app = input.input(input_event('e')); + let input = app.mode().unwrap_input(); + let app = input.input(input_event('e')); -// let input = app.mode().unwrap_input(); -// let app = input.input(input_event('l')); + let input = app.mode().unwrap_input(); + let app = input.input(input_event('l')); -// let input = app.mode().unwrap_input(); -// let app = input.input(input_event('l')); + let input = app.mode().unwrap_input(); + let app = input.input(input_event('l')); -// let input = app.mode().unwrap_input(); -// let app = input.input(input_event('o')); + let input = app.mode().unwrap_input(); + let app = input.input(input_event('o')); -// assert_eq!(app.input_ref().as_ref().unwrap().0.value(), "Hello"); + assert_eq!(app.input_ref().as_ref().unwrap().0.value(), "Hello"); -// app.mode().unwrap_input().confirm().unwrap_browse(); -// } -// } + app.mode().unwrap_input().confirm().unwrap_browse(); + } +} diff --git a/src/tui/app/machine/match_state.rs b/src/tui/app/machine/match_state.rs index 7feab72..362fa96 100644 --- a/src/tui/app/machine/match_state.rs +++ b/src/tui/app/machine/match_state.rs @@ -165,218 +165,218 @@ impl IAppInteractMatch for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use std::sync::mpsc; +#[cfg(test)] +mod tests { + use std::sync::mpsc; -// use musichoard::collection::{ -// album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, -// artist::{ArtistId, ArtistMeta}, -// }; + use musichoard::collection::{ + album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, + artist::{ArtistId, ArtistMeta}, + }; -// use crate::tui::{ -// app::{ -// machine::tests::{inner, music_hoard}, -// IApp, IAppAccess, IAppInput, -// }, -// lib::interface::musicbrainz::api::Match, -// }; + use crate::tui::{ + app::{ + machine::tests::{inner, music_hoard}, + IApp, IAppAccess, IAppInput, + }, + lib::interface::musicbrainz::api::Match, + }; -// use super::*; + use super::*; -// impl Match { -// pub fn new(score: u8, item: T) -> Self { -// Match { -// score, -// item, -// disambiguation: None, -// } -// } -// } + impl Match { + pub fn new(score: u8, item: T) -> Self { + Match { + score, + item, + disambiguation: None, + } + } + } -// fn artist_match() -> MatchStateInfo { -// let artist = ArtistMeta::new(ArtistId::new("Artist")); + fn artist_match() -> MatchStateInfo { + let artist = ArtistMeta::new(ArtistId::new("Artist")); -// let artist_1 = artist.clone(); -// let artist_match_1 = Match::new(100, artist_1); + let artist_1 = artist.clone(); + let artist_match_1 = Match::new(100, artist_1); -// let artist_2 = artist.clone(); -// let mut artist_match_2 = Match::new(100, artist_2); -// artist_match_2.disambiguation = Some(String::from("some disambiguation")); + let artist_2 = artist.clone(); + let mut artist_match_2 = Match::new(100, artist_2); + artist_match_2.disambiguation = Some(String::from("some disambiguation")); -// let list = vec![artist_match_1.clone(), artist_match_2.clone()]; -// MatchStateInfo::artist(artist, list) -// } + let list = vec![artist_match_1.clone(), artist_match_2.clone()]; + MatchStateInfo::artist(artist, list) + } -// fn album_match() -> MatchStateInfo { -// let album = AlbumMeta::new( -// AlbumId::new("Album"), -// AlbumDate::new(Some(1990), Some(5), None), -// Some(AlbumPrimaryType::Album), -// vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], -// ); + fn album_match() -> MatchStateInfo { + let album = AlbumMeta::new( + AlbumId::new("Album"), + AlbumDate::new(Some(1990), Some(5), None), + Some(AlbumPrimaryType::Album), + vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], + ); -// let album_1 = album.clone(); -// let album_match_1 = Match::new(100, album_1); + let album_1 = album.clone(); + let album_match_1 = Match::new(100, album_1); -// let mut album_2 = album.clone(); -// album_2.id.title.push_str(" extra title part"); -// album_2.secondary_types.pop(); -// let album_match_2 = Match::new(100, album_2); + let mut album_2 = album.clone(); + album_2.id.title.push_str(" extra title part"); + album_2.secondary_types.pop(); + let album_match_2 = Match::new(100, album_2); -// let list = vec![album_match_1.clone(), album_match_2.clone()]; -// MatchStateInfo::album(album, list) -// } + let list = vec![album_match_1.clone(), album_match_2.clone()]; + MatchStateInfo::album(album, list) + } -// fn fetch_state() -> FetchState { -// let (_, rx) = mpsc::channel(); -// FetchState::new(rx) -// } + fn fetch_state() -> FetchState { + let (_, rx) = mpsc::channel(); + FetchState::new(rx) + } -// fn match_state(matches_info: Option) -> MatchState { -// MatchState::new(matches_info, fetch_state()) -// } + fn match_state(matches_info: Option) -> MatchState { + MatchState::new(matches_info, fetch_state()) + } -// #[test] -// fn create_empty() { -// let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None)); + #[test] + fn create_empty() { + let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None)); -// let widget_state = WidgetState::default(); + let widget_state = WidgetState::default(); -// assert_eq!(matches.state.current, None); -// assert_eq!(matches.state.state, widget_state); + assert_eq!(matches.state.current, None); + assert_eq!(matches.state.state, widget_state); -// let mut app: App = matches.into(); -// let public = app.get(); -// let public_matches = public.state.unwrap_match(); + let mut app: App = matches.into(); + let public = app.get(); + let public_matches = public.state.unwrap_match(); -// assert_eq!(public_matches.info, None); -// assert_eq!(public_matches.state, &widget_state); -// } + assert_eq!(public_matches.info, None); + assert_eq!(public_matches.state, &widget_state); + } -// #[test] -// fn create_nonempty() { -// let mut album_match = album_match(); -// let matches = AppMachine::match_state( -// inner(music_hoard(vec![])), -// match_state(Some(album_match.clone())), -// ); -// album_match.push_cannot_have_mbid(); -// album_match.push_manual_input_mbid(); + #[test] + fn create_nonempty() { + let mut album_match = album_match(); + let matches = AppMachine::match_state( + inner(music_hoard(vec![])), + match_state(Some(album_match.clone())), + ); + album_match.push_cannot_have_mbid(); + album_match.push_manual_input_mbid(); -// let mut widget_state = WidgetState::default(); -// widget_state.list.select(Some(0)); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); -// assert_eq!(matches.state.current.as_ref(), Some(&album_match)); -// assert_eq!(matches.state.state, widget_state); + assert_eq!(matches.state.current.as_ref(), Some(&album_match)); + assert_eq!(matches.state.state, widget_state); -// let mut app: App = matches.into(); -// let public = app.get(); -// let public_matches = public.state.unwrap_match(); + let mut app: App = matches.into(); + let public = app.get(); + let public_matches = public.state.unwrap_match(); -// assert_eq!(public_matches.info, Some(&album_match)); -// assert_eq!(public_matches.state, &widget_state); -// } + assert_eq!(public_matches.info, Some(&album_match)); + assert_eq!(public_matches.state, &widget_state); + } -// fn match_state_flow(mut matches_info: MatchStateInfo) { -// // tx must exist for rx to return Empty rather than Disconnected. -// #[allow(unused_variables)] -// let (tx, rx) = mpsc::channel(); -// let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx)); + fn match_state_flow(mut matches_info: MatchStateInfo) { + // tx must exist for rx to return Empty rather than Disconnected. + #[allow(unused_variables)] + let (tx, rx) = mpsc::channel(); + let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx)); -// let matches = AppMachine::match_state(inner(music_hoard(vec![])), app_matches); -// matches_info.push_cannot_have_mbid(); -// matches_info.push_manual_input_mbid(); + let matches = AppMachine::match_state(inner(music_hoard(vec![])), app_matches); + matches_info.push_cannot_have_mbid(); + matches_info.push_manual_input_mbid(); -// let mut widget_state = WidgetState::default(); -// widget_state.list.select(Some(0)); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state, widget_state); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state, widget_state); -// let matches = matches.prev_match().unwrap_match(); + let matches = matches.prev_match().unwrap_match(); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state.list.selected(), Some(0)); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state.list.selected(), Some(0)); -// let matches = matches.next_match().unwrap_match(); + let matches = matches.next_match().unwrap_match(); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state.list.selected(), Some(1)); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state.list.selected(), Some(1)); -// // Next is CannotHaveMBID -// let matches = matches.next_match().unwrap_match(); + // Next is CannotHaveMBID + let matches = matches.next_match().unwrap_match(); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state.list.selected(), Some(2)); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state.list.selected(), Some(2)); -// // Next is ManualInputMbid -// let matches = matches.next_match().unwrap_match(); + // Next is ManualInputMbid + let matches = matches.next_match().unwrap_match(); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state.list.selected(), Some(3)); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state.list.selected(), Some(3)); -// let matches = matches.next_match().unwrap_match(); + let matches = matches.next_match().unwrap_match(); -// assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); -// assert_eq!(matches.state.state.list.selected(), Some(3)); + assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); + assert_eq!(matches.state.state.list.selected(), Some(3)); -// // Go prev_match first as selecting on manual input does not go back to fetch. -// let matches = matches.prev_match().unwrap_match(); -// matches.select().unwrap_fetch(); -// } + // Go prev_match first as selecting on manual input does not go back to fetch. + let matches = matches.prev_match().unwrap_match(); + matches.select().unwrap_fetch(); + } -// #[test] -// fn artist_matches_flow() { -// match_state_flow(artist_match()); -// } + #[test] + fn artist_matches_flow() { + match_state_flow(artist_match()); + } -// #[test] -// fn album_matches_flow() { -// match_state_flow(album_match()); -// } + #[test] + fn album_matches_flow() { + match_state_flow(album_match()); + } -// #[test] -// fn abort() { -// let mut album_match = album_match(); -// let matches = AppMachine::match_state( -// inner(music_hoard(vec![])), -// match_state(Some(album_match.clone())), -// ); -// album_match.push_cannot_have_mbid(); -// album_match.push_manual_input_mbid(); + #[test] + fn abort() { + let mut album_match = album_match(); + let matches = AppMachine::match_state( + inner(music_hoard(vec![])), + match_state(Some(album_match.clone())), + ); + album_match.push_cannot_have_mbid(); + album_match.push_manual_input_mbid(); -// let mut widget_state = WidgetState::default(); -// widget_state.list.select(Some(0)); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); -// assert_eq!(matches.state.current.as_ref(), Some(&album_match)); -// assert_eq!(matches.state.state, widget_state); + assert_eq!(matches.state.current.as_ref(), Some(&album_match)); + assert_eq!(matches.state.state, widget_state); -// matches.abort().unwrap_browse(); -// } + matches.abort().unwrap_browse(); + } -// #[test] -// fn select_empty() { -// // Note that what really matters in this test is actually that the transmit channel has -// // disconnected and so the receive within FetchState concludes there are no more matches. -// let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None)); -// matches.select().unwrap_browse(); -// } + #[test] + fn select_empty() { + // Note that what really matters in this test is actually that the transmit channel has + // disconnected and so the receive within FetchState concludes there are no more matches. + let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None)); + matches.select().unwrap_browse(); + } -// #[test] -// fn select_manual_input() { -// let matches = -// AppMachine::match_state(inner(music_hoard(vec![])), match_state(Some(album_match()))); + #[test] + fn select_manual_input() { + let matches = + AppMachine::match_state(inner(music_hoard(vec![])), match_state(Some(album_match()))); -// // album_match has two matches which means that the fourth option should be manual input. -// let matches = matches.next_match().unwrap_match(); -// let matches = matches.next_match().unwrap_match(); -// let matches = matches.next_match().unwrap_match(); -// let matches = matches.next_match().unwrap_match(); + // album_match has two matches which means that the fourth option should be manual input. + let matches = matches.next_match().unwrap_match(); + let matches = matches.next_match().unwrap_match(); + let matches = matches.next_match().unwrap_match(); + let matches = matches.next_match().unwrap_match(); -// let app = matches.select(); + let app = matches.select(); -// let input = app.mode().unwrap_input(); -// input.confirm().unwrap_match(); -// } -// } + let input = app.mode().unwrap_input(); + input.confirm().unwrap_match(); + } +} diff --git a/src/tui/app/machine/mod.rs b/src/tui/app/machine/mod.rs index 86bfbb6..35b6542 100644 --- a/src/tui/app/machine/mod.rs +++ b/src/tui/app/machine/mod.rs @@ -215,378 +215,376 @@ where } } -// #[cfg(test)] -// mod tests { -// use std::sync::mpsc; - -// use musichoard::collection::Collection; - -// use crate::tui::{ -// app::{AppState, IApp, IAppInput, IAppInteractBrowse}, -// lib::{interface::musicbrainz::api::MockIMusicBrainz, MockIMusicHoard}, -// EventChannel, -// }; - -// use super::*; - -// impl AppMode { -// fn unwrap_state(self) -> StateMode { -// match self { -// AppMode::State(state) => state, -// _ => panic!(), -// } -// } - -// pub fn unwrap_input(self) -> InputMode { -// match self { -// AppMode::Input(input) => input, -// _ => panic!(), -// } -// } -// } - -// impl< -// BrowseState, -// InfoState, -// ReloadState, -// SearchState, -// FetchState, -// MatchState, -// ErrorState, -// CriticalState, -// > -// AppState< -// BrowseState, -// InfoState, -// ReloadState, -// SearchState, -// FetchState, -// MatchState, -// ErrorState, -// CriticalState, -// > -// { -// pub fn unwrap_browse(self) -> BrowseState { -// match self { -// AppState::Browse(browse) => browse, -// _ => panic!(), -// } -// } - -// pub fn unwrap_info(self) -> InfoState { -// match self { -// AppState::Info(info) => info, -// _ => panic!(), -// } -// } - -// pub fn unwrap_reload(self) -> ReloadState { -// match self { -// AppState::Reload(reload) => reload, -// _ => panic!(), -// } -// } - -// pub fn unwrap_search(self) -> SearchState { -// match self { -// AppState::Search(search) => search, -// _ => panic!(), -// } -// } - -// pub fn unwrap_fetch(self) -> FetchState { -// match self { -// AppState::Fetch(fetch) => fetch, -// _ => panic!(), -// } -// } - -// pub fn unwrap_match(self) -> MatchState { -// match self { -// AppState::Match(matches) => matches, -// _ => panic!(), -// } -// } - -// pub fn unwrap_error(self) -> ErrorState { -// match self { -// AppState::Error(error) => error, -// _ => panic!(), -// } -// } - -// pub fn unwrap_critical(self) -> CriticalState { -// match self { -// AppState::Critical(critical) => critical, -// _ => panic!(), -// } -// } -// } - -// pub fn music_hoard(collection: Collection) -> MockIMusicHoard { -// let mut music_hoard = MockIMusicHoard::new(); -// music_hoard.expect_get_collection().return_const(collection); - -// music_hoard -// } - -// pub fn music_hoard_init(collection: Collection) -> MockIMusicHoard { -// let mut music_hoard = music_hoard(collection); - -// music_hoard -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); - -// music_hoard -// } - -// pub fn mb_api() -> MockIMusicBrainz { -// MockIMusicBrainz::new() -// } - -// pub fn events() -> EventSender { -// EventChannel::new().sender() -// } - -// pub fn inner(music_hoard: MockIMusicHoard) -> AppInner { -// AppInner::new(music_hoard, mb_api(), events()) -// } - -// pub fn inner_with_mb(music_hoard: MockIMusicHoard, mb_api: MockIMusicBrainz) -> AppInner { -// AppInner::new(music_hoard, mb_api, events()) -// } - -// #[test] -// fn input_mode() { -// let app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); - -// let mode = app.mode(); -// assert!(matches!(mode, AppMode::State(_))); - -// let state = mode.unwrap_state(); -// assert!(matches!(state, AppState::Browse(_))); - -// let mut app = state; -// app.input_mut().replace(Input::default()); - -// let public = app.get(); -// assert!(public.input.is_some()); - -// let mode = app.mode(); -// assert!(matches!(mode, AppMode::Input(_))); - -// let mut app = mode.unwrap_input().cancel(); -// assert!(matches!(app, AppState::Browse(_))); - -// let public = app.get(); -// assert!(public.input.is_none()); -// } - -// #[test] -// fn state_browse() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); - -// let state = app.state(); -// assert!(matches!(state, AppState::Browse(_))); -// app = state; - -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Browse(_))); -// app = state; - -// let public = app.get(); -// assert!(matches!(public.state, AppState::Browse(_))); +#[cfg(test)] +mod tests { + use std::sync::mpsc; + + use musichoard::collection::Collection; + + use crate::tui::{ + app::{AppState, IApp, IAppInput, IAppInteractBrowse}, + lib::{interface::musicbrainz::daemon::MockIMbJobSender, MockIMusicHoard}, + }; + + use super::*; + + impl AppMode { + fn unwrap_state(self) -> StateMode { + match self { + AppMode::State(state) => state, + _ => panic!(), + } + } + + pub fn unwrap_input(self) -> InputMode { + match self { + AppMode::Input(input) => input, + _ => panic!(), + } + } + } + + impl< + BrowseState, + InfoState, + ReloadState, + SearchState, + FetchState, + MatchState, + ErrorState, + CriticalState, + > + AppState< + BrowseState, + InfoState, + ReloadState, + SearchState, + FetchState, + MatchState, + ErrorState, + CriticalState, + > + { + pub fn unwrap_browse(self) -> BrowseState { + match self { + AppState::Browse(browse) => browse, + _ => panic!(), + } + } + + pub fn unwrap_info(self) -> InfoState { + match self { + AppState::Info(info) => info, + _ => panic!(), + } + } + + pub fn unwrap_reload(self) -> ReloadState { + match self { + AppState::Reload(reload) => reload, + _ => panic!(), + } + } + + pub fn unwrap_search(self) -> SearchState { + match self { + AppState::Search(search) => search, + _ => panic!(), + } + } + + pub fn unwrap_fetch(self) -> FetchState { + match self { + AppState::Fetch(fetch) => fetch, + _ => panic!(), + } + } + + pub fn unwrap_match(self) -> MatchState { + match self { + AppState::Match(matches) => matches, + _ => panic!(), + } + } + + pub fn unwrap_error(self) -> ErrorState { + match self { + AppState::Error(error) => error, + _ => panic!(), + } + } + + pub fn unwrap_critical(self) -> CriticalState { + match self { + AppState::Critical(critical) => critical, + _ => panic!(), + } + } + } + + pub fn music_hoard(collection: Collection) -> MockIMusicHoard { + let mut music_hoard = MockIMusicHoard::new(); + music_hoard.expect_get_collection().return_const(collection); + + music_hoard + } + + pub fn music_hoard_init(collection: Collection) -> MockIMusicHoard { + let mut music_hoard = music_hoard(collection); + + music_hoard + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); + + music_hoard + } + + pub fn mb_job_sender() -> MockIMbJobSender { + MockIMbJobSender::new() + } + + pub fn inner(music_hoard: MockIMusicHoard) -> AppInner { + AppInner::new(music_hoard, mb_job_sender()) + } + + pub fn inner_with_mb( + music_hoard: MockIMusicHoard, + mb_job_sender: MockIMbJobSender, + ) -> AppInner { + AppInner::new(music_hoard, mb_job_sender) + } + + #[test] + fn input_mode() { + let app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); + + let mode = app.mode(); + assert!(matches!(mode, AppMode::State(_))); + + let state = mode.unwrap_state(); + assert!(matches!(state, AppState::Browse(_))); + + let mut app = state; + app.input_mut().replace(Input::default()); + + let public = app.get(); + assert!(public.input.is_some()); + + let mode = app.mode(); + assert!(matches!(mode, AppMode::Input(_))); + + let mut app = mode.unwrap_input().cancel(); + assert!(matches!(app, AppState::Browse(_))); + + let public = app.get(); + assert!(public.input.is_none()); + } + + #[test] + fn state_browse() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); + + let state = app.state(); + assert!(matches!(state, AppState::Browse(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Browse(_))); + app = state; + + let public = app.get(); + assert!(matches!(public.state, AppState::Browse(_))); + + let app = app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn state_info() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); + + app = app.unwrap_browse().show_info_overlay(); + + let state = app.state(); + assert!(matches!(state, AppState::Info(_))); + app = state; -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Info(_))); + app = state; -// #[test] -// fn state_info() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); + let public = app.get(); + assert!(matches!(public.state, AppState::Info(_))); -// app = app.unwrap_browse().show_info_overlay(); + let app = app.force_quit(); + assert!(!app.is_running()); + } -// let state = app.state(); -// assert!(matches!(state, AppState::Info(_))); -// app = state; + #[test] + fn state_reload() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Info(_))); -// app = state; + app = app.unwrap_browse().show_reload_menu(); -// let public = app.get(); -// assert!(matches!(public.state, AppState::Info(_))); + let state = app.state(); + assert!(matches!(state, AppState::Reload(_))); + app = state; -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Reload(_))); + app = state; -// #[test] -// fn state_reload() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); + let public = app.get(); + assert!(matches!(public.state, AppState::Reload(_))); -// app = app.unwrap_browse().show_reload_menu(); + let app = app.force_quit(); + assert!(!app.is_running()); + } -// let state = app.state(); -// assert!(matches!(state, AppState::Reload(_))); -// app = state; + #[test] + fn state_search() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Reload(_))); -// app = state; + app = app.unwrap_browse().begin_search(); -// let public = app.get(); -// assert!(matches!(public.state, AppState::Reload(_))); + let state = app.state(); + assert!(matches!(state, AppState::Search(_))); + app = state; -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Search(_))); + app = state; -// #[test] -// fn state_search() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); + let public = app.get(); + assert!(matches!(public.state, AppState::Search(""))); -// app = app.unwrap_browse().begin_search(); + let app = app.force_quit(); + assert!(!app.is_running()); + } -// let state = app.state(); -// assert!(matches!(state, AppState::Search(_))); -// app = state; + #[test] + fn state_fetch() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Search(_))); -// app = state; + let (_, rx) = mpsc::channel(); + let inner = app.unwrap_browse().inner; + let state = FetchState::new(rx); + app = AppMachine::new(inner, state).into(); -// let public = app.get(); -// assert!(matches!(public.state, AppState::Search(""))); + let state = app.state(); + assert!(matches!(state, AppState::Fetch(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Fetch(_))); + app = state; -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } + let public = app.get(); + assert!(matches!(public.state, AppState::Fetch(_))); + + let app = app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn state_match() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); -// #[test] -// fn state_fetch() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); + let (_, rx) = mpsc::channel(); + let fetch = FetchState::new(rx); + app = + AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(None, fetch)).into(); -// let (_, rx) = mpsc::channel(); -// let inner = app.unwrap_browse().inner; -// let state = FetchState::new(rx); -// app = AppMachine::new(inner, state).into(); + let state = app.state(); + assert!(matches!(state, AppState::Match(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Match(_))); + app = state; + + let public = app.get(); + assert!(matches!(public.state, AppState::Match(_))); + + let app = app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn state_error() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); + + app = AppMachine::error_state(app.unwrap_browse().inner, "get rekt").into(); -// let state = app.state(); -// assert!(matches!(state, AppState::Fetch(_))); -// app = state; - -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Fetch(_))); -// app = state; - -// let public = app.get(); -// assert!(matches!(public.state, AppState::Fetch(_))); - -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } - -// #[test] -// fn state_match() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); - -// let (_, rx) = mpsc::channel(); -// let fetch = FetchState::new(rx); -// app = -// AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(None, fetch)).into(); - -// let state = app.state(); -// assert!(matches!(state, AppState::Match(_))); -// app = state; - -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Match(_))); -// app = state; - -// let public = app.get(); -// assert!(matches!(public.state, AppState::Match(_))); - -// let app = app.force_quit(); -// assert!(!app.is_running()); -// } - -// #[test] -// fn state_error() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); - -// app = AppMachine::error_state(app.unwrap_browse().inner, "get rekt").into(); - -// let state = app.state(); -// assert!(matches!(state, AppState::Error(_))); -// app = state; - -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Error(_))); -// app = state; - -// let public = app.get(); -// assert!(matches!(public.state, AppState::Error("get rekt"))); - -// app = app.force_quit(); -// assert!(!app.is_running()); -// } - -// #[test] -// fn state_critical() { -// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); -// assert!(app.is_running()); - -// app = AppMachine::critical_state(app.unwrap_browse().inner, "get rekt").into(); - -// let state = app.state(); -// assert!(matches!(state, AppState::Critical(_))); -// app = state; - -// app = app.no_op(); -// let state = app.state(); -// assert!(matches!(state, AppState::Critical(_))); -// app = state; - -// let public = app.get(); -// assert!(matches!(public.state, AppState::Critical("get rekt"))); - -// app = app.force_quit(); -// assert!(!app.is_running()); -// } - -// #[test] -// fn init_error() { -// let mut music_hoard = MockIMusicHoard::new(); - -// music_hoard -// .expect_rescan_library() -// .times(1) -// .return_once(|| Err(musichoard::Error::LibraryError(String::from("get rekt")))); -// music_hoard.expect_get_collection().return_const(vec![]); - -// let app = App::new(music_hoard, mb_api(), events()); -// assert!(app.is_running()); -// app.unwrap_critical(); -// } -// } + let state = app.state(); + assert!(matches!(state, AppState::Error(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Error(_))); + app = state; + + let public = app.get(); + assert!(matches!(public.state, AppState::Error("get rekt"))); + + app = app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn state_critical() { + let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); + assert!(app.is_running()); + + app = AppMachine::critical_state(app.unwrap_browse().inner, "get rekt").into(); + + let state = app.state(); + assert!(matches!(state, AppState::Critical(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Critical(_))); + app = state; + + let public = app.get(); + assert!(matches!(public.state, AppState::Critical("get rekt"))); + + app = app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn init_error() { + let mut music_hoard = MockIMusicHoard::new(); + + music_hoard + .expect_rescan_library() + .times(1) + .return_once(|| Err(musichoard::Error::LibraryError(String::from("get rekt")))); + music_hoard.expect_get_collection().return_const(vec![]); + + let app = App::new(music_hoard, mb_job_sender()); + assert!(app.is_running()); + app.unwrap_critical(); + } +} #[cfg(nightly)] #[cfg(test)] diff --git a/src/tui/app/machine/reload_state.rs b/src/tui/app/machine/reload_state.rs index 2784587..25def5b 100644 --- a/src/tui/app/machine/reload_state.rs +++ b/src/tui/app/machine/reload_state.rs @@ -68,58 +68,58 @@ impl IAppInteractReloadPrivate for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use crate::tui::app::machine::tests::{inner, music_hoard}; +#[cfg(test)] +mod tests { + use crate::tui::app::machine::tests::{inner, music_hoard}; -// use super::*; + use super::*; -// #[test] -// fn hide_reload_menu() { -// let reload = AppMachine::reload_state(inner(music_hoard(vec![]))); -// let app = reload.hide_reload_menu(); -// app.unwrap_browse(); -// } + #[test] + fn hide_reload_menu() { + let reload = AppMachine::reload_state(inner(music_hoard(vec![]))); + let app = reload.hide_reload_menu(); + app.unwrap_browse(); + } -// #[test] -// fn reload_database() { -// let mut music_hoard = music_hoard(vec![]); + #[test] + fn reload_database() { + let mut music_hoard = music_hoard(vec![]); -// music_hoard -// .expect_reload_database() -// .times(1) -// .return_once(|| Ok(())); + music_hoard + .expect_reload_database() + .times(1) + .return_once(|| Ok(())); -// let reload = AppMachine::reload_state(inner(music_hoard)); -// let app = reload.reload_database(); -// app.unwrap_browse(); -// } + let reload = AppMachine::reload_state(inner(music_hoard)); + let app = reload.reload_database(); + app.unwrap_browse(); + } -// #[test] -// fn reload_library() { -// let mut music_hoard = music_hoard(vec![]); + #[test] + fn reload_library() { + let mut music_hoard = music_hoard(vec![]); -// music_hoard -// .expect_rescan_library() -// .times(1) -// .return_once(|| Ok(())); + music_hoard + .expect_rescan_library() + .times(1) + .return_once(|| Ok(())); -// let reload = AppMachine::reload_state(inner(music_hoard)); -// let app = reload.reload_library(); -// app.unwrap_browse(); -// } + let reload = AppMachine::reload_state(inner(music_hoard)); + let app = reload.reload_library(); + app.unwrap_browse(); + } -// #[test] -// fn reload_error() { -// let mut music_hoard = music_hoard(vec![]); + #[test] + fn reload_error() { + let mut music_hoard = music_hoard(vec![]); -// music_hoard -// .expect_reload_database() -// .times(1) -// .return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt")))); + music_hoard + .expect_reload_database() + .times(1) + .return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt")))); -// let reload = AppMachine::reload_state(inner(music_hoard)); -// let app = reload.reload_database(); -// app.unwrap_error(); -// } -// } + let reload = AppMachine::reload_state(inner(music_hoard)); + let app = reload.reload_database(); + app.unwrap_error(); + } +} diff --git a/src/tui/app/machine/search_state.rs b/src/tui/app/machine/search_state.rs index ded55e8..dbcb9d8 100644 --- a/src/tui/app/machine/search_state.rs +++ b/src/tui/app/machine/search_state.rs @@ -229,343 +229,343 @@ impl IAppInteractSearchPrivate for AppMachine { } } -// #[cfg(test)] -// mod tests { -// use ratatui::widgets::ListState; - -// use crate::tui::{ -// app::machine::tests::{inner, music_hoard}, -// testmod::COLLECTION, -// }; - -// use super::*; - -// fn orig(index: Option) -> ListSelection { -// let mut artist = ListState::default(); -// artist.select(index); - -// ListSelection { -// artist, -// album: ListState::default(), -// track: ListState::default(), -// } -// } - -// #[test] -// fn artist_incremental_search() { -// // Empty collection. -// let mut search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None)); -// assert_eq!(search.inner.selection.selected(), None); - -// search.state.string = String::from("album_artist 'a'"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), None); - -// // Basic test, first element. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from(""); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist 'a'"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// // Basic test, non-first element. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist 'c'"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// // Non-lowercase. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("Album_Artist "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("Album_Artist 'C'"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// // Non-ascii. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist ‘c’"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// // Non-lowercase, non-ascii. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("Album_Artist "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("Album_Artist ‘C’"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// // Stop at name, not sort name. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("the "); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// search.state.string = String::from("the album_artist 'c'"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// // Search next with common prefix. -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.state.string = String::from("album_artist"); -// search.incremental_search(false); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// search.incremental_search(true); -// assert_eq!(search.inner.selection.selected(), Some(1)); - -// search.incremental_search(true); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// search.incremental_search(true); -// assert_eq!(search.inner.selection.selected(), Some(3)); - -// search.incremental_search(true); -// assert_eq!(search.inner.selection.selected(), Some(3)); -// } - -// #[test] -// fn album_incremental_search() { -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// search.inner.selection.increment_category(); -// assert_eq!(search.inner.selection.category(), Category::Album); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(0)); - -// search.state.string = String::from("album_title "); -// search.incremental_search(false); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(0)); - -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('.').unwrap_search(); -// let search = search.append_character('b').unwrap_search(); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(1)); -// } - -// #[test] -// fn track_incremental_search() { -// let mut search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); -// search.inner.selection.increment_category(); -// search.inner.selection.increment_category(); -// assert_eq!(search.inner.selection.category(), Category::Track); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(0)); - -// search.state.string = String::from("track "); -// search.incremental_search(false); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(0)); - -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('.').unwrap_search(); -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('.').unwrap_search(); -// let search = search.append_character('2').unwrap_search(); - -// let sel = &search.inner.selection; -// assert_eq!(sel.selected(), Some(1)); -// } - -// #[test] -// fn search() { -// let search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('l').unwrap_search(); -// let search = search.append_character('b').unwrap_search(); -// let search = search.append_character('u').unwrap_search(); -// let search = search.append_character('m').unwrap_search(); -// let search = search.append_character('_').unwrap_search(); -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('r').unwrap_search(); -// let search = search.append_character('t').unwrap_search(); -// let search = search.append_character('i').unwrap_search(); -// let search = search.append_character('s').unwrap_search(); -// let search = search.append_character('t').unwrap_search(); -// let search = search.append_character(' ').unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.append_character('\'').unwrap_search(); -// let search = search.append_character('c').unwrap_search(); -// let search = search.append_character('\'').unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// let search = search.step_back().unwrap_search(); -// let search = search.step_back().unwrap_search(); -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.append_character('\'').unwrap_search(); -// let search = search.append_character('b').unwrap_search(); -// let search = search.append_character('\'').unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(1)); - -// let app = search.finish_search(); -// let browse = app.unwrap_browse(); -// assert_eq!(browse.inner.selection.selected(), Some(1)); -// } - -// #[test] -// fn search_next_step_back() { -// let search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.append_character('a').unwrap_search(); -// let search = search.search_next().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(1)); - -// let search = search.search_next().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// let search = search.search_next().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(3)); - -// let search = search.search_next().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(3)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(3)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(2)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(1)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(0)); -// } - -// #[test] -// fn cancel_search() { -// let search = -// AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); -// assert_eq!(search.inner.selection.selected(), Some(0)); - -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('l').unwrap_search(); -// let search = search.append_character('b').unwrap_search(); -// let search = search.append_character('u').unwrap_search(); -// let search = search.append_character('m').unwrap_search(); -// let search = search.append_character('_').unwrap_search(); -// let search = search.append_character('a').unwrap_search(); -// let search = search.append_character('r').unwrap_search(); -// let search = search.append_character('t').unwrap_search(); -// let search = search.append_character('i').unwrap_search(); -// let search = search.append_character('s').unwrap_search(); -// let search = search.append_character('t').unwrap_search(); -// let search = search.append_character(' ').unwrap_search(); -// let search = search.append_character('\'').unwrap_search(); -// let search = search.append_character('b').unwrap_search(); -// let search = search.append_character('\'').unwrap_search(); -// assert_eq!(search.inner.selection.selected(), Some(1)); - -// let browse = search.cancel_search().unwrap_browse(); -// assert_eq!(browse.inner.selection.selected(), Some(2)); -// } - -// #[test] -// fn empty_search() { -// let search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None)); -// assert_eq!(search.inner.selection.selected(), None); - -// let search = search.append_character('a').unwrap_search(); -// assert_eq!(search.inner.selection.selected(), None); - -// let search = search.search_next().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), None); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), None); - -// let search = search.step_back().unwrap_search(); -// assert_eq!(search.inner.selection.selected(), None); - -// let browse = search.cancel_search().unwrap_browse(); -// assert_eq!(browse.inner.selection.selected(), None); -// } -// } - -// #[cfg(nightly)] -// #[cfg(test)] -// mod benches { -// // The purpose of these benches was to evaluate the benefit of AhoCorasick over std solutions. -// use test::Bencher; - -// use crate::tui::{app::machine::benchmod::ARTISTS, lib::MockIMusicHoard}; - -// use super::*; - -// type Search = AppMachine; - -// #[bench] -// fn is_char_sensitive(b: &mut Bencher) { -// let mut iter = ARTISTS.iter().cycle(); -// b.iter(|| test::black_box(Search::is_char_sensitive(&iter.next().unwrap()))) -// } - -// #[bench] -// fn normalize_search(b: &mut Bencher) { -// let mut iter = ARTISTS.iter().cycle(); -// b.iter(|| test::black_box(Search::normalize_search(&iter.next().unwrap(), true, true))) -// } -// } +#[cfg(test)] +mod tests { + use ratatui::widgets::ListState; + + use crate::tui::{ + app::machine::tests::{inner, music_hoard}, + testmod::COLLECTION, + }; + + use super::*; + + fn orig(index: Option) -> ListSelection { + let mut artist = ListState::default(); + artist.select(index); + + ListSelection { + artist, + album: ListState::default(), + track: ListState::default(), + } + } + + #[test] + fn artist_incremental_search() { + // Empty collection. + let mut search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None)); + assert_eq!(search.inner.selection.selected(), None); + + search.state.string = String::from("album_artist 'a'"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), None); + + // Basic test, first element. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from(""); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist 'a'"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + // Basic test, non-first element. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist 'c'"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + // Non-lowercase. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("Album_Artist "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("Album_Artist 'C'"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + // Non-ascii. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist ‘c’"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + // Non-lowercase, non-ascii. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("Album_Artist "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("Album_Artist ‘C’"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + // Stop at name, not sort name. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("the "); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + search.state.string = String::from("the album_artist 'c'"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(2)); + + // Search next with common prefix. + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.state.string = String::from("album_artist"); + search.incremental_search(false); + assert_eq!(search.inner.selection.selected(), Some(0)); + + search.incremental_search(true); + assert_eq!(search.inner.selection.selected(), Some(1)); + + search.incremental_search(true); + assert_eq!(search.inner.selection.selected(), Some(2)); + + search.incremental_search(true); + assert_eq!(search.inner.selection.selected(), Some(3)); + + search.incremental_search(true); + assert_eq!(search.inner.selection.selected(), Some(3)); + } + + #[test] + fn album_incremental_search() { + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + search.inner.selection.increment_category(); + assert_eq!(search.inner.selection.category(), Category::Album); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(0)); + + search.state.string = String::from("album_title "); + search.incremental_search(false); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(0)); + + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('.').unwrap_search(); + let search = search.append_character('b').unwrap_search(); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(1)); + } + + #[test] + fn track_incremental_search() { + let mut search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1))); + search.inner.selection.increment_category(); + search.inner.selection.increment_category(); + assert_eq!(search.inner.selection.category(), Category::Track); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(0)); + + search.state.string = String::from("track "); + search.incremental_search(false); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(0)); + + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('.').unwrap_search(); + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('.').unwrap_search(); + let search = search.append_character('2').unwrap_search(); + + let sel = &search.inner.selection; + assert_eq!(sel.selected(), Some(1)); + } + + #[test] + fn search() { + let search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('l').unwrap_search(); + let search = search.append_character('b').unwrap_search(); + let search = search.append_character('u').unwrap_search(); + let search = search.append_character('m').unwrap_search(); + let search = search.append_character('_').unwrap_search(); + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('r').unwrap_search(); + let search = search.append_character('t').unwrap_search(); + let search = search.append_character('i').unwrap_search(); + let search = search.append_character('s').unwrap_search(); + let search = search.append_character('t').unwrap_search(); + let search = search.append_character(' ').unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.append_character('\'').unwrap_search(); + let search = search.append_character('c').unwrap_search(); + let search = search.append_character('\'').unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(2)); + + let search = search.step_back().unwrap_search(); + let search = search.step_back().unwrap_search(); + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.append_character('\'').unwrap_search(); + let search = search.append_character('b').unwrap_search(); + let search = search.append_character('\'').unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(1)); + + let app = search.finish_search(); + let browse = app.unwrap_browse(); + assert_eq!(browse.inner.selection.selected(), Some(1)); + } + + #[test] + fn search_next_step_back() { + let search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.append_character('a').unwrap_search(); + let search = search.search_next().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(1)); + + let search = search.search_next().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(2)); + + let search = search.search_next().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(3)); + + let search = search.search_next().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(3)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(3)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(2)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(1)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(0)); + } + + #[test] + fn cancel_search() { + let search = + AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2))); + assert_eq!(search.inner.selection.selected(), Some(0)); + + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('l').unwrap_search(); + let search = search.append_character('b').unwrap_search(); + let search = search.append_character('u').unwrap_search(); + let search = search.append_character('m').unwrap_search(); + let search = search.append_character('_').unwrap_search(); + let search = search.append_character('a').unwrap_search(); + let search = search.append_character('r').unwrap_search(); + let search = search.append_character('t').unwrap_search(); + let search = search.append_character('i').unwrap_search(); + let search = search.append_character('s').unwrap_search(); + let search = search.append_character('t').unwrap_search(); + let search = search.append_character(' ').unwrap_search(); + let search = search.append_character('\'').unwrap_search(); + let search = search.append_character('b').unwrap_search(); + let search = search.append_character('\'').unwrap_search(); + assert_eq!(search.inner.selection.selected(), Some(1)); + + let browse = search.cancel_search().unwrap_browse(); + assert_eq!(browse.inner.selection.selected(), Some(2)); + } + + #[test] + fn empty_search() { + let search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None)); + assert_eq!(search.inner.selection.selected(), None); + + let search = search.append_character('a').unwrap_search(); + assert_eq!(search.inner.selection.selected(), None); + + let search = search.search_next().unwrap_search(); + assert_eq!(search.inner.selection.selected(), None); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), None); + + let search = search.step_back().unwrap_search(); + assert_eq!(search.inner.selection.selected(), None); + + let browse = search.cancel_search().unwrap_browse(); + assert_eq!(browse.inner.selection.selected(), None); + } +} + +#[cfg(nightly)] +#[cfg(test)] +mod benches { + // The purpose of these benches was to evaluate the benefit of AhoCorasick over std solutions. + use test::Bencher; + + use crate::tui::{app::machine::benchmod::ARTISTS, lib::MockIMusicHoard}; + + use super::*; + + type Search = AppMachine; + + #[bench] + fn is_char_sensitive(b: &mut Bencher) { + let mut iter = ARTISTS.iter().cycle(); + b.iter(|| test::black_box(Search::is_char_sensitive(&iter.next().unwrap()))) + } + + #[bench] + fn normalize_search(b: &mut Bencher) { + let mut iter = ARTISTS.iter().cycle(); + b.iter(|| test::black_box(Search::normalize_search(&iter.next().unwrap(), true, true))) + } +} diff --git a/src/tui/event.rs b/src/tui/event.rs index fb1dd0f..c4b9710 100644 --- a/src/tui/event.rs +++ b/src/tui/event.rs @@ -2,6 +2,9 @@ use crossterm::event::KeyEvent; use std::fmt; use std::sync::mpsc; +#[cfg(test)] +use mockall::automock; + #[derive(Debug)] pub enum EventError { Send(Event), @@ -50,6 +53,7 @@ pub struct EventChannel { receiver: mpsc::Receiver, } +#[cfg_attr(test, automock)] pub trait IKeyEventSender { fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>; } diff --git a/src/tui/lib/external/musicbrainz/daemon/mod.rs b/src/tui/lib/external/musicbrainz/daemon/mod.rs index d016cf3..8560acd 100644 --- a/src/tui/lib/external/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/external/musicbrainz/daemon/mod.rs @@ -296,3 +296,193 @@ impl JobQueue { } } } + +#[cfg(test)] +mod tests { + // fn event_channel() -> (EventSender, EventReceiver) { + // let event_channel = EventChannel::new(); + // let events_tx = event_channel.sender(); + // let events_rx = event_channel.receiver(); + // (events_tx, events_rx) + // } + + + // fn album_expectations_1() -> (AlbumMeta, Vec>) { + // let album_1 = COLLECTION[1].albums[0].meta.clone(); + // let album_4 = COLLECTION[1].albums[3].meta.clone(); + + // let album_match_1_1 = Match::new(100, album_1.clone()); + // let album_match_1_2 = Match::new(50, album_4.clone()); + // let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()]; + + // (album_1, matches_1) + // } + + // fn album_expectations_4() -> (AlbumMeta, Vec>) { + // let album_1 = COLLECTION[1].albums[0].meta.clone(); + // let album_4 = COLLECTION[1].albums[3].meta.clone(); + + // let album_match_4_1 = Match::new(100, album_4.clone()); + // let album_match_4_2 = Match::new(30, album_1.clone()); + // let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()]; + + // (album_4, matches_4) + // } + + // fn search_release_group_expectation( + // api: &mut MockIMusicBrainz, + // seq: &mut Sequence, + // arid: &Mbid, + // album: &AlbumMeta, + // matches: &[Match], + // ) { + // let result = Ok(matches.to_owned()); + // api.expect_search_release_group() + // .with(predicate::eq(arid.clone()), predicate::eq(album.clone())) + // .times(1) + // .in_sequence(seq) + // .return_once(|_, _| result); + // } + + // #[test] + // fn fetch_albums() { + // let mut mb_api = MockIMusicBrainz::new(); + + // let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + + // let (album_1, matches_1) = album_expectations_1(); + // let (album_4, matches_4) = album_expectations_4(); + + // // Other albums have an MBID and so they will be skipped. + // let mut seq = Sequence::new(); + + // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); + // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4); + + // let music_hoard = music_hoard(COLLECTION.to_owned()); + // let (events_tx, events_rx) = event_channel(); + // let inner = AppInner::new(music_hoard, mb_api, events_tx); + + // let (fetch_tx, fetch_rx) = mpsc::channel(); + // // Use the second artist for this test. + // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); + // handle.join().unwrap(); + + // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); + // let result = fetch_rx.try_recv().unwrap(); + // let expected = Ok(MatchStateInfo::Album(AlbumMatches { + // matching: album_1.clone(), + // list: matches_1.iter().cloned().map(Into::into).collect(), + // })); + // assert_eq!(result, expected); + + // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); + // let result = fetch_rx.try_recv().unwrap(); + // let expected = Ok(MatchStateInfo::Album(AlbumMatches { + // matching: album_4.clone(), + // list: matches_4.iter().cloned().map(Into::into).collect(), + // })); + // assert_eq!(result, expected); + // } + + // fn artist_expectations() -> (ArtistMeta, Vec>) { + // let artist = COLLECTION[3].meta.clone(); + + // let artist_match_1 = Match::new(100, artist.clone()); + // let artist_match_2 = Match::new(50, artist.clone()); + // let matches = vec![artist_match_1.clone(), artist_match_2.clone()]; + + // (artist, matches) + // } + + // fn search_artist_expectation( + // api: &mut MockIMusicBrainz, + // seq: &mut Sequence, + // artist: &ArtistMeta, + // matches: &[Match], + // ) { + // let result = Ok(matches.to_owned()); + // api.expect_search_artist() + // .with(predicate::eq(artist.clone())) + // .times(1) + // .in_sequence(seq) + // .return_once(|_| result); + // } + + // #[test] + // fn fetch_artist() { + // let mut mb_api = MockIMusicBrainz::new(); + + // let (artist, matches) = artist_expectations(); + // let mut seq = Sequence::new(); + // search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + + // let music_hoard = music_hoard(COLLECTION.to_owned()); + // let (events_tx, events_rx) = event_channel(); + // let inner = AppInner::new(music_hoard, mb_api, events_tx); + + // let (fetch_tx, fetch_rx) = mpsc::channel(); + // // Use the fourth artist for this test as they have no MBID. + // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); + // handle.join().unwrap(); + + // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); + // let result = fetch_rx.try_recv().unwrap(); + // let expected = Ok(MatchStateInfo::Artist(ArtistMatches { + // matching: artist.clone(), + // list: matches.iter().cloned().map(Into::into).collect(), + // })); + // assert_eq!(result, expected); + // } + + // #[test] + // fn fetch_artist_fetch_disconnect() { + // let mut mb_api = MockIMusicBrainz::new(); + + // let (artist, matches) = artist_expectations(); + // let mut seq = Sequence::new(); + // search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + + // let music_hoard = music_hoard(COLLECTION.to_owned()); + // let (events_tx, events_rx) = event_channel(); + // let inner = AppInner::new(music_hoard, mb_api, events_tx); + + // let (fetch_tx, _) = mpsc::channel(); + // // Use the fourth artist for this test as they have no MBID. + // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); + // handle.join().unwrap(); + + // assert!(events_rx.try_recv().is_err()); + // } + + // #[test] + // fn fetch_albums_event_disconnect() { + // let mut mb_api = MockIMusicBrainz::new(); + + // let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + + // let (album_1, matches_1) = album_expectations_1(); + + // let mut seq = Sequence::new(); + // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); + + // let music_hoard = music_hoard(COLLECTION.to_owned()); + // let (events_tx, _) = event_channel(); + // let inner = AppInner::new(music_hoard, mb_api, events_tx); + + // let (fetch_tx, fetch_rx) = mpsc::channel(); + // // Use the second artist for this test. + // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); + // handle.join().unwrap(); + + // let result = fetch_rx.try_recv().unwrap(); + // let expected = Ok(MatchStateInfo::Album(AlbumMatches { + // matching: album_1.clone(), + // list: matches_1.iter().cloned().map(Into::into).collect(), + // })); + // assert_eq!(result, expected); + + // assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected); + // } + +} diff --git a/src/tui/lib/interface/musicbrainz/daemon/mod.rs b/src/tui/lib/interface/musicbrainz/daemon/mod.rs index 91199ee..1107d00 100644 --- a/src/tui/lib/interface/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/interface/musicbrainz/daemon/mod.rs @@ -4,6 +4,9 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz:: use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as MbApiError}; +#[cfg(test)] +use mockall::automock; + pub enum Error { EventChannelDisconnected, JobChannelDisconnected, @@ -21,6 +24,7 @@ impl fmt::Display for Error { pub type MbApiResult = Result; pub type ResultSender = mpsc::Sender; +#[cfg_attr(test, automock)] pub trait IMbJobSender { fn submit_background_job( &self, @@ -29,19 +33,23 @@ pub trait IMbJobSender { ) -> Result<(), Error>; } +#[derive(Debug, PartialEq, Eq)] pub enum MbParams { Search(SearchParams), } +#[derive(Debug, PartialEq, Eq)] pub enum SearchParams { Artist(SearchArtistParams), ReleaseGroup(SearchReleaseGroupParams), } +#[derive(Debug, PartialEq, Eq)] pub struct SearchArtistParams { pub artist: ArtistMeta, } +#[derive(Debug, PartialEq, Eq)] pub struct SearchReleaseGroupParams { pub arid: Mbid, pub album: AlbumMeta, diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 10c3661..2a2fd80 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -173,154 +173,149 @@ impl Tui { #[cfg(test)] mod testmod; -// #[cfg(test)] -// mod tests { -// use std::{io, thread}; +#[cfg(test)] +mod tests { + use std::{io, thread}; -// use event::EventSender; -// use lib::interface::musicbrainz::api::MockIMusicBrainz; -// use ratatui::{backend::TestBackend, Terminal}; + use lib::interface::musicbrainz::daemon::MockIMbJobSender; + use ratatui::{backend::TestBackend, Terminal}; -// use musichoard::collection::Collection; + use musichoard::collection::Collection; -// use crate::tui::{ -// app::App, handler::MockIEventHandler, lib::MockIMusicHoard, listener::MockIEventListener, -// ui::Ui, -// }; + use crate::tui::{ + app::App, handler::MockIEventHandler, lib::MockIMusicHoard, listener::MockIEventListener, + ui::Ui, + }; -// use super::*; -// use testmod::COLLECTION; + use super::*; + use testmod::COLLECTION; -// 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() + } -// fn music_hoard(collection: Collection) -> MockIMusicHoard { -// let mut music_hoard = MockIMusicHoard::new(); + fn music_hoard(collection: Collection) -> MockIMusicHoard { + let mut music_hoard = MockIMusicHoard::new(); -// music_hoard.expect_reload_database().returning(|| Ok(())); -// music_hoard.expect_rescan_library().returning(|| Ok(())); -// music_hoard.expect_get_collection().return_const(collection); + music_hoard.expect_reload_database().returning(|| Ok(())); + music_hoard.expect_rescan_library().returning(|| Ok(())); + music_hoard.expect_get_collection().return_const(collection); -// music_hoard -// } + music_hoard + } -// fn events() -> EventSender { -// EventChannel::new().sender() -// } + fn app(collection: Collection) -> App { + App::new(music_hoard(collection), MockIMbJobSender::new()) + } -// fn app(collection: Collection) -> App { -// App::new(music_hoard(collection), MockIMusicBrainz::new(), events()) -// } + fn listener() -> MockIEventListener { + let mut listener = MockIEventListener::new(); + listener.expect_spawn().return_once(|| { + thread::spawn(|| { + thread::park(); + EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")) + }) + }); + listener + } -// fn listener() -> MockIEventListener { -// let mut listener = MockIEventListener::new(); -// listener.expect_spawn().return_once(|| { -// thread::spawn(|| { -// thread::park(); -// EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked")) -// }) -// }); -// listener -// } + fn handler() -> MockIEventHandler { + let mut handler = MockIEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|app: App| Ok(app.force_quit())); + handler + } -// fn handler() -> MockIEventHandler { -// let mut handler = MockIEventHandler::new(); -// handler -// .expect_handle_next_event() -// .return_once(|app: App| Ok(app.force_quit())); -// handler -// } + #[test] + fn run() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); + let ui = Ui; -// #[test] -// fn run() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui; + 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; -// #[test] -// fn event_error() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui; + let listener = listener(); -// let listener = listener(); + let mut handler = MockIEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let mut handler = MockIEventHandler::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::Recv; + assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); + } -// let error = EventError::Recv; -// assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); -// } + #[test] + fn listener_error() { + let terminal = terminal(); + let app = app(COLLECTION.to_owned()); + let ui = Ui; -// #[test] -// fn listener_error() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui; + 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 = MockIEventListener::new(); + listener.expect_spawn().return_once(|| listener_handle); -// let mut listener = MockIEventListener::new(); -// listener.expect_spawn().return_once(|| listener_handle); + let mut handler = MockIEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let mut handler = MockIEventHandler::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; -// #[test] -// fn listener_panic() { -// let terminal = terminal(); -// let app = app(COLLECTION.to_owned()); -// let ui = Ui; + 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 = MockIEventListener::new(); + listener.expect_spawn().return_once(|| listener_handle); -// let mut listener = MockIEventListener::new(); -// listener.expect_spawn().return_once(|| listener_handle); + let mut handler = MockIEventHandler::new(); + handler + .expect_handle_next_event() + .return_once(|_| Err(EventError::Recv)); -// let mut handler = MockIEventHandler::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 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 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!("{:?}", io_err).is_empty()); -// assert!(!format!("{:?}", event_err).is_empty()); -// assert!(!format!("{:?}", listener_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/mod.rs b/src/tui/ui/mod.rs index a2fc30f..fbce47c 100644 --- a/src/tui/ui/mod.rs +++ b/src/tui/ui/mod.rs @@ -198,232 +198,232 @@ impl IUi for Ui { } } -// #[cfg(test)] -// mod tests { -// use musichoard::collection::{ -// album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, -// artist::{Artist, ArtistId, ArtistMeta}, -// }; +#[cfg(test)] +mod tests { + use musichoard::collection::{ + album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, + artist::{Artist, ArtistId, ArtistMeta}, + }; -// use crate::tui::{ -// app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic}, -// lib::interface::musicbrainz::api::Match, -// testmod::COLLECTION, -// tests::terminal, -// }; + use crate::tui::{ + app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic}, + lib::interface::musicbrainz::api::Match, + testmod::COLLECTION, + tests::terminal, + }; -// use super::*; + use super::*; -// // Automock does not support returning types with generic lifetimes. -// impl<'app> IAppAccess for AppPublic<'app> { -// fn get(&mut self) -> AppPublic { -// AppPublic { -// inner: AppPublicInner { -// collection: self.inner.collection, -// selection: self.inner.selection, -// }, -// state: match self.state { -// AppState::Browse(()) => AppState::Browse(()), -// AppState::Info(()) => AppState::Info(()), -// AppState::Reload(()) => AppState::Reload(()), -// AppState::Search(s) => AppState::Search(s), -// AppState::Fetch(()) => AppState::Fetch(()), -// AppState::Match(ref mut m) => AppState::Match(MatchStatePublic { -// info: m.info, -// state: m.state, -// }), -// AppState::Error(s) => AppState::Error(s), -// AppState::Critical(s) => AppState::Critical(s), -// }, -// input: self.input, -// } -// } -// } + // Automock does not support returning types with generic lifetimes. + impl<'app> IAppAccess for AppPublic<'app> { + fn get(&mut self) -> AppPublic { + AppPublic { + inner: AppPublicInner { + collection: self.inner.collection, + selection: self.inner.selection, + }, + state: match self.state { + AppState::Browse(()) => AppState::Browse(()), + AppState::Info(()) => AppState::Info(()), + AppState::Reload(()) => AppState::Reload(()), + AppState::Search(s) => AppState::Search(s), + AppState::Fetch(()) => AppState::Fetch(()), + AppState::Match(ref mut m) => AppState::Match(MatchStatePublic { + info: m.info, + state: m.state, + }), + AppState::Error(s) => AppState::Error(s), + AppState::Critical(s) => AppState::Critical(s), + }, + input: self.input, + } + } + } -// fn public_inner<'app>( -// collection: &'app Collection, -// selection: &'app mut Selection, -// ) -> AppPublicInner<'app> { -// AppPublicInner { -// collection, -// selection, -// } -// } + fn public_inner<'app>( + collection: &'app Collection, + selection: &'app mut Selection, + ) -> AppPublicInner<'app> { + AppPublicInner { + collection, + selection, + } + } -// fn artist_matches(matching: ArtistMeta, list: Vec>) -> MatchStateInfo { -// let mut list: Vec> = list.into_iter().map(Into::into).collect(); -// list.push(MatchOption::CannotHaveMbid); -// list.push(MatchOption::ManualInputMbid); -// MatchStateInfo::artist(matching, list) -// } + fn artist_matches(matching: ArtistMeta, list: Vec>) -> MatchStateInfo { + let mut list: Vec> = list.into_iter().map(Into::into).collect(); + list.push(MatchOption::CannotHaveMbid); + list.push(MatchOption::ManualInputMbid); + MatchStateInfo::artist(matching, list) + } -// fn album_matches(matching: AlbumMeta, list: Vec>) -> MatchStateInfo { -// let mut list: Vec> = list.into_iter().map(Into::into).collect(); -// list.push(MatchOption::CannotHaveMbid); -// list.push(MatchOption::ManualInputMbid); -// MatchStateInfo::album(matching, list) -// } + fn album_matches(matching: AlbumMeta, list: Vec>) -> MatchStateInfo { + let mut list: Vec> = list.into_iter().map(Into::into).collect(); + list.push(MatchOption::CannotHaveMbid); + list.push(MatchOption::ManualInputMbid); + MatchStateInfo::album(matching, list) + } -// fn draw_test_suite(collection: &Collection, selection: &mut Selection) { -// let mut terminal = terminal(); + fn draw_test_suite(collection: &Collection, selection: &mut Selection) { + let mut terminal = terminal(); -// let mut app = AppPublic { -// inner: public_inner(collection, selection), -// state: AppState::Browse(()), -// input: None, -// }; -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + let mut app = AppPublic { + inner: public_inner(collection, selection), + state: AppState::Browse(()), + input: None, + }; + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Info(()); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + app.state = AppState::Info(()); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Reload(()); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + app.state = AppState::Reload(()); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Search(""); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + app.state = AppState::Search(""); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Fetch(()); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + app.state = AppState::Fetch(()); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Error("get rekt scrub"); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + app.state = AppState::Error("get rekt scrub"); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// app.state = AppState::Critical("get critically rekt scrub"); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// } + app.state = AppState::Critical("get critically rekt scrub"); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + } -// #[test] -// fn empty() { -// let artists: Vec = vec![]; -// let mut selection = Selection::new(&artists); + #[test] + fn empty() { + let artists: Vec = vec![]; + let mut selection = Selection::new(&artists); -// draw_test_suite(&artists, &mut selection); -// } + draw_test_suite(&artists, &mut selection); + } -// #[test] -// fn empty_album() { -// let mut artists: Vec = vec![Artist::new(ArtistId::new("An artist"))]; -// artists[0] -// .albums -// .push(Album::new("An album", AlbumDate::default(), None, vec![])); -// let mut selection = Selection::new(&artists); + #[test] + fn empty_album() { + let mut artists: Vec = vec![Artist::new(ArtistId::new("An artist"))]; + artists[0] + .albums + .push(Album::new("An album", AlbumDate::default(), None, vec![])); + let mut selection = Selection::new(&artists); -// draw_test_suite(&artists, &mut selection); -// } + draw_test_suite(&artists, &mut selection); + } -// #[test] -// fn collection() { -// let artists = &COLLECTION; -// let mut selection = Selection::new(artists); + #[test] + fn collection() { + let artists = &COLLECTION; + let mut selection = Selection::new(artists); -// draw_test_suite(artists, &mut selection); + draw_test_suite(artists, &mut selection); -// // Change the track (which has a different track format). -// selection.increment_category(); -// selection.increment_category(); -// selection.increment_selection(artists, Delta::Line); + // Change the track (which has a different track format). + selection.increment_category(); + selection.increment_category(); + selection.increment_selection(artists, Delta::Line); -// draw_test_suite(artists, &mut selection); + draw_test_suite(artists, &mut selection); -// // Change the artist (which has a multi-link entry). -// selection.decrement_category(); -// selection.decrement_category(); -// selection.increment_selection(artists, Delta::Line); + // Change the artist (which has a multi-link entry). + selection.decrement_category(); + selection.decrement_category(); + selection.increment_selection(artists, Delta::Line); -// draw_test_suite(artists, &mut selection); -// } + draw_test_suite(artists, &mut selection); + } -// #[test] -// fn draw_empty_matches() { -// let collection = &COLLECTION; -// let mut selection = Selection::new(collection); + #[test] + fn draw_empty_matches() { + let collection = &COLLECTION; + let mut selection = Selection::new(collection); -// let mut terminal = terminal(); + let mut terminal = terminal(); -// let mut widget_state = WidgetState::default(); + let mut widget_state = WidgetState::default(); -// let mut app = AppPublic { -// inner: public_inner(collection, &mut selection), -// state: AppState::Match(MatchStatePublic { -// info: None, -// state: &mut widget_state, -// }), -// input: None, -// }; -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// } + let mut app = AppPublic { + inner: public_inner(collection, &mut selection), + state: AppState::Match(MatchStatePublic { + info: None, + state: &mut widget_state, + }), + input: None, + }; + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + } -// #[test] -// fn draw_artist_matches() { -// let collection = &COLLECTION; -// let mut selection = Selection::new(collection); + #[test] + fn draw_artist_matches() { + let collection = &COLLECTION; + let mut selection = Selection::new(collection); -// let mut terminal = terminal(); + let mut terminal = terminal(); -// let artist = ArtistMeta::new(ArtistId::new("an artist")); -// let artist_match = Match { -// score: 80, -// item: artist.clone(), -// disambiguation: None, -// }; -// let list = vec![artist_match.clone(), artist_match.clone()]; -// let artist_matches = artist_matches(artist, list); + let artist = ArtistMeta::new(ArtistId::new("an artist")); + let artist_match = Match { + score: 80, + item: artist.clone(), + disambiguation: None, + }; + let list = vec![artist_match.clone(), artist_match.clone()]; + let artist_matches = artist_matches(artist, list); -// let mut widget_state = WidgetState::default(); -// widget_state.list.select(Some(0)); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); -// let mut app = AppPublic { -// inner: public_inner(collection, &mut selection), -// state: AppState::Match(MatchStatePublic { -// info: Some(&artist_matches), -// state: &mut widget_state, -// }), -// input: None, -// }; -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + let mut app = AppPublic { + inner: public_inner(collection, &mut selection), + state: AppState::Match(MatchStatePublic { + info: Some(&artist_matches), + state: &mut widget_state, + }), + input: None, + }; + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// let input = tui_input::Input::default(); -// app.input = Some(&input); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// } + let input = tui_input::Input::default(); + app.input = Some(&input); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + } -// #[test] -// fn draw_album_matches() { -// let collection = &COLLECTION; -// let mut selection = Selection::new(collection); + #[test] + fn draw_album_matches() { + let collection = &COLLECTION; + let mut selection = Selection::new(collection); -// let mut terminal = terminal(); + let mut terminal = terminal(); -// let album = AlbumMeta::new( -// AlbumId::new("An Album"), -// AlbumDate::new(Some(1990), Some(5), None), -// Some(AlbumPrimaryType::Album), -// vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], -// ); -// let album_match = Match { -// score: 80, -// item: album.clone(), -// disambiguation: None, -// }; -// let list = vec![album_match.clone(), album_match.clone()]; -// let album_matches = album_matches(album, list); + let album = AlbumMeta::new( + AlbumId::new("An Album"), + AlbumDate::new(Some(1990), Some(5), None), + Some(AlbumPrimaryType::Album), + vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], + ); + let album_match = Match { + score: 80, + item: album.clone(), + disambiguation: None, + }; + let list = vec![album_match.clone(), album_match.clone()]; + let album_matches = album_matches(album, list); -// let mut widget_state = WidgetState::default(); -// widget_state.list.select(Some(0)); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); -// let mut app = AppPublic { -// inner: public_inner(collection, &mut selection), -// state: AppState::Match(MatchStatePublic { -// info: Some(&album_matches), -// state: &mut widget_state, -// }), -// input: None, -// }; -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + let mut app = AppPublic { + inner: public_inner(collection, &mut selection), + state: AppState::Match(MatchStatePublic { + info: Some(&album_matches), + state: &mut widget_state, + }), + input: None, + }; + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// let input = tui_input::Input::default(); -// app.input = Some(&input); -// terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); -// } -// } + let input = tui_input::Input::default(); + app.input = Some(&input); + terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); + } +}