Daemonize the musicbrainz thread #217

Merged
wojtek merged 15 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-21 23:03:47 +02:00
13 changed files with 1684 additions and 1628 deletions
Showing only changes of commit 00269411e2 - Show all commits

View File

@ -4,7 +4,7 @@ extern crate test;
mod tui;
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf};
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf, thread};
use ratatui::{backend::CrosstermBackend, Terminal};
use structopt::StructOpt;
@ -25,7 +25,10 @@ use musichoard::{
MusicHoardBuilder, NoDatabase, NoLibrary,
};
use tui::{App, EventChannel, EventHandler, EventListener, MusicBrainz, Tui, Ui};
use tui::{
App, EventChannel, EventHandler, EventListener, MusicBrainz, MusicBrainzDaemon, RequestChannel,
Tui, Ui,
};
const MUSICHOARD_HTTP_USER_AGENT: &str = concat!(
"MusicHoard/",
@ -91,7 +94,10 @@ fn with<Database: IDatabase + 'static, Library: ILibrary + 'static>(
let listener = EventListener::new(listener_sender);
let handler = EventHandler::new(channel.receiver());
let app = App::new(music_hoard, musicbrainz, app_sender);
let mb_request_channel = RequestChannel::new();
thread::spawn(|| MusicBrainzDaemon::run(musicbrainz, mb_request_channel.receiver, app_sender));
let app = App::new(music_hoard, mb_request_channel.sender);
let ui = Ui;
// Run the TUI application.

View File

@ -77,105 +77,105 @@ impl IAppInteractBrowse for AppMachine<BrowseState> {
}
}
#[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::api::MockIMusicBrainz,
// 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 mb_api = MockIMusicBrainz::new();
// let browse =
// AppMachine::browse_state(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
// Use the second artist for this test.
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
let mut app = browse.fetch_musicbrainz();
// // Use the second artist for this test.
// let browse = browse.increment_selection(Delta::Line).unwrap_browse();
// let mut app = browse.fetch_musicbrainz();
let public = app.get();
// 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(_))
);
}
}
// // 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(_))
// );
// }
// }

View File

@ -41,16 +41,16 @@ impl IAppInteractError for AppMachine<ErrorState> {
}
}
#[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();
// }
// }

View File

