Provide a keyboard shortcut to sync all existing albums with MusicBrainz #167

Merged
wojtek merged 13 commits from 166---provide-a-keyboard-shortcut-to-sync-all-existing-albums-with-musicbrainz into main 2024-08-27 17:55:53 +02:00
5 changed files with 223 additions and 15 deletions
Showing only changes of commit d0532d2efb - Show all commits

View File

@ -20,7 +20,7 @@ pub trait IMusicBrainz {
) -> Result<Vec<Match<Album>>, Error>;
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match<T> {
pub score: u8,
pub item: T,

View File

@ -391,6 +391,11 @@ mod tests {
let mut client = MockIMusicBrainzApiClient::new();
let error = ClientError::Client(String::from("get rekt"));
assert!(!error.to_string().is_empty());
assert!(!format!("{error:?}").is_empty());
let error = ClientError::Status(404);
assert!(!error.to_string().is_empty());
assert!(!format!("{error:?}").is_empty());
client

View File

@ -10,6 +10,7 @@ use crate::tui::{
lib::IMusicHoard,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppMatchesInfo {
pub matching: Album,
pub matches: Vec<Match<Album>>,
@ -124,10 +125,162 @@ impl<MH: IMusicHoard> IAppInteractMatches for AppMachine<MH, AppMatches> {
#[cfg(test)]
mod tests {
use crate::tui::app::machine::tests::{inner, music_hoard};
use musichoard::collection::album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType};
use crate::tui::app::{machine::tests::{inner, music_hoard}, IAppAccess};
use super::*;
fn matches_info_vec() -> Vec<AppMatchesInfo> {
let album_1 = Album::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 {
score: 100,
item: 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 {
score: 100,
item: album_1_2,
};
let matches_info_1 = AppMatchesInfo {
matching: album_1.clone(),
matches: vec![album_match_1_1.clone(), album_match_1_2.clone()],
};
let album_2 = Album::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 {
score: 100,
item: album_2_1,
};
let matches_info_2 = AppMatchesInfo {
matching: album_2.clone(),
matches: vec![album_match_2_1.clone()],
};
vec![matches_info_1, matches_info_2]
}
#[test]
fn create_empty() {
let matches = AppMachine::matches(inner(music_hoard(vec![])), vec![]);
let widget_state = WidgetState::default();
assert_eq!(matches.state.matches_info_vec, vec![]);
assert_eq!(matches.state.index, 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.matching, None);
assert_eq!(public_matches.matches, None);
assert_eq!(public_matches.state, &widget_state);
}
#[test]
fn create_nonempty() {
let matches_info_vec = matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
assert_eq!(matches.state.matches_info_vec, matches_info_vec);
assert_eq!(matches.state.index, Some(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.matching, Some(&matches_info_vec[0].matching));
assert_eq!(public_matches.matches, Some(matches_info_vec[0].matches.as_slice()));
assert_eq!(public_matches.state, &widget_state);
}
#[test]
fn matches_flow() {
let matches_info_vec = matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
assert_eq!(matches.state.matches_info_vec, matches_info_vec);
assert_eq!(matches.state.index, Some(0));
assert_eq!(matches.state.state, widget_state);
let matches = matches.prev_match().unwrap_matches();
assert_eq!(matches.state.index, Some(0));
assert_eq!(matches.state.state.list.selected(), Some(0));
let matches = matches.next_match().unwrap_matches();
assert_eq!(matches.state.index, Some(0));
assert_eq!(matches.state.state.list.selected(), Some(1));
let matches = matches.next_match().unwrap_matches();
assert_eq!(matches.state.index, Some(0));
assert_eq!(matches.state.state.list.selected(), Some(1));
let matches = matches.select().unwrap_matches();
assert_eq!(matches.state.index, Some(1));
assert_eq!(matches.state.state.list.selected(), Some(0));
// And it's done
matches.select().unwrap_browse();
}
#[test]
fn matches_abort() {
let matches_info_vec = matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
assert_eq!(matches.state.matches_info_vec, matches_info_vec);
assert_eq!(matches.state.index, Some(0));
assert_eq!(matches.state.state, widget_state);
matches.abort().unwrap_browse();
}
#[test]
fn matches_select_empty() {
let matches = AppMachine::matches(inner(music_hoard(vec![])), vec![]);
assert_eq!(matches.state.matches_info_vec, vec![]);
assert_eq!(matches.state.index, None);
matches.select().unwrap_browse();
}
#[test]
fn no_op() {
let matches = AppMachine::matches(inner(music_hoard(vec![])), vec![]);

View File

@ -229,11 +229,11 @@ mod tests {
assert!(app.is_running());
let state = app.state();
matches!(state, AppState::Browse(_));
assert!(matches!(state, AppState::Browse(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Browse(_));
assert!(matches!(public.state, AppState::Browse(_)));
let app = app.force_quit();
assert!(!app.is_running());
@ -247,11 +247,11 @@ mod tests {
app = app.unwrap_browse().show_info_overlay();
let state = app.state();
matches!(state, AppState::Info(_));
assert!(matches!(state, AppState::Info(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Info(_));
assert!(matches!(public.state, AppState::Info(_)));
let app = app.force_quit();
assert!(!app.is_running());
@ -265,11 +265,11 @@ mod tests {
app = app.unwrap_browse().show_reload_menu();
let state = app.state();
matches!(state, AppState::Reload(_));
assert!(matches!(state, AppState::Reload(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Reload(_));
assert!(matches!(public.state, AppState::Reload(_)));
let app = app.force_quit();
assert!(!app.is_running());
@ -283,11 +283,29 @@ mod tests {
app = app.unwrap_browse().begin_search();
let state = app.state();
matches!(state, AppState::Search(_));
assert!(matches!(state, AppState::Search(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Search(""));
assert!(matches!(public.state, AppState::Search("")));
let app = app.force_quit();
assert!(!app.is_running());
}
#[test]
fn state_matches() {
let mut app = App::new(music_hoard_init(vec![]));
assert!(app.is_running());
app = AppMachine::matches(app.unwrap_browse().inner, vec![]).into();
let state = app.state();
assert!(matches!(state, AppState::Matches(_)));
app = state;
let public = app.get();
assert!(matches!(public.state, AppState::Matches(_)));
let app = app.force_quit();
assert!(!app.is_running());
@ -301,11 +319,11 @@ mod tests {
app = AppMachine::error(app.unwrap_browse().inner, "get rekt").into();
let state = app.state();
matches!(state, AppState::Error(_));
assert!(matches!(state, AppState::Error(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Error("get rekt"));
assert!(matches!(public.state, AppState::Error("get rekt")));
app = app.force_quit();
assert!(!app.is_running());
@ -319,11 +337,11 @@ mod tests {
app = AppMachine::critical(app.unwrap_browse().inner, "get rekt").into();
let state = app.state();
matches!(state, AppState::Critical(_));
assert!(matches!(state, AppState::Critical(_)));
app = state;
let public = app.get();
matches!(public.state, AppState::Critical("get rekt"));
assert!(matches!(public.state, AppState::Critical("get rekt")));
app = app.force_quit();
assert!(!app.is_running());

View File

@ -909,7 +909,7 @@ impl IUi for Ui {
#[cfg(test)]
mod tests {
use musichoard::collection::artist::ArtistId;
use musichoard::collection::{album::AlbumId, artist::ArtistId};
use crate::tui::{
app::{AppPublic, AppPublicInner, AppPublicMatches, Delta},
@ -947,6 +947,18 @@ mod tests {
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
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(),
};
let mut app = AppPublic {
inner: AppPublicInner {
collection,
@ -965,6 +977,26 @@ mod tests {
app.state = AppState::Search("");
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 {
matching: Some(&album),
matches: Some(&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 {
matching: None,
matches: None,
state: &mut widget_state,
});
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();