aboutsummaryrefslogtreecommitdiff
path: root/helix-tui/tests
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-05-09 08:52:55 +0000
committerBlaž Hrastnik2021-05-09 08:52:55 +0000
commit35606a3daa7ee273845a12f3e03728e0ae23928e (patch)
tree643684eaff6627dbebc4156d33fdb541bf87bbd9 /helix-tui/tests
parent6c705f09e88a4b63c4ed854bc9e956b0539ca8af (diff)
Inline tui as helix-tui fork.
We only rely on some of the rendering primitives and implement our Cursive-style compositor on top.
Diffstat (limited to 'helix-tui/tests')
-rw-r--r--helix-tui/tests/terminal.rs36
-rw-r--r--helix-tui/tests/widgets_block.rs213
-rw-r--r--helix-tui/tests/widgets_list.rs88
-rw-r--r--helix-tui/tests/widgets_paragraph.rs220
-rw-r--r--helix-tui/tests/widgets_table.rs717
5 files changed, 1274 insertions, 0 deletions
diff --git a/helix-tui/tests/terminal.rs b/helix-tui/tests/terminal.rs
new file mode 100644
index 00000000..4734dd9a
--- /dev/null
+++ b/helix-tui/tests/terminal.rs
@@ -0,0 +1,36 @@
+use helix_tui::{
+ backend::{Backend, TestBackend},
+ layout::Rect,
+ widgets::Paragraph,
+ Terminal,
+};
+use std::error::Error;
+
+#[test]
+fn terminal_buffer_size_should_be_limited() {
+ let backend = TestBackend::new(400, 400);
+ let terminal = Terminal::new(backend).unwrap();
+ let size = terminal.backend().size().unwrap();
+ assert_eq!(size.width, 255);
+ assert_eq!(size.height, 255);
+}
+
+// #[test]
+// fn terminal_draw_returns_the_completed_frame() -> Result<(), Box<dyn Error>> {
+// let backend = TestBackend::new(10, 10);
+// let mut terminal = Terminal::new(backend)?;
+// let frame = terminal.draw(|f| {
+// let paragrah = Paragraph::new("Test");
+// f.render_widget(paragrah, f.size());
+// })?;
+// assert_eq!(frame.buffer.get(0, 0).symbol, "T");
+// assert_eq!(frame.area, Rect::new(0, 0, 10, 10));
+// terminal.backend_mut().resize(8, 8);
+// let frame = terminal.draw(|f| {
+// let paragrah = Paragraph::new("test");
+// f.render_widget(paragrah, f.size());
+// })?;
+// assert_eq!(frame.buffer.get(0, 0).symbol, "t");
+// assert_eq!(frame.area, Rect::new(0, 0, 8, 8));
+// Ok(())
+// }
diff --git a/helix-tui/tests/widgets_block.rs b/helix-tui/tests/widgets_block.rs
new file mode 100644
index 00000000..8aaf905b
--- /dev/null
+++ b/helix-tui/tests/widgets_block.rs
@@ -0,0 +1,213 @@
+use helix_tui::{
+ backend::TestBackend,
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Style},
+ text::Span,
+ widgets::{Block, Borders},
+ Terminal,
+};
+
+#[test]
+fn widgets_block_renders() {
+ let backend = TestBackend::new(10, 10);
+ let mut terminal = Terminal::new(backend).unwrap();
+ terminal
+ .draw(|f| {
+ let block = Block::default()
+ .title(Span::styled("Title", Style::default().fg(Color::LightBlue)))
+ .borders(Borders::ALL);
+ f.render_widget(
+ block,
+ Rect {
+ x: 0,
+ y: 0,
+ width: 8,
+ height: 8,
+ },
+ );
+ })
+ .unwrap();
+ let mut expected = Buffer::with_lines(vec![
+ "┌Title─┐ ",
+ "│ │ ",
+ "│ │ ",
+ "│ │ ",
+ "│ │ ",
+ "│ │ ",
+ "│ │ ",
+ "└──────┘ ",
+ " ",
+ " ",
+ ]);
+ for x in 1..=5 {
+ expected.get_mut(x, 0).set_fg(Color::LightBlue);
+ }
+ terminal.backend().assert_buffer(&expected);
+}
+
+#[test]
+fn widgets_block_renders_on_small_areas() {
+ let test_case = |block, area: Rect, expected| {
+ let backend = TestBackend::new(area.width, area.height);
+ let mut terminal = Terminal::new(backend).unwrap();
+ terminal
+ .draw(|f| {
+ f.render_widget(block, area);
+ })
+ .unwrap();
+ terminal.backend().assert_buffer(&expected);
+ };
+
+ let one_cell_test_cases = [
+ (Borders::NONE, "T"),
+ (Borders::LEFT, "│"),
+ (Borders::TOP, "T"),
+ (Borders::RIGHT, "│"),
+ (Borders::BOTTOM, "T"),
+ (Borders::ALL, "┌"),
+ ];
+ for (borders, symbol) in one_cell_test_cases.iter().cloned() {
+ test_case(
+ Block::default().title("Test").borders(borders),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ },
+ Buffer::empty(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ }),
+ );
+ test_case(
+ Block::default().title("Test").borders(borders),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0,
+ },
+ Buffer::empty(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0,
+ }),
+ );
+ test_case(
+ Block::default().title("Test").borders(borders),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ },
+ Buffer::empty(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ }),
+ );
+ test_case(
+ Block::default().title("Test").borders(borders),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1,
+ },
+ Buffer::with_lines(vec![symbol]),
+ );
+ }
+ test_case(
+ Block::default().title("Test").borders(Borders::LEFT),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 4,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["│Tes"]),
+ );
+ test_case(
+ Block::default().title("Test").borders(Borders::RIGHT),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 4,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["Tes│"]),
+ );
+ test_case(
+ Block::default().title("Test").borders(Borders::RIGHT),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 4,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["Tes│"]),
+ );
+ test_case(
+ Block::default()
+ .title("Test")
+ .borders(Borders::LEFT | Borders::RIGHT),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 4,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["│Te│"]),
+ );
+ test_case(
+ Block::default().title("Test").borders(Borders::TOP),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 4,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["Test"]),
+ );
+ test_case(
+ Block::default().title("Test").borders(Borders::TOP),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 5,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["Test─"]),
+ );
+ test_case(
+ Block::default()
+ .title("Test")
+ .borders(Borders::LEFT | Borders::TOP),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 5,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["┌Test"]),
+ );
+ test_case(
+ Block::default()
+ .title("Test")
+ .borders(Borders::LEFT | Borders::TOP),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 6,
+ height: 1,
+ },
+ Buffer::with_lines(vec!["┌Test─"]),
+ );
+}
diff --git a/helix-tui/tests/widgets_list.rs b/helix-tui/tests/widgets_list.rs
new file mode 100644
index 00000000..e59accd8
--- /dev/null
+++ b/helix-tui/tests/widgets_list.rs
@@ -0,0 +1,88 @@
+use helix_tui::{
+ backend::TestBackend,
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Style},
+ symbols,
+ widgets::{Block, Borders, List, ListItem, ListState},
+ Terminal,
+};
+
+#[test]
+fn widgets_list_should_highlight_the_selected_item() {
+ let backend = TestBackend::new(10, 3);
+ let mut terminal = Terminal::new(backend).unwrap();
+ let mut state = ListState::default();
+ state.select(Some(1));
+ terminal
+ .draw(|f| {
+ let size = f.size();
+ let items = vec![
+ ListItem::new("Item 1"),
+ ListItem::new("Item 2"),
+ ListItem::new("Item 3"),
+ ];
+ let list = List::new(items)
+ .highlight_style(Style::default().bg(Color::Yellow))
+ .highlight_symbol(">> ");
+ f.render_stateful_widget(list, size, &mut state);
+ })
+ .unwrap();
+ let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]);
+ for x in 0..10 {
+ expected.get_mut(x, 1).set_bg(Color::Yellow);
+ }
+ terminal.backend().assert_buffer(&expected);
+}
+
+#[test]
+fn widgets_list_should_truncate_items() {
+ let backend = TestBackend::new(10, 2);
+ let mut terminal = Terminal::new(backend).unwrap();
+
+ struct TruncateTestCase<'a> {
+ selected: Option<usize>,
+ items: Vec<ListItem<'a>>,
+ expected: Buffer,
+ }
+
+ let cases = vec![
+ // An item is selected
+ TruncateTestCase {
+ selected: Some(0),
+ items: vec![
+ ListItem::new("A very long line"),
+ ListItem::new("A very long line"),
+ ],
+ expected: Buffer::with_lines(vec![
+ format!(">> A ve{} ", symbols::line::VERTICAL),
+ format!(" A ve{} ", symbols::line::VERTICAL),
+ ]),
+ },
+ // No item is selected
+ TruncateTestCase {
+ selected: None,
+ items: vec![
+ ListItem::new("A very long line"),
+ ListItem::new("A very long line"),
+ ],
+ expected: Buffer::with_lines(vec![
+ format!("A very {} ", symbols::line::VERTICAL),
+ format!("A very {} ", symbols::line::VERTICAL),
+ ]),
+ },
+ ];
+ for case in cases {
+ let mut state = ListState::default();
+ state.select(case.selected);
+ terminal
+ .draw(|f| {
+ let list = List::new(case.items.clone())
+ .block(Block::default().borders(Borders::RIGHT))
+ .highlight_symbol(">> ");
+ f.render_stateful_widget(list, Rect::new(0, 0, 8, 2), &mut state);
+ })
+ .unwrap();
+ terminal.backend().assert_buffer(&case.expected);
+ }
+}
diff --git a/helix-tui/tests/widgets_paragraph.rs b/helix-tui/tests/widgets_paragraph.rs
new file mode 100644
index 00000000..33d693d8
--- /dev/null
+++ b/helix-tui/tests/widgets_paragraph.rs
@@ -0,0 +1,220 @@
+use helix_tui::{
+ backend::TestBackend,
+ buffer::Buffer,
+ layout::Alignment,
+ text::{Span, Spans, Text},
+ widgets::{Block, Borders, Paragraph, Wrap},
+ Terminal,
+};
+
+const SAMPLE_STRING: &str = "The library is based on the principle of immediate rendering with \
+ intermediate buffers. This means that at each new frame you should build all widgets that are \
+ supposed to be part of the UI. While providing a great flexibility for rich and \
+ interactive UI, this may introduce overhead for highly dynamic content.";
+
+#[test]
+fn widgets_paragraph_can_wrap_its_content() {
+ let test_case = |alignment, expected| {
+ let backend = TestBackend::new(20, 10);
+ let mut terminal = Terminal::new(backend).unwrap();
+
+ terminal
+ .draw(|f| {
+ let size = f.size();
+ let text = vec![Spans::from(SAMPLE_STRING)];
+ let paragraph = Paragraph::new(text)
+ .block(Block::default().borders(Borders::ALL))
+ .alignment(alignment)
+ .wrap(Wrap { trim: true });
+ f.render_widget(paragraph, size);
+ })
+ .unwrap();
+ terminal.backend().assert_buffer(&expected);
+ };
+
+ test_case(
+ Alignment::Left,
+ Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│The library is │",
+ "│based on the │",
+ "│principle of │",
+ "│immediate │",
+ "│rendering with │",
+ "│intermediate │",
+ "│buffers. This │",
+ "│means that at each│",
+ "└──────────────────┘",
+ ]),
+ );
+ test_case(
+ Alignment::Right,
+ Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│ The library is│",
+ "│ based on the│",
+ "│ principle of│",
+ "│ immediate│",
+ "│ rendering with│",
+ "│ intermediate│",
+ "│ buffers. This│",
+ "│means that at each│",
+ "└──────────────────┘",
+ ]),
+ );
+ test_case(
+ Alignment::Center,
+ Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│ The library is │",
+ "│ based on the │",
+ "│ principle of │",
+ "│ immediate │",
+ "│ rendering with │",
+ "│ intermediate │",
+ "│ buffers. This │",
+ "│means that at each│",
+ "└──────────────────┘",
+ ]),
+ );
+}
+
+#[test]
+fn widgets_paragraph_renders_double_width_graphemes() {
+ let backend = TestBackend::new(10, 10);
+ let mut terminal = Terminal::new(backend).unwrap();
+
+ let s = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、";
+ terminal
+ .draw(|f| {
+ let size = f.size();
+ let text = vec![Spans::from(s)];
+ let paragraph = Paragraph::new(text)
+ .block(Block::default().borders(Borders::ALL))
+ .wrap(Wrap { trim: true });
+ f.render_widget(paragraph, size);
+ })
+ .unwrap();
+
+ let expected = Buffer::with_lines(vec![
+ "┌────────┐",
+ "│コンピュ│",
+ "│ータ上で│",
+ "│文字を扱│",
+ "│う場合、│",
+ "│典型的に│",
+ "│は文字に│",
+ "│よる通信│",
+ "│を行う場│",
+ "└────────┘",
+ ]);
+ terminal.backend().assert_buffer(&expected);
+}
+
+#[test]
+fn widgets_paragraph_renders_mixed_width_graphemes() {
+ let backend = TestBackend::new(10, 7);
+ let mut terminal = Terminal::new(backend).unwrap();
+
+ let s = "aコンピュータ上で文字を扱う場合、";
+ terminal
+ .draw(|f| {
+ let size = f.size();
+ let text = vec![Spans::from(s)];
+ let paragraph = Paragraph::new(text)
+ .block(Block::default().borders(Borders::ALL))
+ .wrap(Wrap { trim: true });
+ f.render_widget(paragraph, size);
+ })
+ .unwrap();
+
+ let expected = Buffer::with_lines(vec![
+ // The internal width is 8 so only 4 slots for double-width characters.
+ "┌────────┐",
+ "│aコンピ │", // Here we have 1 latin character so only 3 double-width ones can fit.
+ "│ュータ上│",
+ "│で文字を│",
+ "│扱う場合│",
+ "│、 │",
+ "└────────┘",
+ ]);
+ terminal.backend().assert_buffer(&expected);
+}
+
+#[test]
+fn widgets_paragraph_can_wrap_with_a_trailing_nbsp() {
+ let nbsp: &str = "\u{00a0}";
+ let line = Spans::from(vec![Span::raw("NBSP"), Span::raw(nbsp)]);
+ let backend = TestBackend::new(20, 3);
+ let mut terminal = Terminal::new(backend).unwrap();
+ let expected = Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│NBSP\u{00a0} │",
+ "└──────────────────┘",
+ ]);
+ terminal
+ .draw(|f| {
+ let size = f.size();
+
+ let paragraph = Paragraph::new(line).block(Block::default().borders(Borders::ALL));
+ f.render_widget(paragraph, size);
+ })
+ .unwrap();
+ terminal.backend().assert_buffer(&expected);
+}
+#[test]
+fn widgets_paragraph_can_scroll_horizontally() {
+ let test_case = |alignment, scroll, expected| {
+ let backend = TestBackend::new(20, 10);
+ let mut terminal = Terminal::new(backend).unwrap();
+
+ terminal
+ .draw(|f| {
+ let size = f.size();
+ let text = Text::from(
+ "段落现在可以水平滚动了!\nParagraph can scroll horizontally!\nShort line",
+ );
+ let paragraph = Paragraph::new(text)
+ .block(Block::default().borders(Borders::ALL))
+ .alignment(alignment)
+ .scroll(scroll);
+ f.render_widget(paragraph, size);
+ })
+ .unwrap();
+ terminal.backend().assert_buffer(&expected);
+ };
+
+ test_case(
+ Alignment::Left,
+ (0, 7),
+ Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│在可以水平滚动了!│",
+ "│ph can scroll hori│",
+ "│ine │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "└──────────────────┘",
+ ]),
+ );
+ // only support Alignment::Left
+ test_case(
+ Alignment::Right,
+ (0, 7),
+ Buffer::with_lines(vec![
+ "┌──────────────────┐",
+ "│段落现在可以水平滚│",
+ "│Paragraph can scro│",
+ "│ Short line│",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "│ │",
+ "└──────────────────┘",
+ ]),
+ );
+}
diff --git a/helix-tui/tests/widgets_table.rs b/helix-tui/tests/widgets_table.rs
new file mode 100644
index 00000000..0e0b3003
--- /dev/null
+++ b/helix-tui/tests/widgets_table.rs
@@ -0,0 +1,717 @@
+use helix_tui::{
+ backend::TestBackend,
+ buffer::Buffer,
+ layout::Constraint,
+ style::{Color, Modifier, Style},
+ text::{Span, Spans},
+ widgets::{Block, Borders, Cell, 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 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() {
+ 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_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(Spans::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);
+}