mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-26 06:30:29 +00:00
540fd2df03
This PR makes a number of simplifications to the layout and constraint features that were added after v0.25.0. For users upgrading from v0.25.0, the net effect of this PR (along with the other PRs) is the following: - New `Flex` modes have been added. - `Flex::Start` (new default) - `Flex::Center` - `Flex::End` - `Flex::SpaceAround` - `Flex::SpaceBetween` - `Flex::Legacy` (old default) - `Min(v)` grows to allocate excess space in all `Flex` modes instead of shrinking (except in `Flex::Legacy` where it retains old behavior). - `Fill(1)` grows to allocate excess space, growing equally with `Min(v)`. --- The following contains a summary of the changes in this PR and the motivation behind them. **`Flex`** - Removes `Flex::Stretch` - Renames `Flex::StretchLast` to `Flex::Legacy` **`Constraint`** - Removes `Fixed` - Makes `Min(v)` grow as much as possible everywhere (except `Flex::Legacy` where it retains the old behavior) - Makes `Min(v)` grow equally as `Fill(1)` while respecting `Min` lower bounds. When `Fill` and `Min` are used together, they both fill excess space equally. Allowing `Min(v)` to grow still allows users to build the same layouts as before with `Flex::Start` with no breaking changes to the behavior. This PR also removes the unstable feature `SegmentSize`. This is a breaking change to the behavior of constraints. If users want old behavior, they can use `Flex::Legacy`. ```rust Layout::vertical([Length(25), Length(25)]).flex(Flex::Legacy) ``` Users that have constraint that exceed the available space will probably not see any difference or see an improvement in their layouts. Any layout with `Min` will be identical in `Flex::Start` and `Flex::Legacy` so any layout with `Min` will not be breaking. Previously, `Table` used `EvenDistribution` internally by default, but with that gone the default is now `Flex::Start`. This changes the behavior of `Table` (for the better in most cases). The only way for users to get exactly the same as the old behavior is to change their constraints. I imagine most users will be happier out of the box with the new Table default. Resolves https://github.com/ratatui-org/ratatui/issues/843 Thanks to @joshka for the direction
986 lines
38 KiB
Rust
Executable file
986 lines
38 KiB
Rust
Executable file
#![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"]),
|
|
],
|
|
[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.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 Head Head3│",
|
|
"│ │",
|
|
"│Row11 Row1 Row13│",
|
|
"│Row21 Row2 Row23│",
|
|
"│Row31 Row3 Row33│",
|
|
"│Row41 Row4 Row43│",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[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"]),
|
|
],
|
|
widths,
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL));
|
|
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() {
|
|
#[track_caller]
|
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
|
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"]),
|
|
],
|
|
widths,
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.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() {
|
|
#[track_caller]
|
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
|
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"]),
|
|
],
|
|
widths,
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL));
|
|
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 Hea│",
|
|
"│ │",
|
|
"│Row Row12 Row│",
|
|
"│Row Row22 Row│",
|
|
"│Row Row32 Row│",
|
|
"│Row Row42 Row│",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
// 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 Head3 │",
|
|
"│ │",
|
|
"│Row11 Row12 Row13 │",
|
|
"│Row21 Row22 Row23 │",
|
|
"│Row31 Row32 Row33 │",
|
|
"│Row41 Row42 Row43 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_table_columns_widths_can_use_ratio_constraints() {
|
|
#[track_caller]
|
|
fn test_case(widths: &[Constraint], expected: Buffer) {
|
|
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"]),
|
|
],
|
|
widths,
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.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),
|
|
],
|
|
[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.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),
|
|
],
|
|
[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.highlight_spacing(space)
|
|
.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)),
|
|
],
|
|
[
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
],
|
|
)
|
|
.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))
|
|
.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::<Row>::new(),
|
|
[
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
Constraint::Length(6),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]))
|
|
.block(Block::default().borders(Borders::LEFT | Borders::RIGHT))
|
|
.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"])],
|
|
[
|
|
Constraint::Percentage(15),
|
|
Constraint::Percentage(15),
|
|
Constraint::Percentage(25),
|
|
Constraint::Percentage(45),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["h1", "h2", "h3", "h4"]))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.highlight_symbol(">> ")
|
|
.column_spacing(1);
|
|
|
|
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"]),
|
|
],
|
|
[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.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"])],
|
|
[
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
Constraint::Length(5),
|
|
],
|
|
)
|
|
.header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1))
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.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);
|
|
}
|