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:
+2
-258
@@ -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
@@ -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!(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user