mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-24 21:53:21 +00:00
5498a889ae
The `Spans` type (plural, not singular) was replaced with a more ergonomic `Line` type in Ratatui v0.21.0 and marked deprecated byt left for backwards compatibility. This is now removed. - `Line` replaces `Spans` - `Buffer::set_line` replaces `Buffer::set_spans`
366 lines
13 KiB
Rust
366 lines
13 KiB
Rust
use ratatui::{
|
|
backend::TestBackend,
|
|
buffer::Buffer,
|
|
layout::Alignment,
|
|
text::{Line, Span, Text},
|
|
widgets::{Block, Borders, Padding, Paragraph, Wrap},
|
|
Terminal,
|
|
};
|
|
|
|
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
|
|
/// area and comparing the rendered and expected content.
|
|
fn test_case(paragraph: Paragraph, expected: Buffer) {
|
|
let backend = TestBackend::new(expected.area.width, expected.area.height);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
|
|
terminal
|
|
.draw(|f| {
|
|
let size = f.size();
|
|
f.render_widget(paragraph, size);
|
|
})
|
|
.unwrap();
|
|
|
|
terminal.backend().assert_buffer(&expected);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_paragraph_renders_double_width_graphemes() {
|
|
let s = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、";
|
|
|
|
let text = vec![Line::from(s)];
|
|
let paragraph = Paragraph::new(text)
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
test_case(
|
|
paragraph,
|
|
Buffer::with_lines(vec![
|
|
"┌────────┐",
|
|
"│コンピュ│",
|
|
"│ータ上で│",
|
|
"│文字を扱│",
|
|
"│う場合、│",
|
|
"│典型的に│",
|
|
"│は文字に│",
|
|
"│よる通信│",
|
|
"│を行う場│",
|
|
"└────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[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![Line::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 = Line::from(vec![Span::raw("NBSP"), Span::raw(nbsp)]);
|
|
let paragraph = Paragraph::new(line).block(Block::default().borders(Borders::ALL));
|
|
|
|
test_case(
|
|
paragraph,
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│NBSP\u{00a0} │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_paragraph_can_scroll_horizontally() {
|
|
let text =
|
|
Text::from("段落现在可以水平滚动了!\nParagraph can scroll horizontally!\nShort line");
|
|
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
|
|
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Left).scroll((0, 7)),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│在可以水平滚动了!│",
|
|
"│ph can scroll hori│",
|
|
"│ine │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
// only support Alignment::Left
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Right).scroll((0, 7)),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│段落现在可以水平滚│",
|
|
"│Paragraph can scro│",
|
|
"│ Short line│",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"│ │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
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 text = vec![Line::from(SAMPLE_STRING)];
|
|
let paragraph = Paragraph::new(text)
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
test_case(
|
|
paragraph.clone().alignment(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(
|
|
paragraph.clone().alignment(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_case(
|
|
paragraph.clone().alignment(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]
|
|
fn widgets_paragraph_works_with_padding() {
|
|
let text = vec![Line::from(SAMPLE_STRING)];
|
|
let paragraph = Paragraph::new(text)
|
|
.block(Block::default().borders(Borders::ALL).padding(Padding {
|
|
left: 2,
|
|
right: 2,
|
|
top: 1,
|
|
bottom: 1,
|
|
}))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Left),
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────┐",
|
|
"│ │",
|
|
"│ The library is │",
|
|
"│ based on the │",
|
|
"│ principle of │",
|
|
"│ immediate │",
|
|
"│ rendering with │",
|
|
"│ intermediate │",
|
|
"│ buffers. This │",
|
|
"│ means that at │",
|
|
"│ │",
|
|
"└────────────────────┘",
|
|
]),
|
|
);
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Right),
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────┐",
|
|
"│ │",
|
|
"│ The library is │",
|
|
"│ based on the │",
|
|
"│ principle of │",
|
|
"│ immediate │",
|
|
"│ rendering with │",
|
|
"│ intermediate │",
|
|
"│ buffers. This │",
|
|
"│ means that at │",
|
|
"│ │",
|
|
"└────────────────────┘",
|
|
]),
|
|
);
|
|
|
|
let mut text = vec![Line::from("This is always centered.").alignment(Alignment::Center)];
|
|
text.push(Line::from(SAMPLE_STRING));
|
|
let paragraph = Paragraph::new(text)
|
|
.block(Block::default().borders(Borders::ALL).padding(Padding {
|
|
left: 2,
|
|
right: 2,
|
|
top: 1,
|
|
bottom: 1,
|
|
}))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
test_case(
|
|
paragraph.alignment(Alignment::Right),
|
|
Buffer::with_lines(vec![
|
|
"┌────────────────────┐",
|
|
"│ │",
|
|
"│ This is always │",
|
|
"│ centered. │",
|
|
"│ The library is │",
|
|
"│ based on the │",
|
|
"│ principle of │",
|
|
"│ immediate │",
|
|
"│ rendering with │",
|
|
"│ intermediate │",
|
|
"│ buffers. This │",
|
|
"│ means that at │",
|
|
"│ │",
|
|
"└────────────────────┘",
|
|
]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn widgets_paragraph_can_align_spans() {
|
|
let right_s = "This string will override the paragraph alignment to be right aligned.";
|
|
let default_s = "This string will be aligned based on the alignment of the paragraph.";
|
|
|
|
let text = vec![
|
|
Line::from(right_s).alignment(Alignment::Right),
|
|
Line::from(default_s),
|
|
];
|
|
let paragraph = Paragraph::new(text)
|
|
.block(Block::default().borders(Borders::ALL))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Left),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│ This string will│",
|
|
"│ override the│",
|
|
"│ paragraph│",
|
|
"│ alignment to be│",
|
|
"│ right aligned.│",
|
|
"│This string will │",
|
|
"│be aligned based │",
|
|
"│on the alignment │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
test_case(
|
|
paragraph.alignment(Alignment::Center),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│ This string will│",
|
|
"│ override the│",
|
|
"│ paragraph│",
|
|
"│ alignment to be│",
|
|
"│ right aligned.│",
|
|
"│ This string will │",
|
|
"│ be aligned based │",
|
|
"│ on the alignment │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
|
|
let left_lines = vec!["This string", "will override the paragraph alignment"]
|
|
.into_iter()
|
|
.map(|s| Line::from(s).alignment(Alignment::Left))
|
|
.collect::<Vec<_>>();
|
|
let mut lines = vec![
|
|
"This",
|
|
"must be pretty long",
|
|
"in order to effectively show",
|
|
"truncation.",
|
|
]
|
|
.into_iter()
|
|
.map(Line::from)
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut text = left_lines.clone();
|
|
text.append(&mut lines);
|
|
let paragraph = Paragraph::new(text).block(Block::default().borders(Borders::ALL));
|
|
|
|
test_case(
|
|
paragraph.clone().alignment(Alignment::Right),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│This string │",
|
|
"│will override the │",
|
|
"│ This│",
|
|
"│must be pretty lon│",
|
|
"│in order to effect│",
|
|
"│ truncation.│",
|
|
"│ │",
|
|
"│ │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
test_case(
|
|
paragraph.alignment(Alignment::Left),
|
|
Buffer::with_lines(vec![
|
|
"┌──────────────────┐",
|
|
"│This string │",
|
|
"│will override the │",
|
|
"│This │",
|
|
"│must be pretty lon│",
|
|
"│in order to effect│",
|
|
"│truncation. │",
|
|
"│ │",
|
|
"└──────────────────┘",
|
|
]),
|
|
);
|
|
}
|