mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Add some table tests
This commit is contained in:
parent
211cfeb3ee
commit
54ec5ff0c9
@ -22,7 +22,7 @@ impl DataCellValue for usize {}
|
|||||||
impl DataCellValue for Cow<'static, str> {}
|
impl DataCellValue for Cow<'static, str> {}
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[enum_dispatch(DataCellValue)]
|
#[enum_dispatch(DataCellValue)]
|
||||||
pub enum DataCell {
|
pub enum DataCell {
|
||||||
f64(FloatOrd<f64>),
|
f64(FloatOrd<f64>),
|
||||||
|
@ -2,7 +2,7 @@ use tui::{style::Style, widgets::Row};
|
|||||||
|
|
||||||
use super::DataCell;
|
use super::DataCell;
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct DataRow {
|
pub struct DataRow {
|
||||||
cells: Vec<DataCell>,
|
cells: Vec<DataCell>,
|
||||||
style: Option<Style>,
|
style: Option<Style>,
|
||||||
@ -21,6 +21,10 @@ impl DataRow {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cells(&self) -> &[DataCell] {
|
||||||
|
&self.cells
|
||||||
|
}
|
||||||
|
|
||||||
pub fn style(mut self, style: Option<Style>) -> Self {
|
pub fn style(mut self, style: Option<Style>) -> Self {
|
||||||
self.style = style;
|
self.style = style;
|
||||||
self
|
self
|
||||||
|
@ -35,7 +35,7 @@ pub struct StyleSheet {
|
|||||||
|
|
||||||
enum SortStatus {
|
enum SortStatus {
|
||||||
Unsortable,
|
Unsortable,
|
||||||
Sortable { column: usize },
|
Sortable { column: usize, reverse: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sortable, scrollable table for text data.
|
/// A sortable, scrollable table for text data.
|
||||||
@ -47,7 +47,7 @@ pub struct TextTable<Message> {
|
|||||||
show_selected_entry: bool,
|
show_selected_entry: bool,
|
||||||
rows: Vec<DataRow>,
|
rows: Vec<DataRow>,
|
||||||
style_sheet: StyleSheet,
|
style_sheet: StyleSheet,
|
||||||
sortable: SortStatus,
|
sortable: SortStatus, // FIXME: Should this be stored in state?
|
||||||
table_gap: u16,
|
table_gap: u16,
|
||||||
on_select: Option<Box<dyn Fn(usize) -> Message>>,
|
on_select: Option<Box<dyn Fn(usize) -> Message>>,
|
||||||
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
||||||
@ -84,6 +84,14 @@ impl<Message> TextTable<Message> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a new row.
|
||||||
|
pub fn row(mut self, row: DataRow) -> Self {
|
||||||
|
self.rows.push(row);
|
||||||
|
self.try_sort_data();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to try to show a gap between the table headers and data.
|
/// Whether to try to show a gap between the table headers and data.
|
||||||
/// Note that if there isn't enough room, the gap will still be hidden.
|
/// Note that if there isn't enough room, the gap will still be hidden.
|
||||||
///
|
///
|
||||||
@ -106,22 +114,37 @@ impl<Message> TextTable<Message> {
|
|||||||
/// Defaults to unsortable if not set.
|
/// Defaults to unsortable if not set.
|
||||||
pub fn sortable(mut self, sortable: bool) -> Self {
|
pub fn sortable(mut self, sortable: bool) -> Self {
|
||||||
self.sortable = if sortable {
|
self.sortable = if sortable {
|
||||||
SortStatus::Sortable { column: 0 }
|
SortStatus::Sortable {
|
||||||
|
column: 0,
|
||||||
|
reverse: false,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SortStatus::Unsortable
|
SortStatus::Unsortable
|
||||||
};
|
};
|
||||||
|
|
||||||
self.try_sort_data();
|
self.try_sort_data();
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calling this enables sorting, and sets the sort column to `column`.
|
/// Calling this enables sorting, and sets the sort column to `column`.
|
||||||
pub fn sort_column(mut self, column: usize) -> Self {
|
pub fn sort_column(mut self, column: usize) -> Self {
|
||||||
self.sortable = SortStatus::Sortable { column };
|
self.sortable = match self.sortable {
|
||||||
|
SortStatus::Unsortable => SortStatus::Sortable {
|
||||||
|
column,
|
||||||
|
reverse: false,
|
||||||
|
},
|
||||||
|
SortStatus::Sortable { column: _, reverse } => SortStatus::Sortable { column, reverse },
|
||||||
|
};
|
||||||
self.try_sort_data();
|
self.try_sort_data();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calling this enables sorting, and sets the reverse status to `reverse`.
|
||||||
|
pub fn sort_reverse(mut self, reverse: bool) -> Self {
|
||||||
|
self.sortable = match self.sortable {
|
||||||
|
SortStatus::Unsortable => SortStatus::Sortable { column: 0, reverse },
|
||||||
|
SortStatus::Sortable { column, reverse: _ } => SortStatus::Sortable { column, reverse },
|
||||||
|
};
|
||||||
|
self.try_sort_data();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +175,8 @@ impl<Message> TextTable<Message> {
|
|||||||
fn try_sort_data(&mut self) {
|
fn try_sort_data(&mut self) {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
if let SortStatus::Sortable { column } = self.sortable {
|
if let SortStatus::Sortable { column, reverse } = self.sortable {
|
||||||
// TODO: We can avoid some annoying checks vy using const generics - this is waiting on
|
// TODO: We can avoid some annoying checks by using const generics - this is waiting on
|
||||||
// the const_generics_defaults feature, landing in 1.59, however!
|
// the const_generics_defaults feature, landing in 1.59, however!
|
||||||
|
|
||||||
self.rows
|
self.rows
|
||||||
@ -163,6 +186,10 @@ impl<Message> TextTable<Message> {
|
|||||||
(None, Some(_b)) => Ordering::Less,
|
(None, Some(_b)) => Ordering::Less,
|
||||||
(None, None) => Ordering::Equal,
|
(None, None) => Ordering::Equal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if reverse {
|
||||||
|
self.rows.reverse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,8 +330,8 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
let y = mouse_event.row - rect.top();
|
let y = mouse_event.row - rect.top();
|
||||||
|
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
if let SortStatus::Sortable { column } = self.sortable {
|
if let SortStatus::Sortable { column, reverse } = self.sortable {
|
||||||
todo!() // Sort by the clicked column!
|
todo!() // Sort by the clicked column! If already using column, reverse!
|
||||||
// self.sort_data();
|
// self.sort_data();
|
||||||
} else {
|
} else {
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
@ -341,4 +368,179 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {}
|
mod tests {
|
||||||
|
use crate::tuine::{StateMap, ViewContext};
|
||||||
|
|
||||||
|
use super::{DataRow, TextTable};
|
||||||
|
|
||||||
|
type Message = ();
|
||||||
|
|
||||||
|
fn ctx<'a>(map: &'a mut StateMap) -> ViewContext<'a> {
|
||||||
|
ViewContext::new(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sorting() {
|
||||||
|
let rows = vec![
|
||||||
|
DataRow::default().cell("A").cell(2),
|
||||||
|
DataRow::default().cell("B").cell(3),
|
||||||
|
DataRow::default().cell("C").cell(1),
|
||||||
|
];
|
||||||
|
let row_length = rows.len();
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
let mut map = StateMap::default();
|
||||||
|
let table: TextTable<Message> = TextTable::new(&mut ctx(&mut map), vec!["Sensor", "Temp"])
|
||||||
|
.sort_column(index)
|
||||||
|
.rows(rows);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table.rows.len(),
|
||||||
|
row_length,
|
||||||
|
"The number of cells should be equal to the vector passed in."
|
||||||
|
);
|
||||||
|
let mut prev = &table.rows[0].cells()[index];
|
||||||
|
for row in &table.rows[1..] {
|
||||||
|
let curr = &row.cells()[index];
|
||||||
|
assert!(
|
||||||
|
prev <= curr,
|
||||||
|
"The previous value should be less or equal to the current one."
|
||||||
|
);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resorting() {
|
||||||
|
let rows = vec![
|
||||||
|
DataRow::default().cell("A").cell(2),
|
||||||
|
DataRow::default().cell("B").cell(3),
|
||||||
|
DataRow::default().cell("C").cell(1),
|
||||||
|
];
|
||||||
|
let row_length = rows.len();
|
||||||
|
let index = 1;
|
||||||
|
let new_index = 0;
|
||||||
|
|
||||||
|
let mut map = StateMap::default();
|
||||||
|
let table: TextTable<Message> = TextTable::new(&mut ctx(&mut map), vec!["Sensor", "Temp"])
|
||||||
|
.sort_column(index)
|
||||||
|
.rows(rows)
|
||||||
|
.sort_column(new_index);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table.rows.len(),
|
||||||
|
row_length,
|
||||||
|
"The number of cells should be equal to the vector passed in."
|
||||||
|
);
|
||||||
|
let mut prev = &table.rows[0].cells()[new_index];
|
||||||
|
for row in &table.rows[1..] {
|
||||||
|
let curr = &row.cells()[new_index];
|
||||||
|
assert!(
|
||||||
|
prev <= curr,
|
||||||
|
"The previous value should be less or equal to the current one."
|
||||||
|
);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reverse_sorting() {
|
||||||
|
let rows = vec![
|
||||||
|
DataRow::default().cell("A").cell(2),
|
||||||
|
DataRow::default().cell("B").cell(3),
|
||||||
|
DataRow::default().cell("C").cell(1),
|
||||||
|
];
|
||||||
|
let row_length = rows.len();
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
let mut map = StateMap::default();
|
||||||
|
let table: TextTable<Message> = TextTable::new(&mut ctx(&mut map), vec!["Sensor", "Temp"])
|
||||||
|
.sort_column(index)
|
||||||
|
.sort_reverse(true)
|
||||||
|
.rows(rows);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table.rows.len(),
|
||||||
|
row_length,
|
||||||
|
"The number of cells should be equal to the vector passed in."
|
||||||
|
);
|
||||||
|
let mut prev = &table.rows[0].cells()[index];
|
||||||
|
for row in &table.rows[1..] {
|
||||||
|
let curr = &row.cells()[index];
|
||||||
|
assert!(
|
||||||
|
prev >= curr,
|
||||||
|
"The previous value should be bigger or equal to the current one."
|
||||||
|
);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn adding_row() {
|
||||||
|
let rows = vec![
|
||||||
|
DataRow::default().cell("A").cell(2),
|
||||||
|
DataRow::default().cell("B").cell(3),
|
||||||
|
DataRow::default().cell("C").cell(1),
|
||||||
|
];
|
||||||
|
let row_length = rows.len();
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
let mut map = StateMap::default();
|
||||||
|
let table: TextTable<Message> = TextTable::new(&mut ctx(&mut map), vec!["Sensor", "Temp"])
|
||||||
|
.rows(rows)
|
||||||
|
.sort_column(index)
|
||||||
|
.row(DataRow::default().cell("X").cell(0));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table.rows.len(),
|
||||||
|
row_length + 1,
|
||||||
|
"The number of cells should be equal to the vector passed in."
|
||||||
|
);
|
||||||
|
let mut prev = &table.rows[0].cells()[index];
|
||||||
|
for row in &table.rows[1..] {
|
||||||
|
let curr = &row.cells()[index];
|
||||||
|
assert!(
|
||||||
|
prev <= curr,
|
||||||
|
"The previous value should be less or equal to the current one."
|
||||||
|
);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_sort() {
|
||||||
|
let original_rows = vec![
|
||||||
|
DataRow::default().cell("A").cell(2),
|
||||||
|
DataRow::default().cell("B").cell(3),
|
||||||
|
DataRow::default().cell("C").cell(1),
|
||||||
|
DataRow::default().cell("X").cell(0),
|
||||||
|
];
|
||||||
|
let rows = original_rows[0..3].to_vec();
|
||||||
|
let row_length = original_rows.len();
|
||||||
|
|
||||||
|
let mut map = StateMap::default();
|
||||||
|
let table: TextTable<Message> = TextTable::new(&mut ctx(&mut map), vec!["Sensor", "Temp"])
|
||||||
|
.rows(rows)
|
||||||
|
.row(original_rows[3].clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table.rows.len(),
|
||||||
|
row_length,
|
||||||
|
"The number of cells should be equal to the vector passed in."
|
||||||
|
);
|
||||||
|
|
||||||
|
table
|
||||||
|
.rows
|
||||||
|
.into_iter()
|
||||||
|
.zip(original_rows)
|
||||||
|
.for_each(|(a_row, b_row)| {
|
||||||
|
a_row
|
||||||
|
.cells()
|
||||||
|
.into_iter()
|
||||||
|
.zip(b_row.cells())
|
||||||
|
.for_each(|(a, b)| {
|
||||||
|
assert_eq!(a, b, "Each DataCell should be equal.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user