mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-29 14:04:50 +00:00

* feat(table): add option to configure selection layout changes Before this option was available, selecting a row in the table when no row was selected previously made the tables layout change (the same applies to unselecting) by adding the width of the "highlight symbol" in the front of the first column, this option allows to configure this behavior. * refactor(table): refactor "get_columns_widths" to return (x, width) and "render" to make use of that * refactor(table): refactor "get_columns_widths" to take in a selection_width instead of a boolean also refactor "render" to make use of this change * fix(table): rename "highlight_set_selection_space" to "highlight_spacing" * style(table): apply doc-comment suggestions from code review Co-authored-by: Dheepak Krishnamurthy <me@kdheepak.com> --------- Co-authored-by: Dheepak Krishnamurthy <me@kdheepak.com>
959 lines
38 KiB
Rust
959 lines
38 KiB
Rust
#![allow(deprecated)]
|
|
|
|
use ratatui::{
|
|
backend::TestBackend,
|
|
buffer::Buffer,
|
|
layout::Constraint,
|
|
style::{Color, Modifier, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, Cell, HighlightSpacing, Row, Table, TableState},
|
|
Terminal,
|
|
};
|
|
|
|
#[test]
|
|
fn widgets_table_column_spacing_can_be_changed() {
|
|
let test_case = |column_spacing, expected| {
|
|
let backend = TestBackend::new(30, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(&[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
])
|
|
.column_spacing(column_spacing);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
// no space between columns
|
|
test_case(
|
|
0,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1Head2Head3 │",
|
|
"│ │",
|
|
"│Row11Row12Row13 │",
|
|
"│Row21Row22Row23 │",
|
|
"│Row31Row32Row33 │",
|
|
"│Row41Row42Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// one space between columns
|
|
test_case(
|
|
1,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// enough space to just not hide the third column
|
|
test_case(
|
|
6,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// enough space to hide part of the third column
|
|
test_case(
|
|
7,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head│",
|
|
"│ │",
|
|
"│Row11 Row12 Row1│",
|
|
"│Row21 Row22 Row2│",
|
|
"│Row31 Row32 Row3│",
|
|
"│Row41 Row42 Row4│",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_widths_can_use_fixed_length_constraints() {
|
|
let test_case = |widths, expected| {
|
|
let backend = TestBackend::new(30, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(widths);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
// columns of zero width show nothing
|
|
test_case(
|
|
&[
|
|
Constraint::Length(0),
|
|
Constraint::Length(0),
|
|
Constraint::Length(0),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of 1 width trim
|
|
test_case(
|
|
&[
|
|
Constraint::Length(1),
|
|
Constraint::Length(1),
|
|
Constraint::Length(1),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│H H H │",
|
|
"│ │",
|
|
"│R R R │",
|
|
"│R R R │",
|
|
"│R R R │",
|
|
"│R R R │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of large width just before pushing a column off
|
|
test_case(
|
|
&[
|
|
Constraint::Length(8),
|
|
Constraint::Length(8),
|
|
Constraint::Length(8),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_widths_can_use_percentage_constraints() {
|
|
let test_case = |widths, expected| {
|
|
let backend = TestBackend::new(30, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(widths)
|
|
.column_spacing(0);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
// columns of zero width show nothing
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(0),
|
|
Constraint::Percentage(0),
|
|
Constraint::Percentage(0),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of not enough width trims the data
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(11),
|
|
Constraint::Percentage(11),
|
|
Constraint::Percentage(11),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│HeaHeaHea │",
|
|
"│ │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of large width just before pushing a column off
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(33),
|
|
Constraint::Percentage(33),
|
|
Constraint::Percentage(33),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// percentages summing to 100 should give equal widths
|
|
test_case(
|
|
&[Constraint::Percentage(50), Constraint::Percentage(50)],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 │",
|
|
"│ │",
|
|
"│Row11 Row12 │",
|
|
"│Row21 Row22 │",
|
|
"│Row31 Row32 │",
|
|
"│Row41 Row42 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_widths_can_use_mixed_constraints() {
|
|
let test_case = |widths, expected| {
|
|
let backend = TestBackend::new(30, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(widths);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
// columns of zero width show nothing
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(0),
|
|
Constraint::Length(0),
|
|
Constraint::Percentage(0),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of not enough width trims the data
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(11),
|
|
Constraint::Length(20),
|
|
Constraint::Percentage(11),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Hea Head2 He │",
|
|
"│ │",
|
|
"│Row Row12 Ro │",
|
|
"│Row Row22 Ro │",
|
|
"│Row Row32 Ro │",
|
|
"│Row Row42 Ro │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of large width just before pushing a column off
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(33),
|
|
Constraint::Length(10),
|
|
Constraint::Percentage(33),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of large size (>100% total) hide the last column
|
|
test_case(
|
|
&[
|
|
Constraint::Percentage(60),
|
|
Constraint::Length(10),
|
|
Constraint::Percentage(60),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 │",
|
|
"│ │",
|
|
"│Row11 Row12 │",
|
|
"│Row21 Row22 │",
|
|
"│Row31 Row32 │",
|
|
"│Row41 Row42 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_widths_can_use_ratio_constraints() {
|
|
let test_case = |widths, expected| {
|
|
let backend = TestBackend::new(30, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(widths)
|
|
.column_spacing(0);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
// columns of zero width show nothing
|
|
test_case(
|
|
&[
|
|
Constraint::Ratio(0, 1),
|
|
Constraint::Ratio(0, 1),
|
|
Constraint::Ratio(0, 1),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of not enough width trims the data
|
|
test_case(
|
|
&[
|
|
Constraint::Ratio(1, 9),
|
|
Constraint::Ratio(1, 9),
|
|
Constraint::Ratio(1, 9),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│HeaHeaHea │",
|
|
"│ │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│RowRowRow │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// columns of large width just before pushing a column off
|
|
test_case(
|
|
&[
|
|
Constraint::Ratio(1, 3),
|
|
Constraint::Ratio(1, 3),
|
|
Constraint::Ratio(1, 3),
|
|
],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// percentages summing to 100 should give equal widths
|
|
test_case(
|
|
&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 │",
|
|
"│ │",
|
|
"│Row11 Row12 │",
|
|
"│Row21 Row22 │",
|
|
"│Row31 Row32 │",
|
|
"│Row41 Row42 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_can_have_rows_with_multi_lines() {
|
|
let test_case = |state: &mut TableState, expected: Buffer| {
|
|
let backend = TestBackend::new(30, 8);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]).height(2),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]).height(2),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.widths(&[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_stateful_widget(table, size, state);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
let mut state = TableState::default();
|
|
// no selection
|
|
test_case(
|
|
&mut state,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select first
|
|
state.select(Some(0));
|
|
test_case(
|
|
&mut state,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│>> Row11 Row12 Row13 │",
|
|
"│ Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select second (we don't show partially the 4th row)
|
|
state.select(Some(1));
|
|
test_case(
|
|
&mut state,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│ Row11 Row12 Row13 │",
|
|
"│>> Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select 4th (we don't show partially the 1st row)
|
|
state.select(Some(3));
|
|
test_case(
|
|
&mut state,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"│>> Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_enable_always_highlight_spacing() {
|
|
let test_case = |state: &mut TableState, space: HighlightSpacing, expected: Buffer| {
|
|
let backend = TestBackend::new(30, 8);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]).height(2),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]).height(2),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.highlight_spacing(space)
|
|
.widths(&[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_stateful_widget(table, size, state);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
};
|
|
|
|
assert_eq!(HighlightSpacing::default(), HighlightSpacing::WhenSelected);
|
|
|
|
let mut state = TableState::default();
|
|
// no selection, "WhenSelected" should only allocate if selected
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::default(),
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// no selection, "Always" should allocate regardless if selected or not
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::Always,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│ Row11 Row12 Row13 │",
|
|
"│ Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// no selection, "Never" should never allocate regadless if selected or not
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::Never,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select first, "WhenSelected" should only allocate if selected
|
|
state.select(Some(0));
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::default(),
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│>> Row11 Row12 Row13 │",
|
|
"│ Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select first, "Always" should allocate regardless if selected or not
|
|
state.select(Some(0));
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::Always,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│>> Row11 Row12 Row13 │",
|
|
"│ Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│ Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// select first, "Never" should never allocate regadless if selected or not
|
|
state.select(Some(0));
|
|
test_case(
|
|
&mut state,
|
|
HighlightSpacing::Never,
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│ │",
|
|
"│Row31 Row32 Row33 │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_can_have_elements_styled_individually() {
|
|
let backend = TestBackend::new(30, 4);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
let mut state = TableState::default();
|
|
state.select(Some(0));
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row11", "Row12", "Row13"]).style(Style::default().fg(Color::Green)),
|
|
Row::new(vec![
|
|
Cell::from("Row21"),
|
|
Cell::from("Row22").style(Style::default().fg(Color::Yellow)),
|
|
Cell::from(Line::from(vec![
|
|
Span::raw("Row"),
|
|
Span::styled("23", Style::default().fg(Color::Blue)),
|
|
]))
|
|
.style(Style::default().fg(Color::Red)),
|
|
])
|
|
.style(Style::default().fg(Color::LightGreen)),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::LEFT | Borders::RIGHT))
|
|
.highlight_symbol(">> ")
|
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
|
.widths(&[
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_stateful_widget(table, size, &mut state);
|
|
})
|
|
.unwrap();
|
|
|
|
let mut expected = Buffer::with_lines(vec![
|
|
"│ Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│>> Row11 Row12 Row13 │",
|
|
"│ Row21 Row22 Row23 │",
|
|
]);
|
|
// First row = row color + highlight style
|
|
for col in 1..=28 {
|
|
expected.get_mut(col, 2).set_style(
|
|
Style::default()
|
|
.fg(Color::Green)
|
|
.add_modifier(Modifier::BOLD),
|
|
);
|
|
}
|
|
// Second row:
|
|
// 1. row color
|
|
for col in 1..=28 {
|
|
expected
|
|
.get_mut(col, 3)
|
|
.set_style(Style::default().fg(Color::LightGreen));
|
|
}
|
|
// 2. cell color
|
|
for col in 11..=16 {
|
|
expected
|
|
.get_mut(col, 3)
|
|
.set_style(Style::default().fg(Color::Yellow));
|
|
}
|
|
for col in 18..=23 {
|
|
expected
|
|
.get_mut(col, 3)
|
|
.set_style(Style::default().fg(Color::Red));
|
|
}
|
|
// 3. text color
|
|
for col in 21..=22 {
|
|
expected
|
|
.get_mut(col, 3)
|
|
.set_style(Style::default().fg(Color::Blue));
|
|
}
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_should_render_even_if_empty() {
|
|
let backend = TestBackend::new(30, 4);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]))
|
|
.block(Block::default().borders(Borders::LEFT | Borders::RIGHT))
|
|
.widths(&[
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_widget(table, size);
|
|
})
|
|
.unwrap();
|
|
|
|
let expected = Buffer::with_lines(vec![
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
]);
|
|
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_dont_panic() {
|
|
let test_case = |state: &mut TableState, table: Table, width: u16| {
|
|
let backend = TestBackend::new(width, 8);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
f.render_stateful_widget(table, size, state);
|
|
})
|
|
.unwrap();
|
|
};
|
|
|
|
// based on https://github.com/fdehau/tui-rs/issues/470#issuecomment-852562848
|
|
let table1_width = 98;
|
|
let table1 = Table::new(vec![Row::new(vec!["r1", "r2", "r3", "r4"])])
|
|
.header(Row::new(vec!["h1", "h2", "h3", "h4"]))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.column_spacing(1)
|
|
.widths(&[
|
|
Constraint::Percentage(15),
|
|
Constraint::Percentage(15),
|
|
Constraint::Percentage(25),
|
|
Constraint::Percentage(45),
|
|
]);
|
|
|
|
let mut state = TableState::default();
|
|
|
|
// select first, which would cause a panic before fix
|
|
state.select(Some(0));
|
|
test_case(&mut state, table1.clone(), table1_width);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_should_clamp_offset_if_rows_are_removed() {
|
|
let backend = TestBackend::new(30, 8);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
let mut state = TableState::default();
|
|
|
|
// render with 6 items => offset will be at 2
|
|
state.select(Some(5));
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![
|
|
Row::new(vec!["Row01", "Row02", "Row03"]),
|
|
Row::new(vec!["Row11", "Row12", "Row13"]),
|
|
Row::new(vec!["Row21", "Row22", "Row23"]),
|
|
Row::new(vec!["Row31", "Row32", "Row33"]),
|
|
Row::new(vec!["Row41", "Row42", "Row43"]),
|
|
Row::new(vec!["Row51", "Row52", "Row53"]),
|
|
])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(&[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_stateful_widget(table, size, &mut state);
|
|
})
|
|
.unwrap();
|
|
let expected = Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│Row51 Row52 Row53 │",
|
|
"└────────────────────────────┘",
|
|
]);
|
|
terminal.backend().assert_buffer(&expected);
|
|
|
|
// render with 1 item => offset will be at 1
|
|
state.select(Some(1));
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
let table = Table::new(vec![Row::new(vec!["Row31", "Row32", "Row33"])])
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.widths(&[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
])
|
|
.column_spacing(1);
|
|
f.render_stateful_widget(table, size, &mut state);
|
|
})
|
|
.unwrap();
|
|
let expected = Buffer::with_lines(vec![
|
|
"┌────────────────────────────┐",
|
|
"│Head1 Head2 Head3 │",
|
|
"│ │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]);
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|