ratatui/tests/widgets_list.rs
EdJoPaTo 2cfe82a47e
refactor(buffer): deprecate assert_buffer_eq! in favor of assert_eq! (#1007)
- Simplify `assert_buffer_eq!` logic.
- Deprecate `assert_buffer_eq!`.
- Introduce `TestBackend::assert_buffer_lines`.

Also simplify many tests involving buffer comparisons.

For the deprecation, just use `assert_eq` instead of `assert_buffer_eq`:

```diff
-assert_buffer_eq!(actual, expected);
+assert_eq!(actual, expected);
```

---

I noticed `assert_buffer_eq!` creating no test coverage reports and
looked into this macro. First I simplified it. Then I noticed a bunch of
`assert_eq!(buffer, …)` and other indirect usages of this macro (like
`TestBackend::assert_buffer`).

The good thing here is that it's mainly used in tests so not many
changes to the library code.
2024-05-13 18:13:46 -07:00

381 lines
12 KiB
Rust

use ratatui::{
backend::TestBackend,
buffer::Buffer,
layout::Rect,
style::{Color, Style},
symbols,
text::Line,
widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState},
Terminal,
};
use rstest::rstest;
#[test]
fn list_should_shows_the_length() {
let items = vec![
ListItem::new("Item 1"),
ListItem::new("Item 2"),
ListItem::new("Item 3"),
];
let list = List::new(items);
assert_eq!(list.len(), 3);
assert!(!list.is_empty());
let empty_list = List::default();
assert_eq!(empty_list.len(), 0);
assert!(empty_list.is_empty());
}
#[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();
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
" 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_highlight_the_selected_item_wide_symbol() {
let backend = TestBackend::new(10, 3);
let mut terminal = Terminal::new(backend).unwrap();
let mut state = ListState::default();
let wide_symbol = "";
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(wide_symbol);
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
" 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() {
struct TruncateTestCase<'a> {
selected: Option<usize>,
items: Vec<ListItem<'a>>,
expected: Buffer,
}
let backend = TestBackend::new(10, 2);
let mut terminal = Terminal::new(backend).unwrap();
let cases = [
// 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([
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([
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::new().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);
}
}
#[test]
fn widgets_list_should_clamp_offset_if_items_are_removed() {
let backend = TestBackend::new(10, 4);
let mut terminal = Terminal::new(backend).unwrap();
let mut state = ListState::default();
// render with 6 items => offset will be at 2
state.select(Some(5));
terminal
.draw(|f| {
let size = f.size();
let items = vec![
ListItem::new("Item 0"),
ListItem::new("Item 1"),
ListItem::new("Item 2"),
ListItem::new("Item 3"),
ListItem::new("Item 4"),
ListItem::new("Item 5"),
];
let list = List::new(items).highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
terminal.backend().assert_buffer_lines([
" Item 2 ",
" Item 3 ",
" Item 4 ",
">> Item 5 ",
]);
// render again with 1 items => check offset is clamped to 1
state.select(Some(1));
terminal
.draw(|f| {
let size = f.size();
let items = vec![ListItem::new("Item 3")];
let list = List::new(items).highlight_symbol(">> ");
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
terminal.backend().assert_buffer_lines([
" Item 3 ",
" ",
" ",
" ",
]);
}
#[test]
fn widgets_list_should_display_multiline_items() {
let backend = TestBackend::new(10, 6);
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(vec![Line::from("Item 1"), Line::from("Item 1a")]),
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
];
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([
" Item 1 ",
" Item 1a",
">> Item 2 ",
" Item 2b",
" Item 3 ",
" Item 3c",
]);
for x in 0..10 {
expected.get_mut(x, 2).set_bg(Color::Yellow);
expected.get_mut(x, 3).set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
#[test]
fn widgets_list_should_repeat_highlight_symbol() {
let backend = TestBackend::new(10, 6);
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(vec![Line::from("Item 1"), Line::from("Item 1a")]),
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
];
let list = List::new(items)
.highlight_style(Style::default().bg(Color::Yellow))
.highlight_symbol(">> ")
.repeat_highlight_symbol(true);
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines([
" Item 1 ",
" Item 1a",
">> Item 2 ",
">> Item 2b",
" Item 3 ",
" Item 3c",
]);
for x in 0..10 {
expected.get_mut(x, 2).set_bg(Color::Yellow);
expected.get_mut(x, 3).set_bg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
#[test]
fn widget_list_should_not_ignore_empty_string_items() {
let backend = TestBackend::new(6, 4);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let items = vec![
ListItem::new("Item 1"),
ListItem::new(""),
ListItem::new(""),
ListItem::new("Item 4"),
];
let list = List::new(items)
.style(Style::default())
.highlight_style(Style::default());
f.render_widget(list, f.size());
})
.unwrap();
terminal
.backend()
.assert_buffer_lines(["Item 1", "", "", "Item 4"]);
}
#[rstest]
#[case::none_when_selected(None, HighlightSpacing::WhenSelected, [
"┌─────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└─────────────┘",
])]
#[case::none_always(None, HighlightSpacing::Always, [
"┌─────────────┐",
"│ Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└─────────────┘",
])]
#[case::none_never(None, HighlightSpacing::Never, [
"┌─────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└─────────────┘",
])]
#[case::first_when_selected(Some(0), HighlightSpacing::WhenSelected, [
"┌─────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└─────────────┘",
])]
#[case::first_always(Some(0), HighlightSpacing::Always, [
"┌─────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└─────────────┘",
])]
#[case::first_never(Some(0), HighlightSpacing::Never, [
"┌─────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└─────────────┘",
])]
fn widgets_list_enable_always_highlight_spacing<'line, Lines>(
#[case] selected: Option<usize>,
#[case] space: HighlightSpacing,
#[case] expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
{
let mut state = ListState::default().with_selected(selected);
let backend = TestBackend::new(15, 8);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let table = List::new(vec![
ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]),
ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]),
ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]),
])
.block(Block::bordered())
.highlight_symbol(">> ")
.highlight_spacing(space);
f.render_stateful_widget(table, size, &mut state);
})
.unwrap();
terminal
.backend()
.assert_buffer(&Buffer::with_lines(expected));
}