Merge branch 'feat/advance_filter' into dev

This commit is contained in:
Jack Wills
2024-07-24 14:20:46 +00:00
5 changed files with 390 additions and 55 deletions
+4
View File
@@ -75,6 +75,10 @@ macro_rules! unit_struct {
pub fn set(&mut self, value: String) { pub fn set(&mut self, value: String) {
self.0 = value; self.0 = value;
} }
pub fn contains(&self, term: &str) -> bool {
self.0.to_lowercase().contains(term)
}
} }
impl std::fmt::Display for $name { impl std::fmt::Display for $name {
+258 -30
View File
@@ -55,26 +55,85 @@ impl fmt::Display for Header {
} }
} }
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FilterBy {
#[default]
Name,
Image,
Status,
All,
}
/// Convert errors into strings to display
impl fmt::Display for FilterBy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Name => "Name",
Self::Image => "Image",
Self::Status => "Status",
Self::All => "All",
}
)
}
}
impl FilterBy {
const fn next(self) -> Option<Self> {
match self {
Self::Name => Some(Self::Image),
Self::Image => Some(Self::Status),
Self::Status => Some(Self::All),
Self::All => None,
}
}
const fn prev(self) -> Option<Self> {
match self {
Self::Name => None,
Self::Image => Some(Self::Name),
Self::Status => Some(Self::Image),
Self::All => Some(Self::Status),
}
}
}
#[derive(Debug, Clone)]
pub struct Filter {
pub term: Option<String>,
pub by: FilterBy,
}
impl Filter {
pub fn new() -> Self {
Self {
term: None,
by: FilterBy::default(),
}
}
}
/// Global app_state, stored in an Arc<Mutex> /// Global app_state, stored in an Arc<Mutex>
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg(not(test))] #[cfg(not(test))]
pub struct AppData { pub struct AppData {
containers: StatefulList<ContainerItem>, containers: StatefulList<ContainerItem>,
error: Option<AppError>, error: Option<AppError>,
filter_term: Option<String>, filter: Filter,
sorted_by: Option<(Header, SortedOrder)>,
hidden_containers: Vec<ContainerItem>, hidden_containers: Vec<ContainerItem>,
sorted_by: Option<(Header, SortedOrder)>,
pub args: CliArgs, pub args: CliArgs,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg(test)] #[cfg(test)]
pub struct AppData { pub struct AppData {
pub hidden_containers: Vec<ContainerItem>,
pub args: CliArgs, pub args: CliArgs,
pub containers: StatefulList<ContainerItem>, pub containers: StatefulList<ContainerItem>,
pub error: Option<AppError>, pub error: Option<AppError>,
pub filter_term: Option<String>, pub filter: Filter,
pub hidden_containers: Vec<ContainerItem>,
pub sorted_by: Option<(Header, SortedOrder)>, pub sorted_by: Option<(Header, SortedOrder)>,
} }
@@ -87,7 +146,7 @@ impl AppData {
hidden_containers: vec![], hidden_containers: vec![],
error: None, error: None,
sorted_by: None, sorted_by: None,
filter_term: None, filter: Filter::new(),
} }
} }
@@ -104,20 +163,33 @@ impl AppData {
/// Get the current filter term /// Get the current filter term
pub const fn get_filter_term(&self) -> Option<&String> { pub const fn get_filter_term(&self) -> Option<&String> {
self.filter_term.as_ref() self.filter.term.as_ref()
} }
/// Check the container name against the current filter /// Get the current filter by choice
fn can_insert(&self, name: &str) -> bool { pub const fn get_filter_by(&self) -> FilterBy {
self.filter_term.as_ref().map_or(true, |term| { self.filter.by
name.to_string() }
.to_lowercase()
.contains(&term.to_lowercase()) /// Check if a given container can be inserted into the "visible" list, based on current filter term and filter_by
fn can_insert(&self, container: &ContainerItem) -> bool {
self.filter.term.as_ref().map_or(true, |term| {
let term = term.to_lowercase();
match self.filter.by {
FilterBy::All => {
container.name.contains(&term)
|| container.image.contains(&term)
|| container.status.to_lowercase().contains(&term)
}
FilterBy::Image => container.image.contains(&term),
FilterBy::Name => container.name.contains(&term),
FilterBy::Status => container.status.to_lowercase().contains(&term),
}
}) })
} }
/// Remove items from the containers list based on the filter term, and insert into a "hidden" vec /// Remove items from the containers list based on the filter term, and insert into a "hidden" vec
/// sets the state to start if any filtering has occured /// sets the state to start if any filtering has occurred
/// Also search in the "hidden" vec for items and insert back into the main containers vec /// Also search in the "hidden" vec for items and insert back into the main containers vec
fn filter_containers(&mut self) { fn filter_containers(&mut self) {
let pre_len = self.get_container_len(); let pre_len = self.get_container_len();
@@ -127,7 +199,7 @@ impl AppData {
.hidden_containers .hidden_containers
.iter() .iter()
.cloned() .cloned()
.partition(|item| self.can_insert(item.name.get())); .partition(|item| self.can_insert(item));
while let Some(x) = new_items.pop() { while let Some(x) = new_items.pop() {
self.containers.items.push(x); self.containers.items.push(x);
@@ -140,7 +212,7 @@ impl AppData {
.items .items
.iter() .iter()
.cloned() .cloned()
.partition(|item| self.can_insert(item.name.get())); .partition(|item| self.can_insert(item));
self.containers.items = new_items; self.containers.items = new_items;
self.hidden_containers.extend(tmp_items); self.hidden_containers.extend(tmp_items);
@@ -151,31 +223,54 @@ impl AppData {
} }
} }
/// Re-filter the containers, used after the filter.by has been changed
fn re_filter(&mut self) {
self.containers.items.append(&mut self.hidden_containers);
self.hidden_containers = vec![];
self.filter_containers();
}
/// Set a single char into the filter term /// Set a single char into the filter term
pub fn filter_term_push(&mut self, c: char) { pub fn filter_term_push(&mut self, c: char) {
if let Some(term) = self.filter_term.as_mut() { if let Some(term) = self.filter.term.as_mut() {
term.push(c); term.push(c);
} else { } else {
self.filter_term = Some(format!("{c}")); self.filter.term = Some(format!("{c}"));
}; };
self.filter_containers(); self.filter_containers();
} }
/// Delete the final char of the filter term /// Delete the final char of the filter term
pub fn filter_term_pop(&mut self) { pub fn filter_term_pop(&mut self) {
if let Some(term) = self.filter_term.as_mut() { if let Some(term) = self.filter.term.as_mut() {
// should now search for items in the tmp vec, and insert into containers if found // should now search for items in the tmp vec, and insert into containers if found
term.pop(); term.pop();
if term.is_empty() { if term.is_empty() {
self.filter_term = None; self.filter.term = None;
} }
} }
self.filter_containers(); self.filter_containers();
} }
/// Remove the filter term completely, empty the "hidden" container vec // change the filter_by option
pub fn filter_by_next(&mut self) {
if let Some(by) = self.filter.by.next() {
self.filter.by = by;
self.re_filter();
}
}
// change the filter_by option
pub fn filter_by_prev(&mut self) {
if let Some(by) = self.filter.by.prev() {
self.filter.by = by;
self.re_filter();
}
}
/// Remove the filter completely
pub fn filter_term_clear(&mut self) { pub fn filter_term_clear(&mut self) {
self.filter_term = None; self.filter.term = None;
while let Some(i) = self.hidden_containers.pop() { while let Some(i) = self.hidden_containers.pop() {
if self.get_container_by_id(&i.id).is_none() { if self.get_container_by_id(&i.id).is_none() {
self.containers.items.push(i); self.containers.items.push(i);
@@ -307,9 +402,14 @@ impl AppData {
&self.containers.items &self.containers.items
} }
/// Get title for containers section /// Get title for containers section, add a suffix indicating if the containers are currently under filter
pub fn container_title(&self) -> String { pub fn container_title(&self) -> String {
self.containers.get_state_title() let suffix = if !self.hidden_containers.is_empty() && !self.containers.items.is_empty() {
" - filtered"
} else {
""
};
format!("{}{}", self.containers.get_state_title(), suffix)
} }
/// Select the first container /// Select the first container
@@ -595,7 +695,7 @@ impl AppData {
/// Find the widths for the strings in the containers panel. /// Find the widths for the strings in the containers panel.
/// So can display nicely and evenly /// So can display nicely and evenly
/// Searches in both containes & hidden_containers /// Searches in both contains & hidden_containers
pub fn get_width(&self) -> Columns { pub fn get_width(&self) -> Columns {
let mut columns = Columns::new(); let mut columns = Columns::new();
let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12); let count = |x: &str| u8::try_from(x.chars().count()).unwrap_or(12);
@@ -777,10 +877,10 @@ impl AppData {
}; };
} else { } else {
// container not known, so make new ContainerItem and push into containers Ve // container not known, so make new ContainerItem and push into containers Ve
let can_insert = self.can_insert(&name);
let container = ContainerItem::new( let container = ContainerItem::new(
created, id, image, is_oxker, name, ports, state, status, created, id, image, is_oxker, name, ports, state, status,
); );
let can_insert = self.can_insert(&container);
if can_insert { if can_insert {
self.containers.items.push(container); self.containers.items.push(container);
} else { } else {
@@ -1551,8 +1651,8 @@ mod tests {
// ****** // // ****** //
#[test] #[test]
/// Data is filtered correctly /// Data is filtered correctly by name
fn test_app_data_filter() { fn test_app_data_filter_by_name() {
let (_, containers) = gen_containers(); let (_, containers) = gen_containers();
let mut app_data = gen_appdata(&containers); let mut app_data = gen_appdata(&containers);
@@ -1571,9 +1671,137 @@ mod tests {
assert_eq!(post_len, 1); assert_eq!(post_len, 1);
// Can insert checks against the current filter term // Can insert checks against the current filter term
assert!(app_data.can_insert("_2")); // todo!("fix me");
assert!(!app_data.can_insert("_")); assert!(app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert("_3")); assert!(!app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
/// Data is filtered correctly by image
fn test_app_data_filter_by_image() {
let (_, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter_term().is_none());
let pre_len = app_data.containers.items.len();
for c in ['i', 'm', 'a', 'g', 'e', '_', '2'] {
app_data.filter_term_push(c);
}
// app_data.filter_term_push('2');
app_data.filter_by_next();
assert_eq!(app_data.get_filter_by(), FilterBy::Image);
assert_eq!(app_data.get_filter_term(), Some(&"image_2".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(!app_data.can_insert(&containers[0]));
assert!(app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
/// Data is filtered correctly by status
fn test_app_data_filter_by_status() {
let (_, mut containers) = gen_containers();
"Exited".clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter_term().is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(app_data.get_filter_by(), FilterBy::Status);
assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
/// Data is filtered correctly by all
fn test_app_data_filter_by_all() {
let (_, mut containers) = gen_containers();
"Exited".clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter_term().is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(app_data.get_filter_by(), FilterBy::All);
assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
}
#[test]
/// Data is filtered correctly after various next() and previous() commands
fn test_app_data_filter_prev() {
let (_, mut containers) = gen_containers();
"Exited".clone_into(&mut containers[0].status);
let mut app_data = gen_appdata(&containers);
assert!(app_data.get_filter_term().is_none());
let pre_len = app_data.containers.items.len();
app_data.filter_term_push('x');
app_data.filter_by_next();
app_data.filter_by_next();
assert_eq!(app_data.get_filter_by(), FilterBy::Status);
assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 1);
assert!(app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
app_data.filter_by_prev();
assert_eq!(app_data.get_filter_by(), FilterBy::Image);
assert_eq!(app_data.get_filter_term(), Some(&"x".to_string()));
app_data.filter_containers();
let post_len = app_data.containers.items.len();
assert!(pre_len != post_len);
assert_eq!(post_len, 0);
assert!(!app_data.can_insert(&containers[0]));
assert!(!app_data.can_insert(&containers[1]));
assert!(!app_data.can_insert(&containers[2]));
} }
// **** // // **** //
+6
View File
@@ -401,6 +401,12 @@ impl InputHandler {
KeyCode::Char(x) => { KeyCode::Char(x) => {
self.app_data.lock().filter_term_push(x); self.app_data.lock().filter_term_push(x);
} }
KeyCode::Right => {
self.app_data.lock().filter_by_next();
}
KeyCode::Left => {
self.app_data.lock().filter_by_prev();
}
_ => (), _ => (),
} }
} }
+4 -2
View File
@@ -175,7 +175,9 @@ mod tests {
use bollard::service::{ContainerSummary, Port}; use bollard::service::{ContainerSummary, Port};
use crate::{ use crate::{
app_data::{AppData, ContainerId, ContainerItem, ContainerPorts, State, StatefulList}, app_data::{
AppData, ContainerId, ContainerItem, ContainerPorts, Filter, State, StatefulList,
},
parse_args::CliArgs, parse_args::CliArgs,
}; };
@@ -217,7 +219,7 @@ mod tests {
hidden_containers: vec![], hidden_containers: vec![],
error: None, error: None,
sorted_by: None, sorted_by: None,
filter_term: None, filter: Filter::new(),
args: gen_args(), args: gen_args(),
} }
} }
+118 -23
View File
@@ -13,7 +13,7 @@ use ratatui::{
use std::{default::Default, time::Instant}; use std::{default::Default, time::Instant};
use std::{fmt::Display, sync::Arc}; use std::{fmt::Display, sync::Arc};
use crate::app_data::{ContainerItem, ContainerName, Header, SortedOrder}; use crate::app_data::{ContainerItem, ContainerName, FilterBy, Header, SortedOrder};
use crate::{ use crate::{
app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats},
app_error::AppError, app_error::AppError,
@@ -422,17 +422,59 @@ fn make_chart<'a, T: Stats + Display>(
) )
} }
/// Create the filter_by by spans, coloured dependant on which one is selected
fn filter_by_spans(app_data: &Arc<Mutex<AppData>>) -> [Span; 4] {
let filter_by = app_data.lock().get_filter_by();
let selected = Style::default().bg(Color::Gray).fg(Color::Black);
let not_selected = Style::default().bg(Color::Reset).fg(Color::Reset);
// This should be refactored somehow
let name = [" Name ", " Image ", " Status ", " All "];
match filter_by {
FilterBy::Name => [
Span::styled(name[0], selected),
Span::styled(name[1], not_selected),
Span::styled(name[2], not_selected),
Span::styled(name[3], not_selected),
],
FilterBy::Image => [
Span::styled(name[0], not_selected),
Span::styled(name[1], selected),
Span::styled(name[2], not_selected),
Span::styled(name[3], not_selected),
],
FilterBy::Status => [
Span::styled(name[0], not_selected),
Span::styled(name[1], not_selected),
Span::styled(name[2], selected),
Span::styled(name[3], not_selected),
],
FilterBy::All => [
Span::styled(name[0], not_selected),
Span::styled(name[1], not_selected),
Span::styled(name[2], not_selected),
Span::styled(name[3], selected),
],
}
}
/// Draw the filter bar /// Draw the filter bar
pub fn filter_bar(area: Rect, frame: &mut Frame, app_data: &Arc<Mutex<AppData>>) { pub fn filter_bar(area: Rect, frame: &mut Frame, app_data: &Arc<Mutex<AppData>>) {
let style_but = Style::default().fg(Color::Black).bg(Color::Magenta); let style_but = Style::default().fg(Color::Black).bg(Color::Magenta);
let style_desc = Style::default().fg(Color::Gray).bg(Color::Reset); let style_desc = Style::default().fg(Color::Gray).bg(Color::Reset);
let line = Line::from(vec![
// Span::styled(" Enter ", style_but), let mut line = vec![
// Span::styled(" done ", style_desc),
Span::styled(" Esc ", style_but), Span::styled(" Esc ", style_but),
Span::styled(" clear ", style_desc), Span::styled(" clear ", style_desc),
Span::styled(" ← by → ", style_but),
Span::from(" "),
];
line.extend_from_slice(&filter_by_spans(app_data));
line.extend_from_slice(&[
Span::styled( Span::styled(
"filter: ", " term: ",
Style::default() Style::default()
.fg(Color::Magenta) .fg(Color::Magenta)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
@@ -445,7 +487,7 @@ pub fn filter_bar(area: Rect, frame: &mut Frame, app_data: &Arc<Mutex<AppData>>)
Style::default().fg(Color::Gray), Style::default().fg(Color::Gray),
), ),
]); ]);
frame.render_widget(line, area); frame.render_widget(Line::from(line), area);
} }
/// Draw heading bar at top of program, always visible /// Draw heading bar at top of program, always visible
@@ -579,12 +621,6 @@ pub fn heading_bar(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// // Draw loading icon, or not, and a prefix with a single space
// let loading_paragraph = Paragraph::new(format!("{:>2}", data.loading_icon))
// .block(block(Color::White))
// .alignment(Alignment::Center);
// frame.render_widget(loading_paragraph, split_bar[0]);
let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>(); let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>();
let headers_section = Layout::default() let headers_section = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
@@ -998,7 +1034,7 @@ pub fn error(f: &mut Frame, error: AppError, seconds: Option<u8>) {
} }
/// Draw info box in one of the 9 BoxLocations /// Draw info box in one of the 9 BoxLocations
// TODO is this broken? // TODO is this broken - I don't think so
pub fn info(f: &mut Frame, text: &str, instant: Instant, gui_state: &Arc<Mutex<GuiState>>) { pub fn info(f: &mut Frame, text: &str, instant: Instant, gui_state: &Arc<Mutex<GuiState>>) {
let block = Block::default() let block = Block::default()
.title("") .title("")
@@ -1148,6 +1184,7 @@ mod tests {
.chunks(usize::from(w)) .chunks(usize::from(w))
.enumerate() .enumerate()
} }
// ******************** // // ******************** //
// DockerControls panel // // DockerControls panel //
// ******************** // // ******************** //
@@ -2056,7 +2093,7 @@ mod tests {
} }
} }
/// CPU and Memroy charts used in multiple tests, based on data from above insert_chart_data() /// CPU and Memory charts used in multiple tests, based on data from above insert_chart_data()
const EXPECTED: [&str; 10] = [ const EXPECTED: [&str; 10] = [
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮", "╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮",
"│10.00%│ • ││100.00 kB│ •• │", "│10.00%│ • ││100.00 kB│ •• │",
@@ -2762,7 +2799,9 @@ mod tests {
// ********** // // ********** //
#[test] #[test]
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
/// Filter row is drawn correctly & colors are correct /// Filter row is drawn correctly & colors are correct
/// Colours change when filter_by option is changed
fn test_draw_blocks_filter_row() { fn test_draw_blocks_filter_row() {
let (w, h) = (140, 1); let (w, h) = (140, 1);
let mut setup = test_setup(w, h, true, true); let mut setup = test_setup(w, h, true, true);
@@ -2779,7 +2818,7 @@ mod tests {
.unwrap(); .unwrap();
let expected = [ let expected = [
" Esc clear filter: " " Esc clear ← by → Name Image Status All term: "
]; ];
for (row_index, result_row) in get_result(&setup, w) { for (row_index, result_row) in get_result(&setup, w) {
@@ -2787,7 +2826,7 @@ mod tests {
for (result_cell_index, result_cell) in result_row.iter().enumerate() { for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]); assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
match result_cell_index { match result_cell_index {
0..=4 => { 0..=4 | 12..=19 => {
assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.bg, Color::Magenta);
assert_eq!(result_cell.fg, Color::Black); assert_eq!(result_cell.fg, Color::Black);
} }
@@ -2795,9 +2834,14 @@ mod tests {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray); assert_eq!(result_cell.fg, Color::Gray);
} }
12..=19 => { 21..=26 => {
assert_eq!(result_cell.bg, Color::Gray);
assert_eq!(result_cell.fg, Color::Black);
}
47..=53 => {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Magenta); assert_eq!(result_cell.fg, Color::Magenta);
assert_eq!(result_cell.modifier, Modifier::BOLD);
} }
_ => { _ => {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
@@ -2807,7 +2851,9 @@ mod tests {
} }
} }
// Test when char added to search term
setup.app_data.lock().filter_term_push('c'); setup.app_data.lock().filter_term_push('c');
setup.app_data.lock().filter_term_push('d');
setup setup
.terminal .terminal
@@ -2817,7 +2863,7 @@ mod tests {
.unwrap(); .unwrap();
let expected = [ let expected = [
" Esc clear filter: c " " Esc clear ← by → Name Image Status All term: cd "
]; ];
for (row_index, result_row) in get_result(&setup, w) { for (row_index, result_row) in get_result(&setup, w) {
@@ -2826,17 +2872,66 @@ mod tests {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]); assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
match result_cell_index { match result_cell_index {
0..=4 => { 0..=4 | 12..=19 => {
assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.bg, Color::Magenta);
assert_eq!(result_cell.fg, Color::Black); assert_eq!(result_cell.fg, Color::Black);
} }
5..=11 | 20 => { 5..=11 | 54..=55 => {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray); assert_eq!(result_cell.fg, Color::Gray);
} }
12..=19 => { 21..=26 => {
assert_eq!(result_cell.bg, Color::Gray);
assert_eq!(result_cell.fg, Color::Black);
}
47..=53 => {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Magenta); assert_eq!(result_cell.fg, Color::Magenta);
assert_eq!(result_cell.modifier, Modifier::BOLD);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Reset);
}
}
}
}
// Test when filter_by chances
setup.app_data.lock().filter_by_next();
setup
.terminal
.draw(|f| {
super::filter_bar(setup.area, f, &setup.app_data);
})
.unwrap();
let expected = [
" Esc clear ← by → Name Image Status All term: cd "
];
for (row_index, result_row) in get_result(&setup, w) {
let expected_row = expected_to_vec(&expected, row_index);
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
assert_eq!(result_cell.symbol(), expected_row[result_cell_index]);
match result_cell_index {
0..=4 | 12..=19 => {
assert_eq!(result_cell.bg, Color::Magenta);
assert_eq!(result_cell.fg, Color::Black);
}
5..=11 | 54..=55 => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
27..=33 => {
assert_eq!(result_cell.bg, Color::Gray);
assert_eq!(result_cell.fg, Color::Black);
}
47..=53 => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Magenta);
assert_eq!(result_cell.modifier, Modifier::BOLD);
} }
_ => { _ => {
assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.bg, Color::Reset);
@@ -3314,7 +3409,7 @@ mod tests {
let expected = [ let expected = [
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help ", " name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help ",
"╭ Containers 1/1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮", "╭ Containers 1/1 - filtered ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮",
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │", "│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │",
"│ ││ restart │", "│ ││ restart │",
"│ ││ stop │", "│ ││ stop │",
@@ -3342,7 +3437,7 @@ mod tests {
"│ │• •• ││ │• •• ││ │", "│ │• •• ││ │• •• ││ │",
"│ │ ││ │ ││ │", "│ │ ││ │ ││ │",
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯", "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯",
" Esc clear filter: r_1 " " Esc clear ← by → Name Image Status All term: r_1 "
]; ];
setup setup
.terminal .terminal