Refactored table sorting into the ManagarrTable widget and StatefulTable so any and all tables created can support sorting with minimal UI changes and thus only need to focus on the handlers. I'm going to continue this effort tomorrow and look at what other widgets can be created to simplify things. Most likely, prompt boxes.

This commit is contained in:
2024-02-12 19:15:02 -07:00
parent adda82f7f3
commit 6ba78cb4ba
29 changed files with 1691 additions and 1716 deletions
+2 -258
View File
@@ -2,267 +2,14 @@ use std::cell::RefCell;
use std::fmt::{Debug, Display, Formatter};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use ratatui::widgets::{ListState, TableState};
use regex::Regex;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Number;
pub mod radarr_models;
pub mod servarr_data;
macro_rules! stateful_iterable {
($name:ident, $state:ty) => {
#[derive(Default)]
pub struct $name<T> {
pub state: $state,
pub items: Vec<T>,
pub filter: Option<HorizontallyScrollableText>,
pub search: Option<HorizontallyScrollableText>,
pub filtered_items: Option<Vec<T>>,
pub filtered_state: Option<$state>,
}
impl<T> Scrollable for $name<T> {
fn scroll_down(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
let selected_row = match self.filtered_state.as_ref().unwrap().selected() {
Some(i) => {
if i >= filtered_items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(selected_row));
return;
}
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_up(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
let selected_row = match self.filtered_state.as_ref().unwrap().selected() {
Some(i) => {
if i == 0 {
filtered_items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(selected_row));
return;
}
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_to_top(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
self.filtered_state.as_mut().unwrap().select(Some(0));
return;
}
if self.items.is_empty() {
return;
}
self.state.select(Some(0));
}
fn scroll_to_bottom(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(filtered_items.len() - 1));
return;
}
if self.items.is_empty() {
return;
}
self.state.select(Some(self.items.len() - 1));
}
}
#[allow(dead_code)]
impl<T> $name<T>
where
T: Clone + PartialEq + Eq + Debug,
{
pub fn set_items(&mut self, items: Vec<T>) {
let items_len = items.len();
self.items = items;
if !self.items.is_empty() {
let selected_row = self.state.selected().map_or(0, |i| {
if i > 0 && i < items_len {
i
} else if i >= items_len {
items_len - 1
} else {
0
}
});
self.state.select(Some(selected_row));
}
}
pub fn set_filtered_items(&mut self, filtered_items: Vec<T>) {
self.filtered_items = Some(filtered_items);
let mut filtered_state: $state = Default::default();
filtered_state.select(Some(0));
self.filtered_state = Some(filtered_state);
}
pub fn select_index(&mut self, index: Option<usize>) {
if let Some(filtered_state) = &mut self.filtered_state {
filtered_state.select(index);
} else {
self.state.select(index);
}
}
pub fn current_selection(&self) -> &T {
if let Some(filtered_items) = &self.filtered_items {
&filtered_items[self
.filtered_state
.as_ref()
.unwrap()
.selected()
.unwrap_or(0)]
} else {
&self.items[self.state.selected().unwrap_or(0)]
}
}
pub fn apply_filter(&mut self, filter_field: fn(&T) -> &str) -> bool {
let filter_matches = match self.filter {
Some(ref filter) if !filter.text.is_empty() => {
let scrubbed_filter = strip_non_search_characters(&filter.text.clone());
self
.items
.iter()
.filter(|item| {
strip_non_search_characters(filter_field(&item)).contains(&scrubbed_filter)
})
.cloned()
.collect()
}
_ => Vec::new(),
};
self.filter = None;
if filter_matches.is_empty() {
return false;
}
self.set_filtered_items(filter_matches);
return true;
}
pub fn reset_filter(&mut self) {
self.filter = None;
self.filtered_items = None;
self.filtered_state = None;
}
pub fn apply_search(&mut self, search_field: fn(&T) -> &str) -> bool {
let search_index = if let Some(search) = self.search.as_ref() {
let search_string = search.text.clone().to_lowercase();
self
.filtered_items
.as_ref()
.unwrap_or(&self.items)
.iter()
.position(|item| {
strip_non_search_characters(search_field(&item)).contains(&search_string)
})
} else {
None
};
self.search = None;
if search_index.is_none() {
return false;
}
self.select_index(search_index);
return true;
}
pub fn reset_search(&mut self) {
self.search = None;
}
}
};
}
pub mod stateful_list;
pub mod stateful_table;
#[cfg(test)]
#[path = "model_tests.rs"]
@@ -289,9 +36,6 @@ pub trait Scrollable {
fn scroll_to_bottom(&mut self);
}
stateful_iterable!(StatefulList, ListState);
stateful_iterable!(StatefulTable, TableState);
#[derive(Default)]
pub struct ScrollableText {
pub items: Vec<String>,
+1 -965
View File
@@ -3,7 +3,6 @@ mod tests {
use std::cell::RefCell;
use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::widgets::{ListState, TableState};
use serde::de::value::Error as ValueError;
use serde::de::value::F64Deserializer;
use serde::de::value::I64Deserializer;
@@ -13,8 +12,7 @@ mod tests {
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::{from_i64, strip_non_search_characters};
use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList,
StatefulTable, TabRoute, TabState,
BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, TabRoute, TabState,
};
const BLOCKS: [ActiveRadarrBlock; 6] = [
@@ -26,940 +24,6 @@ mod tests {
ActiveRadarrBlock::AddMovieConfirmPrompt,
];
#[test]
fn test_stateful_table_scrolling_on_empty_table_performs_no_op() {
let mut stateful_table: StatefulTable<String> = StatefulTable::default();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_to_top();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_to_bottom();
}
#[test]
fn test_stateful_table_filtered_scrolling_on_empty_table_performs_no_op() {
let mut filtered_stateful_table: StatefulTable<String> = StatefulTable {
filtered_items: Some(Vec::new()),
filtered_state: Some(TableState::default()),
..StatefulTable::default()
};
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_to_top();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_to_bottom();
}
#[test]
fn test_stateful_table_scroll() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_to_bottom();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_to_top();
assert_eq!(stateful_table.state.selected(), Some(0));
}
#[test]
fn test_stateful_table_filtered_items_scroll() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_to_bottom();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_to_top();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_table_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(items_vec.clone());
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.state.select(Some(1));
stateful_table.set_items(items_vec.clone());
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.state.select(Some(3));
stateful_table.set_items(items_vec);
assert_eq!(stateful_table.state.selected(), Some(2));
}
#[test]
fn test_stateful_table_set_filtered_items() {
let filtered_items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut filtered_stateful_table: StatefulTable<&str> = StatefulTable::default();
filtered_stateful_table.set_filtered_items(filtered_items_vec.clone());
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
assert_eq!(
filtered_stateful_table.filtered_items,
Some(filtered_items_vec.clone())
);
}
#[test]
fn test_stateful_table_current_selection() {
let mut stateful_table = create_test_stateful_table();
assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[0]);
stateful_table.state.select(Some(1));
assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]);
}
#[test]
fn test_filtered_stateful_table_current_selection() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_str_eq!(
filtered_stateful_table.current_selection(),
&filtered_stateful_table.filtered_items.as_ref().unwrap()[0]
);
filtered_stateful_table
.filtered_state
.as_mut()
.unwrap()
.select(Some(1));
assert_str_eq!(
filtered_stateful_table.current_selection(),
&filtered_stateful_table.filtered_items.as_ref().unwrap()[1]
);
}
#[test]
fn test_stateful_table_select_index() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.select_index(Some(1));
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.select_index(None);
assert_eq!(stateful_table.state.selected(), None);
}
#[test]
fn test_filtered_stateful_table_select_index() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.select_index(Some(1));
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.select_index(None);
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
}
#[test]
fn test_stateful_table_scroll_up() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(0));
}
#[test]
fn test_filtered_stateful_table_scroll_up() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_table_apply_filter() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.filter = Some("i".into());
let expected_items = vec!["this", "is"];
let mut expected_state = TableState::default();
expected_state.select(Some(0));
let has_matches = stateful_table.apply_filter(|&item| item);
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, Some(expected_items));
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(has_matches);
}
#[test]
fn test_stateful_table_apply_filter_no_matches() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.filter = Some("z".into());
let has_matches = stateful_table.apply_filter(|&item| item);
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, None);
assert_eq!(stateful_table.filtered_state, None);
assert!(!has_matches);
}
#[test]
fn test_stateful_table_reset_filter() {
let mut stateful_table = create_test_filtered_stateful_table();
stateful_table.reset_filter();
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, None);
assert_eq!(stateful_table.filtered_state, None);
}
#[test]
fn test_stateful_table_apply_search() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("test".into());
let mut expected_state = TableState::default();
expected_state.select(Some(3));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.state, expected_state);
assert!(has_match);
}
#[test]
fn test_stateful_table_apply_search_no_match() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("shi-mon-a!".into());
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert!(!has_match);
}
#[test]
fn test_filtered_stateful_table_apply_search() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("test".into());
let mut expected_state = TableState::default();
expected_state.select(Some(3));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(has_match);
}
#[test]
fn test_filtered_stateful_table_apply_search_no_match() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("shi-mon-a!".into());
let mut expected_state = TableState::default();
expected_state.select(Some(0));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(!has_match);
}
#[test]
fn test_stateful_table_reset_search() {
let mut stateful_table = create_test_stateful_table();
stateful_table.search = Some("test".into());
stateful_table.reset_search();
assert_eq!(stateful_table.search, None);
}
#[test]
fn test_stateful_list_scrolling_on_empty_list_performs_no_op() {
let mut stateful_list: StatefulList<String> = StatefulList::default();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_to_top();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_to_bottom();
}
#[test]
fn test_filtered_stateful_list_scrolling_on_empty_list_performs_no_op() {
let mut filtered_stateful_list: StatefulList<String> = StatefulList {
filtered_items: Some(Vec::new()),
filtered_state: Some(ListState::default()),
..StatefulList::default()
};
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_list.scroll_up();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_list.scroll_down();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_list.scroll_to_top();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_list.scroll_to_bottom();
}
#[test]
fn test_stateful_list_scroll() {
let mut stateful_list = create_test_stateful_list();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_to_bottom();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_to_top();
assert_eq!(stateful_list.state.selected(), Some(0));
}
#[test]
fn test_filtered_stateful_list_scroll() {
let mut filtered_stateful_list = create_test_filtered_stateful_list();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_list.scroll_down();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_list.scroll_down();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_list.scroll_up();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_list.scroll_up();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_list.scroll_to_bottom();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_list.scroll_to_top();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_list_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(items_vec.clone());
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.state.select(Some(1));
stateful_list.set_items(items_vec.clone());
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.state.select(Some(3));
stateful_list.set_items(items_vec);
assert_eq!(stateful_list.state.selected(), Some(2));
}
#[test]
fn test_stateful_list_set_filtered_items() {
let filtered_items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut filtered_stateful_list: StatefulList<&str> = StatefulList::default();
filtered_stateful_list.set_filtered_items(filtered_items_vec.clone());
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
assert_eq!(
filtered_stateful_list.filtered_items,
Some(filtered_items_vec.clone())
);
}
#[test]
fn test_stateful_list_current_selection() {
let mut stateful_list = create_test_stateful_list();
assert_str_eq!(stateful_list.current_selection(), &stateful_list.items[0]);
stateful_list.state.select(Some(1));
assert_str_eq!(stateful_list.current_selection(), &stateful_list.items[1]);
}
#[test]
fn test_filtered_stateful_list_current_selection() {
let mut filtered_stateful_list = create_test_filtered_stateful_list();
assert_str_eq!(
filtered_stateful_list.current_selection(),
&filtered_stateful_list.filtered_items.as_ref().unwrap()[0]
);
filtered_stateful_list
.filtered_state
.as_mut()
.unwrap()
.select(Some(1));
assert_str_eq!(
filtered_stateful_list.current_selection(),
&filtered_stateful_list.filtered_items.as_ref().unwrap()[1]
);
}
#[test]
fn test_stateful_list_select_index() {
let mut stateful_list = create_test_stateful_list();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.select_index(Some(1));
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.select_index(None);
assert_eq!(stateful_list.state.selected(), None);
}
#[test]
fn test_filtered_stateful_list_select_index() {
let mut filtered_stateful_list = create_test_filtered_stateful_list();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_list.select_index(Some(1));
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_list.select_index(None);
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
}
#[test]
fn test_stateful_list_scroll_up() {
let mut stateful_list = create_test_stateful_list();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(0));
}
#[test]
fn test_filtered_stateful_list_scroll_up() {
let mut filtered_stateful_list = create_test_filtered_stateful_list();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_list.scroll_up();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_list.scroll_up();
assert_eq!(
filtered_stateful_list
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_list_apply_filter() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(vec!["this", "is", "a", "test"]);
stateful_list.filter = Some("i".into());
let expected_items = vec!["this", "is"];
let mut expected_state = ListState::default();
expected_state.select(Some(0));
let has_matches = stateful_list.apply_filter(|&item| item);
assert_eq!(stateful_list.filter, None);
assert_eq!(stateful_list.filtered_items, Some(expected_items));
assert_eq!(stateful_list.filtered_state, Some(expected_state));
assert!(has_matches);
}
#[test]
fn test_stateful_list_apply_filter_no_matches() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(vec!["this", "is", "a", "test"]);
stateful_list.filter = Some("z".into());
let has_matches = stateful_list.apply_filter(|&item| item);
assert_eq!(stateful_list.filter, None);
assert_eq!(stateful_list.filtered_items, None);
assert_eq!(stateful_list.filtered_state, None);
assert!(!has_matches);
}
#[test]
fn test_stateful_list_reset_filter() {
let mut stateful_list = create_test_filtered_stateful_list();
stateful_list.reset_filter();
assert_eq!(stateful_list.filter, None);
assert_eq!(stateful_list.filtered_items, None);
assert_eq!(stateful_list.filtered_state, None);
}
#[test]
fn test_stateful_list_apply_search() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(vec!["this", "is", "a", "test"]);
stateful_list.search = Some("test".into());
let mut expected_state = ListState::default();
expected_state.select(Some(3));
let has_match = stateful_list.apply_search(|&item| item);
assert_eq!(stateful_list.search, None);
assert_eq!(stateful_list.state, expected_state);
assert!(has_match);
}
#[test]
fn test_stateful_list_apply_search_no_match() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(vec!["this", "is", "a", "test"]);
stateful_list.search = Some("shi-mon-a!".into());
let has_match = stateful_list.apply_search(|&item| item);
assert_eq!(stateful_list.search, None);
assert!(!has_match);
}
#[test]
fn test_filtered_stateful_list_apply_search() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_list.search = Some("test".into());
let mut expected_state = ListState::default();
expected_state.select(Some(3));
let has_match = stateful_list.apply_search(|&item| item);
assert_eq!(stateful_list.search, None);
assert_eq!(stateful_list.filtered_state, Some(expected_state));
assert!(has_match);
}
#[test]
fn test_filtered_stateful_list_apply_search_no_match() {
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_list.search = Some("shi-mon-a!".into());
let mut expected_state = ListState::default();
expected_state.select(Some(0));
let has_match = stateful_list.apply_search(|&item| item);
assert_eq!(stateful_list.search, None);
assert_eq!(stateful_list.filtered_state, Some(expected_state));
assert!(!has_match);
}
#[test]
fn test_stateful_list_reset_search() {
let mut stateful_list = create_test_stateful_list();
stateful_list.search = Some("test".into());
stateful_list.reset_search();
assert_eq!(stateful_list.search, None);
}
#[test]
fn test_scrollable_text_with_string() {
let scrollable_text = ScrollableText::with_string("Test \n String \n".to_owned());
@@ -1488,34 +552,6 @@ mod tests {
]
}
fn create_test_stateful_table() -> StatefulTable<&'static str> {
let mut stateful_table = StatefulTable::default();
stateful_table.set_items(vec!["Test 1", "Test 2"]);
stateful_table
}
fn create_test_filtered_stateful_table() -> StatefulTable<&'static str> {
let mut stateful_table = StatefulTable::default();
stateful_table.set_filtered_items(vec!["Test 1", "Test 2"]);
stateful_table
}
fn create_test_stateful_list() -> StatefulList<&'static str> {
let mut stateful_list = StatefulList::default();
stateful_list.set_items(vec!["Test 1", "Test 2"]);
stateful_list
}
fn create_test_filtered_stateful_list() -> StatefulList<&'static str> {
let mut stateful_list = StatefulList::default();
stateful_list.set_filtered_items(vec!["Test 1", "Test 2"]);
stateful_list
}
#[test]
fn test_strip_non_alphanumeric_characters() {
assert_eq!(
+1 -15
View File
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
use derivative::Derivative;
use serde::{Deserialize, Serialize};
use serde_json::{Number, Value};
use strum_macros::{Display, EnumIter};
use strum_macros::EnumIter;
use crate::models::HorizontallyScrollableText;
@@ -454,20 +454,6 @@ pub struct ReleaseDownloadBody {
pub movie_id: i64,
}
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, Display)]
pub enum ReleaseField {
#[default]
Source,
Age,
Rejected,
Title,
Indexer,
Size,
Peers,
Language,
Quality,
}
#[derive(Default, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RootFolder {
+6 -5
View File
@@ -1,10 +1,13 @@
use strum::IntoEnumIterator;
use crate::models::radarr_models::{
Collection, Credit, Indexer, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release,
ReleaseField, RootFolder,
RootFolder,
};
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
use crate::models::{HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable};
use strum::IntoEnumIterator;
use crate::models::stateful_list::StatefulList;
use crate::models::stateful_table::StatefulTable;
use crate::models::{HorizontallyScrollableText, ScrollableText};
#[cfg(test)]
#[path = "modals_tests.rs"]
@@ -20,8 +23,6 @@ pub struct MovieDetailsModal {
pub movie_cast: StatefulTable<Credit>,
pub movie_crew: StatefulTable<Credit>,
pub movie_releases: StatefulTable<Release>,
pub movie_releases_sort: StatefulList<ReleaseField>,
pub sort_ascending: Option<bool>,
}
#[derive(Default, Debug, PartialEq, Eq)]
@@ -8,7 +8,7 @@ mod test {
};
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
use crate::models::StatefulTable;
use crate::models::stateful_table::StatefulTable;
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
@@ -13,9 +13,10 @@ use crate::models::servarr_data::radarr::modals::{
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
MovieDetailsModal,
};
use crate::models::stateful_list::StatefulList;
use crate::models::stateful_table::StatefulTable;
use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, StatefulList,
StatefulTable, TabRoute, TabState,
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, TabRoute, TabState,
};
use crate::network::radarr_network::RadarrEvent;
use bimap::BiMap;
@@ -1,11 +1,12 @@
#[cfg(test)]
pub mod utils {
use crate::models::radarr_models::{
AddMovieSearchResult, CollectionMovie, Credit, MovieHistoryItem, Release, ReleaseField,
AddMovieSearchResult, CollectionMovie, Credit, MovieHistoryItem, Release,
};
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
use crate::models::{HorizontallyScrollableText, ScrollableText, StatefulTable};
use crate::models::stateful_table::StatefulTable;
use crate::models::{HorizontallyScrollableText, ScrollableText};
pub fn create_test_radarr_data<'a>() -> RadarrData<'a> {
let mut movie_details_modal = MovieDetailsModal {
@@ -24,10 +25,6 @@ pub mod utils {
movie_details_modal
.movie_releases
.set_items(vec![Release::default()]);
movie_details_modal
.movie_releases_sort
.set_items(vec![ReleaseField::default()]);
movie_details_modal.sort_ascending = Some(true);
let mut radarr_data = RadarrData {
delete_movie_files: true,
+95
View File
@@ -0,0 +1,95 @@
use crate::models::Scrollable;
use ratatui::widgets::ListState;
use std::fmt::Debug;
#[cfg(test)]
#[path = "stateful_list_tests.rs"]
mod stateful_list_tests;
#[derive(Default)]
pub struct StatefulList<T> {
pub state: ListState,
pub items: Vec<T>,
}
impl<T> Scrollable for StatefulList<T> {
fn scroll_down(&mut self) {
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_up(&mut self) {
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_to_top(&mut self) {
if self.items.is_empty() {
return;
}
self.state.select(Some(0));
}
fn scroll_to_bottom(&mut self) {
if self.items.is_empty() {
return;
}
self.state.select(Some(self.items.len() - 1));
}
}
impl<T> StatefulList<T>
where
T: Clone + PartialEq + Eq + Debug,
{
pub fn set_items(&mut self, items: Vec<T>) {
let items_len = items.len();
self.items = items;
if !self.items.is_empty() {
let selected_row = self.state.selected().map_or(0, |i| {
if i > 0 && i < items_len {
i
} else if i >= items_len {
items_len - 1
} else {
0
}
});
self.state.select(Some(selected_row));
}
}
pub fn current_selection(&self) -> &T {
&self.items[self.state.selected().unwrap_or(0)]
}
}
+111
View File
@@ -0,0 +1,111 @@
#[cfg(test)]
mod tests {
use crate::models::stateful_list::StatefulList;
use crate::models::Scrollable;
use pretty_assertions::{assert_eq, assert_str_eq};
#[test]
fn test_stateful_list_scrolling_on_empty_list_performs_no_op() {
let mut stateful_list: StatefulList<String> = StatefulList::default();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_to_top();
assert_eq!(stateful_list.state.selected(), None);
stateful_list.scroll_to_bottom();
}
#[test]
fn test_stateful_list_scroll() {
let mut stateful_list = create_test_stateful_list();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_down();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_to_bottom();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_to_top();
assert_eq!(stateful_list.state.selected(), Some(0));
}
#[test]
fn test_stateful_list_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut stateful_list: StatefulList<&str> = StatefulList::default();
stateful_list.set_items(items_vec.clone());
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.state.select(Some(1));
stateful_list.set_items(items_vec.clone());
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.state.select(Some(3));
stateful_list.set_items(items_vec);
assert_eq!(stateful_list.state.selected(), Some(2));
}
#[test]
fn test_stateful_list_current_selection() {
let mut stateful_list = create_test_stateful_list();
assert_str_eq!(stateful_list.current_selection(), &stateful_list.items[0]);
stateful_list.state.select(Some(1));
assert_str_eq!(stateful_list.current_selection(), &stateful_list.items[1]);
}
#[test]
fn test_stateful_list_scroll_up() {
let mut stateful_list = create_test_stateful_list();
assert_eq!(stateful_list.state.selected(), Some(0));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(1));
stateful_list.scroll_up();
assert_eq!(stateful_list.state.selected(), Some(0));
}
fn create_test_stateful_list() -> StatefulList<&'static str> {
let mut stateful_list = StatefulList::default();
stateful_list.set_items(vec!["Test 1", "Test 2"]);
stateful_list
}
}
+299
View File
@@ -0,0 +1,299 @@
use crate::models::stateful_list::StatefulList;
use crate::models::{strip_non_search_characters, HorizontallyScrollableText, Scrollable};
use ratatui::widgets::TableState;
use std::cmp::Ordering;
use std::fmt::Debug;
#[cfg(test)]
#[path = "stateful_table_tests.rs"]
mod stateful_table_tests;
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct SortOption<T>
where
T: Clone + PartialEq + Eq + Debug,
{
pub name: &'static str,
pub cmp_fn: Option<fn(&T, &T) -> Ordering>,
}
#[derive(Default)]
pub struct StatefulTable<T>
where
T: Clone + PartialEq + Eq + Debug,
{
pub state: TableState,
pub items: Vec<T>,
pub filter: Option<HorizontallyScrollableText>,
pub search: Option<HorizontallyScrollableText>,
pub filtered_items: Option<Vec<T>>,
pub filtered_state: Option<TableState>,
pub sort_asc: bool,
pub sort: Option<StatefulList<SortOption<T>>>,
}
impl<T> Scrollable for StatefulTable<T>
where
T: Clone + PartialEq + Eq + Debug,
{
fn scroll_down(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
let selected_row = match self.filtered_state.as_ref().unwrap().selected() {
Some(i) => {
if i >= filtered_items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(selected_row));
return;
}
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_up(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
let selected_row = match self.filtered_state.as_ref().unwrap().selected() {
Some(i) => {
if i == 0 {
filtered_items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(selected_row));
return;
}
if self.items.is_empty() {
return;
}
let selected_row = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(selected_row));
}
fn scroll_to_top(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
self.filtered_state.as_mut().unwrap().select(Some(0));
return;
}
if self.items.is_empty() {
return;
}
self.state.select(Some(0));
}
fn scroll_to_bottom(&mut self) {
if let Some(filtered_items) = self.filtered_items.as_ref() {
if filtered_items.is_empty() {
return;
}
self
.filtered_state
.as_mut()
.unwrap()
.select(Some(filtered_items.len() - 1));
return;
}
if self.items.is_empty() {
return;
}
self.state.select(Some(self.items.len() - 1));
}
}
impl<T> StatefulTable<T>
where
T: Clone + PartialEq + Eq + Debug + Default,
{
pub fn set_items(&mut self, items: Vec<T>) {
let items_len = items.len();
self.items = items;
if !self.items.is_empty() {
let selected_row = self.state.selected().map_or(0, |i| {
if i > 0 && i < items_len {
i
} else if i >= items_len {
items_len - 1
} else {
0
}
});
self.state.select(Some(selected_row));
}
}
pub fn set_filtered_items(&mut self, filtered_items: Vec<T>) {
self.filtered_items = Some(filtered_items);
let mut filtered_state: TableState = Default::default();
filtered_state.select(Some(0));
self.filtered_state = Some(filtered_state);
}
pub fn select_index(&mut self, index: Option<usize>) {
if let Some(filtered_state) = &mut self.filtered_state {
filtered_state.select(index);
} else {
self.state.select(index);
}
}
pub fn current_selection(&self) -> &T {
if let Some(filtered_items) = &self.filtered_items {
&filtered_items[self
.filtered_state
.as_ref()
.unwrap()
.selected()
.unwrap_or(0)]
} else {
&self.items[self.state.selected().unwrap_or(0)]
}
}
pub fn sorting(&mut self, sort_options: Vec<SortOption<T>>) {
let mut sort_options_list = StatefulList::default();
sort_options_list.set_items(sort_options);
self.sort = Some(sort_options_list);
}
pub fn apply_sorting(&mut self) {
if let Some(sort_options) = &mut self.sort {
self.sort_asc = !self.sort_asc;
let selected_sort_option = sort_options.current_selection();
let mut items = self.filtered_items.as_ref().unwrap_or(&self.items).clone();
if let Some(cmp_fn) = selected_sort_option.cmp_fn {
if !self.sort_asc {
items.sort_by(|a, b| cmp_fn(a, b).reverse());
} else {
items.sort_by(cmp_fn);
}
if self.filtered_items.is_some() {
self.set_filtered_items(items.clone());
} else {
self.set_items(items);
}
}
}
}
pub fn apply_filter(&mut self, filter_field: fn(&T) -> &str) -> bool {
let filter_matches = match self.filter {
Some(ref filter) if !filter.text.is_empty() => {
let scrubbed_filter = strip_non_search_characters(&filter.text.clone());
self
.items
.iter()
.filter(|item| strip_non_search_characters(filter_field(item)).contains(&scrubbed_filter))
.cloned()
.collect()
}
_ => Vec::new(),
};
self.filter = None;
if filter_matches.is_empty() {
return false;
}
self.set_filtered_items(filter_matches);
true
}
pub fn reset_filter(&mut self) {
self.filter = None;
self.filtered_items = None;
self.filtered_state = None;
}
pub fn apply_search(&mut self, search_field: fn(&T) -> &str) -> bool {
let search_index = if let Some(search) = self.search.as_ref() {
let search_string = search.text.clone().to_lowercase();
self
.filtered_items
.as_ref()
.unwrap_or(&self.items)
.iter()
.position(|item| strip_non_search_characters(search_field(item)).contains(&search_string))
} else {
None
};
self.search = None;
if search_index.is_none() {
return false;
}
self.select_index(search_index);
true
}
pub fn reset_search(&mut self) {
self.search = None;
}
}
+615
View File
@@ -0,0 +1,615 @@
#[cfg(test)]
mod tests {
use crate::models::stateful_table::{SortOption, StatefulTable};
use crate::models::Scrollable;
use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::widgets::TableState;
#[test]
fn test_stateful_table_scrolling_on_empty_table_performs_no_op() {
let mut stateful_table: StatefulTable<String> = StatefulTable::default();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_to_top();
assert_eq!(stateful_table.state.selected(), None);
stateful_table.scroll_to_bottom();
}
#[test]
fn test_stateful_table_filtered_scrolling_on_empty_table_performs_no_op() {
let mut filtered_stateful_table: StatefulTable<String> = StatefulTable {
filtered_items: Some(Vec::new()),
filtered_state: Some(TableState::default()),
..StatefulTable::default()
};
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_to_top();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
filtered_stateful_table.scroll_to_bottom();
}
#[test]
fn test_stateful_table_scroll() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_down();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_to_bottom();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_to_top();
assert_eq!(stateful_table.state.selected(), Some(0));
}
#[test]
fn test_stateful_table_filtered_items_scroll() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_down();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_to_bottom();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_to_top();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_table_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(items_vec.clone());
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.state.select(Some(1));
stateful_table.set_items(items_vec.clone());
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.state.select(Some(3));
stateful_table.set_items(items_vec);
assert_eq!(stateful_table.state.selected(), Some(2));
}
#[test]
fn test_stateful_table_set_filtered_items() {
let filtered_items_vec = vec!["Test 1", "Test 2", "Test 3"];
let mut filtered_stateful_table: StatefulTable<&str> = StatefulTable::default();
filtered_stateful_table.set_filtered_items(filtered_items_vec.clone());
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
assert_eq!(
filtered_stateful_table.filtered_items,
Some(filtered_items_vec.clone())
);
}
#[test]
fn test_stateful_table_current_selection() {
let mut stateful_table = create_test_stateful_table();
assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[0]);
stateful_table.state.select(Some(1));
assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]);
}
#[test]
fn test_stateful_table_sorting() {
let sort_options: Vec<SortOption<String>> = vec![
SortOption {
name: "Test 1",
cmp_fn: None,
},
SortOption {
name: "Test 2",
cmp_fn: None,
},
];
let mut stateful_table: StatefulTable<String> = StatefulTable::default();
stateful_table.sorting(sort_options.clone());
assert_eq!(
stateful_table.sort.as_ref().unwrap().items,
sort_options.clone()
);
assert_eq!(
stateful_table.sort.as_ref().unwrap().current_selection(),
&sort_options[0]
);
}
#[test]
fn test_stateful_table_apply_sorting_no_op_no_sort_options() {
let mut stateful_table = create_test_stateful_table();
let expected_items = stateful_table.items.clone();
stateful_table.apply_sorting();
assert_eq!(stateful_table.items, expected_items);
assert!(!stateful_table.sort_asc);
}
#[test]
fn test_stateful_table_apply_sorting_no_op_no_cmp_fn() {
let mut stateful_table = create_test_stateful_table();
stateful_table.sorting(vec![SortOption {
name: "Test 1",
cmp_fn: None,
}]);
let expected_items = stateful_table.items.clone();
stateful_table.apply_sorting();
assert_eq!(stateful_table.items, expected_items);
assert!(stateful_table.sort_asc);
}
#[test]
fn test_filtered_stateful_table_apply_sorting_no_op_no_cmp_fn() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
filtered_stateful_table.sorting(vec![SortOption {
name: "Test 1",
cmp_fn: None,
}]);
let expected_items = filtered_stateful_table
.filtered_items
.as_ref()
.unwrap()
.clone();
filtered_stateful_table.apply_sorting();
assert_eq!(
*filtered_stateful_table.filtered_items.as_ref().unwrap(),
expected_items
);
assert!(filtered_stateful_table.sort_asc);
}
#[test]
fn test_stateful_table_apply_sorting() {
let mut stateful_table = create_test_stateful_table();
stateful_table.sorting(vec![SortOption {
name: "Test 1",
cmp_fn: Some(|a, b| a.cmp(b)),
}]);
let mut expected_items = stateful_table.items.clone();
expected_items.sort();
stateful_table.apply_sorting();
assert_eq!(stateful_table.items, expected_items);
assert!(stateful_table.sort_asc);
stateful_table.apply_sorting();
expected_items.reverse();
assert_eq!(stateful_table.items, expected_items);
assert!(!stateful_table.sort_asc);
}
#[test]
fn test_filtered_stateful_table_apply_sorting() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
filtered_stateful_table.sorting(vec![SortOption {
name: "Test 1",
cmp_fn: Some(|a, b| a.cmp(b)),
}]);
let mut expected_items = filtered_stateful_table
.filtered_items
.as_mut()
.unwrap()
.clone();
expected_items.sort();
filtered_stateful_table.apply_sorting();
assert_eq!(
*filtered_stateful_table.filtered_items.as_ref().unwrap(),
expected_items
);
assert!(filtered_stateful_table.sort_asc);
filtered_stateful_table.apply_sorting();
expected_items.reverse();
assert_eq!(
*filtered_stateful_table.filtered_items.as_ref().unwrap(),
expected_items
);
assert!(!filtered_stateful_table.sort_asc);
}
#[test]
fn test_filtered_stateful_table_current_selection() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_str_eq!(
filtered_stateful_table.current_selection(),
&filtered_stateful_table.filtered_items.as_ref().unwrap()[0]
);
filtered_stateful_table
.filtered_state
.as_mut()
.unwrap()
.select(Some(1));
assert_str_eq!(
filtered_stateful_table.current_selection(),
&filtered_stateful_table.filtered_items.as_ref().unwrap()[1]
);
}
#[test]
fn test_stateful_table_select_index() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.select_index(Some(1));
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.select_index(None);
assert_eq!(stateful_table.state.selected(), None);
}
#[test]
fn test_filtered_stateful_table_select_index() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.select_index(Some(1));
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.select_index(None);
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
None
);
}
#[test]
fn test_stateful_table_scroll_up() {
let mut stateful_table = create_test_stateful_table();
assert_eq!(stateful_table.state.selected(), Some(0));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(1));
stateful_table.scroll_up();
assert_eq!(stateful_table.state.selected(), Some(0));
}
#[test]
fn test_filtered_stateful_table_scroll_up() {
let mut filtered_stateful_table = create_test_filtered_stateful_table();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(1)
);
filtered_stateful_table.scroll_up();
assert_eq!(
filtered_stateful_table
.filtered_state
.as_ref()
.unwrap()
.selected(),
Some(0)
);
}
#[test]
fn test_stateful_table_apply_filter() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.filter = Some("i".into());
let expected_items = vec!["this", "is"];
let mut expected_state = TableState::default();
expected_state.select(Some(0));
let has_matches = stateful_table.apply_filter(|&item| item);
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, Some(expected_items));
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(has_matches);
}
#[test]
fn test_stateful_table_apply_filter_no_matches() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.filter = Some("z".into());
let has_matches = stateful_table.apply_filter(|&item| item);
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, None);
assert_eq!(stateful_table.filtered_state, None);
assert!(!has_matches);
}
#[test]
fn test_stateful_table_reset_filter() {
let mut stateful_table = create_test_filtered_stateful_table();
stateful_table.reset_filter();
assert_eq!(stateful_table.filter, None);
assert_eq!(stateful_table.filtered_items, None);
assert_eq!(stateful_table.filtered_state, None);
}
#[test]
fn test_stateful_table_apply_search() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("test".into());
let mut expected_state = TableState::default();
expected_state.select(Some(3));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.state, expected_state);
assert!(has_match);
}
#[test]
fn test_stateful_table_apply_search_no_match() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("shi-mon-a!".into());
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert!(!has_match);
}
#[test]
fn test_filtered_stateful_table_apply_search() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("test".into());
let mut expected_state = TableState::default();
expected_state.select(Some(3));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(has_match);
}
#[test]
fn test_filtered_stateful_table_apply_search_no_match() {
let mut stateful_table: StatefulTable<&str> = StatefulTable::default();
stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]);
stateful_table.search = Some("shi-mon-a!".into());
let mut expected_state = TableState::default();
expected_state.select(Some(0));
let has_match = stateful_table.apply_search(|&item| item);
assert_eq!(stateful_table.search, None);
assert_eq!(stateful_table.filtered_state, Some(expected_state));
assert!(!has_match);
}
#[test]
fn test_stateful_table_reset_search() {
let mut stateful_table = create_test_stateful_table();
stateful_table.search = Some("test".into());
stateful_table.reset_search();
assert_eq!(stateful_table.search, None);
}
fn create_test_stateful_table() -> StatefulTable<&'static str> {
let mut stateful_table = StatefulTable::default();
stateful_table.set_items(vec!["Test 1", "Test 2"]);
stateful_table
}
fn create_test_filtered_stateful_table() -> StatefulTable<&'static str> {
let mut stateful_table = StatefulTable::default();
stateful_table.set_filtered_items(vec!["Test 1", "Test 2"]);
stateful_table
}
}