Provide search functionality through the TUI #134
1131
src/tui/app/app.rs
1131
src/tui/app/app.rs
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
|
mod state;
|
||||||
|
|
||||||
use musichoard::collection::Collection;
|
use musichoard::collection::Collection;
|
||||||
|
|
||||||
|
108
src/tui/app/state/browse.rs
Normal file
108
src/tui/app/state/browse.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
selection::{Delta, ListSelection},
|
||||||
|
state::{critical::AppCritical, AppInner, AppMachine},
|
||||||
|
AppPublic, AppState, IAppInteractBrowse,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppBrowse;
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppBrowse> {
|
||||||
|
pub fn browse(inner: AppInner<MH>) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppBrowse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(mut music_hoard: MH) -> Result<Self, AppMachine<MH, AppCritical>> {
|
||||||
|
let init_result = Self::init(&mut music_hoard);
|
||||||
|
let inner = AppInner::new(music_hoard);
|
||||||
|
match init_result {
|
||||||
|
Ok(()) => Ok(AppMachine::browse(inner)),
|
||||||
|
Err(err) => Err(AppMachine::critical(inner, err.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(music_hoard: &mut MH) -> Result<(), musichoard::Error> {
|
||||||
|
music_hoard.load_from_database()?;
|
||||||
|
music_hoard.rescan_library()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppBrowse>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppBrowse>) -> Self {
|
||||||
|
AppState::Browse(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppBrowse>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppBrowse>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Browse(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractBrowse for AppMachine<MH, AppBrowse> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn save_and_quit(mut self) -> Self::APP {
|
||||||
|
match self.inner.music_hoard.save_to_database() {
|
||||||
|
Ok(_) => {
|
||||||
|
self.inner.running = false;
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_category(mut self) -> Self::APP {
|
||||||
|
self.inner.selection.increment_category();
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrement_category(mut self) -> Self::APP {
|
||||||
|
self.inner.selection.decrement_category();
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_selection(mut self, delta: Delta) -> Self::APP {
|
||||||
|
self.inner
|
||||||
|
.selection
|
||||||
|
.increment_selection(self.inner.music_hoard.get_collection(), delta);
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrement_selection(mut self, delta: Delta) -> Self::APP {
|
||||||
|
self.inner
|
||||||
|
.selection
|
||||||
|
.decrement_selection(self.inner.music_hoard.get_collection(), delta);
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_info_overlay(self) -> Self::APP {
|
||||||
|
AppMachine::info(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_reload_menu(self) -> Self::APP {
|
||||||
|
AppMachine::reload(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_search(mut self) -> Self::APP {
|
||||||
|
let orig = ListSelection::get(&self.inner.selection);
|
||||||
|
self.inner
|
||||||
|
.selection
|
||||||
|
.reset_artist(self.inner.music_hoard.get_collection());
|
||||||
|
AppMachine::search(self.inner, orig).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
46
src/tui/app/state/critical.rs
Normal file
46
src/tui/app/state/critical.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
state::{AppInner, AppMachine},
|
||||||
|
AppPublic, AppState, IAppInteractCritical,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppCritical {
|
||||||
|
string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppCritical> {
|
||||||
|
pub fn critical<S: Into<String>>(inner: AppInner<MH>, string: S) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppCritical {
|
||||||
|
string: string.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppCritical>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppCritical>) -> Self {
|
||||||
|
AppState::Critical(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppCritical>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppCritical>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Critical(&machine.state.string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractCritical for AppMachine<MH, AppCritical> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
46
src/tui/app/state/error.rs
Normal file
46
src/tui/app/state/error.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
state::{AppInner, AppMachine},
|
||||||
|
AppPublic, AppState, IAppInteractError,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppError {
|
||||||
|
string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppError> {
|
||||||
|
pub fn error<S: Into<String>>(inner: AppInner<MH>, string: S) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppError {
|
||||||
|
string: string.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppError>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppError>) -> Self {
|
||||||
|
AppState::Error(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppError>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppError>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Error(&machine.state.string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractError for AppMachine<MH, AppError> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn dismiss_error(self) -> Self::APP {
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
}
|
46
src/tui/app/state/info.rs
Normal file
46
src/tui/app/state/info.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
state::{AppInner, AppMachine},
|
||||||
|
AppPublic, AppState, IAppInteractInfo,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppInfo;
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppInfo> {
|
||||||
|
pub fn info(inner: AppInner<MH>) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppInfo>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppInfo>) -> Self {
|
||||||
|
AppState::Info(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppInfo>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppInfo>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Info(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractInfo for AppMachine<MH, AppInfo> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn hide_info_overlay(self) -> Self::APP {
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
778
src/tui/app/state/mod.rs
Normal file
778
src/tui/app/state/mod.rs
Normal file
@ -0,0 +1,778 @@
|
|||||||
|
pub mod browse;
|
||||||
|
pub mod critical;
|
||||||
|
pub mod error;
|
||||||
|
pub mod info;
|
||||||
|
pub mod reload;
|
||||||
|
pub mod search;
|
||||||
|
|
||||||
|
use crate::tui::{
|
||||||
|
app::{selection::Selection, AppPublicInner},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppMachine<MH: IMusicHoard, STATE> {
|
||||||
|
inner: AppInner<MH>,
|
||||||
|
state: STATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard, STATE> AppMachine<MH, STATE> {
|
||||||
|
pub fn inner_ref(&self) -> &AppInner<MH> {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_mut(&mut self) -> &mut AppInner<MH> {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppInner<MH: IMusicHoard> {
|
||||||
|
running: bool,
|
||||||
|
music_hoard: MH,
|
||||||
|
selection: Selection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppInner<MH> {
|
||||||
|
pub fn new(music_hoard: MH) -> Self {
|
||||||
|
let selection = Selection::new(music_hoard.get_collection());
|
||||||
|
AppInner {
|
||||||
|
running: true,
|
||||||
|
music_hoard,
|
||||||
|
selection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_running(&self) -> bool {
|
||||||
|
self.running
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppInner<MH>> for AppPublicInner<'a> {
|
||||||
|
fn from(inner: &'a mut AppInner<MH>) -> Self {
|
||||||
|
AppPublicInner {
|
||||||
|
collection: inner.music_hoard.get_collection(),
|
||||||
|
selection: &mut inner.selection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use musichoard::collection::Collection;
|
||||||
|
|
||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
selection::{Category, Delta},
|
||||||
|
AppPublicState, AppState, IAppInteract, IAppInteractBrowse, IAppInteractError,
|
||||||
|
IAppInteractInfo, IAppInteractReload, IAppInteractSearch,
|
||||||
|
},
|
||||||
|
lib::MockIMusicHoard,
|
||||||
|
testmod::COLLECTION,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
|
||||||
|
fn unwrap_browse(self) -> BS {
|
||||||
|
match self {
|
||||||
|
AppState::Browse(browse) => browse,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_info(self) -> IS {
|
||||||
|
match self {
|
||||||
|
AppState::Info(info) => info,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_reload(self) -> RS {
|
||||||
|
match self {
|
||||||
|
AppState::Reload(reload) => reload,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_search(self) -> SS {
|
||||||
|
match self {
|
||||||
|
AppState::Search(search) => search,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_error(self) -> ES {
|
||||||
|
match self {
|
||||||
|
AppState::Error(error) => error,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_critical(self) -> CS {
|
||||||
|
match self {
|
||||||
|
AppState::Critical(critical) => critical,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn music_hoard(collection: Collection) -> MockIMusicHoard {
|
||||||
|
let mut music_hoard = MockIMusicHoard::new();
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_load_from_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
music_hoard
|
||||||
|
.expect_rescan_library()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
music_hoard.expect_get_collection().return_const(collection);
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn app_is_state() {
|
||||||
|
let state = AppPublicState::Search("get rekt");
|
||||||
|
assert!(state.is_search());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn running_quit() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_save_to_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
|
let app = App::new(music_hoard);
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let browse = app.unwrap_browse();
|
||||||
|
|
||||||
|
let app = browse.save_and_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_quit() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_save_to_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
|
let app = App::new(music_hoard);
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let app = App::Error(AppMachine::error(
|
||||||
|
app.unwrap_browse().inner,
|
||||||
|
String::from("get rekt"),
|
||||||
|
));
|
||||||
|
|
||||||
|
let error = app.unwrap_error();
|
||||||
|
|
||||||
|
let browse = error.dismiss_error().unwrap_browse();
|
||||||
|
|
||||||
|
let app = browse.save_and_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn running_force_quit() {
|
||||||
|
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let app = app.force_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_force_quit() {
|
||||||
|
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let app = App::Error(AppMachine::error(
|
||||||
|
app.unwrap_browse().inner,
|
||||||
|
String::from("get rekt"),
|
||||||
|
));
|
||||||
|
|
||||||
|
let app = app.force_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_save_to_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
|
let browse = App::new(music_hoard).unwrap_browse();
|
||||||
|
|
||||||
|
browse.save_and_quit().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_error() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_save_to_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
|
||||||
|
let browse = App::new(music_hoard).unwrap_browse();
|
||||||
|
|
||||||
|
browse.save_and_quit().unwrap_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_error() {
|
||||||
|
let mut music_hoard = MockIMusicHoard::new();
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_load_from_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
music_hoard.expect_get_collection().return_const(vec![]);
|
||||||
|
|
||||||
|
let app = App::new(music_hoard);
|
||||||
|
|
||||||
|
assert!(app.is_running());
|
||||||
|
app.unwrap_critical();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modifiers() {
|
||||||
|
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let browse = app.unwrap_browse();
|
||||||
|
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.decrement_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.decrement_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.decrement_category().unwrap_browse();
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.decrement_category().unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_tracks() {
|
||||||
|
let mut collection = COLLECTION.to_owned();
|
||||||
|
collection[0].albums[0].tracks = vec![];
|
||||||
|
|
||||||
|
let app = App::new(music_hoard(collection));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let browse = app.unwrap_browse();
|
||||||
|
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_albums() {
|
||||||
|
let mut collection = COLLECTION.to_owned();
|
||||||
|
collection[0].albums = vec![];
|
||||||
|
|
||||||
|
let app = App::new(music_hoard(collection));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let browse = app.unwrap_browse();
|
||||||
|
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_artists() {
|
||||||
|
let app = App::new(music_hoard(vec![]));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
let browse = app.unwrap_browse();
|
||||||
|
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Artist);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Album);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||||
|
let selection = &browse.inner.selection;
|
||||||
|
assert_eq!(selection.active, Category::Track);
|
||||||
|
assert_eq!(selection.artist.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||||
|
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn info_overlay() {
|
||||||
|
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||||
|
|
||||||
|
let info = browse.show_info_overlay().unwrap_info();
|
||||||
|
|
||||||
|
info.hide_info_overlay().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reload_hide_menu() {
|
||||||
|
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||||
|
|
||||||
|
let reload = browse.show_reload_menu().unwrap_reload();
|
||||||
|
|
||||||
|
reload.hide_reload_menu().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reload_database() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_load_from_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
|
let browse = App::new(music_hoard).unwrap_browse();
|
||||||
|
|
||||||
|
let reload = browse.show_reload_menu().unwrap_reload();
|
||||||
|
|
||||||
|
reload.reload_database().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reload_library() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_rescan_library()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
|
let browse = App::new(music_hoard).unwrap_browse();
|
||||||
|
|
||||||
|
let reload = browse.show_reload_menu().unwrap_reload();
|
||||||
|
|
||||||
|
reload.reload_library().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reload_error() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_load_from_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
|
||||||
|
let browse = App::new(music_hoard).unwrap_browse();
|
||||||
|
|
||||||
|
let reload = browse.show_reload_menu().unwrap_reload();
|
||||||
|
|
||||||
|
let error = reload.reload_database().unwrap_error();
|
||||||
|
|
||||||
|
error.dismiss_error().unwrap_browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search() {
|
||||||
|
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let search = browse.begin_search().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.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.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.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.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.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.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.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.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let browse = search.finish_search().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_next() {
|
||||||
|
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let search = browse.begin_search().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let search = search.append_character('a').unwrap_search();
|
||||||
|
|
||||||
|
let search = search.search_next().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let search = search.search_next().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
let search = search.search_next().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||||
|
|
||||||
|
let search = search.search_next().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cancel_search() {
|
||||||
|
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
let search = browse.begin_search().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.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('c').unwrap_search();
|
||||||
|
let search = search.append_character('\'').unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
let browse = search.cancel_search().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_search() {
|
||||||
|
let browse = App::new(music_hoard(vec![])).unwrap_browse();
|
||||||
|
|
||||||
|
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||||
|
let browse = browse.increment_category().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let search = browse.begin_search().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let search = search.append_character('a').unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let search = search.search_next().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let search = search.step_back().unwrap_search();
|
||||||
|
|
||||||
|
assert_eq!(search.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
let browse = search.cancel_search().unwrap_browse();
|
||||||
|
|
||||||
|
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||||
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
||||||
|
}
|
||||||
|
}
|
82
src/tui/app/state/reload.rs
Normal file
82
src/tui/app/state/reload.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
selection::IdSelection,
|
||||||
|
state::{AppInner, AppMachine},
|
||||||
|
AppPublic, AppState, IAppInteractReload,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppReload;
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppReload> {
|
||||||
|
pub fn reload(inner: AppInner<MH>) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppReload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppReload>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppReload>) -> Self {
|
||||||
|
AppState::Reload(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppReload>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppReload>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Reload(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractReload for AppMachine<MH, AppReload> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn reload_library(mut self) -> Self::APP {
|
||||||
|
let previous = IdSelection::get(
|
||||||
|
self.inner.music_hoard.get_collection(),
|
||||||
|
&self.inner.selection,
|
||||||
|
);
|
||||||
|
let result = self.inner.music_hoard.rescan_library();
|
||||||
|
self.refresh(previous, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_database(mut self) -> Self::APP {
|
||||||
|
let previous = IdSelection::get(
|
||||||
|
self.inner.music_hoard.get_collection(),
|
||||||
|
&self.inner.selection,
|
||||||
|
);
|
||||||
|
let result = self.inner.music_hoard.load_from_database();
|
||||||
|
self.refresh(previous, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_reload_menu(self) -> Self::APP {
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait IAppInteractReloadPrivate<MH: IMusicHoard> {
|
||||||
|
fn refresh(self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractReloadPrivate<MH> for AppMachine<MH, AppReload> {
|
||||||
|
fn refresh(mut self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH> {
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
self.inner
|
||||||
|
.selection
|
||||||
|
.select_by_id(self.inner.music_hoard.get_collection(), previous);
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/tui/app/state/search.rs
Normal file
100
src/tui/app/state/search.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use crate::tui::{
|
||||||
|
app::{
|
||||||
|
app::App,
|
||||||
|
selection::ListSelection,
|
||||||
|
state::{AppInner, AppMachine},
|
||||||
|
AppState, IAppInteractSearch, AppPublic,
|
||||||
|
},
|
||||||
|
lib::IMusicHoard,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AppSearch {
|
||||||
|
string: String,
|
||||||
|
orig: ListSelection,
|
||||||
|
memo: Vec<AppSearchMemo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppSearchMemo {
|
||||||
|
index: Option<usize>,
|
||||||
|
char: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> AppMachine<MH, AppSearch> {
|
||||||
|
pub fn search(inner: AppInner<MH>, orig: ListSelection) -> Self {
|
||||||
|
AppMachine {
|
||||||
|
inner,
|
||||||
|
state: AppSearch {
|
||||||
|
string: String::new(),
|
||||||
|
orig,
|
||||||
|
memo: vec![],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> From<AppMachine<MH, AppSearch>> for App<MH> {
|
||||||
|
fn from(machine: AppMachine<MH, AppSearch>) -> Self {
|
||||||
|
AppState::Search(machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppSearch>> for AppPublic<'a> {
|
||||||
|
fn from(machine: &'a mut AppMachine<MH, AppSearch>) -> Self {
|
||||||
|
AppPublic {
|
||||||
|
inner: (&mut machine.inner).into(),
|
||||||
|
state: AppState::Search(&machine.state.string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
|
||||||
|
type APP = App<MH>;
|
||||||
|
|
||||||
|
fn append_character(mut self, ch: char) -> Self::APP {
|
||||||
|
let collection = self.inner.music_hoard.get_collection();
|
||||||
|
self.state.string.push(ch);
|
||||||
|
let index =
|
||||||
|
self.inner
|
||||||
|
.selection
|
||||||
|
.incremental_artist_search(collection, &self.state.string, false);
|
||||||
|
self.state.memo.push(AppSearchMemo { index, char: true });
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_next(mut self) -> Self::APP {
|
||||||
|
let collection = self.inner.music_hoard.get_collection();
|
||||||
|
if !self.state.string.is_empty() {
|
||||||
|
let index = self.inner.selection.incremental_artist_search(
|
||||||
|
collection,
|
||||||
|
&self.state.string,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.state.memo.push(AppSearchMemo { index, char: false });
|
||||||
|
}
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_back(mut self) -> Self::APP {
|
||||||
|
let collection = self.inner.music_hoard.get_collection();
|
||||||
|
if let Some(memo) = self.state.memo.pop() {
|
||||||
|
if memo.char {
|
||||||
|
self.state.string.pop();
|
||||||
|
}
|
||||||
|
self.inner.selection.select_artist(collection, memo.index);
|
||||||
|
}
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_search(self) -> Self::APP {
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_search(mut self) -> Self::APP {
|
||||||
|
self.inner.selection.select_by_list(self.state.orig);
|
||||||
|
AppMachine::browse(self.inner).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user