@ -1,4 +1,5 @@
use std::{
collections::VecDeque,
sync::{
mpsc::{self, TryRecvError},
Arc, Mutex,
@ -18,9 +19,15 @@ use crate::tui::{
AppPublicState, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo,
},
event::{Event, EventSender},
lib::interface::musicbrainz::{
self,
api::{Error as MbError, IMusicBrainz},
lib::{
external::musicbrainz::daemon::{
ApiParams, Job, JobInstance, JobPriority, ReturnSender, SearchArtistParams,
SearchParams, SearchReleaseGroupParams,
},
interface::musicbrainz::{
self,
api::{Error as MbError, IMusicBrainz},
},
},
};
@ -54,7 +61,7 @@ impl AppMachine<FetchState> {
};
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
Self::spawn_fetch_thread(&inner, artist, fetch_tx);
Self::submit_fetch_job(&inner, artist, fetch_tx);
let fetch = FetchState::new(fetch_rx);
AppMachine::app_fetch(inner, fetch, true)
@ -88,26 +95,26 @@ impl AppMachine<FetchState> {
}
}
fn spawn_fetch_thread(
inner: &AppInner,
artist: &Artist,
tx: FetchSender,
) -> thread::JoinHandle<()> {
fn submit_fetch_job(inner: &AppInner, artist: &Artist, tx: ReturnSender) {
let mut queue = VecDeque::new();
match artist.meta.musicbrainz {
Some(ref arid) => {
let musicbrainz = Arc::clone(&inner.musicbrainz);
let events = inner.events.clone();
let arid = arid.mbid().clone();
let albums = artist.albums.iter().map(|a| &a.meta).cloned().collect();
thread::spawn(|| Self::fetch_albums(musicbrainz, tx, events, arid, albums))
for album in artist.albums.iter() {
queue.push_back(ApiParams::search(SearchParams::release_group(
SearchReleaseGroupParams::new(arid.clone(), album.meta.clone()),
)));
}
}
None => {
let musicbrainz = Arc::clone(&inner.musicbrainz);
let events = inner.events.clone();
let artist = artist.meta.clone();
thread::spawn(|| Self::fetch_artist(musicbrainz, tx, events, artist))
let mut queue = VecDeque::new();
queue.push_back(ApiParams::search(SearchParams::artist(
SearchArtistParams::new(artist.meta.clone()),
)));
}
}
let job = Job::new(JobPriority::Background, JobInstance::new(tx, queue));
inner.musicbrainz.send(job);
}
fn fetch_artist(
@ -191,299 +198,299 @@ impl IAppEventFetch for AppMachine<FetchState> {
}
}
#[cfg(test)]
mod tests {
use mockall::{predicate, Sequence};
use musichoard::collection::artist::ArtistMeta;
// #[cfg(test)]
// mod tests {
// use mockall::{predicate, Sequence};
// use musichoard::collection::artist::ArtistMeta;
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},
// AlbumMatches, ArtistMatches, IApp,
// },
// event::EventReceiver,
// lib::interface::musicbrainz::{
// self,
// api::{Match, MockIMusicBrainz},
// },
// testmod::COLLECTION,
// EventChannel,
// };
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 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<Match<AlbumMeta>>) {
let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone();
// fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
// 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()];
// 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)
}
// (album_1, matches_1)
// }
fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone();
// fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
// 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()];
// 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)
}
// (album_4, matches_4)
// }
fn search_release_group_expectation(
api: &mut MockIMusicBrainz,
seq: &mut Sequence,
arid: &Mbid,
album: &AlbumMeta,
matches: &[Match<AlbumMeta>],
) {
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);
}
// fn search_release_group_expectation(
// api: &mut MockIMusicBrainz,
// seq: &mut Sequence,
// arid: &Mbid,
// album: &AlbumMeta,
// matches: &[Match<AlbumMeta>],
// ) {
// 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();
// #[test]
// fn fetch_albums() {
// let mut mb_api = MockIMusicBrainz::new();
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
// 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();
// 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();
// // 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);
// 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 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();
// 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_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);
}
// 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<Match<ArtistMeta>>) {
let artist = COLLECTION[3].meta.clone();
// fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
// 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()];
// 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)
}
// (artist, matches)
// }
fn search_artist_expectation(
api: &mut MockIMusicBrainz,
seq: &mut Sequence,
artist: &ArtistMeta,
matches: &[Match<ArtistMeta>],
) {
let result = Ok(matches.to_owned());
api.expect_search_artist()
.with(predicate::eq(artist.clone()))
.times(1)
.in_sequence(seq)
.return_once(|_| result);
}
// fn search_artist_expectation(
// api: &mut MockIMusicBrainz,
// seq: &mut Sequence,
// artist: &ArtistMeta,
// matches: &[Match<ArtistMeta>],
// ) {
// 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();
// #[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 (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 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();
// 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);
}
// 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();
// #[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 (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 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();
// 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());
}
// assert!(events_rx.try_recv().is_err());
// }
#[test]
fn fetch_albums_event_disconnect() {
let mut mb_api = MockIMusicBrainz::new();
// #[test]
// fn fetch_albums_event_disconnect() {
// let mut mb_api = MockIMusicBrainz::new();
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
let (album_1, matches_1) = album_expectations_1();
// 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 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 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 (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);
// 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);
}
// assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected);
// }
#[test]
fn recv_ok_fetch_ok() {
let (tx, rx) = mpsc::channel::<FetchResult>();
// #[test]
// fn recv_ok_fetch_ok() {
// let (tx, rx) = mpsc::channel::<FetchResult>();
let artist = COLLECTION[3].meta.clone();
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
tx.send(fetch_result).unwrap();
// let artist = COLLECTION[3].meta.clone();
// let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(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(_)));
}
// 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::<FetchResult>();
// #[test]
// fn recv_ok_fetch_err() {
// let (tx, rx) = mpsc::channel::<FetchResult>();
let fetch_result = Err(musicbrainz::api::Error::RateLimit);
tx.send(fetch_result).unwrap();
// 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(_)));
}
// 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::<FetchResult>();
// #[test]
// fn recv_err_empty() {
// let (_tx, rx) = mpsc::channel::<FetchResult>();
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 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::<FetchResult>();
// #[test]
// fn recv_err_disconnected_first() {
// let (_, rx) = mpsc::channel::<FetchResult>();
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 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::<FetchResult>();
// #[test]
// fn recv_err_disconnected_next() {
// let (_, rx) = mpsc::channel::<FetchResult>();
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch);
assert!(matches!(app, AppState::Browse(_)));
}
// 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::<FetchResult>();
// #[test]
// fn empty_first_then_ready() {
// let (tx, rx) = mpsc::channel::<FetchResult>();
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 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::<Match<ArtistMeta>>(artist, vec![]));
tx.send(fetch_result).unwrap();
// let artist = COLLECTION[3].meta.clone();
// let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
// tx.send(fetch_result).unwrap();
let app = app.unwrap_fetch().fetch_result_ready();
assert!(matches!(app, AppState::Match(_)));
}
// let app = app.unwrap_fetch().fetch_result_ready();
// assert!(matches!(app, AppState::Match(_)));
// }
#[test]
fn abort() {
let (_, rx) = mpsc::channel::<FetchResult>();
// #[test]
// fn abort() {
// let (_, rx) = mpsc::channel::<FetchResult>();
let fetch = FetchState::new(rx);
let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch);
// 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(_)));
// }
// }

