From c6a6d21199e75030a3454ae976d3319d555ad828 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Fri, 6 Sep 2024 22:25:35 +0200 Subject: [PATCH] Unit tests for matches --- src/tui/app/machine/fetch.rs | 347 +++++++++++++++++++++++++++------ src/tui/app/machine/matches.rs | 287 ++++++++++++--------------- 2 files changed, 407 insertions(+), 227 deletions(-) diff --git a/src/tui/app/machine/fetch.rs b/src/tui/app/machine/fetch.rs index fb48d1c..36f2620 100644 --- a/src/tui/app/machine/fetch.rs +++ b/src/tui/app/machine/fetch.rs @@ -27,6 +27,17 @@ pub struct AppFetch { fetch_rx: FetchReceiver, } +impl AppFetch { + pub fn new(fetch_rx: FetchReceiver) -> Self { + AppFetch { fetch_rx } + } +} + +pub type FetchError = MbError; +pub type FetchResult = Result; +pub type FetchSender = mpsc::Sender; +pub type FetchReceiver = mpsc::Receiver; + impl AppMachine { fn fetch(inner: AppInner, state: AppFetch) -> Self { AppMachine { inner, state } @@ -57,15 +68,14 @@ impl AppMachine { } }; - let fetch = AppFetch { fetch_rx }; + let fetch = AppFetch::new(fetch_rx); AppMachine::app_fetch_next(inner, fetch, true) } pub fn app_fetch_next(inner: AppInner, fetch: AppFetch, first: bool) -> App { match fetch.fetch_rx.try_recv() { Ok(fetch_result) => match fetch_result { - Ok(mut next_match) => { - next_match.push_cannot_have_mbid(); + Ok(next_match) => { let current = Some(next_match); AppMachine::matches(inner, AppMatches::new(current, fetch)).into() } @@ -76,7 +86,7 @@ impl AppMachine { Err(recv_err) => match recv_err { TryRecvError::Disconnected => { if first { - AppMachine::matches(inner, AppMatches::empty(fetch)).into() + AppMachine::matches(inner, AppMatches::new(None, fetch)).into() } else { AppMachine::browse(inner).into() } @@ -85,65 +95,7 @@ impl AppMachine { }, } } -} -impl From> for App { - fn from(machine: AppMachine) -> Self { - AppState::Fetch(machine) - } -} - -impl<'a> From<&'a mut AppMachine> for AppPublic<'a> { - fn from(machine: &'a mut AppMachine) -> Self { - AppPublic { - inner: (&mut machine.inner).into(), - state: AppState::Fetch(()), - } - } -} - -impl IAppInteractFetch for AppMachine { - type APP = App; - - fn abort(self) -> Self::APP { - AppMachine::browse(self.inner).into() - } - - fn no_op(self) -> Self::APP { - self.into() - } -} - -impl IAppEventFetch for AppMachine { - type APP = App; - - fn fetch_result_ready(self) -> Self::APP { - Self::app_fetch_next(self.inner, self.state, false) - } -} - -type FetchError = MbError; -type FetchResult = Result; -type FetchSender = mpsc::Sender; -type FetchReceiver = mpsc::Receiver; - -trait IAppInteractFetchPrivate { - fn fetch_artist( - musicbrainz: Arc>, - fetch_tx: FetchSender, - events: EventSender, - artist: ArtistMeta, - ); - fn fetch_albums( - musicbrainz: Arc>, - fetch_tx: FetchSender, - events: EventSender, - arid: Mbid, - albums: Vec, - ); -} - -impl IAppInteractFetchPrivate for AppMachine { fn fetch_artist( musicbrainz: Arc>, fetch_tx: FetchSender, @@ -192,3 +144,274 @@ impl IAppInteractFetchPrivate for AppMachine { } } } + +impl From> for App { + fn from(machine: AppMachine) -> Self { + AppState::Fetch(machine) + } +} + +impl<'a> From<&'a mut AppMachine> for AppPublic<'a> { + fn from(machine: &'a mut AppMachine) -> Self { + AppPublic { + inner: (&mut machine.inner).into(), + state: AppState::Fetch(()), + } + } +} + +impl IAppInteractFetch for AppMachine { + type APP = App; + + fn abort(self) -> Self::APP { + AppMachine::browse(self.inner).into() + } + + fn no_op(self) -> Self::APP { + self.into() + } +} + +impl IAppEventFetch for AppMachine { + type APP = App; + + fn fetch_result_ready(self) -> Self::APP { + Self::app_fetch_next(self.inner, self.state, false) + } +} + +#[cfg(test)] +mod tests { + + // use std::sync::mpsc; + + // use musichoard::collection::{ + // album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, + // artist::{ArtistId, ArtistMeta}, + // }; + + // use crate::tui::{ + // app::{ + // machine::tests::{inner, music_hoard}, + // IAppAccess, + // }, + // lib::interface::musicbrainz::Match, + // }; + + // use super::*; + + // impl Match { + // pub fn new(score: u8, item: T) -> Self { + // Match { + // score, + // item, + // disambiguation: None, + // } + // } + // } + + // fn artist_matches_info_vec() -> Vec { + // let artist_1 = ArtistMeta::new(ArtistId::new("Artist 1")); + + // let artist_1_1 = artist_1.clone(); + // let artist_match_1_1 = Match::new(100, artist_1_1); + + // let artist_1_2 = artist_1.clone(); + // let mut artist_match_1_2 = Match::new(100, artist_1_2); + // artist_match_1_2.disambiguation = Some(String::from("some disambiguation")); + + // let list = vec![artist_match_1_1.clone(), artist_match_1_2.clone()]; + // let matches_info_1 = AppMatchesInfo::artist(artist_1.clone(), list); + + // let artist_2 = ArtistMeta::new(ArtistId::new("Artist 2")); + + // let artist_2_1 = artist_1.clone(); + // let album_match_2_1 = Match::new(100, artist_2_1); + + // let list = vec![album_match_2_1.clone()]; + // let matches_info_2 = AppMatchesInfo::artist(artist_2.clone(), list); + + // vec![matches_info_1, matches_info_2] + // } + + // fn album_matches_info_vec() -> Vec { + // let album_1 = AlbumMeta::new( + // AlbumId::new("Album 1"), + // AlbumDate::new(Some(1990), Some(5), None), + // Some(AlbumPrimaryType::Album), + // vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], + // ); + + // let album_1_1 = album_1.clone(); + // let album_match_1_1 = Match::new(100, album_1_1); + + // let mut album_1_2 = album_1.clone(); + // album_1_2.id.title.push_str(" extra title part"); + // album_1_2.secondary_types.pop(); + // let album_match_1_2 = Match::new(100, album_1_2); + + // let list = vec![album_match_1_1.clone(), album_match_1_2.clone()]; + // let matches_info_1 = AppMatchesInfo::album(album_1.clone(), list); + + // let album_2 = AlbumMeta::new( + // AlbumId::new("Album 2"), + // AlbumDate::new(Some(2001), None, None), + // Some(AlbumPrimaryType::Album), + // vec![], + // ); + + // let album_2_1 = album_1.clone(); + // let album_match_2_1 = Match::new(100, album_2_1); + + // let list = vec![album_match_2_1.clone()]; + // let matches_info_2 = AppMatchesInfo::album(album_2.clone(), list); + + // vec![matches_info_1, matches_info_2] + // } + + // fn receiver(matches_info_vec: Vec) -> FetchReceiver { + // let (tx, rx) = mpsc::channel(); + // for matches_info in matches_info_vec.into_iter() { + // tx.send(Ok(matches_info)).unwrap(); + // } + // rx + // } + + // fn push_cannot_have_mbid(matches_info_vec: &mut [AppMatchesInfo]) { + // for matches_info in matches_info_vec.iter_mut() { + // matches_info.push_cannot_have_mbid(); + // } + // } + + // #[test] + // fn create_empty() { + // let matches = AppMachine::matches(inner(music_hoard(vec![])), AppMatches::empty(fetch())); + + // let widget_state = WidgetState::default(); + + // assert_eq!(matches.state.current, None); + // assert_eq!(matches.state.state, widget_state); + + // let mut app = matches.no_op(); + // let public = app.get(); + // let public_matches = public.state.unwrap_matches(); + + // assert_eq!(public_matches.matches, None); + // assert_eq!(public_matches.state, &widget_state); + // } + + // #[test] + // fn create_nonempty() { + // let mut matches_info_vec = album_matches_info_vec(); + // let matches = AppMachine::app_matches( + // inner(music_hoard(vec![])), + // receiver(matches_info_vec.clone()), + // ) + // .unwrap_matches(); + // push_cannot_have_mbid(&mut matches_info_vec); + + // let mut widget_state = WidgetState::default(); + // widget_state.list.select(Some(0)); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state, widget_state); + + // let mut app = matches.no_op(); + // let public = app.get(); + // let public_matches = public.state.unwrap_matches(); + + // assert_eq!(public_matches.matches, Some(&matches_info_vec[0])); + // assert_eq!(public_matches.state, &widget_state); + // } + + // fn matches_flow(mut matches_info_vec: Vec) { + // let matches = AppMachine::app_matches( + // inner(music_hoard(vec![])), + // receiver(matches_info_vec.clone()), + // ) + // .unwrap_matches(); + // push_cannot_have_mbid(&mut matches_info_vec); + + // let mut widget_state = WidgetState::default(); + // widget_state.list.select(Some(0)); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state, widget_state); + + // let matches = matches.prev_match().unwrap_matches(); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state.list.selected(), Some(0)); + + // let matches = matches.next_match().unwrap_matches(); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state.list.selected(), Some(1)); + + // // Next is CannotHaveMBID + // let matches = matches.next_match().unwrap_matches(); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state.list.selected(), Some(2)); + + // let matches = matches.next_match().unwrap_matches(); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state.list.selected(), Some(2)); + + // let matches = matches.select().unwrap_matches(); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[1])); + // assert_eq!(matches.state.state.list.selected(), Some(0)); + + // // And it's done + // matches.select().unwrap_browse(); + // } + + // #[test] + // fn artist_matches_flow() { + // matches_flow(artist_matches_info_vec()); + // } + + // #[test] + // fn album_matches_flow() { + // matches_flow(album_matches_info_vec()); + // } + + // #[test] + // fn matches_abort() { + // let mut matches_info_vec = album_matches_info_vec(); + // let matches = AppMachine::app_matches( + // inner(music_hoard(vec![])), + // receiver(matches_info_vec.clone()), + // ) + // .unwrap_matches(); + // push_cannot_have_mbid(&mut matches_info_vec); + + // let mut widget_state = WidgetState::default(); + // widget_state.list.select(Some(0)); + + // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); + // assert_eq!(matches.state.state, widget_state); + + // matches.abort().unwrap_browse(); + // } + + // #[test] + // fn matches_select_empty() { + // let matches = + // AppMachine::app_matches(inner(music_hoard(vec![])), receiver(vec![])).unwrap_matches(); + + // assert_eq!(matches.state.current, None); + + // matches.select().unwrap_browse(); + // } + + // #[test] + // fn no_op() { + // let matches = + // AppMachine::app_matches(inner(music_hoard(vec![])), receiver(vec![])).unwrap_matches(); + // let app = matches.no_op(); + // app.unwrap_matches(); + // } +} diff --git a/src/tui/app/machine/matches.rs b/src/tui/app/machine/matches.rs index 41e5ba3..cd80d99 100644 --- a/src/tui/app/machine/matches.rs +++ b/src/tui/app/machine/matches.rs @@ -51,14 +51,11 @@ pub struct AppMatches { } impl AppMatches { - pub fn empty(fetch: AppFetch) -> Self { - Self::new(None, fetch) - } - - pub fn new(current: Option, fetch: AppFetch) -> Self { + pub fn new(mut current: Option, fetch: AppFetch) -> Self { let mut state = WidgetState::default(); - if current.is_some() { + if let Some(ref mut current) = current { state.list.select(Some(0)); + current.push_cannot_have_mbid(); } AppMatches { current, @@ -160,209 +157,169 @@ mod tests { } } - fn artist_matches_info_vec() -> Vec { - let artist_1 = ArtistMeta::new(ArtistId::new("Artist 1")); + fn artist_match() -> AppMatchesInfo { + let artist = ArtistMeta::new(ArtistId::new("Artist")); - let artist_1_1 = artist_1.clone(); - let artist_match_1_1 = Match::new(100, artist_1_1); + let artist_1 = artist.clone(); + let artist_match_1 = Match::new(100, artist_1); - let artist_1_2 = artist_1.clone(); - let mut artist_match_1_2 = Match::new(100, artist_1_2); - artist_match_1_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_1.clone(), artist_match_1_2.clone()]; - let matches_info_1 = AppMatchesInfo::artist(artist_1.clone(), list); - - let artist_2 = ArtistMeta::new(ArtistId::new("Artist 2")); - - let artist_2_1 = artist_1.clone(); - let album_match_2_1 = Match::new(100, artist_2_1); - - let list = vec![album_match_2_1.clone()]; - let matches_info_2 = AppMatchesInfo::artist(artist_2.clone(), list); - - vec![matches_info_1, matches_info_2] + let list = vec![artist_match_1.clone(), artist_match_2.clone()]; + AppMatchesInfo::artist(artist, list) } - fn album_matches_info_vec() -> Vec { - let album_1 = AlbumMeta::new( - AlbumId::new("Album 1"), + fn album_match() -> AppMatchesInfo { + 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_1 = album_1.clone(); - let album_match_1_1 = Match::new(100, album_1_1); + let album_1 = album.clone(); + let album_match_1 = Match::new(100, album_1); - let mut album_1_2 = album_1.clone(); - album_1_2.id.title.push_str(" extra title part"); - album_1_2.secondary_types.pop(); - let album_match_1_2 = Match::new(100, album_1_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_1.clone(), album_match_1_2.clone()]; - let matches_info_1 = AppMatchesInfo::album(album_1.clone(), list); - - let album_2 = AlbumMeta::new( - AlbumId::new("Album 2"), - AlbumDate::new(Some(2001), None, None), - Some(AlbumPrimaryType::Album), - vec![], - ); - - let album_2_1 = album_1.clone(); - let album_match_2_1 = Match::new(100, album_2_1); - - let list = vec![album_match_2_1.clone()]; - let matches_info_2 = AppMatchesInfo::album(album_2.clone(), list); - - vec![matches_info_1, matches_info_2] + let list = vec![album_match_1.clone(), album_match_2.clone()]; + AppMatchesInfo::album(album, list) } - // fn receiver(matches_info_vec: Vec) -> FetchReceiver { - // let (tx, rx) = mpsc::channel(); - // for matches_info in matches_info_vec.into_iter() { - // tx.send(Ok(matches_info)).unwrap(); - // } - // rx - // } + fn fetch() -> AppFetch { + let (_, rx) = mpsc::channel(); + AppFetch::new(rx) + } - // fn push_cannot_have_mbid(matches_info_vec: &mut [AppMatchesInfo]) { - // for matches_info in matches_info_vec.iter_mut() { - // matches_info.push_cannot_have_mbid(); - // } - // } + fn matches(matches_info: Option) -> AppMatches { + AppMatches::new(matches_info, fetch()) + } - // #[test] - // fn create_empty() { - // let matches = - // AppMachine::app_matches(inner(music_hoard(vec![])), receiver(vec![])).unwrap_matches(); + #[test] + fn create_empty() { + let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(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 = matches.no_op(); - // let public = app.get(); - // let public_matches = public.state.unwrap_matches(); + let mut app = matches.no_op(); + let public = app.get(); + let public_matches = public.state.unwrap_matches(); - // assert_eq!(public_matches.matches, None); - // assert_eq!(public_matches.state, &widget_state); - // } + assert_eq!(public_matches.matches, None); + assert_eq!(public_matches.state, &widget_state); + } - // #[test] - // fn create_nonempty() { - // let mut matches_info_vec = album_matches_info_vec(); - // let matches = AppMachine::app_matches( - // inner(music_hoard(vec![])), - // receiver(matches_info_vec.clone()), - // ) - // .unwrap_matches(); - // push_cannot_have_mbid(&mut matches_info_vec); + #[test] + fn create_nonempty() { + let mut album_match = album_match(); + let matches = AppMachine::matches( + inner(music_hoard(vec![])), + matches(Some(album_match.clone())), + ); + album_match.push_cannot_have_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_vec[0])); - // 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 = matches.no_op(); - // let public = app.get(); - // let public_matches = public.state.unwrap_matches(); + let mut app = matches.no_op(); + let public = app.get(); + let public_matches = public.state.unwrap_matches(); - // assert_eq!(public_matches.matches, Some(&matches_info_vec[0])); - // assert_eq!(public_matches.state, &widget_state); - // } + assert_eq!(public_matches.matches, Some(&album_match)); + assert_eq!(public_matches.state, &widget_state); + } - // fn matches_flow(mut matches_info_vec: Vec) { - // let matches = AppMachine::app_matches( - // inner(music_hoard(vec![])), - // receiver(matches_info_vec.clone()), - // ) - // .unwrap_matches(); - // push_cannot_have_mbid(&mut matches_info_vec); + fn matches_flow(mut matches_info: AppMatchesInfo) { + // tx must exist for rx to return Empty rather than Disconnected. + #[allow(unused_variables)] + let (tx, rx) = mpsc::channel(); + let app_matches = AppMatches::new(Some(matches_info.clone()), AppFetch::new(rx)); - // let mut widget_state = WidgetState::default(); - // widget_state.list.select(Some(0)); + let matches = AppMachine::matches(inner(music_hoard(vec![])), app_matches); + matches_info.push_cannot_have_mbid(); - // assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); - // assert_eq!(matches.state.state, widget_state); + let mut widget_state = WidgetState::default(); + widget_state.list.select(Some(0)); - // let matches = matches.prev_match().unwrap_matches(); + 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_vec[0])); - // assert_eq!(matches.state.state.list.selected(), Some(0)); + let matches = matches.prev_match().unwrap_matches(); - // let matches = matches.next_match().unwrap_matches(); + 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_vec[0])); - // assert_eq!(matches.state.state.list.selected(), Some(1)); + let matches = matches.next_match().unwrap_matches(); - // // Next is CannotHaveMBID - // let matches = matches.next_match().unwrap_matches(); + 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_vec[0])); - // assert_eq!(matches.state.state.list.selected(), Some(2)); + // Next is CannotHaveMBID + let matches = matches.next_match().unwrap_matches(); - // let matches = matches.next_match().unwrap_matches(); + 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_vec[0])); - // assert_eq!(matches.state.state.list.selected(), Some(2)); + let matches = matches.next_match().unwrap_matches(); - // let matches = matches.select().unwrap_matches(); + 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_vec[1])); - // assert_eq!(matches.state.state.list.selected(), Some(0)); + // And it's done + matches.select().unwrap_fetch(); + } - // // And it's done - // matches.select().unwrap_browse(); - // } + #[test] + fn artist_matches_flow() { + matches_flow(artist_match()); + } - // #[test] - // fn artist_matches_flow() { - // matches_flow(artist_matches_info_vec()); - // } + #[test] + fn album_matches_flow() { + matches_flow(album_match()); + } - // #[test] - // fn album_matches_flow() { - // matches_flow(album_matches_info_vec()); - // } + #[test] + fn matches_abort() { + let mut album_match = album_match(); + let matches = AppMachine::matches( + inner(music_hoard(vec![])), + matches(Some(album_match.clone())), + ); + album_match.push_cannot_have_mbid(); - // #[test] - // fn matches_abort() { - // let mut matches_info_vec = album_matches_info_vec(); - // let matches = AppMachine::app_matches( - // inner(music_hoard(vec![])), - // receiver(matches_info_vec.clone()), - // ) - // .unwrap_matches(); - // push_cannot_have_mbid(&mut matches_info_vec); + 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(&matches_info_vec[0])); - // assert_eq!(matches.state.state, widget_state); + matches.abort().unwrap_browse(); + } - // matches.abort().unwrap_browse(); - // } + #[test] + fn matches_select_empty() { + // Note that what really matters in this test is actually that the transmit channel has + // disconnected and so the receive within AppFetch concludes there are no more matches. + let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None)); + matches.select().unwrap_browse(); + } - // #[test] - // fn matches_select_empty() { - // let matches = - // AppMachine::app_matches(inner(music_hoard(vec![])), receiver(vec![])).unwrap_matches(); - - // assert_eq!(matches.state.current, None); - - // matches.select().unwrap_browse(); - // } - - // #[test] - // fn no_op() { - // let matches = - // AppMachine::app_matches(inner(music_hoard(vec![])), receiver(vec![])).unwrap_matches(); - // let app = matches.no_op(); - // app.unwrap_matches(); - // } + #[test] + fn no_op() { + let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None)); + let app = matches.no_op(); + app.unwrap_matches(); + } }