mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
a23ecd9b45
Code which previously called `buf.get(x, y)` or `buf.get_mut(x, y)` should now use index operators, or be transitioned to `buff.cell()` or `buf.cell_mut()` for safe access that avoids panics by returning `Option<&Cell>` and `Option<&mut Cell>`. The new methods accept `Into<Position>` instead of `x` and `y` coordinates, which makes them more ergonomic to use. ```rust let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); let cell = buf[(0, 0)]; let cell = buf[Position::new(0, 0)]; let symbol = buf.cell((0, 0)).map(|cell| cell.symbol()); let symbol = buf.cell(Position::new(0, 0)).map(|cell| cell.symbol()); buf[(0, 0)].set_symbol("🐀"); buf[Position::new(0, 0)].set_symbol("🐀"); buf.cell_mut((0, 0)).map(|cell| cell.set_symbol("🐀")); buf.cell_mut(Position::new(0, 0)).map(|cell| cell.set_symbol("🐀")); ``` The existing `get()` and `get_mut()` methods are marked as deprecated. These are fairly widely used and we will leave these methods around on the buffer for a longer time than our normal deprecation approach (2 major release) Addresses part of: https://github.com/ratatui-org/ratatui/issues/1011 --------- Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
491 lines
17 KiB
Rust
491 lines
17 KiB
Rust
use ratatui::{
|
|
backend::TestBackend,
|
|
buffer::Buffer,
|
|
layout::{Alignment, Rect},
|
|
style::{Color, Style},
|
|
text::Span,
|
|
widgets::{
|
|
block::title::{Position, Title},
|
|
Block, Borders,
|
|
},
|
|
Terminal,
|
|
};
|
|
use rstest::rstest;
|
|
|
|
#[test]
|
|
fn widgets_block_renders() {
|
|
let backend = TestBackend::new(10, 10);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
let block =
|
|
Block::bordered().title(Span::styled("Title", Style::default().fg(Color::LightBlue)));
|
|
terminal
|
|
.draw(|frame| frame.render_widget(block, Rect::new(0, 0, 8, 8)))
|
|
.unwrap();
|
|
let mut expected = Buffer::with_lines([
|
|
"┌Title─┐ ",
|
|
"│ │ ",
|
|
"│ │ ",
|
|
"│ │ ",
|
|
"│ │ ",
|
|
"│ │ ",
|
|
"│ │ ",
|
|
"└──────┘ ",
|
|
" ",
|
|
" ",
|
|
]);
|
|
for x in 1..=5 {
|
|
expected[(x, 0)].set_fg(Color::LightBlue);
|
|
}
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_block_titles_overlap() {
|
|
#[track_caller]
|
|
fn test_case<'line, Lines>(block: Block, area: Rect, expected: Lines)
|
|
where
|
|
Lines: IntoIterator,
|
|
Lines::Item: Into<ratatui::text::Line<'line>>,
|
|
{
|
|
let backend = TestBackend::new(area.width, area.height);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|frame| frame.render_widget(block, area))
|
|
.unwrap();
|
|
terminal.backend().assert_buffer_lines(expected);
|
|
}
|
|
|
|
// Left overrides the center
|
|
test_case(
|
|
Block::new()
|
|
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
|
.title(Title::from("bbb").alignment(Alignment::Center))
|
|
.title(Title::from("ccc").alignment(Alignment::Right)),
|
|
Rect::new(0, 0, 10, 1),
|
|
["aaaaab ccc"],
|
|
);
|
|
|
|
// Left alignment overrides the center alignment which overrides the right alignment
|
|
test_case(
|
|
Block::new()
|
|
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
|
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
|
.title(Title::from("ccccc").alignment(Alignment::Right)),
|
|
Rect::new(0, 0, 11, 1),
|
|
["aaaaabbbccc"],
|
|
);
|
|
|
|
// Multiple left alignment overrides the center alignment and the right alignment
|
|
test_case(
|
|
Block::new()
|
|
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
|
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
|
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
|
.title(Title::from("ccccc").alignment(Alignment::Right)),
|
|
Rect::new(0, 0, 11, 1),
|
|
["aaaaabaaaaa"],
|
|
);
|
|
|
|
// The right alignment doesn't override the center alignment, but pierces through it
|
|
test_case(
|
|
Block::new()
|
|
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
|
.title(Title::from("ccccccccccc").alignment(Alignment::Right)),
|
|
Rect::new(0, 0, 11, 1),
|
|
["cccbbbbbccc"],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_block_renders_on_small_areas() {
|
|
#[track_caller]
|
|
fn test_case(block: Block, area: Rect, expected: &Buffer) {
|
|
let backend = TestBackend::new(area.width, area.height);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.draw(|frame| frame.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 {
|
|
test_case(
|
|
Block::new().borders(borders).title("Test"),
|
|
Rect::new(0, 0, 0, 0),
|
|
&Buffer::empty(Rect::new(0, 0, 0, 0)),
|
|
);
|
|
test_case(
|
|
Block::new().borders(borders).title("Test"),
|
|
Rect::new(0, 0, 1, 0),
|
|
&Buffer::empty(Rect::new(0, 0, 1, 0)),
|
|
);
|
|
test_case(
|
|
Block::new().borders(borders).title("Test"),
|
|
Rect::new(0, 0, 0, 1),
|
|
&Buffer::empty(Rect::new(0, 0, 0, 1)),
|
|
);
|
|
test_case(
|
|
Block::new().borders(borders).title("Test"),
|
|
Rect::new(0, 0, 1, 1),
|
|
&Buffer::with_lines([symbol]),
|
|
);
|
|
}
|
|
test_case(
|
|
Block::new().borders(Borders::LEFT).title("Test"),
|
|
Rect::new(0, 0, 4, 1),
|
|
&Buffer::with_lines(["│Tes"]),
|
|
);
|
|
test_case(
|
|
Block::new().borders(Borders::RIGHT).title("Test"),
|
|
Rect::new(0, 0, 4, 1),
|
|
&Buffer::with_lines(["Tes│"]),
|
|
);
|
|
test_case(
|
|
Block::new().borders(Borders::RIGHT).title("Test"),
|
|
Rect::new(0, 0, 4, 1),
|
|
&Buffer::with_lines(["Tes│"]),
|
|
);
|
|
test_case(
|
|
Block::new()
|
|
.borders(Borders::LEFT | Borders::RIGHT)
|
|
.title("Test"),
|
|
Rect::new(0, 0, 4, 1),
|
|
&Buffer::with_lines(["│Te│"]),
|
|
);
|
|
test_case(
|
|
Block::new().borders(Borders::TOP).title("Test"),
|
|
Rect::new(0, 0, 4, 1),
|
|
&Buffer::with_lines(["Test"]),
|
|
);
|
|
test_case(
|
|
Block::new().borders(Borders::TOP).title("Test"),
|
|
Rect::new(0, 0, 5, 1),
|
|
&Buffer::with_lines(["Test─"]),
|
|
);
|
|
test_case(
|
|
Block::new()
|
|
.borders(Borders::LEFT | Borders::TOP)
|
|
.title("Test"),
|
|
Rect::new(0, 0, 5, 1),
|
|
&Buffer::with_lines(["┌Test"]),
|
|
);
|
|
test_case(
|
|
Block::new()
|
|
.borders(Borders::LEFT | Borders::TOP)
|
|
.title("Test"),
|
|
Rect::new(0, 0, 6, 1),
|
|
&Buffer::with_lines(["┌Test─"]),
|
|
);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::left_with_all_borders(Alignment::Left, Borders::ALL, [
|
|
" ┌Title──────┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::left_without_top_border(Alignment::Left, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │Title │ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::left_without_left_border(Alignment::Left, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" Title───────┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::left_without_right_border(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌Title─────── ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::left_without_borders(Alignment::Left, Borders::NONE, [
|
|
" Title ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
#[case::center_with_all_borders(Alignment::Center, Borders::ALL, [
|
|
" ┌───Title───┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::center_without_top_border(Alignment::Center, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │ Title │ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::center_without_left_border(Alignment::Center, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ───Title────┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::center_without_right_border(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌───Title──── ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::center_without_borders(Alignment::Center, Borders::NONE, [
|
|
" Title ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
#[case::right_with_all_borders(Alignment::Right, Borders::ALL, [
|
|
" ┌──────Title┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::right_without_top_border(Alignment::Right, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │ Title│ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::right_without_left_border(Alignment::Right, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ───────Title┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::right_without_right_border(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌───────Title ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::right_without_borders(Alignment::Right, Borders::NONE, [
|
|
" Title ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
fn widgets_block_title_alignment_top<'line, Lines>(
|
|
#[case] alignment: Alignment,
|
|
#[case] borders: Borders,
|
|
#[case] expected: Lines,
|
|
) where
|
|
Lines: IntoIterator,
|
|
Lines::Item: Into<ratatui::text::Line<'line>>,
|
|
{
|
|
let backend = TestBackend::new(15, 3);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
let block1 = Block::new()
|
|
.borders(borders)
|
|
.title(Title::from(Span::raw("Title")).alignment(alignment));
|
|
|
|
let block2 = Block::new()
|
|
.borders(borders)
|
|
.title_alignment(alignment)
|
|
.title("Title");
|
|
let area = Rect::new(1, 0, 13, 3);
|
|
let expected = Buffer::with_lines(expected);
|
|
|
|
for block in [block1, block2] {
|
|
terminal
|
|
.draw(|frame| frame.render_widget(block, area))
|
|
.unwrap();
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::left(Alignment::Left, Borders::ALL, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" └Title──────┘ ",
|
|
])]
|
|
#[case::left(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::RIGHT, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" │Title │ ",
|
|
])]
|
|
#[case::left(Alignment::Left, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ────────────┐ ",
|
|
" │ ",
|
|
" Title───────┘ ",
|
|
])]
|
|
#[case::left(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌──────────── ",
|
|
" │ ",
|
|
" └Title─────── ",
|
|
])]
|
|
#[case::left(Alignment::Left, Borders::NONE, [
|
|
" ",
|
|
" ",
|
|
" Title ",
|
|
])]
|
|
#[case::left(Alignment::Center, Borders::ALL, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" └───Title───┘ ",
|
|
])]
|
|
#[case::left(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::RIGHT, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" │ Title │ ",
|
|
])]
|
|
#[case::left(Alignment::Center, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ────────────┐ ",
|
|
" │ ",
|
|
" ───Title────┘ ",
|
|
])]
|
|
#[case::left(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌──────────── ",
|
|
" │ ",
|
|
" └───Title──── ",
|
|
])]
|
|
#[case::left(Alignment::Center, Borders::NONE, [
|
|
" ",
|
|
" ",
|
|
" Title ",
|
|
])]
|
|
#[case::left(Alignment::Right, Borders::ALL, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" └──────Title┘ ",
|
|
])]
|
|
#[case::left(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::RIGHT, [
|
|
" ┌───────────┐ ",
|
|
" │ │ ",
|
|
" │ Title│ ",
|
|
])]
|
|
#[case::left(Alignment::Right, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ────────────┐ ",
|
|
" │ ",
|
|
" ───────Title┘ ",
|
|
])]
|
|
#[case::left(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌──────────── ",
|
|
" │ ",
|
|
" └───────Title ",
|
|
])]
|
|
#[case::left(Alignment::Right, Borders::NONE, [
|
|
" ",
|
|
" ",
|
|
" Title ",
|
|
])]
|
|
fn widgets_block_title_alignment_bottom<'line, Lines>(
|
|
#[case] alignment: Alignment,
|
|
#[case] borders: Borders,
|
|
#[case] expected: Lines,
|
|
) where
|
|
Lines: IntoIterator,
|
|
Lines::Item: Into<ratatui::text::Line<'line>>,
|
|
{
|
|
let backend = TestBackend::new(15, 3);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
let title = Title::from(Span::styled("Title", Style::default()))
|
|
.alignment(alignment)
|
|
.position(Position::Bottom);
|
|
let block = Block::default().title(title).borders(borders);
|
|
let area = Rect::new(1, 0, 13, 3);
|
|
terminal
|
|
.draw(|frame| frame.render_widget(block, area))
|
|
.unwrap();
|
|
terminal.backend().assert_buffer_lines(expected);
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::left_with_all_borders(Title::from("foo"), Title::from("bar"), Borders::ALL, [
|
|
" ┌foo─bar────┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::left_without_top_border(Title::from("foo"), Title::from("bar"), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │foo bar │ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::left_without_left_border(Title::from("foo"), Title::from("bar"), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" foo─bar─────┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::left_without_right_border(Title::from("foo"), Title::from("bar"), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌foo─bar───── ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::left_without_borders(Title::from("foo"), Title::from("bar"), Borders::NONE, [
|
|
" foo bar ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
#[case::center_with_borders(Title::from("foo").alignment(Alignment::Center), Title::from("bar").alignment(Alignment::Center), Borders::ALL, [
|
|
" ┌──foo─bar──┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::center_without_top_border(Title::from("foo").alignment(Alignment::Center), Title::from("bar").alignment(Alignment::Center), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │ foo bar │ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::center_without_left_border(Title::from("foo").alignment(Alignment::Center), Title::from("bar").alignment(Alignment::Center), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ──foo─bar───┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::center_without_right_border(Title::from("foo").alignment(Alignment::Center), Title::from("bar").alignment(Alignment::Center), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌──foo─bar─── ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::center_without_borders(Title::from("foo").alignment(Alignment::Center), Title::from("bar").alignment(Alignment::Center), Borders::NONE, [
|
|
" foo bar ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
#[case::right_with_all_borders(Title::from("foo").alignment(Alignment::Right), Title::from("bar").alignment(Alignment::Right), Borders::ALL, [
|
|
" ┌────foo─bar┐ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::right_without_top_border(Title::from("foo").alignment(Alignment::Right), Title::from("bar").alignment(Alignment::Right), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [
|
|
" │ foo bar│ ",
|
|
" │ │ ",
|
|
" └───────────┘ ",
|
|
])]
|
|
#[case::right_without_left_border(Title::from("foo").alignment(Alignment::Right), Title::from("bar").alignment(Alignment::Right), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [
|
|
" ─────foo─bar┐ ",
|
|
" │ ",
|
|
" ────────────┘ ",
|
|
])]
|
|
#[case::right_without_right_border(Title::from("foo").alignment(Alignment::Right), Title::from("bar").alignment(Alignment::Right), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [
|
|
" ┌─────foo─bar ",
|
|
" │ ",
|
|
" └──────────── ",
|
|
])]
|
|
#[case::right_without_borders(Title::from("foo").alignment(Alignment::Right), Title::from("bar").alignment(Alignment::Right), Borders::NONE, [
|
|
" foo bar ",
|
|
" ",
|
|
" ",
|
|
])]
|
|
fn widgets_block_multiple_titles<'line, Lines>(
|
|
#[case] title_a: Title,
|
|
#[case] title_b: Title,
|
|
#[case] borders: Borders,
|
|
#[case] expected: Lines,
|
|
) where
|
|
Lines: IntoIterator,
|
|
Lines::Item: Into<ratatui::text::Line<'line>>,
|
|
{
|
|
let backend = TestBackend::new(15, 3);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
let block = Block::default()
|
|
.title(title_a)
|
|
.title(title_b)
|
|
.borders(borders);
|
|
let area = Rect::new(1, 0, 13, 3);
|
|
terminal
|
|
.draw(|f| {
|
|
f.render_widget(block, area);
|
|
})
|
|
.unwrap();
|
|
terminal.backend().assert_buffer_lines(expected);
|
|
}
|