Provide search functionality through the TUI #134
@ -48,7 +48,6 @@ pub struct TrackSelection {
|
|||||||
pub state: WidgetState,
|
pub state: WidgetState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should be with browse state (maybe?)
|
|
||||||
pub enum Delta {
|
pub enum Delta {
|
||||||
Line,
|
Line,
|
||||||
Page,
|
Page,
|
||||||
@ -85,6 +84,10 @@ impl Selection {
|
|||||||
self.artist.select(artists, index);
|
self.artist.select(artists, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_artist(&self) -> Option<usize> {
|
||||||
|
self.artist.selected()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset_artist(&mut self, artists: &[Artist]) {
|
pub fn reset_artist(&mut self, artists: &[Artist]) {
|
||||||
if self.artist.state.list.selected() != Some(0) {
|
if self.artist.state.list.selected() != Some(0) {
|
||||||
self.select_by_id(artists, IdSelection { artist: None });
|
self.select_by_id(artists, IdSelection { artist: None });
|
||||||
@ -107,16 +110,6 @@ impl Selection {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn incremental_artist_search(
|
|
||||||
&mut self,
|
|
||||||
collection: &Collection,
|
|
||||||
artist_name: &str,
|
|
||||||
next: bool,
|
|
||||||
) -> Option<usize> {
|
|
||||||
self.artist
|
|
||||||
.incremental_search(collection, artist_name, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) {
|
pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) {
|
||||||
match self.active {
|
match self.active {
|
||||||
Category::Artist => self.increment_artist(collection, delta),
|
Category::Artist => self.increment_artist(collection, delta),
|
||||||
@ -200,6 +193,10 @@ impl ArtistSelection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selected(&self) -> Option<usize> {
|
||||||
|
self.state.list.selected()
|
||||||
|
}
|
||||||
|
|
||||||
fn select(&mut self, artists: &[Artist], to: Option<usize>) {
|
fn select(&mut self, artists: &[Artist], to: Option<usize>) {
|
||||||
match to {
|
match to {
|
||||||
Some(to) => self.select_to(artists, to),
|
Some(to) => self.select_to(artists, to),
|
||||||
@ -215,91 +212,6 @@ impl ArtistSelection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: use aho_corasick for normalization - AhoCorasick does not implement PartialEq. It
|
|
||||||
// makes more sense to be places in app.rs as it would make ArtistSelection non-trivial.
|
|
||||||
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String {
|
|
||||||
let normalized = if lowercase {
|
|
||||||
search.to_lowercase()
|
|
||||||
} else {
|
|
||||||
search.to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unlikely that this covers all possible strings, but it should at least cover strings
|
|
||||||
// relevant for music (at least in English). The list of characters handled is based on
|
|
||||||
// https://wiki.musicbrainz.org/User:Yurim/Punctuation_and_Special_Characters.
|
|
||||||
if asciify {
|
|
||||||
normalized
|
|
||||||
// U+2010 hyphen, U+2012 figure dash, U+2013 en dash, U+2014 em dash,
|
|
||||||
// U+2015 horizontal bar
|
|
||||||
.replace(['‐', '‒', '–', '—', '―'], "-")
|
|
||||||
.replace(['‘', '’'], "'") // U+2018, U+2019
|
|
||||||
.replace(['“', '”'], "\"") // U+201C, U+201D
|
|
||||||
.replace('…', "...") // U+2026
|
|
||||||
.replace('−', "-") // U+2212 minus sign
|
|
||||||
} else {
|
|
||||||
normalized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_case_sensitive(artist_name: &str) -> bool {
|
|
||||||
artist_name
|
|
||||||
.chars()
|
|
||||||
.any(|ch| ch.is_alphabetic() && ch.is_uppercase())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_char_sensitive(artist_name: &str) -> bool {
|
|
||||||
let special_chars: &[char] = &['‐', '‒', '–', '—', '―', '‘', '’', '“', '”', '…', '−'];
|
|
||||||
artist_name.chars().any(|ch| special_chars.contains(&ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn incremental_search_predicate(
|
|
||||||
case_sensitive: bool,
|
|
||||||
char_sensitive: bool,
|
|
||||||
search_name: &String,
|
|
||||||
probe: &Artist,
|
|
||||||
) -> bool {
|
|
||||||
let name = Self::normalize_search(&probe.id.name, !case_sensitive, !char_sensitive);
|
|
||||||
let mut result = name.starts_with(search_name);
|
|
||||||
|
|
||||||
if let Some(ref probe_sort) = probe.sort {
|
|
||||||
let name = Self::normalize_search(&probe_sort.name, !case_sensitive, !char_sensitive);
|
|
||||||
result = result || name.starts_with(search_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: search logic should be with the search state
|
|
||||||
fn incremental_search(
|
|
||||||
&mut self,
|
|
||||||
artists: &[Artist],
|
|
||||||
artist_name: &str,
|
|
||||||
next: bool,
|
|
||||||
) -> Option<usize> {
|
|
||||||
let previous = self.state.list.selected();
|
|
||||||
|
|
||||||
if let Some(mut index) = self.state.list.selected() {
|
|
||||||
let case_sensitive = Self::is_case_sensitive(artist_name);
|
|
||||||
let char_sensitive = Self::is_char_sensitive(artist_name);
|
|
||||||
let search = Self::normalize_search(artist_name, !case_sensitive, !char_sensitive);
|
|
||||||
|
|
||||||
if next && ((index + 1) < artists.len()) {
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
let slice = &artists[index..];
|
|
||||||
|
|
||||||
let result = slice.iter().position(|probe| {
|
|
||||||
Self::incremental_search_predicate(case_sensitive, char_sensitive, &search, probe)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(slice_index) = result {
|
|
||||||
self.select_to(artists, index + slice_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previous
|
|
||||||
}
|
|
||||||
|
|
||||||
fn increment_by(&mut self, artists: &[Artist], by: usize) {
|
fn increment_by(&mut self, artists: &[Artist], by: usize) {
|
||||||
if let Some(index) = self.state.list.selected() {
|
if let Some(index) = self.state.list.selected() {
|
||||||
let result = index.saturating_add(by);
|
let result = index.saturating_add(by);
|
||||||
@ -904,83 +816,4 @@ mod tests {
|
|||||||
sel.reinitialise(&[], active_artist);
|
sel.reinitialise(&[], active_artist);
|
||||||
assert_eq!(sel, expected);
|
assert_eq!(sel, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn artist_incremental_search() {
|
|
||||||
let artists = &COLLECTION;
|
|
||||||
|
|
||||||
// Empty collection.
|
|
||||||
let mut sel = ArtistSelection::initialise(&[]);
|
|
||||||
assert_eq!(sel.state.list.selected(), None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist 'a'", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), None);
|
|
||||||
|
|
||||||
// Basic test, first element.
|
|
||||||
let mut sel = ArtistSelection::initialise(artists);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist 'a'", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
// Basic test, non-first element.
|
|
||||||
sel.reinitialise(artists, None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist 'c'", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
// Non-lowercase.
|
|
||||||
sel.reinitialise(artists, None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "Album_Artist ", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "Album_Artist 'C'", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
// Non-ascii.
|
|
||||||
sel.reinitialise(artists, None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ‘c’", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
// Stop at name, not sort name.
|
|
||||||
sel.reinitialise(artists, None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "the", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "the album_artist 'c'", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
// Search next with common prefix.
|
|
||||||
sel.reinitialise(artists, None);
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", false);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(0));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", true);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(1));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", true);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(2));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", true);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(3));
|
|
||||||
|
|
||||||
sel.incremental_search(artists, "album_artist ", true);
|
|
||||||
assert_eq!(sel.state.list.selected(), Some(3));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use musichoard::collection::artist::Artist;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
app::App,
|
app::App,
|
||||||
@ -51,25 +53,18 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
|
|||||||
type APP = App<MH>;
|
type APP = App<MH>;
|
||||||
|
|
||||||
fn append_character(mut self, ch: char) -> Self::APP {
|
fn append_character(mut self, ch: char) -> Self::APP {
|
||||||
let collection = self.inner.music_hoard.get_collection();
|
|
||||||
self.state.string.push(ch);
|
self.state.string.push(ch);
|
||||||
let index =
|
let index = self.inner.selection.artist.state.list.selected();
|
||||||
self.inner
|
|
||||||
.selection
|
|
||||||
.incremental_artist_search(collection, &self.state.string, false);
|
|
||||||
self.state.memo.push(AppSearchMemo { index, char: true });
|
self.state.memo.push(AppSearchMemo { index, char: true });
|
||||||
|
self.incremental_search(false);
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_next(mut self) -> Self::APP {
|
fn search_next(mut self) -> Self::APP {
|
||||||
let collection = self.inner.music_hoard.get_collection();
|
|
||||||
if !self.state.string.is_empty() {
|
if !self.state.string.is_empty() {
|
||||||
let index = self.inner.selection.incremental_artist_search(
|
let index = self.inner.selection.artist.state.list.selected();
|
||||||
collection,
|
|
||||||
&self.state.string,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
self.state.memo.push(AppSearchMemo { index, char: false });
|
self.state.memo.push(AppSearchMemo { index, char: false });
|
||||||
|
self.incremental_search(true);
|
||||||
}
|
}
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
@ -99,6 +94,103 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait IAppInteractSearchPrivate {
|
||||||
|
fn incremental_search(&mut self, next: bool);
|
||||||
|
fn incremental_search_predicate(
|
||||||
|
case_sensitive: bool,
|
||||||
|
char_sensitive: bool,
|
||||||
|
search_name: &String,
|
||||||
|
probe: &Artist,
|
||||||
|
) -> bool;
|
||||||
|
|
||||||
|
fn is_case_sensitive(artist_name: &str) -> bool;
|
||||||
|
fn is_char_sensitive(artist_name: &str) -> bool;
|
||||||
|
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MH: IMusicHoard> IAppInteractSearchPrivate for AppMachine<MH, AppSearch> {
|
||||||
|
fn incremental_search(&mut self, next: bool) {
|
||||||
|
let artists = self.inner.music_hoard.get_collection();
|
||||||
|
let artist_name = &self.state.string;
|
||||||
|
|
||||||
|
let sel = &mut self.inner.selection;
|
||||||
|
if let Some(mut index) = sel.selected_artist() {
|
||||||
|
let case_sensitive = Self::is_case_sensitive(artist_name);
|
||||||
|
let char_sensitive = Self::is_char_sensitive(artist_name);
|
||||||
|
let search = Self::normalize_search(artist_name, !case_sensitive, !char_sensitive);
|
||||||
|
|
||||||
|
if next && ((index + 1) < artists.len()) {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
let slice = &artists[index..];
|
||||||
|
|
||||||
|
let result = slice.iter().position(|probe| {
|
||||||
|
Self::incremental_search_predicate(case_sensitive, char_sensitive, &search, probe)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(slice_index) = result {
|
||||||
|
sel.select_artist(artists, Some(index + slice_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incremental_search_predicate(
|
||||||
|
case_sensitive: bool,
|
||||||
|
char_sensitive: bool,
|
||||||
|
search_name: &String,
|
||||||
|
probe: &Artist,
|
||||||
|
) -> bool {
|
||||||
|
let name = Self::normalize_search(&probe.id.name, !case_sensitive, !char_sensitive);
|
||||||
|
let mut result = name.starts_with(search_name);
|
||||||
|
|
||||||
|
if let Some(ref probe_sort) = probe.sort {
|
||||||
|
if !result {
|
||||||
|
let name =
|
||||||
|
Self::normalize_search(&probe_sort.name, !case_sensitive, !char_sensitive);
|
||||||
|
result = name.starts_with(search_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_case_sensitive(artist_name: &str) -> bool {
|
||||||
|
artist_name
|
||||||
|
.chars()
|
||||||
|
.any(|ch| ch.is_alphabetic() && ch.is_uppercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_char_sensitive(artist_name: &str) -> bool {
|
||||||
|
let special_chars: &[char] = &['‐', '‒', '–', '—', '―', '−', '‘', '’', '“', '”', '…'];
|
||||||
|
artist_name.chars().any(|ch| special_chars.contains(&ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: use aho_corasick for normalization - AhoCorasick does not implement PartialEq. It
|
||||||
|
// makes more sense to be places in app.rs as it would make ArtistSelection non-trivial.
|
||||||
|
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String {
|
||||||
|
let normalized = if lowercase {
|
||||||
|
search.to_lowercase()
|
||||||
|
} else {
|
||||||
|
search.to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unlikely that this covers all possible strings, but it should at least cover strings
|
||||||
|
// relevant for music (at least in English). The list of characters handled is based on
|
||||||
|
// https://wiki.musicbrainz.org/User:Yurim/Punctuation_and_Special_Characters.
|
||||||
|
if asciify {
|
||||||
|
normalized
|
||||||
|
// U+2010 hyphen, U+2012 figure dash, U+2013 en dash, U+2014 em dash,
|
||||||
|
// U+2015 horizontal bar, U+2212 minus sign
|
||||||
|
.replace(['‐', '‒', '–', '—', '―', '−'], "-")
|
||||||
|
.replace(['‘', '’'], "'") // U+2018, U+2019
|
||||||
|
.replace(['“', '”'], "\"") // U+201C, U+201D
|
||||||
|
.replace('…', "...") // U+2026
|
||||||
|
} else {
|
||||||
|
normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
@ -121,6 +213,107 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn artist_incremental_search() {
|
||||||
|
// Empty collection.
|
||||||
|
let mut search = AppMachine::search(inner(music_hoard(vec![])), orig(None));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist 'a'");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||||
|
|
||||||
|
// Basic test, first element.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist ");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist 'a'");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
// Basic test, non-first element.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist ");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist 'c'");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
// Non-lowercase.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("Album_Artist ");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("Album_Artist 'C'");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
// Non-ascii.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist ");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist ‘c’");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
// Stop at name, not sort name.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("the ");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
search.state.string = String::from("the album_artist 'c'");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
// Search next with common prefix.
|
||||||
|
let mut search =
|
||||||
|
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.state.string = String::from("album_artist");
|
||||||
|
search.incremental_search(false);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||||
|
|
||||||
|
search.incremental_search(true);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||||
|
|
||||||
|
search.incremental_search(true);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||||
|
|
||||||
|
search.incremental_search(true);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||||
|
|
||||||
|
search.incremental_search(true);
|
||||||
|
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search() {
|
fn search() {
|
||||||
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
|
||||||
|
Loading…
Reference in New Issue
Block a user