diff --git a/src/tui/app/machine/input.rs b/src/tui/app/machine/input.rs index 189b113..9981777 100644 --- a/src/tui/app/machine/input.rs +++ b/src/tui/app/machine/input.rs @@ -45,8 +45,8 @@ impl IAppInput for AppInputMode { fn confirm(mut self) -> Self::APP { if let AppState::Match(state) = &mut self.app { - state.submit_input(self.input) - }; + state.submit_input(self.input); + } self.app } @@ -54,3 +54,45 @@ impl IAppInput for AppInputMode { self.app } } + +#[cfg(test)] +mod tests { + use crate::tui::app::{ + machine::tests::{events, mb_api, music_hoard_init}, + IApp, + }; + + use super::*; + + fn input_event(c: char) -> InputEvent { + InputEvent::new( + crossterm::event::KeyCode::Char(c), + crossterm::event::KeyModifiers::empty(), + ) + } + + #[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('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('o')); + + assert_eq!(app.input_ref().as_ref().unwrap().0.value(), "Hello"); + + app.mode().unwrap_input().confirm().unwrap_browse(); + } +} diff --git a/src/tui/app/machine/match_state.rs b/src/tui/app/machine/match_state.rs index 89964d0..efcf7b7 100644 --- a/src/tui/app/machine/match_state.rs +++ b/src/tui/app/machine/match_state.rs @@ -177,7 +177,7 @@ mod tests { use crate::tui::{ app::{ machine::tests::{inner, music_hoard}, - IAppAccess, + IApp, IAppAccess, IAppInput, }, lib::interface::musicbrainz::Match, }; @@ -362,4 +362,21 @@ mod tests { 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()))); + + // 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 input = app.mode().unwrap_input(); + input.confirm().unwrap_match(); + } } diff --git a/src/tui/app/machine/mod.rs b/src/tui/app/machine/mod.rs index 9f611ab..f7c5d08 100644 --- a/src/tui/app/machine/mod.rs +++ b/src/tui/app/machine/mod.rs @@ -54,6 +54,36 @@ pub struct AppInner { events: EventSender, } +macro_rules! app_field_ref { + ($app:ident, $field:ident) => { + match $app { + AppState::Browse(state) => &state.$field, + AppState::Info(state) => &state.$field, + AppState::Reload(state) => &state.$field, + AppState::Search(state) => &state.$field, + AppState::Fetch(state) => &state.$field, + AppState::Match(state) => &state.$field, + AppState::Error(state) => &state.$field, + AppState::Critical(state) => &state.$field, + } + }; +} + +macro_rules! app_field_mut { + ($app:ident, $field:ident) => { + match $app { + AppState::Browse(state) => &mut state.$field, + AppState::Info(state) => &mut state.$field, + AppState::Reload(state) => &mut state.$field, + AppState::Search(state) => &mut state.$field, + AppState::Fetch(state) => &mut state.$field, + AppState::Match(state) => &mut state.$field, + AppState::Error(state) => &mut state.$field, + AppState::Critical(state) => &mut state.$field, + } + }; +} + impl App { pub fn new( mut music_hoard: MH, @@ -74,42 +104,20 @@ impl App { } fn inner_ref(&self) -> &AppInner { - match self { - AppState::Browse(browse_state) => &browse_state.inner, - AppState::Info(info_state) => &info_state.inner, - AppState::Reload(reload_state) => &reload_state.inner, - AppState::Search(search_state) => &search_state.inner, - AppState::Fetch(fetch_state) => &fetch_state.inner, - AppState::Match(match_state) => &match_state.inner, - AppState::Error(error_state) => &error_state.inner, - AppState::Critical(critical_state) => &critical_state.inner, - } + app_field_ref!(self, inner) } fn inner_mut(&mut self) -> &mut AppInner { - match self { - AppState::Browse(browse_state) => &mut browse_state.inner, - AppState::Info(info_state) => &mut info_state.inner, - AppState::Reload(reload_state) => &mut reload_state.inner, - AppState::Search(search_state) => &mut search_state.inner, - AppState::Fetch(fetch_state) => &mut fetch_state.inner, - AppState::Match(match_state) => &mut match_state.inner, - AppState::Error(error_state) => &mut error_state.inner, - AppState::Critical(critical_state) => &mut critical_state.inner, - } + app_field_mut!(self, inner) + } + + #[cfg(test)] + fn input_ref(&self) -> &Option { + app_field_ref!(self, input) } fn input_mut(&mut self) -> &mut Option { - match self { - AppState::Browse(state) => &mut state.input, - AppState::Info(state) => &mut state.input, - AppState::Reload(state) => &mut state.input, - AppState::Search(state) => &mut state.input, - AppState::Fetch(state) => &mut state.input, - AppState::Match(state) => &mut state.input, - AppState::Error(state) => &mut state.input, - AppState::Critical(state) => &mut state.input, - } + app_field_mut!(self, input) } } @@ -221,13 +229,29 @@ mod tests { use musichoard::collection::Collection; use crate::tui::{ - app::{AppState, IApp, IAppInteractBrowse}, + app::{AppState, IApp, IAppInput, IAppInteractBrowse}, lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard}, EventChannel, }; use super::*; + impl AppMode { + fn unwrap_state(self) -> StateMode { + match self { + AppMode::State(state) => state, + _ => panic!(), + } + } + + pub fn unwrap_input(self) -> InputMode { + match self { + AppMode::Input(input) => input, + _ => panic!(), + } + } + } + impl< BrowseState, InfoState, @@ -313,7 +337,7 @@ mod tests { music_hoard } - fn music_hoard_init(collection: Collection) -> MockIMusicHoard { + pub fn music_hoard_init(collection: Collection) -> MockIMusicHoard { let mut music_hoard = music_hoard(collection); music_hoard @@ -324,11 +348,11 @@ mod tests { music_hoard } - fn mb_api() -> MockIMusicBrainz { + pub fn mb_api() -> MockIMusicBrainz { MockIMusicBrainz::new() } - fn events() -> EventSender { + pub fn events() -> EventSender { EventChannel::new().sender() } @@ -340,6 +364,33 @@ mod tests { 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());