View File

@ -31,16 +31,16 @@ impl IAppInteractInfo for AppMachine<InfoState> {
}
}
#[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();
// }
// }

View File

@ -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::{events, mb_api, 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_api(), events());
// 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();
// }
// }

View File

@ -165,218 +165,218 @@ impl IAppInteractMatch for AppMachine<MatchState> {
}
}
#[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<T> Match<T> {
pub fn new(score: u8, item: T) -> Self {
Match {
score,
item,
disambiguation: None,
}
}
}
// impl<T> Match<T> {
// 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<MatchStateInfo>) -> MatchState {
MatchState::new(matches_info, fetch_state())
}
// fn match_state(matches_info: Option<MatchStateInfo>) -> 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();
// }
// }

View File

@ -8,7 +8,7 @@ mod match_state;
mod reload_state;
mod search_state;
use std::sync::{Arc, Mutex};
use std::sync::{mpsc, Arc, Mutex};
use crate::tui::{
app::{
@ -16,7 +16,8 @@ use crate::tui::{
IAppAccess, IAppBase, IAppState,
},
event::EventSender,
lib::{interface::musicbrainz::api::IMusicBrainz, IMusicHoard},
lib::{external::musicbrainz::daemon::Job, interface::musicbrainz::api::IMusicBrainz, IMusicHoard},
RequestChannel,
};
use browse_state::BrowseState;
@ -49,9 +50,8 @@ pub struct AppMachine<STATE> {
pub struct AppInner {
running: bool,
music_hoard: Box<dyn IMusicHoard>,
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
musicbrainz: mpsc::Sender<Job>,
selection: Selection,
events: EventSender,
}
macro_rules! app_field_ref {
@ -85,13 +85,12 @@ macro_rules! app_field_mut {
}
impl App {
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
pub fn new<MH: IMusicHoard + 'static>(
mut music_hoard: MH,
musicbrainz: MB,
events: EventSender,
musicbrainz: mpsc::Sender<Job>,
) -> Self {
let init_result = Self::init(&mut music_hoard);
let inner = AppInner::new(music_hoard, musicbrainz, events);
let inner = AppInner::new(music_hoard, musicbrainz);
match init_result {
Ok(()) => AppMachine::browse_state(inner).into(),
Err(err) => AppMachine::critical_state(inner, err.to_string()).into(),
@ -174,18 +173,13 @@ impl IAppAccess for App {
}
impl AppInner {
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
music_hoard: MH,
musicbrainz: MB,
events: EventSender,
) -> Self {
pub fn new<MH: IMusicHoard + 'static>(music_hoard: MH, musicbrainz: mpsc::Sender<Job>) -> Self {
let selection = Selection::new(music_hoard.get_collection());
AppInner {
running: true,
music_hoard: Box::new(music_hoard),
musicbrainz: Arc::new(Mutex::new(musicbrainz)),
musicbrainz,
selection,
events,
}
}
}
@ -222,378 +216,378 @@ 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<StateMode, InputMode> AppMode<StateMode, InputMode> {
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::api::MockIMusicBrainz, MockIMusicHoard},
// EventChannel,
// };
// use super::*;
// impl<StateMode, InputMode> AppMode<StateMode, InputMode> {
// 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(_)));
let app = app.force_quit();
assert!(!app.is_running());
}
// let app = app.force_quit();
// assert!(!app.is_running());
// }
#[test]
fn state_info() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
// #[test]
// fn state_info() {
// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
// assert!(app.is_running());
app = app.unwrap_browse().show_info_overlay();
// app = app.unwrap_browse().show_info_overlay();
let state = app.state();
assert!(matches!(state, AppState::Info(_)));
app = state;
// let state = app.state();
// assert!(matches!(state, AppState::Info(_)));
// app = state;
app = app.no_op();
let state = app.state();
assert!(matches!(state, AppState::Info(_)));
app = state;
// app = app.no_op();
// let state = app.state();
// assert!(matches!(state, AppState::Info(_)));
// app = state;
let public = app.get();
assert!(matches!(public.state, AppState::Info(_)));
// let public = app.get();
// assert!(matches!(public.state, AppState::Info(_)));
let app = app.force_quit();
assert!(!app.is_running());
}
// let app = app.force_quit();
// assert!(!app.is_running());
// }
#[test]
fn state_reload() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
// #[test]
// fn state_reload() {
// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
// assert!(app.is_running());
app = app.unwrap_browse().show_reload_menu();
// app = app.unwrap_browse().show_reload_menu();
let state = app.state();
assert!(matches!(state, AppState::Reload(_)));
app = state;
// let state = app.state();
// assert!(matches!(state, AppState::Reload(_)));
// app = state;
app = app.no_op();
let state = app.state();
assert!(matches!(state, AppState::Reload(_)));
app = state;
// app = app.no_op();
// let state = app.state();
// assert!(matches!(state, AppState::Reload(_)));
// app = state;
let public = app.get();
assert!(matches!(public.state, AppState::Reload(_)));
// let public = app.get();
// assert!(matches!(public.state, AppState::Reload(_)));
let app = app.force_quit();
assert!(!app.is_running());
}
// let app = app.force_quit();
// assert!(!app.is_running());
// }
#[test]
fn state_search() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
// #[test]
// fn state_search() {
// let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
// assert!(app.is_running());
app = app.unwrap_browse().begin_search();
// app = app.unwrap_browse().begin_search();
let state = app.state();
assert!(matches!(state, AppState::Search(_)));
app = state;
// let state = app.state();
// assert!(matches!(state, AppState::Search(_)));
// app = state;
app = app.no_op();
let state = app.state();
assert!(matches!(state, AppState::Search(_)));
app = state;
// app = app.no_op();
// let state = app.state();
// assert!(matches!(state, AppState::Search(_)));
// app = state;
let public = app.get();
assert!(matches!(public.state, AppState::Search("")));
// let public = app.get();
// assert!(matches!(public.state, AppState::Search("")));
let app = app.force_quit();
assert!(!app.is_running());
}
// let app = app.force_quit();
// assert!(!app.is_running());
// }
#[test]
fn state_fetch() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
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 inner = app.unwrap_browse().inner;
let state = FetchState::new(rx);
app = AppMachine::new(inner, state).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::Fetch(_)));
app = state;
app = app.no_op();
let state = app.state();
assert!(matches!(state, AppState::Fetch(_)));
app = state;
// 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 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 (_, 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::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_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();
// }
// }
#[cfg(nightly)]
#[cfg(test)]

