Make fetch also fetch artist MBID if it is missing #201
@ -354,7 +354,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fetch_musicbrainz_api_error() {
|
fn fetch_musicbrainz_artist_api_error() {
|
||||||
|
let mut mb_api = Box::new(MockIMusicBrainz::new());
|
||||||
|
|
||||||
|
let error = Err(musicbrainz::Error::RateLimit);
|
||||||
|
|
||||||
|
mb_api
|
||||||
|
.expect_search_artist()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| error);
|
||||||
|
|
||||||
|
let browse = AppMachine::browse(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
|
||||||
|
|
||||||
|
// Use the fourth artist for this test as they have no MBID.
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
|
||||||
|
let app = browse.fetch_musicbrainz();
|
||||||
|
app.unwrap_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fetch_musicbrainz_album_api_error() {
|
||||||
let mut mb_api = Box::new(MockIMusicBrainz::new());
|
let mut mb_api = Box::new(MockIMusicBrainz::new());
|
||||||
|
|
||||||
let error = Err(musicbrainz::Error::RateLimit);
|
let error = Err(musicbrainz::Error::RateLimit);
|
||||||
|
@ -204,7 +204,10 @@ impl IAppInteractMatches for AppMachine<AppMatches> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use musichoard::collection::album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType};
|
use musichoard::collection::{
|
||||||
|
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
|
||||||
|
artist::ArtistId,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::tui::app::{
|
use crate::tui::app::{
|
||||||
machine::tests::{inner, music_hoard},
|
machine::tests::{inner, music_hoard},
|
||||||
@ -222,7 +225,31 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_info_vec() -> Vec<AppMatchesInfo> {
|
fn artist_matches_info_vec() -> Vec<AppMatchesInfo> {
|
||||||
|
let artist_1 = Artist::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.set_disambiguation("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 = Artist::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<AppMatchesInfo> {
|
||||||
let album_1 = Album::new(
|
let album_1 = Album::new(
|
||||||
AlbumId::new("Album 1"),
|
AlbumId::new("Album 1"),
|
||||||
AlbumDate::new(Some(1990), Some(5), None),
|
AlbumDate::new(Some(1990), Some(5), None),
|
||||||
@ -231,25 +258,15 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let album_1_1 = album_1.clone();
|
let album_1_1 = album_1.clone();
|
||||||
let album_match_1_1 = Match {
|
let album_match_1_1 = Match::new(100, album_1_1);
|
||||||
score: 100,
|
|
||||||
item: album_1_1,
|
|
||||||
disambiguation: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut album_1_2 = album_1.clone();
|
let mut album_1_2 = album_1.clone();
|
||||||
album_1_2.id.title.push_str(" extra title part");
|
album_1_2.id.title.push_str(" extra title part");
|
||||||
album_1_2.secondary_types.pop();
|
album_1_2.secondary_types.pop();
|
||||||
let album_match_1_2 = Match {
|
let album_match_1_2 = Match::new(100, album_1_2);
|
||||||
score: 100,
|
|
||||||
item: album_1_2,
|
|
||||||
disambiguation: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let matches_info_1 = AppMatchesInfo::Album(AppAlbumMatchesInfo {
|
let list = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||||
matching: album_1.clone(),
|
let matches_info_1 = AppMatchesInfo::album(album_1.clone(), list);
|
||||||
list: vec![album_match_1_1.clone(), album_match_1_2.clone()],
|
|
||||||
});
|
|
||||||
|
|
||||||
let album_2 = Album::new(
|
let album_2 = Album::new(
|
||||||
AlbumId::new("Album 2"),
|
AlbumId::new("Album 2"),
|
||||||
@ -259,16 +276,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let album_2_1 = album_1.clone();
|
let album_2_1 = album_1.clone();
|
||||||
let album_match_2_1 = Match {
|
let album_match_2_1 = Match::new(100, album_2_1);
|
||||||
score: 100,
|
|
||||||
item: album_2_1,
|
|
||||||
disambiguation: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let matches_info_2 = AppMatchesInfo::Album(AppAlbumMatchesInfo {
|
let list = vec![album_match_2_1.clone()];
|
||||||
matching: album_2.clone(),
|
let matches_info_2 = AppMatchesInfo::album(album_2.clone(), list);
|
||||||
list: vec![album_match_2_1.clone()],
|
|
||||||
});
|
|
||||||
|
|
||||||
vec![matches_info_1, matches_info_2]
|
vec![matches_info_1, matches_info_2]
|
||||||
}
|
}
|
||||||
@ -293,7 +304,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_nonempty() {
|
fn create_nonempty() {
|
||||||
let matches_info_vec = matches_info_vec();
|
let matches_info_vec = album_matches_info_vec();
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
||||||
|
|
||||||
let mut widget_state = WidgetState::default();
|
let mut widget_state = WidgetState::default();
|
||||||
@ -323,9 +334,7 @@ mod tests {
|
|||||||
assert_eq!(public_matches.state, &widget_state);
|
assert_eq!(public_matches.state, &widget_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn matches_flow(matches_info_vec: Vec<AppMatchesInfo>) {
|
||||||
fn matches_flow() {
|
|
||||||
let matches_info_vec = matches_info_vec();
|
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
||||||
|
|
||||||
let mut widget_state = WidgetState::default();
|
let mut widget_state = WidgetState::default();
|
||||||
@ -359,9 +368,19 @@ mod tests {
|
|||||||
matches.select().unwrap_browse();
|
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]
|
#[test]
|
||||||
fn matches_abort() {
|
fn matches_abort() {
|
||||||
let matches_info_vec = matches_info_vec();
|
let matches_info_vec = album_matches_info_vec();
|
||||||
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
|
||||||
|
|
||||||
let mut widget_state = WidgetState::default();
|
let mut widget_state = WidgetState::default();
|
||||||
|
@ -98,11 +98,11 @@ impl UiDisplay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_artist_matching_info(artist: &Artist) -> String {
|
pub fn display_artist_matching(artist: &Artist) -> String {
|
||||||
format!("Matching artist: {}", &artist.id.name)
|
format!("Matching artist: {}", &artist.id.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_album_matching_info(album: &Album) -> String {
|
pub fn display_album_matching(album: &Album) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Matching album: {} | {}",
|
"Matching album: {} | {}",
|
||||||
UiDisplay::display_album_date(&album.date),
|
UiDisplay::display_album_date(&album.date),
|
||||||
@ -110,25 +110,34 @@ impl UiDisplay {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_matching_nothing_info() -> &'static str {
|
pub fn display_nothing_matching() -> &'static str {
|
||||||
"Matching nothing"
|
"Matching nothing"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_matching_info(matches: Option<&AppPublicMatchesKind>) -> String {
|
pub fn display_matching_info(matches: Option<&AppPublicMatchesKind>) -> String {
|
||||||
match matches.as_ref() {
|
match matches.as_ref() {
|
||||||
Some(kind) => match kind {
|
Some(kind) => match kind {
|
||||||
AppPublicMatchesKind::Artist(m) => {
|
AppPublicMatchesKind::Artist(m) => UiDisplay::display_artist_matching(m.matching),
|
||||||
UiDisplay::display_artist_matching_info(m.matching)
|
AppPublicMatchesKind::Album(m) => UiDisplay::display_album_matching(m.matching),
|
||||||
}
|
|
||||||
AppPublicMatchesKind::Album(m) => {
|
|
||||||
UiDisplay::display_album_matching_info(m.matching)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => UiDisplay::display_matching_nothing_info().to_string(),
|
None => UiDisplay::display_nothing_matching().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_album_match_string(match_album: &Match<Album>) -> String {
|
pub fn display_artist_match(match_artist: &Match<Artist>) -> String {
|
||||||
|
format!(
|
||||||
|
"{}{} ({}%)",
|
||||||
|
&match_artist.item.id.name,
|
||||||
|
&match_artist
|
||||||
|
.disambiguation
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| format!(" ({d})"))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
match_artist.score,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_album_match(match_album: &Match<Album>) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{:010} | {} [{}] ({}%)",
|
"{:010} | {} [{}] ({}%)",
|
||||||
UiDisplay::display_album_date(&match_album.item.date),
|
UiDisplay::display_album_date(&match_album.item.date),
|
||||||
@ -140,17 +149,6 @@ impl UiDisplay {
|
|||||||
match_album.score,
|
match_album.score,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_artist_match_string(match_artist: &Match<Artist>) -> String {
|
|
||||||
let disambiguation_string = match match_artist.disambiguation.as_ref() {
|
|
||||||
Some(d) => format!(" ({d})"),
|
|
||||||
None => String::from(""),
|
|
||||||
};
|
|
||||||
format!(
|
|
||||||
"{}{} ({}%)",
|
|
||||||
&match_artist.item.id.name, &disambiguation_string, match_artist.score,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -26,19 +26,19 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
|||||||
|
|
||||||
fn empty(state: &'b mut WidgetState) -> Self {
|
fn empty(state: &'b mut WidgetState) -> Self {
|
||||||
MatchesState {
|
MatchesState {
|
||||||
matching: UiDisplay::display_matching_nothing_info().to_string(),
|
matching: UiDisplay::display_nothing_matching().to_string(),
|
||||||
list: List::default(),
|
list: List::default(),
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artists(matching: &Artist, matches: &[Match<Artist>], state: &'b mut WidgetState) -> Self {
|
fn artists(matching: &Artist, matches: &[Match<Artist>], state: &'b mut WidgetState) -> Self {
|
||||||
let matching = UiDisplay::display_artist_matching_info(matching);
|
let matching = UiDisplay::display_artist_matching(matching);
|
||||||
|
|
||||||
let list = List::new(
|
let list = List::new(
|
||||||
matches
|
matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(UiDisplay::display_artist_match_string)
|
.map(UiDisplay::display_artist_match)
|
||||||
.map(ListItem::new)
|
.map(ListItem::new)
|
||||||
.collect::<Vec<ListItem>>(),
|
.collect::<Vec<ListItem>>(),
|
||||||
);
|
);
|
||||||
@ -51,12 +51,12 @@ impl<'a, 'b> MatchesState<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn albums(matching: &Album, matches: &[Match<Album>], state: &'b mut WidgetState) -> Self {
|
fn albums(matching: &Album, matches: &[Match<Album>], state: &'b mut WidgetState) -> Self {
|
||||||
let matching = UiDisplay::display_album_matching_info(matching);
|
let matching = UiDisplay::display_album_matching(matching);
|
||||||
|
|
||||||
let list = List::new(
|
let list = List::new(
|
||||||
matches
|
matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(UiDisplay::display_album_match_string)
|
.map(UiDisplay::display_album_match)
|
||||||
.map(ListItem::new)
|
.map(ListItem::new)
|
||||||
.collect::<Vec<ListItem>>(),
|
.collect::<Vec<ListItem>>(),
|
||||||
);
|
);
|
||||||
|
@ -246,27 +246,35 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn public_inner<'app>(
|
||||||
|
collection: &'app Collection,
|
||||||
|
selection: &'app mut Selection,
|
||||||
|
) -> AppPublicInner<'app> {
|
||||||
|
AppPublicInner {
|
||||||
|
collection,
|
||||||
|
selection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_matches<'app>(
|
||||||
|
matching: &'app Artist,
|
||||||
|
list: &'app [Match<Artist>],
|
||||||
|
) -> AppPublicMatchesKind<'app> {
|
||||||
|
AppPublicMatchesKind::Artist(AppPublicArtistMatches { matching, list })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn album_matches<'app>(
|
||||||
|
matching: &'app Album,
|
||||||
|
list: &'app [Match<Album>],
|
||||||
|
) -> AppPublicMatchesKind<'app> {
|
||||||
|
AppPublicMatchesKind::Album(AppPublicAlbumMatches { matching, list })
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
|
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
|
||||||
let mut terminal = terminal();
|
let mut terminal = terminal();
|
||||||
|
|
||||||
let album = Album::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 mut app = AppPublic {
|
let mut app = AppPublic {
|
||||||
inner: AppPublicInner {
|
inner: public_inner(collection, selection),
|
||||||
collection,
|
|
||||||
selection,
|
|
||||||
},
|
|
||||||
state: AppState::Browse(()),
|
state: AppState::Browse(()),
|
||||||
};
|
};
|
||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
@ -280,27 +288,6 @@ mod tests {
|
|||||||
app.state = AppState::Search("");
|
app.state = AppState::Search("");
|
||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
|
|
||||||
let album_matches = [album_match.clone(), album_match.clone()];
|
|
||||||
let mut widget_state = WidgetState::default();
|
|
||||||
widget_state.list.select(Some(0));
|
|
||||||
|
|
||||||
app.state = AppState::Matches(AppPublicMatches {
|
|
||||||
matches: Some(AppPublicMatchesKind::Album(AppPublicAlbumMatches {
|
|
||||||
matching: &album,
|
|
||||||
list: &album_matches,
|
|
||||||
})),
|
|
||||||
state: &mut widget_state,
|
|
||||||
});
|
|
||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
|
||||||
|
|
||||||
let mut widget_state = WidgetState::default();
|
|
||||||
|
|
||||||
app.state = AppState::Matches(AppPublicMatches {
|
|
||||||
matches: None,
|
|
||||||
state: &mut widget_state,
|
|
||||||
});
|
|
||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
|
||||||
|
|
||||||
app.state = AppState::Error("get rekt scrub");
|
app.state = AppState::Error("get rekt scrub");
|
||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
|
|
||||||
@ -348,4 +335,86 @@ mod tests {
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
let mut terminal = terminal();
|
||||||
|
|
||||||
|
let mut widget_state = WidgetState::default();
|
||||||
|
|
||||||
|
let mut app = AppPublic {
|
||||||
|
inner: public_inner(collection, &mut selection),
|
||||||
|
state: AppState::Matches(AppPublicMatches {
|
||||||
|
matches: None,
|
||||||
|
state: &mut widget_state,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_artist_matches() {
|
||||||
|
let collection = &COLLECTION;
|
||||||
|
let mut selection = Selection::new(collection);
|
||||||
|
|
||||||
|
let mut terminal = terminal();
|
||||||
|
|
||||||
|
let artist = Artist::new(ArtistId::new("an artist"));
|
||||||
|
|
||||||
|
let artist_match = Match {
|
||||||
|
score: 80,
|
||||||
|
item: artist.clone(),
|
||||||
|
disambiguation: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let list = [artist_match.clone(), artist_match.clone()];
|
||||||
|
let mut widget_state = WidgetState::default();
|
||||||
|
widget_state.list.select(Some(0));
|
||||||
|
|
||||||
|
let mut app = AppPublic {
|
||||||
|
inner: public_inner(collection, &mut selection),
|
||||||
|
state: AppState::Matches(AppPublicMatches {
|
||||||
|
matches: Some(artist_matches(&artist, &list)),
|
||||||
|
state: &mut widget_state,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_album_matches() {
|
||||||
|
let collection = &COLLECTION;
|
||||||
|
let mut selection = Selection::new(collection);
|
||||||
|
|
||||||
|
let mut terminal = terminal();
|
||||||
|
|
||||||
|
let album = Album::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 = [album_match.clone(), album_match.clone()];
|
||||||
|
let mut widget_state = WidgetState::default();
|
||||||
|
widget_state.list.select(Some(0));
|
||||||
|
|
||||||
|
let mut app = AppPublic {
|
||||||
|
inner: public_inner(collection, &mut selection),
|
||||||
|
state: AppState::Matches(AppPublicMatches {
|
||||||
|
matches: Some(album_matches(&album, &list)),
|
||||||
|
state: &mut widget_state,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user