mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 14:10:31 +00:00
56455e0fee
Previously the layout used the floor of the calculated start and width as the value to use for the split Rects. This resulted in gaps between the split rects. This change modifies the layout to round to the nearest column instead of taking the floor of the start and width. This results in the start and end of each rect being rounded the same way and being strictly adjacent without gaps. Because there is a required constraint that ensures that the last end is equal to the area end, there is no longer the need to fixup the last item width when the fill (as e.g. width = x.99 now rounds to x+1 not x). The colors example has been updated to use Ratio(1, 8) instead of Percentage(13), as this now renders without gaps for all possible sizes, whereas previously it would have left odd gaps between columns.
960 lines
38 KiB
Rust
960 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() {
|
|
#[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"]),
|
|
])
|
|
.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 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 │",
|
|
"│ │",
|
|
"│Row11 Row12 │",
|
|
"│Row21 Row22 │",
|
|
"│Row31 Row32 │",
|
|
"│Row41 Row42 │",
|
|
"│ │",
|
|
"│ │",
|
|
"└────────────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[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"]),
|
|
])
|
|
.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);
|
|
}
|