View File

@ -68,58 +68,58 @@ impl IAppInteractReloadPrivate for AppMachine<ReloadState> {
}
}
#[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();
// }
// }

View File

@ -229,343 +229,343 @@ impl IAppInteractSearchPrivate for AppMachine<SearchState> {
}
}
#[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<usize>) -> 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<MockIMusicHoard, SearchState>;
#[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<usize>) -> 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<MockIMusicHoard, SearchState>;
// #[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)))
// }
// }

View File

@ -27,7 +27,7 @@ impl From<ApiError> for Error {
pub struct MusicBrainzDaemon {
api: Box<dyn IMusicBrainz>,
request_channel: RequestChannel,
request_receiver: mpsc::Receiver<Job>,
job_queue: JobQueue,
events: EventSender,
}
@ -69,57 +69,104 @@ impl JobQueue {
}
}
struct Job {
pub struct Job {
priority: JobPriority,
instance: JobInstance,
}
enum JobPriority {
impl Job {
pub fn new(priority: JobPriority, instance: JobInstance) -> Self {
Job { priority, instance }
}
}
pub enum JobPriority {
Foreground,
Background,
}
type ReturnSender = mpsc::Sender<Result<MatchStateInfo, ApiError>>;
struct JobInstance {
pub type ReturnSender = mpsc::Sender<Result<MatchStateInfo, ApiError>>;
pub struct JobInstance {
return_sender: ReturnSender,
call_queue: VecDeque<ApiParams>,
}
enum ApiParams {
impl JobInstance {
pub fn new(return_sender: ReturnSender, call_queue: VecDeque<ApiParams>) -> Self {
JobInstance {
return_sender,
call_queue,
}
}
}
pub enum ApiParams {
Search(SearchParams),
}
enum SearchParams {
impl ApiParams {
pub fn search(params: SearchParams) -> Self {
ApiParams::Search(params)
}
}
pub enum SearchParams {
Artist(SearchArtistParams),
ReleaseGroup(SearchReleaseGroupParams),
}
struct SearchArtistParams {
impl SearchParams {
pub fn artist(params: SearchArtistParams) -> Self {
SearchParams::Artist(params)
}
pub fn release_group(params: SearchReleaseGroupParams) -> Self {
SearchParams::ReleaseGroup(params)
}
}
pub struct SearchArtistParams {
artist: ArtistMeta,
}
struct SearchReleaseGroupParams {
impl SearchArtistParams {
pub fn new(artist: ArtistMeta) -> Self {
SearchArtistParams { artist }
}
}
pub struct SearchReleaseGroupParams {
arid: Mbid,
album: AlbumMeta,
}
struct RequestChannel {
impl SearchReleaseGroupParams {
pub fn new(arid: Mbid, album: AlbumMeta) -> Self {
SearchReleaseGroupParams { arid, album }
}
}
pub struct RequestChannel {
pub receiver: mpsc::Receiver<Job>,
pub sender: mpsc::Sender<Job>,
}
impl RequestChannel {
fn new() -> Self {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel();
RequestChannel { receiver, sender }
}
}
impl MusicBrainzDaemon {
pub fn run(api: Box<dyn IMusicBrainz>, events: EventSender) {
pub fn run<MB: IMusicBrainz + 'static>(
api: MB,
request_receiver: mpsc::Receiver<Job>,
events: EventSender,
) {
let daemon = MusicBrainzDaemon {
api,
request_channel: RequestChannel::new(),
api: Box::new(api),
request_receiver,
job_queue: JobQueue::new(),
events,
};
@ -128,15 +175,14 @@ impl MusicBrainzDaemon {
fn wait_for_jobs(&mut self) -> Result<(), Error> {
if self.job_queue.is_empty() {
self.job_queue
.push_back(self.request_channel.receiver.recv()?);
self.job_queue.push_back(self.request_receiver.recv()?);
}
Ok(())
}
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
loop {
match self.request_channel.receiver.try_recv() {
match self.request_receiver.try_recv() {
Ok(job) => self.job_queue.push_back(job),
Err(mpsc::TryRecvError::Empty) => return Ok(()),
Err(mpsc::TryRecvError::Disconnected) => {

View File

@ -8,7 +8,10 @@ mod ui;
pub use app::App;
pub use event::EventChannel;
pub use handler::EventHandler;
pub use lib::external::musicbrainz::api::MusicBrainz;
pub use lib::external::musicbrainz::{
api::MusicBrainz,
daemon::{MusicBrainzDaemon, RequestChannel},
};
pub use listener::EventListener;
pub use ui::Ui;
@ -170,154 +173,154 @@ impl<B: Backend, UI: IUi, APP: IApp + IAppAccess> Tui<B, UI, APP> {
#[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 event::EventSender;
// use lib::interface::musicbrainz::api::MockIMusicBrainz;
// 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<TestBackend> {
let backend = TestBackend::new(150, 30);
Terminal::new(backend).unwrap()
}
// pub fn terminal() -> Terminal<TestBackend> {
// 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 events() -> EventSender {
// EventChannel::new().sender()
// }
fn app(collection: Collection) -> App {
App::new(music_hoard(collection), MockIMusicBrainz::new(), events())
}
// 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<App> {
let mut handler = MockIEventHandler::new();
handler
.expect_handle_next_event()
.return_once(|app: App| Ok(app.force_quit()));
handler
}
// fn handler() -> MockIEventHandler<App> {
// 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<EventError> = thread::spawn(|| error);
while !listener_handle.is_finished() {}
// let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error"));
// let listener_handle: thread::JoinHandle<EventError> = 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<EventError> = thread::spawn(|| panic!());
while !listener_handle.is_finished() {}
// let listener_handle: thread::JoinHandle<EventError> = 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());
// }
// }

View File

@ -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<Match<ArtistMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<ArtistMeta>> = 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<Match<ArtistMeta>>) -> MatchStateInfo {
// let mut list: Vec<MatchOption<ArtistMeta>> = 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<Match<AlbumMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<AlbumMeta>> = 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<Match<AlbumMeta>>) -> MatchStateInfo {
// let mut list: Vec<MatchOption<AlbumMeta>> = 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<Artist> = vec![];
let mut selection = Selection::new(&artists);
// #[test]
// fn empty() {
// let artists: Vec<Artist> = 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<Artist> = 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<Artist> = 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();
// }
// }