Merge branch 'main' into jm/buffer_opt

This commit is contained in:
Josh McKinney 2024-05-21 09:07:29 -07:00
commit 71e8bd299c
No known key found for this signature in database
GPG key ID: 722287396A903BC5
58 changed files with 9283 additions and 9552 deletions

View file

@ -1,16 +0,0 @@
#!/usr/bin/env bash
if !(command cargo-make >/dev/null 2>&1); then # Check if cargo-make is installed
echo Attempting to run cargo-make as part of the pre-push hook but it\'s not installed.
echo Please install it by running the following command:
echo
echo " cargo install --force cargo-make"
echo
echo If you don\'t want to run cargo-make as part of the pre-push hook, you can run
echo the following command instead of git push:
echo
echo " git push --no-verify"
exit 1
fi
cargo make ci

10551
CHANGELOG.md

File diff suppressed because it is too large Load diff

View file

@ -56,11 +56,9 @@ documented.
### Run CI tests before pushing a PR
We're using [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run git hooks,
which will run `cargo make ci` before each push. To initialize the hook run `cargo test`. If
`cargo-make` is not installed, it will provide instructions to install it for you. This will ensure
that your code is formatted, compiles and passes all tests before you push. If you need to skip this
check, you can use `git push --no-verify`.
Running `cargo make ci` before pushing will perform the same checks that we do in the CI process.
It's not mandatory to do this before pushing, however it may save you time to do so instead of
waiting for GitHub to run the checks.
### Sign your commits

View file

@ -1,6 +1,6 @@
[package]
name = "ratatui"
version = "0.26.2" # crate version
version = "0.26.3" # crate version
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
description = "A library that's all about cooking up terminal user interfaces"
documentation = "https://docs.rs/ratatui/latest/ratatui/"
@ -25,37 +25,34 @@ rust-version = "1.74.0"
[badges]
[dependencies]
crossterm = { version = "0.27", optional = true }
termion = { version = "3.0", optional = true }
termwiz = { version = "0.22.0", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
bitflags = "2.3"
cassowary = "0.3"
indoc = "2.0"
compact_str = "0.7.1"
crossterm = { version = "0.27", optional = true }
document-features = { version = "0.2.7", optional = true }
itertools = "0.12"
lru = "0.12.0"
paste = "1.0.2"
serde = { version = "1", optional = true, features = ["derive"] }
stability = "0.2.0"
strum = { version = "0.26", features = ["derive"] }
termion = { version = "3.0", optional = true }
termwiz = { version = "0.22.0", optional = true }
time = { version = "0.3.11", optional = true, features = ["local-offset"] }
unicode-segmentation = "1.10"
unicode-truncate = "1"
unicode-width = "0.1"
document-features = { version = "0.2.7", optional = true }
lru = "0.12.0"
stability = "0.2.0"
compact_str = "0.7.1"
[dev-dependencies]
anyhow = "1.0.71"
argh = "0.1.12"
better-panic = "0.3.0"
cargo-husky = { version = "1.5.0", default-features = false, features = [
"user-hooks",
] }
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
derive_builder = "0.20.0"
fakeit = "1.1"
font8x8 = "0.3.1"
indoc = "2"
palette = "0.7.3"
pretty_assertions = "1.4.0"
rand = "0.8.5"
@ -163,6 +160,10 @@ harness = false
name = "block"
harness = false
[[bench]]
name = "line"
harness = false
[[bench]]
name = "list"
harness = false
@ -270,6 +271,12 @@ name = "list"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "minimal"
required-features = ["crossterm"]
# prefer to show the more featureful examples in the docs
doc-scrape-examples = false
[[example]]
name = "modifiers"
required-features = ["crossterm"]

View file

@ -7,11 +7,12 @@ skip_core_tasks = true
# all features except the backend ones
ALL_FEATURES = "all-widgets,macros,serde"
[env.ALL_FEATURES_FLAG]
# Windows does not support building termion, so this avoids the build failure by providing two
# sets of flags, one for Windows and one for other platforms.
# Windows: --features=all-widgets,macros,serde,crossterm,termwiz,underline-color
# Other: --features=all-widgets,macros,serde,crossterm,termion,termwiz,underline-color
ALL_FEATURES_FLAG = { source = "${CARGO_MAKE_RUST_TARGET_OS}", default_value = "--features=all-widgets,macros,serde,crossterm,termion,termwiz,unstable", mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz,unstable" } }
source = "${CARGO_MAKE_RUST_TARGET_OS}"
default_value = "--features=all-widgets,macros,serde,crossterm,termion,termwiz,underline-color,unstable"
mapping = { "windows" = "--features=all-widgets,macros,serde,crossterm,termwiz,underline-color,unstable" }
[tasks.default]
alias = "ci"

39
benches/line.rs Normal file
View file

@ -0,0 +1,39 @@
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, Criterion};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Stylize,
text::Line,
widgets::Widget,
};
fn line_render(criterion: &mut Criterion) {
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
let mut group = criterion.benchmark_group(format!("line_render/{alignment}"));
group.sample_size(1000);
let line = &Line::from(vec![
"This".red(),
" ".green(),
"is".italic(),
" ".blue(),
"SPARTA!!".bold(),
])
.alignment(alignment);
for width in [0, 3, 4, 6, 7, 10, 42] {
let area = Rect::new(0, 0, width, 1);
group.bench_function(width.to_string(), |bencher| {
let mut buffer = Buffer::empty(area);
bencher.iter(|| black_box(line).render(area, &mut buffer));
});
}
group.finish();
}
}
criterion_group!(benches, line_render);
criterion_main!(benches);

View file

@ -26,7 +26,7 @@ body = """
{% endif -%}
{% macro commit(commit) -%}
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui-org/ratatui/commit/" ~ commit.id }})
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui-org/ratatui/commit/" ~ commit.id }}) \
*({{commit.scope | default(value = "uncategorized") | lower }})* {{ commit.message | upper_first | trim }}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}\
{% if commit.github.pr_number %} in [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}){%- endif %}\

View file

@ -1,3 +1,5 @@
avoid-breaking-exported-api = false
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
# ratatui -> bitflags v2.3
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2

View file

@ -286,10 +286,10 @@ fn draw_legend(f: &mut Frame, area: Rect) {
"- Company B",
Style::default().fg(Color::Yellow),
)),
Line::from(vec![Span::styled(
Line::from(Span::styled(
"- Company C",
Style::default().fg(Color::White),
)]),
)),
];
let block = Block::bordered().style(Style::default().fg(Color::White));

View file

@ -276,8 +276,6 @@ fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelS
#[cfg(test)]
mod tests {
use ratatui::assert_buffer_eq;
use super::*;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
@ -308,7 +306,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 8));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ████ ██ ███ ████ ██ ",
"██ ██ ██ ██ ",
"███ ███ █████ ███ ██ ██ ████ ██ ███ █████ ████ ",
@ -318,7 +316,7 @@ mod tests {
" ████ ████ ██ ██ ██ ████ ████ ███████ ████ ██ ██ ████ ",
" █████ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -329,7 +327,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 6));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"██████ █ ███",
"█ ██ █ ██ ██",
" ██ ██ ███ ██ ██ █████ ████ ████ █████ ████ ██",
@ -337,7 +335,7 @@ mod tests {
" ██ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██████ ██ ██",
" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ ██",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -348,7 +346,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 16));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"██ ██ ███ █ ██ ",
"███ ███ ██ ██ ",
"███████ ██ ██ ██ █████ ███ ",
@ -366,7 +364,7 @@ mod tests {
"███████ ████ ██ ██ ████ █████ ",
" ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -378,18 +376,17 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 8));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
" ████ █ ███ ███ ",
"██ ██ ██ ██ ██ ",
"███ █████ ██ ██ ██ ████ ██ ",
" ███ ██ ██ ██ ██ ██ ██ █████ ",
" ███ ██ ██ ██ ██ ██████ ██ ██ ",
"██ ██ ██ █ █████ ██ ██ ██ ██ ",
" ████ ██ ██ ████ ████ ███ ██ ",
" █████ ",
let expected = Buffer::with_lines([
" ████ █ ███ ███ ".bold(),
"██ ██ ██ ██ ██ ".bold(),
"███ █████ ██ ██ ██ ████ ██ ".bold(),
" ███ ██ ██ ██ ██ ██ ██ █████ ".bold(),
" ███ ██ ██ ██ ██ ██████ ██ ██ ".bold(),
"██ ██ ██ █ █████ ██ ██ ██ ██ ".bold(),
" ████ ██ ██ ████ ████ ███ ██ ".bold(),
" █████ ".bold(),
]);
expected.set_style(Rect::new(0, 0, 48, 8), Style::new().bold());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -404,7 +401,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 24));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"██████ ███ ",
" ██ ██ ██ ",
" ██ ██ ████ ██ ",
@ -433,7 +430,7 @@ mod tests {
expected.set_style(Rect::new(0, 0, 24, 8), Style::new().red());
expected.set_style(Rect::new(0, 8, 40, 8), Style::new().green());
expected.set_style(Rect::new(0, 16, 32, 8), Style::new().blue());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -445,13 +442,13 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 4));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"▄█▀▀█▄ ▀▀ ▀██ ▀██▀ ▀▀ ",
"▀██▄ ▀██ ██▀▀█▄ ▄█▀▀▄█▀ ██ ▄█▀▀█▄ ██ ▀██ ██▀▀█▄ ▄█▀▀█▄ ",
"▄▄ ▀██ ██ ██ ██ ▀█▄▄██ ██ ██▀▀▀▀ ██ ▄█ ██ ██ ██ ██▀▀▀▀ ",
" ▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -463,12 +460,12 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 3));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"█▀██▀█ ▄█ ▀██",
" ██ ▀█▄█▀█▄ ██ ██ ██▀▀█▄ ▄█▀▀█▄ ▀▀▀█▄ ▀██▀▀ ▄█▀▀█▄ ▄▄▄██",
" ██ ██ ▀▀ ██ ██ ██ ██ ██ ▄▄ ▄█▀▀██ ██ ▄ ██▀▀▀▀ ██ ██",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -480,7 +477,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"██▄ ▄██ ▀██ ▄█ ▀▀ ",
"███████ ██ ██ ██ ▀██▀▀ ▀██ ",
"██ ▀ ██ ██ ██ ██ ██ ▄ ██ ",
@ -490,7 +487,7 @@ mod tests {
" ██ ▄█ ██ ██ ██ ██▀▀▀▀ ▀▀▀█▄ ",
"▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀▀▀▀ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -503,14 +500,13 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 4));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
"▄█▀▀█▄ ▄█ ▀██ ▀██ ",
"▀██▄ ▀██▀▀ ██ ██ ██ ▄█▀▀█▄ ▄▄▄██ ",
"▄▄ ▀██ ██ ▄ ▀█▄▄██ ██ ██▀▀▀▀ ██ ██ ",
" ▀▀▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ",
let expected = Buffer::with_lines([
"▄█▀▀█▄ ▄█ ▀██ ▀██ ".bold(),
"▀██▄ ▀██▀▀ ██ ██ ██ ▄█▀▀█▄ ▄▄▄██ ".bold(),
"▄▄ ▀██ ██ ▄ ▀█▄▄██ ██ ██▀▀▀▀ ██ ██ ".bold(),
" ▀▀▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ".bold(),
]);
expected.set_style(Rect::new(0, 0, 48, 4), Style::new().bold());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -526,7 +522,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 12));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"▀██▀▀█▄ ▀██ ",
" ██▄▄█▀ ▄█▀▀█▄ ▄▄▄██ ",
" ██ ▀█▄ ██▀▀▀▀ ██ ██ ",
@ -543,7 +539,7 @@ mod tests {
expected.set_style(Rect::new(0, 0, 24, 4), Style::new().red());
expected.set_style(Rect::new(0, 4, 40, 4), Style::new().green());
expected.set_style(Rect::new(0, 8, 32, 4), Style::new().blue());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -555,7 +551,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"▐█▌ █ ▐█ ██ █ ",
"█ █ █ ▐▌ ",
"█▌ ▐█ ██▌ ▐█▐▌ █ ▐█▌ ▐▌ ▐█ ██▌ ▐█▌ ",
@ -565,7 +561,7 @@ mod tests {
"▐█▌ ▐█▌ █ █ █ ▐█▌ ▐█▌ ███▌▐█▌ █ █ ▐█▌ ",
" ██▌ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -577,7 +573,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 6));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"███ ▐ ▐█",
"▌█▐ █ █",
" █ █▐█ █ █ ██▌ ▐█▌ ▐█▌ ▐██ ▐█▌ █",
@ -585,7 +581,7 @@ mod tests {
" █ ▐▌▐▌█ █ █ █ █ ▐██ █ ███ █ █",
" █ ▐▌ █ █ █ █ █ █ █ █ █▐ █ █ █",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -597,7 +593,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 16));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"█ ▐▌ ▐█ ▐ █ ",
"█▌█▌ █ █ ",
"███▌█ █ █ ▐██ ▐█ ",
@ -615,7 +611,7 @@ mod tests {
"███▌▐█▌ █ █ ▐█▌ ██▌ ",
" ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -628,18 +624,17 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 8));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
"▐█▌ ▐ ▐█ ▐█ ",
"█ █ █ █ █ ",
"█▌ ▐██ █ █ █ ▐█▌ █ ",
"▐█ █ █ █ █ █ █ ▐██ ",
" ▐█ █ █ █ █ ███ █ █ ",
"█ █ █▐ ▐██ █ █ █ █ ",
"▐█▌ ▐▌ █ ▐█▌ ▐█▌ ▐█▐▌",
" ██▌ ",
let expected = Buffer::with_lines([
"▐█▌ ▐ ▐█ ▐█ ".bold(),
"█ █ █ █ █ ".bold(),
"█▌ ▐██ █ █ █ ▐█▌ █ ".bold(),
"▐█ █ █ █ █ █ █ ▐██ ".bold(),
" ▐█ █ █ █ █ ███ █ █ ".bold(),
"█ █ █▐ ▐██ █ █ █ █ ".bold(),
"▐█▌ ▐▌ █ ▐█▌ ▐█▌ ▐█▐▌".bold(),
" ██▌ ".bold(),
]);
expected.set_style(Rect::new(0, 0, 24, 8), Style::new().bold());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -655,7 +650,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 24));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"███ ▐█ ",
"▐▌▐▌ █ ",
"▐▌▐▌▐█▌ █ ",
@ -684,7 +679,7 @@ mod tests {
expected.set_style(Rect::new(0, 0, 12, 8), Style::new().red());
expected.set_style(Rect::new(0, 8, 20, 8), Style::new().green());
expected.set_style(Rect::new(0, 16, 16, 8), Style::new().blue());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -717,13 +712,13 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 4));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"▟▀▙ ▀ ▝█ ▜▛ ▀ ",
"▜▙ ▝█ █▀▙ ▟▀▟▘ █ ▟▀▙ ▐▌ ▝█ █▀▙ ▟▀▙ ",
"▄▝█ █ █ █ ▜▄█ █ █▀▀ ▐▌▗▌ █ █ █ █▀▀ ",
"▝▀▘ ▝▀▘ ▀ ▀ ▄▄▛ ▝▀▘ ▝▀▘ ▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -735,12 +730,12 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 3));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"▛█▜ ▟ ▝█",
" █ ▜▟▜▖█ █ █▀▙ ▟▀▙ ▝▀▙ ▝█▀ ▟▀▙ ▗▄█",
" █ ▐▌▝▘█ █ █ █ █ ▄ ▟▀█ █▗ █▀▀ █ █",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -752,7 +747,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 8));
big_text.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"█▖▟▌ ▝█ ▟ ▀ ",
"███▌█ █ █ ▝█▀ ▝█ ",
"█▝▐▌█ █ █ █▗ █ ",
@ -762,7 +757,7 @@ mod tests {
"▐▌▗▌ █ █ █ █▀▀ ▝▀▙ ",
"▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ▀▀▘ ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -775,14 +770,13 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 4));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
"▟▀▙ ▟ ▝█ ▝█ ",
"▜▙ ▝█▀ █ █ █ ▟▀▙ ▗▄█ ",
"▄▝█ █▗ ▜▄█ █ █▀▀ █ █ ",
"▝▀▘ ▝▘ ▄▄▛ ▝▀▘ ▝▀▘ ▝▀▝▘",
let expected = Buffer::with_lines([
"▟▀▙ ▟ ▝█ ▝█ ".bold(),
"▜▙ ▝█▀ █ █ █ ▟▀▙ ▗▄█ ".bold(),
"▄▝█ █▗ ▜▄█ █ █▀▀ █ █ ".bold(),
"▝▀▘ ▝▘ ▄▄▛ ▝▀▘ ▝▀▘ ▝▀▝▘".bold(),
]);
expected.set_style(Rect::new(0, 0, 24, 4), Style::new().bold());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
@ -798,7 +792,7 @@ mod tests {
.build()?;
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 12));
big_text.render(buf.area, &mut buf);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"▜▛▜▖ ▝█ ",
"▐▙▟▘▟▀▙ ▗▄█ ",
"▐▌▜▖█▀▀ █ █ ",
@ -815,7 +809,7 @@ mod tests {
expected.set_style(Rect::new(0, 0, 12, 4), Style::new().red());
expected.set_style(Rect::new(0, 4, 20, 4), Style::new().green());
expected.set_style(Rect::new(0, 8, 16, 4), Style::new().blue());
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
Ok(())
}
}

44
examples/minimal.rs Normal file
View file

@ -0,0 +1,44 @@
//! # [Ratatui] Minimal example
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui-org/ratatui
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, text::Text, Terminal};
/// This is a bare minimum example. There are many approaches to running an application loop, so
/// this is not meant to be prescriptive. See the [examples] folder for more complete examples.
/// In particular, the [hello-world] example is a good starting point.
///
/// [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
/// [hello-world]: https://github.com/ratatui-org/ratatui/blob/main/examples/hello_world.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
enable_raw_mode()?;
execute!(terminal.backend_mut(), EnterAlternateScreen)?;
loop {
terminal.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.size()))?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
}
}
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}

View file

@ -271,7 +271,7 @@ impl From<CellAttributes> for Style {
style.fg = Some(value.foreground().into());
style.bg = Some(value.background().into());
#[cfg(feature = "underline_color")]
#[cfg(feature = "underline-color")]
{
style.underline_color = Some(value.underline_color().into());
}
@ -407,7 +407,6 @@ fn u16_max(i: usize) -> u16 {
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Stylize;
mod into_color {
use Color as C;
@ -576,11 +575,19 @@ mod tests {
#[test]
fn from_cell_attribute_for_style() {
use crate::style::Stylize;
#[cfg(feature = "underline-color")]
const STYLE: Style = Style::new()
.underline_color(Color::Reset)
.fg(Color::Reset)
.bg(Color::Reset);
#[cfg(not(feature = "underline-color"))]
const STYLE: Style = Style::new().fg(Color::Reset).bg(Color::Reset);
// default
assert_eq!(
Style::from(CellAttributes::default()),
Style::new().fg(Color::Reset).bg(Color::Reset)
);
assert_eq!(Style::from(CellAttributes::default()), STYLE);
// foreground color
assert_eq!(
Style::from(
@ -588,7 +595,7 @@ mod tests {
.set_foreground(ColorAttribute::PaletteIndex(31))
.to_owned()
),
Style::new().fg(Color::Indexed(31)).bg(Color::Reset)
STYLE.fg(Color::Indexed(31))
);
// background color
assert_eq!(
@ -597,21 +604,7 @@ mod tests {
.set_background(ColorAttribute::PaletteIndex(31))
.to_owned()
),
Style::new().fg(Color::Reset).bg(Color::Indexed(31))
);
// underline color
#[cfg(feature = "underline_color")]
assert_eq!(
Style::from(
CellAttributes::default()
.set_underline_color(AnsiColor::Red)
.set
.to_owned()
),
Style::new()
.fg(Color::Reset)
.bg(Color::Reset)
.underline_color(Color::Red)
STYLE.bg(Color::Indexed(31))
);
// underlined
assert_eq!(
@ -620,12 +613,12 @@ mod tests {
.set_underline(Underline::Single)
.to_owned()
),
Style::new().fg(Color::Reset).bg(Color::Reset).underlined()
STYLE.underlined()
);
// blink
assert_eq!(
Style::from(CellAttributes::default().set_blink(Blink::Slow).to_owned()),
Style::new().fg(Color::Reset).bg(Color::Reset).slow_blink()
STYLE.slow_blink()
);
// intensity
assert_eq!(
@ -634,27 +627,38 @@ mod tests {
.set_intensity(Intensity::Bold)
.to_owned()
),
Style::new().fg(Color::Reset).bg(Color::Reset).bold()
STYLE.bold()
);
// italic
assert_eq!(
Style::from(CellAttributes::default().set_italic(true).to_owned()),
Style::new().fg(Color::Reset).bg(Color::Reset).italic()
STYLE.italic()
);
// reversed
assert_eq!(
Style::from(CellAttributes::default().set_reverse(true).to_owned()),
Style::new().fg(Color::Reset).bg(Color::Reset).reversed()
STYLE.reversed()
);
// strikethrough
assert_eq!(
Style::from(CellAttributes::default().set_strikethrough(true).to_owned()),
Style::new().fg(Color::Reset).bg(Color::Reset).crossed_out()
STYLE.crossed_out()
);
// hidden
assert_eq!(
Style::from(CellAttributes::default().set_invisible(true).to_owned()),
Style::new().fg(Color::Reset).bg(Color::Reset).hidden()
STYLE.hidden()
);
// underline color
#[cfg(feature = "underline-color")]
assert_eq!(
Style::from(
CellAttributes::default()
.set_underline_color(AnsiColor::Red)
.to_owned()
),
STYLE.underline_color(Color::Indexed(9))
);
}
}

View file

@ -2,21 +2,19 @@
//! It is used in the integration tests to verify the correctness of the library.
use std::{
fmt::{Display, Write},
fmt::{self, Write},
io,
};
use unicode_width::UnicodeWidthStr;
use crate::{
assert_buffer_eq,
backend::{Backend, ClearType, WindowSize},
buffer::{Buffer, Cell},
layout::{Rect, Size},
};
/// A [`Backend`] implementation used for integration testing that that renders to an in memory
/// buffer.
/// A [`Backend`] implementation used for integration testing that renders to an memory buffer.
///
/// Note: that although many of the integration and unit tests in ratatui are written using this
/// backend, it is preferable to write unit tests for widgets directly against the buffer rather
@ -30,7 +28,7 @@ use crate::{
///
/// let mut backend = TestBackend::new(10, 2);
/// backend.clear()?;
/// backend.assert_buffer(&Buffer::with_lines(vec![" "; 2]));
/// backend.assert_buffer_lines([" "; 2]);
/// # std::io::Result::Ok(())
/// ```
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@ -97,18 +95,40 @@ impl TestBackend {
}
/// Asserts that the `TestBackend`'s buffer is equal to the expected buffer.
/// If the buffers are not equal, a panic occurs with a detailed error message
/// showing the differences between the expected and actual buffers.
///
/// This is a shortcut for `assert_eq!(self.buffer(), &expected)`.
///
/// # Panics
/// When they are not equal, a panic occurs with a detailed error message showing the
/// differences between the expected and actual buffers.
#[allow(deprecated)]
#[track_caller]
pub fn assert_buffer(&self, expected: &Buffer) {
assert_buffer_eq!(&self.buffer, expected);
// TODO: use assert_eq!()
crate::assert_buffer_eq!(&self.buffer, expected);
}
/// Asserts that the `TestBackend`'s buffer is equal to the expected lines.
///
/// This is a shortcut for `assert_eq!(self.buffer(), &Buffer::with_lines(expected))`.
///
/// # Panics
/// When they are not equal, a panic occurs with a detailed error message showing the
/// differences between the expected and actual buffers.
#[track_caller]
pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines)
where
Lines: IntoIterator,
Lines::Item: Into<crate::text::Line<'line>>,
{
self.assert_buffer(&Buffer::with_lines(expected));
}
}
impl Display for TestBackend {
impl fmt::Display for TestBackend {
/// Formats the `TestBackend` for display by calling the `buffer_view` function
/// on its internal buffer.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", buffer_view(&self.buffer))
}
}
@ -246,7 +266,7 @@ mod tests {
TestBackend {
width: 10,
height: 2,
buffer: Buffer::with_lines(vec![" "; 2]),
buffer: Buffer::with_lines([" "; 2]),
cursor: false,
pos: (0, 0),
}
@ -254,14 +274,14 @@ mod tests {
}
#[test]
fn test_buffer_view() {
let buffer = Buffer::with_lines(vec!["aaaa"; 2]);
let buffer = Buffer::with_lines(["aaaa"; 2]);
assert_eq!(buffer_view(&buffer), "\"aaaa\"\n\"aaaa\"\n");
}
#[test]
fn buffer_view_with_overwrites() {
let multi_byte_char = "👨‍👩‍👧‍👦"; // renders 8 wide
let buffer = Buffer::with_lines(vec![multi_byte_char]);
let buffer = Buffer::with_lines([multi_byte_char]);
assert_eq!(
buffer_view(&buffer),
format!(
@ -274,29 +294,27 @@ mod tests {
#[test]
fn buffer() {
let backend = TestBackend::new(10, 2);
assert_eq!(backend.buffer(), &Buffer::with_lines(vec![" "; 2]));
backend.assert_buffer_lines([" "; 2]);
}
#[test]
fn resize() {
let mut backend = TestBackend::new(10, 2);
backend.resize(5, 5);
assert_eq!(backend.buffer(), &Buffer::with_lines(vec![" "; 5]));
backend.assert_buffer_lines([" "; 5]);
}
#[test]
fn assert_buffer() {
let backend = TestBackend::new(10, 2);
let buffer = Buffer::with_lines(vec![" "; 2]);
backend.assert_buffer(&buffer);
backend.assert_buffer_lines([" "; 2]);
}
#[test]
#[should_panic = "buffer contents not equal"]
fn assert_buffer_panics() {
let backend = TestBackend::new(10, 2);
let buffer = Buffer::with_lines(vec!["aaaaaaaaaa"; 2]);
backend.assert_buffer(&buffer);
backend.assert_buffer_lines(["aaaaaaaaaa"; 2]);
}
#[test]
@ -312,7 +330,7 @@ mod tests {
cell.set_symbol("a");
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec!["a "; 2]));
backend.assert_buffer_lines(["a "; 2]);
}
#[test]
@ -344,24 +362,19 @@ mod tests {
#[test]
fn clear() {
let mut backend = TestBackend::new(10, 4);
let mut backend = TestBackend::new(4, 2);
let mut cell = Cell::default();
cell.set_symbol("a");
backend.draw([(0, 0, &cell)].into_iter()).unwrap();
backend.draw([(0, 1, &cell)].into_iter()).unwrap();
backend.clear().unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
" ",
" ",
" ",
" ",
]));
backend.assert_buffer_lines([" ", " "]);
}
#[test]
fn clear_region_all() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@ -370,19 +383,19 @@ mod tests {
]);
backend.clear_region(ClearType::All).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
" ",
" ",
" ",
" ",
" ",
]));
]);
}
#[test]
fn clear_region_after_cursor() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@ -392,19 +405,19 @@ mod tests {
backend.set_cursor(3, 2).unwrap();
backend.clear_region(ClearType::AfterCursor).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaa ",
" ",
" ",
]));
]);
}
#[test]
fn clear_region_before_cursor() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@ -414,19 +427,19 @@ mod tests {
backend.set_cursor(5, 3).unwrap();
backend.clear_region(ClearType::BeforeCursor).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
" ",
" ",
" ",
" aaaaa",
"aaaaaaaaaa",
]));
]);
}
#[test]
fn clear_region_current_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@ -436,19 +449,19 @@ mod tests {
backend.set_cursor(3, 1).unwrap();
backend.clear_region(ClearType::CurrentLine).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"aaaaaaaaaa",
" ",
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
]));
]);
}
#[test]
fn clear_region_until_new_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
@ -458,19 +471,19 @@ mod tests {
backend.set_cursor(3, 0).unwrap();
backend.clear_region(ClearType::UntilNewLine).unwrap();
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"aaa ",
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
"aaaaaaaaaa",
]));
]);
}
#[test]
fn append_lines_not_at_last_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -496,19 +509,19 @@ mod tests {
assert_eq!(backend.get_cursor().unwrap(), (4, 4));
// As such the buffer should remain unchanged
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
"dddddddddd",
"eeeeeeeeee",
]));
]);
}
#[test]
fn append_lines_at_last_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -522,7 +535,7 @@ mod tests {
backend.append_lines(1).unwrap();
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"bbbbbbbbbb",
"cccccccccc",
"dddddddddd",
@ -538,7 +551,7 @@ mod tests {
#[test]
fn append_multiple_lines_not_at_last_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -555,19 +568,19 @@ mod tests {
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
// As such the buffer should remain unchanged
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
"dddddddddd",
"eeeeeeeeee",
]));
]);
}
#[test]
fn append_multiple_lines_past_last_line() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -580,19 +593,19 @@ mod tests {
backend.append_lines(3).unwrap();
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"cccccccccc",
"dddddddddd",
"eeeeeeeeee",
" ",
" ",
]));
]);
}
#[test]
fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -605,19 +618,19 @@ mod tests {
backend.append_lines(5).unwrap();
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
" ",
" ",
" ",
" ",
" ",
]));
]);
}
#[test]
fn append_multiple_lines_where_cursor_appends_height_lines() {
let mut backend = TestBackend::new(10, 5);
backend.buffer = Buffer::with_lines(vec![
backend.buffer = Buffer::with_lines([
"aaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccc",
@ -630,13 +643,13 @@ mod tests {
backend.append_lines(5).unwrap();
assert_eq!(backend.get_cursor().unwrap(), (1, 4));
backend.assert_buffer(&Buffer::with_lines(vec![
backend.assert_buffer_lines([
"bbbbbbbbbb",
"cccccccccc",
"dddddddddd",
"eeeeeeeeee",
" ",
]));
]);
}
#[test]

View file

@ -1,57 +1,44 @@
/// Assert that two buffers are equal by comparing their areas and content.
///
/// On panic, displays the areas or the content and a diff of the contents.
/// # Panics
/// When the buffers differ this method panics and displays the differences similar to
/// `assert_eq!()`.
#[deprecated = "use assert_eq!(&actual, &expected)"]
#[macro_export]
macro_rules! assert_buffer_eq {
($actual_expr:expr, $expected_expr:expr) => {
match (&$actual_expr, &$expected_expr) {
(actual, expected) => {
if actual.area != expected.area {
panic!(
indoc::indoc!(
"
buffer areas not equal
expected: {:?}
actual: {:?}"
),
expected, actual
);
}
let diff = expected.diff(&actual);
if !diff.is_empty() {
let nice_diff = diff
.iter()
.enumerate()
.map(|(i, (x, y, cell))| {
let expected_cell = expected.get(*x, *y);
indoc::formatdoc! {"
{i}: at ({x}, {y})
expected: {expected_cell:?}
actual: {cell:?}
"}
})
.collect::<Vec<String>>()
.join("\n");
panic!(
indoc::indoc!(
"
buffer contents not equal
expected: {:?}
actual: {:?}
diff:
{}"
),
expected, actual, nice_diff
);
}
assert!(
actual.area == expected.area,
"buffer areas not equal\nexpected: {expected:?}\nactual: {actual:?}",
);
let nice_diff = expected
.diff(actual)
.into_iter()
.enumerate()
.map(|(i, (x, y, cell))| {
let expected_cell = expected.get(x, y);
format!("{i}: at ({x}, {y})\n expected: {expected_cell:?}\n actual: {cell:?}")
})
.collect::<Vec<String>>()
.join("\n");
assert!(
nice_diff.is_empty(),
"buffer contents not equal\nexpected: {expected:?}\nactual: {actual:?}\ndiff:\n{nice_diff}",
);
// shouldn't get here, but this guards against future behavior
// that changes equality but not area or content
assert_eq!(actual, expected, "buffers not equal");
assert_eq!(
actual, expected,
"buffers are not equal in an unexpected way. Please open an issue about this."
);
}
}
};
}
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use crate::prelude::*;

View file

@ -1,3 +1,5 @@
use std::{fmt, ops};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
@ -92,8 +94,8 @@ impl Buffer {
/// Returns a reference to Cell at the given coordinates
///
/// Callers should generally use the Index trait (`buf[(x, y)]`) or the [`cell`] method instead
/// of this method.
/// Callers should generally use the Index trait (`buf[(x, y)]`) or the [`Buffer::cell`] method
/// instead of this method.
///
/// Note that conventionally methods named `get` usually return `Option<&T>`, but this method
/// panics instead. This is kept for backwards compatibility. See `get_opt` for a safe
@ -110,8 +112,8 @@ impl Buffer {
/// Returns a mutable reference to Cell at the given coordinates
///
/// Callers should generally use the `IndexMut` trait (`&mut buf[(x, y)]`) or the [`cell_mut`]
/// method instead of this method.
/// Callers should generally use the `IndexMut` trait (`&mut buf[(x, y)]`) or the
/// [`Buffer::cell_mut`] method instead of this method.
///
/// Note that conventionally methods named `get_mut` usually return `Option<&mut T>`, but this
/// method panics instead. This is kept for backwards compatibility. See `cell_mut` for a safe
@ -209,6 +211,8 @@ impl Buffer {
/// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
///
/// Returns `None` if the given coordinates are outside of the Buffer's area.
///
/// Note that this is private because of <https://github.com/ratatui-org/ratatui/issues/1122>
const fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
let area = self.area;
if x < area.left() || x >= area.right() || y < area.top() || y >= area.bottom() {
@ -447,7 +451,7 @@ impl Buffer {
}
}
impl<P: Into<Position>> std::ops::Index<P> for Buffer {
impl<P: Into<Position>> ops::Index<P> for Buffer {
type Output = Cell;
/// Returns the Cell at the given position
@ -471,7 +475,7 @@ impl<P: Into<Position>> std::ops::Index<P> for Buffer {
}
}
impl<P: Into<Position>> std::ops::IndexMut<P> for Buffer {
impl<P: Into<Position>> ops::IndexMut<P> for Buffer {
/// Returns a mutable reference to the Cell at the given position
///
/// # Panics
@ -493,7 +497,7 @@ impl<P: Into<Position>> std::ops::IndexMut<P> for Buffer {
}
}
impl std::fmt::Debug for Buffer {
impl fmt::Debug for Buffer {
/// Writes a debug representation of the buffer to the given formatter.
///
/// The format is like a pretty printed struct, with the following fields:
@ -501,11 +505,14 @@ impl std::fmt::Debug for Buffer {
/// * `content`: displayed as a list of strings representing the content of the buffer
/// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue,
/// modifier: Modifier::BOLD }` only showing a value when there is a change in style.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Buffer {{\n area: {:?},\n content: [\n",
&self.area
))?;
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("Buffer {{\n area: {:?}", &self.area))?;
if self.area.is_empty() {
return f.write_str("\n}");
}
f.write_str(",\n content: [\n")?;
let mut last_style = None;
let mut styles = vec![];
for (y, line) in self.content.chunks(self.area.width as usize).enumerate() {
@ -536,12 +543,13 @@ impl std::fmt::Debug for Buffer {
}
}
}
f.write_str("\",")?;
if !overwritten.is_empty() {
f.write_fmt(format_args!(
"// hidden by multi-width symbols: {overwritten:?}"
" // hidden by multi-width symbols: {overwritten:?}"
))?;
}
f.write_str("\",\n")?;
f.write_str("\n")?;
}
f.write_str(" ],\n styles: [\n")?;
for s in styles {
@ -569,7 +577,6 @@ mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::assert_buffer_eq;
fn cell(s: &str) -> Cell {
let mut cell = Cell::default();
@ -578,10 +585,40 @@ mod tests {
}
#[test]
fn debug() {
let mut buf = Buffer::empty(Rect::new(0, 0, 12, 2));
buf.set_string(0, 0, "Hello World!", Style::default());
buf.set_string(
fn debug_empty_buffer() {
let buffer = Buffer::empty(Rect::ZERO);
let result = format!("{buffer:?}");
println!("{result}");
let expected = "Buffer {\n area: Rect { x: 0, y: 0, width: 0, height: 0 }\n}";
assert_eq!(result, expected);
}
#[cfg(feature = "underline-color")]
#[test]
fn debug_grapheme_override() {
let buffer = Buffer::with_lines(["a🦀b"]);
let result = format!("{buffer:?}");
println!("{result}");
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 4, height: 1 },
content: [
"a🦀b", // hidden by multi-width symbols: [(2, " ")]
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}"#
);
assert_eq!(result, expected);
}
#[test]
fn debug_some_example() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 12, 2));
buffer.set_string(0, 0, "Hello World!", Style::default());
buffer.set_string(
0,
1,
"G'day World!",
@ -590,42 +627,40 @@ mod tests {
.bg(Color::Yellow)
.add_modifier(Modifier::BOLD),
);
let result = format!("{buffer:?}");
println!("{result}");
#[cfg(feature = "underline-color")]
assert_eq!(
format!("{buf:?}"),
indoc::indoc!(
"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
\"Hello World!\",
\"G'day World!\",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
]
}"
)
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
"Hello World!",
"G'day World!",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
]
}"#
);
#[cfg(not(feature = "underline-color"))]
assert_eq!(
format!("{buf:?}"),
indoc::indoc!(
"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
\"Hello World!\",
\"G'day World!\",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
]
}"
)
let expected = indoc::indoc!(
r#"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
"Hello World!",
"G'day World!",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, modifier: BOLD,
]
}"#
);
assert_eq!(result, expected);
}
#[test]
@ -719,7 +754,7 @@ mod tests {
let mut buf = Buffer::with_lines(["Cat", "Dog"]);
buf[(0, 0)].set_symbol("B");
buf[Position::new(0, 1)].set_symbol("L");
assert_buffer_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
assert_eq!(buf, Buffer::with_lines(["Bat", "Log"]));
}
#[rstest]
@ -742,27 +777,27 @@ mod tests {
// Zero-width
buffer.set_stringn(0, 0, "aaa", 0, Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" "]));
assert_eq!(buffer, Buffer::with_lines([" "]));
buffer.set_string(0, 0, "aaa", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
assert_eq!(buffer, Buffer::with_lines(["aaa "]));
// Width limit:
buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
assert_eq!(buffer, Buffer::with_lines(["bbbb "]));
buffer.set_string(0, 0, "12345", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345"]));
assert_eq!(buffer, Buffer::with_lines(["12345"]));
// Width truncation:
buffer.set_string(0, 0, "123456", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345"]));
assert_eq!(buffer, Buffer::with_lines(["12345"]));
// multi-line
buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
buffer.set_string(0, 0, "12345", Style::default());
buffer.set_string(0, 1, "67890", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["12345", "67890"]));
assert_eq!(buffer, Buffer::with_lines(["12345", "67890"]));
}
#[test]
@ -773,7 +808,7 @@ mod tests {
// multi-width overwrite
buffer.set_string(0, 0, "aaaaa", Style::default());
buffer.set_string(0, 0, "称号", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["称号a"]));
assert_eq!(buffer, Buffer::with_lines(["称号a"]));
}
#[test]
@ -784,12 +819,12 @@ mod tests {
// Leading grapheme with zero width
let s = "\u{1}a";
buffer.set_stringn(0, 0, s, 1, Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["a"]));
assert_eq!(buffer, Buffer::with_lines(["a"]));
// Trailing grapheme with zero with
let s = "a\u{1}";
buffer.set_stringn(0, 0, s, 1, Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["a"]));
assert_eq!(buffer, Buffer::with_lines(["a"]));
}
#[test]
@ -797,11 +832,11 @@ mod tests {
let area = Rect::new(0, 0, 5, 1);
let mut buffer = Buffer::empty(area);
buffer.set_string(0, 0, "コン", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["コン "]));
assert_eq!(buffer, Buffer::with_lines(["コン "]));
// Only 1 space left.
buffer.set_string(0, 0, "コンピ", Style::default());
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["コン "]));
assert_eq!(buffer, Buffer::with_lines(["コン "]));
}
#[fixture]
@ -826,7 +861,7 @@ mod tests {
// set_line
let mut expected_buffer = Buffer::empty(small_one_line_buffer.area);
expected_buffer.set_string(0, 0, expected, Style::default());
assert_buffer_eq!(small_one_line_buffer, expected_buffer);
assert_eq!(small_one_line_buffer, expected_buffer);
}
#[rstest]
@ -867,28 +902,39 @@ mod tests {
#[test]
fn set_style() {
let mut buffer = Buffer::with_lines(vec!["aaaaa", "bbbbb", "ccccc"]);
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
buffer.set_style(Rect::new(0, 1, 5, 1), Style::new().red());
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec!["aaaaa".into(), "bbbbb".red(), "ccccc".into(),])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"aaaaa".into(),
"bbbbb".red(),
"ccccc".into(),
]);
assert_eq!(buffer, expected);
}
#[test]
fn set_style_does_not_panic_when_out_of_area() {
let mut buffer = Buffer::with_lines(vec!["aaaaa", "bbbbb", "ccccc"]);
let mut buffer = Buffer::with_lines(["aaaaa", "bbbbb", "ccccc"]);
buffer.set_style(Rect::new(0, 1, 10, 3), Style::new().red());
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec!["aaaaa".into(), "bbbbb".red(), "ccccc".red(),])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"aaaaa".into(),
"bbbbb".red(),
"ccccc".red(),
]);
assert_eq!(buffer, expected);
}
#[test]
fn with_lines() {
let buffer =
Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
#[rustfmt::skip]
let buffer = Buffer::with_lines([
"┌────────┐",
"│コンピュ│",
"│ーa 上で│",
"└────────┘",
]);
assert_eq!(buffer.area.x, 0);
assert_eq!(buffer.area.y, 0);
assert_eq!(buffer.area.width, 10);
@ -924,14 +970,14 @@ mod tests {
#[test]
fn diff_single_width() {
let prev = Buffer::with_lines(vec![
let prev = Buffer::with_lines([
" ",
"┌Title─┐ ",
"│ │ ",
"│ │ ",
"└──────┘ ",
]);
let next = Buffer::with_lines(vec![
let next = Buffer::with_lines([
" ",
"┌TITLE─┐ ",
"│ │ ",
@ -953,11 +999,11 @@ mod tests {
#[test]
#[rustfmt::skip]
fn diff_multi_width() {
let prev = Buffer::with_lines(vec![
let prev = Buffer::with_lines([
"┌Title─┐ ",
"└──────┘ ",
]);
let next = Buffer::with_lines(vec![
let next = Buffer::with_lines([
"┌称号──┐ ",
"└──────┘ ",
]);
@ -976,8 +1022,8 @@ mod tests {
#[test]
fn diff_multi_width_offset() {
let prev = Buffer::with_lines(vec!["┌称号──┐"]);
let next = Buffer::with_lines(vec!["┌─称号─┐"]);
let prev = Buffer::with_lines(["┌称号──┐"]);
let next = Buffer::with_lines(["┌─称号─┐"]);
let diff = prev.diff(&next);
assert_eq!(
@ -988,8 +1034,8 @@ mod tests {
#[test]
fn diff_skip() {
let prev = Buffer::with_lines(vec!["123"]);
let mut next = Buffer::with_lines(vec!["456"]);
let prev = Buffer::with_lines(["123"]);
let mut next = Buffer::with_lines(["456"]);
for i in 1..3 {
next.content[i].set_skip(true);
}
@ -1019,7 +1065,7 @@ mod tests {
Cell::default().set_symbol("2"),
);
one.merge(&two);
assert_buffer_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
assert_eq!(one, Buffer::with_lines(["11", "11", "22", "22"]));
}
#[test]
@ -1043,10 +1089,7 @@ mod tests {
Cell::default().set_symbol("2"),
);
one.merge(&two);
assert_buffer_eq!(
one,
Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
);
assert_eq!(one, Buffer::with_lines(["22 ", "22 ", " 11", " 11"]));
}
#[test]
@ -1070,14 +1113,14 @@ mod tests {
Cell::default().set_symbol("2"),
);
one.merge(&two);
let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
let mut merged = Buffer::with_lines(["222 ", "222 ", "2221", "2221"]);
merged.area = Rect {
x: 1,
y: 1,
width: 4,
height: 4,
};
assert_buffer_eq!(one, merged);
assert_eq!(one, merged);
}
#[test]
@ -1136,6 +1179,6 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 3, 2));
buf.set_string(0, 0, "foo", Style::new().red());
buf.set_string(0, 1, "bar", Style::new().blue());
assert_eq!(buf, Buffer::with_lines(vec!["foo".red(), "bar".blue()]));
assert_eq!(buf, Buffer::with_lines(["foo".red(), "bar".blue()]));
}
}

View file

@ -1,5 +1,3 @@
use std::fmt::Debug;
use compact_str::CompactString;
use crate::prelude::*;

View file

@ -1,4 +1,4 @@
use std::fmt::{self, Display};
use std::fmt;
use itertools::Itertools;
use strum::EnumIs;
@ -362,7 +362,7 @@ impl Default for Constraint {
}
}
impl Display for Constraint {
impl fmt::Display for Constraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Percentage(p) => write!(f, "Percentage({p})"),

View file

@ -448,7 +448,7 @@ impl Layout {
/// # }
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
let (areas, _) = self.split_with_spacers(area);
areas.to_vec().try_into().expect("invalid number of rects")
areas.as_ref().try_into().expect("invalid number of rects")
}
/// Split the rect into a number of sub-rects according to the given [`Layout`] and return just
@ -482,7 +482,7 @@ impl Layout {
pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N] {
let (_, spacers) = self.split_with_spacers(area);
spacers
.to_vec()
.as_ref()
.try_into()
.expect("invalid number of rects")
}
@ -1334,7 +1334,6 @@ mod tests {
use rstest::rstest;
use crate::{
assert_buffer_eq,
layout::flex::Flex,
prelude::{Constraint::*, *},
widgets::Paragraph,
@ -1361,8 +1360,7 @@ mod tests {
let s = c.to_string().repeat(area.width as usize);
Paragraph::new(s).render(layout[i], &mut buffer);
}
let expected = Buffer::with_lines(vec![expected]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]

View file

@ -1,4 +1,4 @@
use std::fmt::{self, Display};
use std::fmt;
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Margin {
@ -15,7 +15,7 @@ impl Margin {
}
}
impl Display for Margin {
impl fmt::Display for Margin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}x{}", self.horizontal, self.vertical)
}

View file

@ -47,6 +47,14 @@ impl fmt::Display for Rect {
}
impl Rect {
/// A zero sized Rect at position 0,0
pub const ZERO: Self = Self {
x: 0,
y: 0,
width: 0,
height: 0,
};
/// Creates a new `Rect`, with width and height limited to keep the area under max `u16`. If
/// clipped, aspect ratio will be preserved.
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
@ -111,13 +119,14 @@ impl Rect {
/// Returns a new `Rect` inside the current one, with the given margin on each side.
///
/// If the margin is larger than the `Rect`, the returned `Rect` will have no area.
#[allow(clippy::trivially_copy_pass_by_ref)] // See PR #1008
#[must_use = "method returns the modified value"]
pub fn inner(self, margin: &Margin) -> Self {
pub const fn inner(self, margin: &Margin) -> Self {
let doubled_margin_horizontal = margin.horizontal.saturating_mul(2);
let doubled_margin_vertical = margin.vertical.saturating_mul(2);
if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
Self::default()
Self::ZERO
} else {
Self {
x: self.x.saturating_add(margin.horizontal),
@ -313,6 +322,18 @@ impl Rect {
height: self.height,
}
}
/// indents the x value of the `Rect` by a given `offset`
///
/// This is pub(crate) for now as we need to stabilize the naming / design of this API.
#[must_use]
pub(crate) const fn indent_x(self, offset: u16) -> Self {
Self {
x: self.x.saturating_add(offset),
width: self.width.saturating_sub(offset),
..self
}
}
}
impl From<(Position, Size)> for Rect {

View file

@ -68,14 +68,14 @@
//! [`prelude`]: crate::prelude
//! [`Span`]: crate::text::Span
use std::fmt::{self, Debug};
use std::fmt;
use bitflags::bitflags;
mod color;
mod stylize;
pub use color::Color;
pub use color::{Color, ParseColorError};
pub use stylize::{Styled, Stylize};
pub mod palette;
@ -119,7 +119,7 @@ impl fmt::Debug for Modifier {
if self.is_empty() {
return write!(f, "NONE");
}
fmt::Debug::fmt(&self.0, f)
write!(f, "{}", self.0)
}
}
@ -550,6 +550,8 @@ impl From<(Color, Color, Modifier, Modifier)> for Style {
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[test]
@ -606,26 +608,21 @@ mod tests {
}
}
#[test]
fn modifier_debug() {
assert_eq!(format!("{:?}", Modifier::empty()), "NONE");
assert_eq!(format!("{:?}", Modifier::BOLD), "BOLD");
assert_eq!(format!("{:?}", Modifier::DIM), "DIM");
assert_eq!(format!("{:?}", Modifier::ITALIC), "ITALIC");
assert_eq!(format!("{:?}", Modifier::UNDERLINED), "UNDERLINED");
assert_eq!(format!("{:?}", Modifier::SLOW_BLINK), "SLOW_BLINK");
assert_eq!(format!("{:?}", Modifier::RAPID_BLINK), "RAPID_BLINK");
assert_eq!(format!("{:?}", Modifier::REVERSED), "REVERSED");
assert_eq!(format!("{:?}", Modifier::HIDDEN), "HIDDEN");
assert_eq!(format!("{:?}", Modifier::CROSSED_OUT), "CROSSED_OUT");
assert_eq!(
format!("{:?}", Modifier::BOLD | Modifier::DIM),
"BOLD | DIM"
);
assert_eq!(
format!("{:?}", Modifier::all()),
"BOLD | DIM | ITALIC | UNDERLINED | SLOW_BLINK | RAPID_BLINK | REVERSED | HIDDEN | CROSSED_OUT"
);
#[rstest]
#[case(Modifier::empty(), "NONE")]
#[case(Modifier::BOLD, "BOLD")]
#[case(Modifier::DIM, "DIM")]
#[case(Modifier::ITALIC, "ITALIC")]
#[case(Modifier::UNDERLINED, "UNDERLINED")]
#[case(Modifier::SLOW_BLINK, "SLOW_BLINK")]
#[case(Modifier::RAPID_BLINK, "RAPID_BLINK")]
#[case(Modifier::REVERSED, "REVERSED")]
#[case(Modifier::HIDDEN, "HIDDEN")]
#[case(Modifier::CROSSED_OUT, "CROSSED_OUT")]
#[case(Modifier::BOLD | Modifier::DIM, "BOLD | DIM")]
#[case(Modifier::all(), "BOLD | DIM | ITALIC | UNDERLINED | SLOW_BLINK | RAPID_BLINK | REVERSED | HIDDEN | CROSSED_OUT")]
fn modifier_debug(#[case] modifier: Modifier, #[case] expected: &str) {
assert_eq!(format!("{modifier:?}"), expected);
}
#[test]

View file

@ -1,9 +1,6 @@
#![allow(clippy::unreadable_literal)]
use std::{
fmt::{self, Debug, Display},
str::FromStr,
};
use std::{fmt, str::FromStr};
/// ANSI Color
///
@ -142,7 +139,7 @@ impl Color {
#[cfg(feature = "serde")]
impl serde::Serialize for Color {
/// This utilises the [`Display`] implementation for serialization.
/// This utilises the [`fmt::Display`] implementation for serialization.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
@ -243,8 +240,8 @@ impl<'de> serde::Deserialize<'de> for Color {
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct ParseColorError;
impl std::fmt::Display for ParseColorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for ParseColorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Failed to parse Colors")
}
}
@ -336,7 +333,7 @@ impl FromStr for Color {
}
}
impl Display for Color {
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Reset => write!(f, "Reset"),

View file

@ -83,6 +83,7 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::Block};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
@ -90,6 +91,7 @@ impl Frame<'_> {
/// let block = Block::new();
/// let area = Rect::new(0, 0, 5, 5);
/// frame.render_widget_ref(block, area);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]
#[stability::unstable(feature = "widget-ref")]
@ -138,6 +140,7 @@ impl Frame<'_> {
/// # Example
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// # use ratatui::{backend::TestBackend, prelude::*, widgets::*};
/// # let backend = TestBackend::new(5, 5);
/// # let mut terminal = Terminal::new(backend).unwrap();
@ -146,6 +149,7 @@ impl Frame<'_> {
/// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]);
/// let area = Rect::new(0, 0, 5, 5);
/// frame.render_stateful_widget_ref(list, area, &mut state);
/// # }
/// ```
#[allow(clippy::needless_pass_by_value)]
#[stability::unstable(feature = "widget-ref")]

View file

@ -1,5 +1,8 @@
#![deny(missing_docs)]
use std::borrow::Cow;
#![warn(clippy::pedantic, clippy::nursery, clippy::arithmetic_side_effects)]
use std::{borrow::Cow, fmt};
use unicode_truncate::UnicodeTruncateStr;
use super::StyledGrapheme;
use crate::prelude::*;
@ -453,39 +456,6 @@ impl<'a> Line<'a> {
self.spans.iter_mut()
}
/// Returns a line that's truncated corresponding to it's alignment and result width
#[must_use = "method returns the modified value"]
fn truncated(&'a self, result_width: u16) -> Self {
let mut truncated_line = Line::default();
let width = self.width() as u16;
let mut offset = match self.alignment {
Some(Alignment::Center) => (width.saturating_sub(result_width)) / 2,
Some(Alignment::Right) => width.saturating_sub(result_width),
_ => 0,
};
let mut x = 0;
for span in &self.spans {
let span_width = span.width() as u16;
if offset >= span_width {
offset -= span_width;
continue;
}
let mut new_span = span.clone();
let new_span_width = span_width - offset;
if x + new_span_width > result_width {
let span_end = (result_width - x + offset) as usize;
new_span.content = Cow::from(&span.content[offset as usize..span_end]);
truncated_line.spans.push(new_span);
break;
}
new_span.content = Cow::from(&span.content[offset as usize..]);
truncated_line.spans.push(new_span);
x += new_span_width;
offset = 0;
}
truncated_line
}
/// Adds a span to the line.
///
/// `span` can be any type that is convertible into a `Span`. For example, you can pass a
@ -585,38 +555,100 @@ impl Widget for Line<'_> {
impl WidgetRef for Line<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;
}
let line_width = self.width();
if line_width == 0 {
return;
}
buf.set_style(area, self.style);
let width = self.width() as u16;
let mut x = area.left();
let line = if width > area.width {
self.truncated(area.width)
} else {
let offset = match self.alignment {
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
Some(Alignment::Right) => area.width.saturating_sub(width),
let area_width = usize::from(area.width);
let can_render_complete_line = line_width <= area_width;
if can_render_complete_line {
let indent_width = match self.alignment {
Some(Alignment::Center) => (area_width.saturating_sub(line_width)) / 2,
Some(Alignment::Right) => area_width.saturating_sub(line_width),
Some(Alignment::Left) | None => 0,
};
x = x.saturating_add(offset);
self.to_owned()
};
for span in &line.spans {
let span_width = span.width() as u16;
let span_area = Rect {
x,
width: span_width.min(area.right().saturating_sub(x)),
..area
let indent_width = u16::try_from(indent_width).unwrap_or(u16::MAX);
let area = area.indent_x(indent_width);
render_spans(&self.spans, area, buf, 0);
} else {
// There is not enough space to render the whole line. As the right side is truncated by
// the area width, only truncate the left.
let skip_width = match self.alignment {
Some(Alignment::Center) => (line_width.saturating_sub(area_width)) / 2,
Some(Alignment::Right) => line_width.saturating_sub(area_width),
Some(Alignment::Left) | None => 0,
};
span.render(span_area, buf);
x = x.saturating_add(span_width);
if x >= area.right() {
break;
}
}
render_spans(&self.spans, area, buf, skip_width);
};
}
}
impl std::fmt::Display for Line<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// Renders all the spans of the line that should be visible.
fn render_spans(spans: &[Span], mut area: Rect, buf: &mut Buffer, span_skip_width: usize) {
for (span, span_width, offset) in spans_after_width(spans, span_skip_width) {
area = area.indent_x(offset);
if area.is_empty() {
break;
}
span.render_ref(area, buf);
let span_width = u16::try_from(span_width).unwrap_or(u16::MAX);
area = area.indent_x(span_width);
}
}
/// Returns an iterator over the spans that lie after a given skip widtch from the start of the
/// `Line` (including a partially visible span if the `skip_width` lands within a span).
fn spans_after_width<'a>(
spans: &'a [Span],
mut skip_width: usize,
) -> impl Iterator<Item = (Span<'a>, usize, u16)> {
spans
.iter()
.map(|span| (span, span.width()))
// Filter non visible spans out.
.filter_map(move |(span, span_width)| {
// Ignore spans that are completely before the offset. Decrement `span_skip_width` by
// the span width until we find a span that is partially or completely visible.
if skip_width >= span_width {
skip_width = skip_width.saturating_sub(span_width);
return None;
}
// Apply the skip from the start of the span, not the end as the end will be trimmed
// when rendering the span to the buffer.
let available_width = span_width.saturating_sub(skip_width);
skip_width = 0; // ensure the next span is rendered in full
Some((span, span_width, available_width))
})
.map(|(span, span_width, available_width)| {
if span_width <= available_width {
// Span is fully visible. Clone here is fast as the underlying content is `Cow`.
return (span.clone(), span_width, 0u16);
}
// Span is only partially visible. As the end is truncated by the area width, only
// truncate the start of the span.
let (content, actual_width) = span.content.unicode_truncate_start(available_width);
// When the first grapheme of the span was truncated, start rendering from a position
// that takes that into account by indenting the start of the area
let first_grapheme_offset = available_width.saturating_sub(actual_width);
let first_grapheme_offset = u16::try_from(first_grapheme_offset).unwrap_or(u16::MAX);
(
Span::styled(content, span.style),
actual_width,
first_grapheme_offset,
)
})
}
impl fmt::Display for Line<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for span in &self.spans {
write!(f, "{span}")?;
}
@ -643,7 +675,6 @@ mod tests {
use rstest::{fixture, rstest};
use super::*;
use crate::assert_buffer_eq;
#[fixture]
fn small_buf() -> Buffer {
@ -887,53 +918,6 @@ mod tests {
assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
}
#[test]
fn render_truncates_left() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.left_aligned()
.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec!["Hello"]);
assert_buffer_eq!(buf, expected);
}
#[test]
fn render_truncates_right() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.right_aligned()
.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec!["world"]);
assert_buffer_eq!(buf, expected);
}
#[test]
fn render_truncates_center() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.centered()
.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec!["lo wo"]);
assert_buffer_eq!(buf, expected);
}
#[test]
fn truncate_line_with_multiple_spans() {
let line = Line::default().spans(vec!["foo", "bar"]);
assert_eq!(
line.right_aligned().truncated(4).to_string(),
String::from("obar")
);
}
#[test]
fn truncation_ignores_useless_spans() {
let line = Line::default().spans(vec!["foo", "bar"]);
assert_eq!(
line.right_aligned().truncated(3),
Line::default().spans(vec!["bar"])
);
}
#[test]
fn left_aligned() {
@ -965,8 +949,12 @@ mod tests {
}
mod widget {
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use super::*;
use crate::assert_buffer_eq;
use crate::buffer::Cell;
const BLUE: Style = Style::new().fg(Color::Blue);
const GREEN: Style = Style::new().fg(Color::Green);
const ITALIC: Style = Style::new().add_modifier(Modifier::ITALIC);
@ -984,37 +972,36 @@ mod tests {
fn render() {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
let mut expected = Buffer::with_lines(vec!["Hello world! "]);
let mut expected = Buffer::with_lines(["Hello world! "]);
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[rstest]
fn render_out_of_bounds(hello_world: Line<'static>, mut small_buf: Buffer) {
let out_of_bounds = Rect::new(20, 20, 10, 1);
hello_world.render(out_of_bounds, &mut small_buf);
assert_buffer_eq!(small_buf, Buffer::empty(small_buf.area));
assert_eq!(small_buf, Buffer::empty(small_buf.area));
}
#[test]
fn render_only_styles_line_area() {
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
hello_world().render(Rect::new(0, 0, 15, 1), &mut buf);
let mut expected = Buffer::with_lines(vec!["Hello world! "]);
let mut expected = Buffer::with_lines(["Hello world! "]);
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
expected.set_style(Rect::new(0, 0, 6, 1), BLUE);
expected.set_style(Rect::new(6, 0, 6, 1), GREEN);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
fn render_truncates() {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
Line::from("Hello world!").render(Rect::new(0, 0, 5, 1), &mut buf);
let expected = Buffer::with_lines(vec!["Hello "]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[test]
@ -1022,11 +1009,11 @@ mod tests {
let line = hello_world().alignment(Alignment::Center);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
line.render(Rect::new(0, 0, 15, 1), &mut buf);
let mut expected = Buffer::with_lines(vec![" Hello world! "]);
let mut expected = Buffer::with_lines([" Hello world! "]);
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
expected.set_style(Rect::new(1, 0, 6, 1), BLUE);
expected.set_style(Rect::new(7, 0, 6, 1), GREEN);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1034,11 +1021,255 @@ mod tests {
let line = hello_world().alignment(Alignment::Right);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
line.render(Rect::new(0, 0, 15, 1), &mut buf);
let mut expected = Buffer::with_lines(vec![" Hello world!"]);
let mut expected = Buffer::with_lines([" Hello world!"]);
expected.set_style(Rect::new(0, 0, 15, 1), ITALIC);
expected.set_style(Rect::new(3, 0, 6, 1), BLUE);
expected.set_style(Rect::new(9, 0, 6, 1), GREEN);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
fn render_truncates_left() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.left_aligned()
.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello"]));
}
#[test]
fn render_truncates_right() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.right_aligned()
.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["world"]));
}
#[test]
fn render_truncates_center() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
Line::from("Hello world")
.centered()
.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["lo wo"]));
}
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
#[test]
fn regression_1032() {
let line = Line::from(
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得する"
);
let mut buf = Buffer::empty(Rect::new(0, 0, 83, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([
"🦀 RFC8628 OAuth 2.0 Device Authorization GrantでCLIからGithubのaccess tokenを取得 "
]));
}
/// Documentary test to highlight the crab emoji width / length discrepancy
///
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
#[test]
fn crab_emoji_width() {
let crab = "🦀";
assert_eq!(crab.len(), 4); // bytes
assert_eq!(crab.chars().count(), 1);
assert_eq!(crab.graphemes(true).count(), 1);
assert_eq!(crab.width(), 2); // display width
}
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
#[rstest]
#[case::left_4(Alignment::Left, 4, "1234")]
#[case::left_5(Alignment::Left, 5, "1234 ")]
#[case::left_6(Alignment::Left, 6, "1234🦀")]
#[case::left_7(Alignment::Left, 7, "1234🦀7")]
#[case::right_4(Alignment::Right, 4, "7890")]
#[case::right_5(Alignment::Right, 5, " 7890")]
#[case::right_6(Alignment::Right, 6, "🦀7890")]
#[case::right_7(Alignment::Right, 7, "4🦀7890")]
fn render_truncates_emoji(
#[case] alignment: Alignment,
#[case] buf_width: u16,
#[case] expected: &str,
) {
let line = Line::from("1234🦀7890").alignment(alignment);
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
///
/// centering is tricky because there's an ambiguity about whether to take one more char
/// from the left or the right when the line width is odd. This interacts with the width of
/// the crab emoji, which is 2 characters wide by hitting the left or right side of the
/// emoji.
#[rstest]
#[case::center_6_0(6, 0, "")]
#[case::center_6_1(6, 1, " ")] // lef side of "🦀"
#[case::center_6_2(6, 2, "🦀")]
#[case::center_6_3(6, 3, "b🦀")]
#[case::center_6_4(6, 4, "b🦀c")]
#[case::center_7_0(7, 0, "")]
#[case::center_7_1(7, 1, " ")] // right side of "🦀"
#[case::center_7_2(7, 2, "🦀")]
#[case::center_7_3(7, 3, "🦀c")]
#[case::center_7_4(7, 4, "b🦀c")]
#[case::center_8_0(8, 0, "")]
#[case::center_8_1(8, 1, " ")] // right side of "🦀"
#[case::center_8_2(8, 2, " c")] // right side of "🦀c"
#[case::center_8_3(8, 3, "🦀c")]
#[case::center_8_4(8, 4, "🦀cd")]
#[case::center_8_5(8, 5, "b🦀cd")]
#[case::center_9_0(9, 0, "")]
#[case::center_9_1(9, 1, "c")]
#[case::center_9_2(9, 2, " c")] // right side of "🦀c"
#[case::center_9_3(9, 3, " cd")]
#[case::center_9_4(9, 4, "🦀cd")]
#[case::center_9_5(9, 5, "🦀cde")]
#[case::center_9_6(9, 6, "b🦀cde")]
fn render_truncates_emoji_center(
#[case] line_width: u16,
#[case] buf_width: u16,
#[case] expected: &str,
) {
// because the crab emoji is 2 characters wide, it will can cause the centering tests
// intersect with either the left or right part of the emoji, which causes the emoji to
// be not rendered. Checking for four different widths of the line is enough to cover
// all the possible cases.
let value = match line_width {
6 => "ab🦀cd",
7 => "ab🦀cde",
8 => "ab🦀cdef",
9 => "ab🦀cdefg",
_ => unreachable!(),
};
let line = Line::from(value).centered();
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
/// Ensures the rendering also works away from the 0x0 position.
///
/// Particularly of note is that an emoji that is truncated will not overwrite the
/// characters that are already in the buffer. This is inentional (consider how a line
/// that is rendered on a border should not overwrite the border with a partial emoji).
#[rstest]
#[case::left(Alignment::Left, "XXa🦀bcXXX")]
#[case::center(Alignment::Center, "XX🦀bc🦀XX")]
#[case::right(Alignment::Right, "XXXbc🦀dXX")]
fn render_truncates_away_from_0x0(#[case] alignment: Alignment, #[case] expected: &str) {
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).alignment(alignment);
// Fill buffer with stuff to ensure the output is indeed padded
let mut buf = Buffer::filled(Rect::new(0, 0, 10, 1), Cell::default().set_symbol("X"));
let area = Rect::new(2, 0, 6, 1);
line.render_ref(area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
/// When two spans are rendered after each other the first needs to be padded in accordance
/// to the skipped unicode width. In this case the first crab does not fit at width 6 which
/// takes a front white space.
#[rstest]
#[case::right_4(4, "c🦀d")]
#[case::right_5(5, "bc🦀d")]
#[case::right_6(6, "Xbc🦀d")]
#[case::right_7(7, "🦀bc🦀d")]
#[case::right_8(8, "a🦀bc🦀d")]
fn render_right_aligned_multi_span(#[case] buf_width: u16, #[case] expected: &str) {
let line = Line::from(vec![Span::raw("a🦀b"), Span::raw("c🦀d")]).right_aligned();
let area = Rect::new(0, 0, buf_width, 1);
// Fill buffer with stuff to ensure the output is indeed padded
let mut buf = Buffer::filled(area, Cell::default().set_symbol("X"));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
///
/// Flag emoji are actually two independent characters, so they can be truncated in the
/// middle of the emoji. This test documents just the emoji part of the test.
#[test]
fn flag_emoji() {
let str = "🇺🇸1234";
assert_eq!(str.len(), 12); // flag is 4 bytes
assert_eq!(str.chars().count(), 6); // flag is 2 chars
assert_eq!(str.graphemes(true).count(), 5); // flag is 1 grapheme
assert_eq!(str.width(), 6); // flag is 2 display width
}
/// Part of a regression test for <https://github.com/ratatui-org/ratatui/issues/1032> which
/// found panics with truncating lines that contained multi-byte characters.
#[rstest]
#[case::flag_1(1, " ")]
#[case::flag_2(2, "🇺🇸")]
#[case::flag_3(3, "🇺🇸1")]
#[case::flag_4(4, "🇺🇸12")]
#[case::flag_5(5, "🇺🇸123")]
#[case::flag_6(6, "🇺🇸1234")]
#[case::flag_7(7, "🇺🇸1234 ")]
fn render_truncates_flag(#[case] buf_width: u16, #[case] expected: &str) {
let line = Line::from("🇺🇸1234");
let mut buf = Buffer::empty(Rect::new(0, 0, buf_width, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
// Buffer width is `u16`. A line can be longer.
#[rstest]
#[case::left(Alignment::Left, "This is some content with a some")]
#[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
fn render_truncates_very_long_line_of_many_spans(
#[case] alignment: Alignment,
#[case] expected: &str,
) {
let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
let min_width = usize::from(u16::MAX).saturating_add(1);
// width == len as only ASCII is used here
let factor = min_width.div_ceil(part.len());
let line = Line::from(vec![Span::raw(part); factor]).alignment(alignment);
dbg!(line.width());
assert!(line.width() >= min_width);
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
// Buffer width is `u16`. A single span inside a line can be longer.
#[rstest]
#[case::left(Alignment::Left, "This is some content with a some")]
#[case::right(Alignment::Right, "horribly long Line over u16::MAX")]
fn render_truncates_very_long_single_span_line(
#[case] alignment: Alignment,
#[case] expected: &str,
) {
let part = "This is some content with a somewhat long width to be repeated over and over again to create horribly long Line over u16::MAX";
let min_width = usize::from(u16::MAX).saturating_add(1);
// width == len as only ASCII is used here
let factor = min_width.div_ceil(part.len());
let line = Line::from(vec![Span::raw(part.repeat(factor))]).alignment(alignment);
dbg!(line.width());
assert!(line.width() >= min_width);
let mut buf = Buffer::empty(Rect::new(0, 0, 32, 1));
line.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([expected]));
}
}

View file

@ -1,7 +1,4 @@
use std::{
borrow::Cow,
fmt::{self, Debug, Display},
};
use std::{borrow::Cow, fmt};
use super::Text;
@ -19,7 +16,7 @@ use super::Text;
/// let password = Masked::new("12345", 'x');
///
/// Paragraph::new(password).render(buffer.area, &mut buffer);
/// assert_eq!(buffer, Buffer::with_lines(vec!["xxxxx"]));
/// assert_eq!(buffer, Buffer::with_lines(["xxxxx"]));
/// ```
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Masked<'a> {
@ -46,17 +43,18 @@ impl<'a> Masked<'a> {
}
}
impl Debug for Masked<'_> {
impl fmt::Debug for Masked<'_> {
/// Debug representation of a masked string is the underlying string
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.inner, f)
// note that calling display instead of Debug here is intentional
fmt::Display::fmt(&self.inner, f)
}
}
impl Display for Masked<'_> {
impl fmt::Display for Masked<'_> {
/// Display representation of a masked string is the masked string
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.value(), f)
fmt::Display::fmt(&self.value(), f)
}
}
@ -112,12 +110,14 @@ mod tests {
fn debug() {
let masked = Masked::new("12345", 'x');
assert_eq!(format!("{masked:?}"), "12345");
assert_eq!(format!("{masked:.3?}"), "123", "Debug truncates");
}
#[test]
fn display() {
let masked = Masked::new("12345", 'x');
assert_eq!(format!("{masked}"), "xxxxx");
assert_eq!(format!("{masked:.3}"), "xxx", "Display truncates");
}
#[test]

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Debug};
use std::{borrow::Cow, fmt};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
@ -395,9 +395,9 @@ impl WidgetRef for Span<'_> {
}
}
impl std::fmt::Display for Span<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.content)
impl fmt::Display for Span<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.content, f)
}
}
@ -529,15 +529,15 @@ mod tests {
#[test]
fn display_span() {
let span = Span::raw("test content");
assert_eq!(format!("{span}"), "test content");
assert_eq!(format!("{span:.4}"), "test");
}
#[test]
fn display_styled_span() {
let stylized_span = Span::styled("stylized test content", Style::new().green());
assert_eq!(format!("{stylized_span}"), "stylized test content");
assert_eq!(format!("{stylized_span:.8}"), "stylized");
}
#[test]
@ -565,7 +565,6 @@ mod tests {
use rstest::rstest;
use super::*;
use crate::assert_buffer_eq;
#[test]
fn render() {
@ -573,12 +572,11 @@ mod tests {
let span = Span::styled("test content", style);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
span.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![Line::from(vec![
let expected = Buffer::with_lines([Line::from(vec![
"test content".green().on_yellow(),
" ".into(),
])]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[rstest]
@ -598,10 +596,11 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
span.render(Rect::new(0, 0, 5, 1), &mut buf);
let mut expected = Buffer::with_lines(vec![Line::from("test ")]);
expected.set_style(Rect::new(0, 0, 5, 1), (Color::Green, Color::Yellow));
assert_buffer_eq!(buf, expected);
let expected = Buffer::with_lines([Line::from(vec![
"test ".green().on_yellow(),
" ".into(),
])]);
assert_eq!(buf, expected);
}
/// When there is already a style set on the buffer, the style of the span should be
@ -613,12 +612,11 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
buf.set_style(buf.area, Style::new().italic());
span.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![Line::from(vec![
let expected = Buffer::with_lines([Line::from(vec![
"test content".green().on_yellow().italic(),
" ".italic(),
])]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
/// When the span contains a multi-width grapheme, the grapheme will ensure that the cells
@ -629,12 +627,11 @@ mod tests {
let span = Span::styled("test 😃 content", style);
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
span.render(buf.area, &mut buf);
// The existing code in buffer.set_line() handles multi-width graphemes by clearing the
// cells of the hidden characters. This test ensures that the existing behavior is
// preserved.
let expected = Buffer::with_lines(vec!["test 😃 content".green().on_yellow()]);
assert_buffer_eq!(buf, expected);
let expected = Buffer::with_lines(["test 😃 content".green().on_yellow()]);
assert_eq!(buf, expected);
}
/// When the span contains a multi-width grapheme that does not fit in the area passed to
@ -647,11 +644,9 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
span.render(buf.area, &mut buf);
let expected = Buffer::with_lines(vec![Line::from(vec![
"test ".green().on_yellow(),
" ".into(),
])]);
assert_buffer_eq!(buf, expected);
let expected =
Buffer::with_lines([Line::from(vec!["test ".green().on_yellow(), " ".into()])]);
assert_eq!(buf, expected);
}
/// When the area passed to render overflows the buffer, the content should be truncated
@ -663,11 +658,11 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
span.render(Rect::new(10, 0, 20, 1), &mut buf);
let expected = Buffer::with_lines(vec![Line::from(vec![
let expected = Buffer::with_lines([Line::from(vec![
" ".into(),
"test ".green().on_yellow(),
])]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
}
}

View file

@ -1,5 +1,5 @@
#![warn(missing_docs)]
use std::borrow::Cow;
use std::{borrow::Cow, fmt};
use itertools::{Itertools, Position};
@ -580,8 +580,8 @@ where
}
}
impl std::fmt::Display for Text<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for Text<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (position, line) in self.iter().with_position() {
if position == Position::Last {
write!(f, "{line}")?;
@ -962,19 +962,14 @@ mod tests {
mod widget {
use super::*;
use crate::assert_buffer_eq;
#[test]
fn render() {
let text = Text::from("foo");
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
let expected_buf = Buffer::with_lines(vec!["foo "]);
assert_buffer_eq!(buf, expected_buf);
assert_eq!(buf, Buffer::with_lines(["foo "]));
}
#[rstest]
@ -987,40 +982,28 @@ mod tests {
#[test]
fn render_right_aligned() {
let text = Text::from("foo").alignment(Alignment::Right);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
let expected_buf = Buffer::with_lines(vec![" foo"]);
assert_buffer_eq!(buf, expected_buf);
assert_eq!(buf, Buffer::with_lines([" foo"]));
}
#[test]
fn render_centered_odd() {
let text = Text::from("foo").alignment(Alignment::Center);
let area = Rect::new(0, 0, 5, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
let expected_buf = Buffer::with_lines(vec![" foo "]);
assert_buffer_eq!(buf, expected_buf);
assert_eq!(buf, Buffer::with_lines([" foo "]));
}
#[test]
fn render_centered_even() {
let text = Text::from("foo").alignment(Alignment::Center);
let area = Rect::new(0, 0, 6, 1);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
let expected_buf = Buffer::with_lines(vec![" foo "]);
assert_buffer_eq!(buf, expected_buf);
assert_eq!(buf, Buffer::with_lines([" foo "]));
}
#[test]
@ -1030,14 +1013,10 @@ mod tests {
Line::from("bar").alignment(Alignment::Center),
])
.alignment(Alignment::Right);
let area = Rect::new(0, 0, 5, 2);
let mut buf = Buffer::empty(area);
text.render(area, &mut buf);
let expected_buf = Buffer::with_lines(vec![" foo", " bar "]);
assert_buffer_eq!(buf, expected_buf);
assert_eq!(buf, Buffer::with_lines([" foo", " bar "]));
}
#[test]
@ -1046,10 +1025,9 @@ mod tests {
let mut buf = Buffer::empty(area);
Text::from("foo".on_blue()).render(area, &mut buf);
let mut expected = Buffer::with_lines(vec!["foo "]);
let mut expected = Buffer::with_lines(["foo "]);
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1057,10 +1035,9 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
Text::from("foobar".on_blue()).render(Rect::new(0, 0, 3, 1), &mut buf);
let mut expected = Buffer::with_lines(vec!["foo "]);
let mut expected = Buffer::with_lines(["foo "]);
expected.set_style(Rect::new(0, 0, 3, 1), Style::new().bg(Color::Blue));
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
}

View file

@ -248,6 +248,7 @@ pub trait StatefulWidget {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Greeting;
@ -294,6 +295,7 @@ pub trait StatefulWidget {
/// widget.render_ref(area, buf);
/// }
/// # }
/// # }
/// ```
#[stability::unstable(feature = "widget-ref")]
pub trait WidgetRef {
@ -321,6 +323,7 @@ impl<W: WidgetRef> Widget for &W {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct Parent {
@ -340,6 +343,7 @@ impl<W: WidgetRef> Widget for &W {
/// self.child.render_ref(area, buf);
/// }
/// }
/// # }
/// ```
impl<W: WidgetRef> WidgetRef for Option<W> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
@ -368,6 +372,7 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "unstable-widget-ref")] {
/// use ratatui::{prelude::*, widgets::*};
///
/// struct PersonalGreeting;
@ -386,10 +391,11 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
/// }
/// }
///
/// # fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// fn render(area: Rect, buf: &mut Buffer) {
/// let widget = PersonalGreeting;
/// let mut state = "world".to_string();
/// widget.render(area, buf, &mut state);
/// }
/// # }
/// ```
#[stability::unstable(feature = "widget-ref")]
@ -470,90 +476,79 @@ mod tests {
use super::*;
use crate::prelude::*;
struct Greeting;
struct Farewell;
struct PersonalGreeting;
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
impl Widget for Farewell {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_ref(area, buf);
}
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.render_ref(area, buf, state);
}
}
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[fixture]
fn buf() -> Buffer {
Buffer::empty(Rect::new(0, 0, 20, 1))
}
#[rstest]
fn widget_render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
mod widget {
use super::*;
#[rstest]
fn widget_ref_render(mut buf: Buffer) {
let widget = Greeting;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
struct Greeting;
/// This test is to ensure that the blanket implementation of `Widget` for `&W` where `W`
/// implements `WidgetRef` works as expected.
#[rstest]
fn widget_blanket_render(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn widget_box_render_ref(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn widget_vec_box_render(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
impl Widget for Greeting {
fn render(self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer) {
let widget = Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
}
mod widget_ref {
use super::*;
struct Greeting;
struct Farewell;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
impl WidgetRef for Farewell {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Goodbye").right_aligned().render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer) {
let widget = Greeting;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
/// Ensure that the blanket implementation of `Widget` for `&W` where `W` implements
/// `WidgetRef` works as expected.
#[rstest]
fn blanket_render(mut buf: Buffer) {
let widget = &Greeting;
widget.render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn box_render_ref(mut buf: Buffer) {
let widget: Box<dyn WidgetRef> = Box::new(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn vec_box_render(mut buf: Buffer) {
let widgets: Vec<Box<dyn WidgetRef>> = vec![Box::new(Greeting), Box::new(Farewell)];
for widget in widgets {
widget.render_ref(buf.area, &mut buf);
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"]));
}
#[fixture]
@ -561,98 +556,143 @@ mod tests {
"world".to_string()
}
#[rstest]
fn stateful_widget_render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
mod stateful_widget {
use super::*;
struct PersonalGreeting;
impl StatefulWidget for PersonalGreeting {
type State = String;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}
#[rstest]
fn stateful_widget_ref_render(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
mod stateful_widget_ref {
use super::*;
struct PersonalGreeting;
impl StatefulWidgetRef for PersonalGreeting {
type State = String;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
Line::from(format!("Hello {state}")).render(area, buf);
}
}
#[rstest]
fn render_ref(mut buf: Buffer, mut state: String) {
let widget = PersonalGreeting;
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W
// where W implements StatefulWidgetRef is added. (see the comment in the blanket
// implementation for more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W`
// where /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
#[rstest]
fn box_render_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render_ref(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
}
}
// Note this cannot be tested until the blanket implementation of StatefulWidget for &W where W
// implements StatefulWidgetRef is added. (see the comment in the blanket implementation for
// more).
// /// This test is to ensure that the blanket implementation of `StatefulWidget` for `&W` where
// /// `W` implements `StatefulWidgetRef` works as expected.
// #[rstest]
// fn stateful_widget_blanket_render(mut buf: Buffer, mut state: String) {
// let widget = &PersonalGreeting;
// widget.render(buf.area, &mut buf, &mut state);
// assert_eq!(buf, Buffer::with_lines(["Hello world "]));
// }
mod option_widget_ref {
use super::*;
#[rstest]
fn stateful_widget_box_render(mut buf: Buffer, mut state: String) {
let widget = Box::new(PersonalGreeting);
widget.render(buf.area, &mut buf, &mut state);
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
struct Greeting;
impl WidgetRef for Greeting {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
Line::from("Hello").render(area, buf);
}
}
#[rstest]
fn render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
}
#[rstest]
fn render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
}
#[rstest]
fn widget_option_render_ref_some(mut buf: Buffer) {
let widget = Some(Greeting);
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["Hello "]));
mod str {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}
#[rstest]
fn widget_option_render_ref_none(mut buf: Buffer) {
let widget: Option<Greeting> = None;
widget.render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines([" "]));
}
mod string {
use super::*;
#[rstest]
fn render(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_render(mut buf: Buffer) {
"hello world".render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn render_ref(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_render_ref(mut buf: Buffer) {
"hello world".render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn option_render(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_option_render(mut buf: Buffer) {
Some("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn str_option_render_ref(mut buf: Buffer) {
Some("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_render(mut buf: Buffer) {
String::from("hello world").render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_render_ref(mut buf: Buffer) {
String::from("hello world").render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_option_render(mut buf: Buffer) {
Some(String::from("hello world")).render(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
#[rstest]
fn string_option_render_ref(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]),);
#[rstest]
fn option_render_ref(mut buf: Buffer) {
Some(String::from("hello world")).render_ref(buf.area, &mut buf);
assert_eq!(buf, Buffer::with_lines(["hello world "]));
}
}
}

View file

@ -615,34 +615,33 @@ mod tests {
use itertools::iproduct;
use super::*;
use crate::{assert_buffer_eq, widgets::BorderType};
use crate::widgets::BorderType;
#[test]
fn default() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default();
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" "; 3]));
assert_eq!(buffer, Buffer::with_lines([" "; 3]));
}
#[test]
fn data() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default().data(&[("foo", 1), ("bar", 2)]);
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"",
"1 2 ",
"f b ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
"1 2 ",
"f b ",
]);
assert_eq!(buffer, expected);
}
#[test]
fn block() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 5));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 5));
let block = Block::bordered()
.border_type(BorderType::Double)
.title("Block");
@ -650,112 +649,106 @@ mod tests {
.data(&[("foo", 1), ("bar", 2)])
.block(block);
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"╔Block════════╗",
"║ █ ║",
"║1 2 ║",
"║f b ║",
"╚═════════════╝",
])
);
let expected = Buffer::with_lines([
"╔Block═══╗",
"║ █ ║",
"║1 2 ║",
"║f b ║",
"╚════════╝",
]);
assert_eq!(buffer, expected);
}
#[test]
fn max() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let without_max = BarChart::default().data(&[("foo", 1), ("bar", 2), ("baz", 100)]);
without_max.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"",
"",
"f b b ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
"",
"f b b ",
]);
assert_eq!(buffer, expected);
let with_max = BarChart::default()
.data(&[("foo", 1), ("bar", 2), ("baz", 100)])
.max(2);
with_max.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" █ █ ",
"1 2 █ ",
"f b b ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
" █ █ ",
"1 2 █ ",
"f b b ",
]);
assert_eq!(buffer, expected);
}
#[test]
fn bar_style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.bar_style(Style::new().red());
widget.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
"",
"1 2 ",
"f b ",
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"",
"1 2 ",
"f b ",
]);
for (x, y) in iproduct!([0, 2], [0, 1]) {
expected.get_mut(x, y).set_fg(Color::Red);
}
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
fn bar_width() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.bar_width(3);
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ███ ",
"█1█ █2█ ",
"foo bar ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
" ███ ",
"█1█ █2█ ",
"foo bar ",
]);
assert_eq!(buffer, expected);
}
#[test]
fn bar_gap() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.bar_gap(2);
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"",
"1 2 ",
"f b ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
"1 2 ",
"f b ",
]);
assert_eq!(buffer, expected);
}
#[test]
fn bar_set() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 0), ("bar", 1), ("baz", 3)])
.bar_set(symbols::bar::THREE_LEVELS);
widget.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"",
" ▄ 3 ",
"f b b ",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
" ▄ 3 ",
"f b b ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -775,67 +768,68 @@ mod tests {
])
.bar_set(symbols::bar::NINE_LEVELS);
widget.render(Rect::new(0, 1, 18, 2), &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
"a b c d e f g h i ",
])
);
let expected = Buffer::with_lines([
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8 ",
"a b c d e f g h i ",
]);
assert_eq!(buffer, expected);
}
#[test]
fn value_style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.bar_width(3)
.value_style(Style::new().red());
widget.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
" ███ ",
"█1█ █2█ ",
"foo bar ",
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
" ███ ",
"█1█ █2█ ",
"foo bar ",
]);
expected.get_mut(1, 1).set_fg(Color::Red);
expected.get_mut(5, 1).set_fg(Color::Red);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
fn label_style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.label_style(Style::new().red());
widget.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
"",
"1 2 ",
"f b ",
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"",
"1 2 ",
"f b ",
]);
expected.get_mut(0, 2).set_fg(Color::Red);
expected.get_mut(2, 2).set_fg(Color::Red);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
fn style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
let widget = BarChart::default()
.data(&[("foo", 1), ("bar", 2)])
.style(Style::new().red());
widget.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
"",
"1 2 ",
"f b ",
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"",
"1 2 ",
"f b ",
]);
for (x, y) in iproduct!(0..15, 0..3) {
for (x, y) in iproduct!(0..10, 0..3) {
expected.get_mut(x, y).set_fg(Color::Red);
}
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -861,8 +855,13 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec!["", "1 2", "G "]);
assert_buffer_eq!(buffer, expected);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
"1 2",
"G ",
]);
assert_eq!(buffer, expected);
}
fn build_test_barchart<'a>() -> BarChart<'a> {
@ -888,7 +887,7 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 8));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"2█ ",
"3██ ",
"4███ ",
@ -898,8 +897,7 @@ mod tests {
"5████",
"G2 ",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -908,7 +906,7 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 7));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"2█ ",
"3██ ",
"4███ ",
@ -917,8 +915,7 @@ mod tests {
"4███ ",
"5████",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -927,9 +924,15 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 5));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec!["2█ ", "3██ ", "4███ ", "G1 ", "3██ "]);
assert_buffer_eq!(buffer, expected);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"2█ ",
"3██ ",
"4███ ",
"G1 ",
"3██ ",
]);
assert_eq!(buffer, expected);
}
fn test_horizontal_bars_label_width_greater_than_bar(bar_color: Option<Color>) {
@ -952,7 +955,7 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 2));
chart.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec!["label", "5████"]);
let mut expected = Buffer::with_lines(["label", "5████"]);
// first line has a yellow foreground. first cell contains italic "5"
expected.get_mut(0, 1).modifier.insert(Modifier::ITALIC);
@ -977,7 +980,7 @@ mod tests {
expected.get_mut(3, 0).set_fg(expected_color);
expected.get_mut(4, 0).set_fg(expected_color);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -1000,9 +1003,13 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec!["Jan 10█ ", "Feb 20████", "Mar 5 "]);
assert_buffer_eq!(buffer, expected);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Jan 10█ ",
"Feb 20████",
"Mar 5 ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1023,13 +1030,13 @@ mod tests {
// G1 should have the bold red style
// bold: because of BarChart::label_style
// red: is included with the label itself
let mut expected = Buffer::with_lines(vec!["2████", "G1 "]);
let mut expected = Buffer::with_lines(["2████", "G1 "]);
let cell = expected.get_mut(0, 1).set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
let cell = expected.get_mut(1, 1).set_fg(Color::Red);
cell.modifier.insert(Modifier::BOLD);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -1046,17 +1053,14 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ▂ █ ▂",
" ▄ █ █ ▄ █",
"▆ 2 3 4 ▆ 2 3",
"a b c c a b c",
" G1 G2 ",
])
);
let expected = Buffer::with_lines([
" ▂ █ ▂",
" ▄ █ █ ▄ █",
"▆ 2 3 4 ▆ 2 3",
"a b c c a b c",
" G1 G2 ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1069,9 +1073,13 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 3, 3));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec!["", "▆ 5", " G"]);
assert_buffer_eq!(buffer, expected);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"",
"▆ 5",
" G",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1094,15 +1102,14 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 5));
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ▆▆▆ ███",
" ███ ███",
"▃▃▃ ███ ███",
"写█ 写█ 写█",
"B1 B2 B2 ",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -1114,7 +1121,7 @@ mod tests {
.bar_gap(0);
let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 10));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
assert_eq!(buffer, Buffer::empty(Rect::new(0, 0, 0, 10)));
}
#[test]
@ -1139,8 +1146,7 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 1));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
assert_eq!(buffer, Buffer::with_lines([" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8"]));
}
#[test]
@ -1165,15 +1171,12 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
"a b c d e f g h i",
])
);
let expected = Buffer::with_lines([
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
"a b c d e f g h i",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1198,15 +1201,12 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
"a b c d e f g h i",
" Group ",
])
);
let expected = Buffer::with_lines([
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
"a b c d e f g h i",
" Group ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1231,15 +1231,12 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 26, 3));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" 1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
"a b c d e f g h i ",
" Group ",
])
);
let expected = Buffer::with_lines([
" 1▁ 2▂ 3▃ 4▄ 5▅ 6▆ 7▇ 8█",
"a b c d e f g h i ",
" Group ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1264,16 +1261,13 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 4));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ▂ ▄ ▆ █",
" ▂ ▄ ▆ 4 5 6 7 8",
"a b c d e f g h i",
" Group ",
])
);
let expected = Buffer::with_lines([
" ▂ ▄ ▆ █",
" ▂ ▄ ▆ 4 5 6 7 8",
"a b c d e f g h i",
" Group ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1296,15 +1290,12 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 17, 3));
chart.render(Rect::new(0, 1, buffer.area.width, 2), &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
" Group ",
])
);
let expected = Buffer::with_lines([
" ",
" ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8",
" Group ",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1315,13 +1306,9 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 59, 1));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █",
])
);
let expected =
Buffer::with_lines([" ▁ ▁ ▁ ▁ ▂ ▂ ▂ ▃ ▃ ▃ ▃ ▄ ▄ ▄ ▄ ▅ ▅ ▅ ▆ ▆ ▆ ▆ ▇ ▇ ▇ █"]);
assert_eq!(buffer, expected);
}
#[test]
@ -1333,17 +1320,14 @@ mod tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 7, 6));
chart.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ██ ",
" ██ ",
"▄▄ ██ ",
"██ ██ ",
"1█ 2█ ",
"a b ",
])
);
let expected = Buffer::with_lines([
" ██ ",
" ██ ",
"▄▄ ██ ",
"██ ██ ",
"1█ 2█ ",
"a b ",
]);
assert_eq!(buffer, expected);
}
}

View file

@ -860,10 +860,10 @@ impl<'a> Styled for Block<'a> {
#[cfg(test)]
mod tests {
use rstest::rstest;
use strum::ParseError;
use super::*;
use crate::assert_buffer_eq;
#[test]
fn create_with_all_borders() {
@ -871,179 +871,65 @@ mod tests {
assert_eq!(block.borders, Borders::all());
}
#[allow(clippy::too_many_lines)]
#[test]
fn inner_takes_into_account_the_borders() {
// No borders
assert_eq!(
Block::new().inner(Rect::default()),
Rect::new(0, 0, 0, 0),
"no borders, width=0, height=0"
);
assert_eq!(
Block::new().inner(Rect::new(0, 0, 1, 1)),
Rect::new(0, 0, 1, 1),
"no borders, width=1, height=1"
);
// Left border
assert_eq!(
Block::new()
.borders(Borders::LEFT)
.inner(Rect::new(0, 0, 0, 1)),
Rect::new(0, 0, 0, 1),
"left, width=0"
);
assert_eq!(
Block::new()
.borders(Borders::LEFT)
.inner(Rect::new(0, 0, 1, 1)),
Rect::new(1, 0, 0, 1),
"left, width=1"
);
assert_eq!(
Block::new()
.borders(Borders::LEFT)
.inner(Rect::new(0, 0, 2, 1)),
Rect::new(1, 0, 1, 1),
"left, width=2"
);
// Top border
assert_eq!(
Block::new()
.borders(Borders::TOP)
.inner(Rect::new(0, 0, 1, 0)),
Rect::new(0, 0, 1, 0),
"top, height=0"
);
assert_eq!(
Block::new()
.borders(Borders::TOP)
.inner(Rect::new(0, 0, 1, 1)),
Rect::new(0, 1, 1, 0),
"top, height=1"
);
assert_eq!(
Block::new()
.borders(Borders::TOP)
.inner(Rect::new(0, 0, 1, 2)),
Rect::new(0, 1, 1, 1),
"top, height=2"
);
// Right border
assert_eq!(
Block::new()
.borders(Borders::RIGHT)
.inner(Rect::new(0, 0, 0, 1)),
Rect::new(0, 0, 0, 1),
"right, width=0"
);
assert_eq!(
Block::new()
.borders(Borders::RIGHT)
.inner(Rect::new(0, 0, 1, 1)),
Rect::new(0, 0, 0, 1),
"right, width=1"
);
assert_eq!(
Block::new()
.borders(Borders::RIGHT)
.inner(Rect::new(0, 0, 2, 1)),
Rect::new(0, 0, 1, 1),
"right, width=2"
);
// Bottom border
assert_eq!(
Block::new()
.borders(Borders::BOTTOM)
.inner(Rect::new(0, 0, 1, 0)),
Rect::new(0, 0, 1, 0),
"bottom, height=0"
);
assert_eq!(
Block::new()
.borders(Borders::BOTTOM)
.inner(Rect::new(0, 0, 1, 1)),
Rect::new(0, 0, 1, 0),
"bottom, height=1"
);
assert_eq!(
Block::new()
.borders(Borders::BOTTOM)
.inner(Rect::new(0, 0, 1, 2)),
Rect::new(0, 0, 1, 1),
"bottom, height=2"
);
// All borders
assert_eq!(
Block::bordered().inner(Rect::default()),
Rect::new(0, 0, 0, 0),
"all borders, width=0, height=0"
);
assert_eq!(
Block::bordered().inner(Rect::new(0, 0, 1, 1)),
Rect::new(1, 1, 0, 0),
"all borders, width=1, height=1"
);
assert_eq!(
Block::bordered().inner(Rect::new(0, 0, 2, 2)),
Rect::new(1, 1, 0, 0),
"all borders, width=2, height=2"
);
assert_eq!(
Block::bordered().inner(Rect::new(0, 0, 3, 3)),
Rect::new(1, 1, 1, 1),
"all borders, width=3, height=3"
);
#[rstest]
#[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
#[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
#[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
#[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
#[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
#[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
#[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
#[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
#[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
#[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
#[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
#[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
#[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
#[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
#[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
#[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
#[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
#[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
#[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
#[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
#[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
#[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
fn inner_takes_into_account_the_borders(
#[case] borders: Borders,
#[case] area: Rect,
#[case] expected: Rect,
) {
let block = Block::new().borders(borders);
assert_eq!(block.inner(area), expected);
}
#[test]
fn inner_takes_into_account_the_title() {
assert_eq!(
Block::new().title("Test").inner(Rect::new(0, 0, 0, 1)),
Rect::new(0, 1, 0, 0),
);
assert_eq!(
Block::new()
.title(Title::from("Test").alignment(Alignment::Center))
.inner(Rect::new(0, 0, 0, 1)),
Rect::new(0, 1, 0, 0),
);
assert_eq!(
Block::new()
.title(Title::from("Test").alignment(Alignment::Right))
.inner(Rect::new(0, 0, 0, 1)),
Rect::new(0, 1, 0, 0),
);
#[rstest]
#[case::left(Alignment::Left)]
#[case::center(Alignment::Center)]
#[case::right(Alignment::Right)]
fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
let area = Rect::new(0, 0, 0, 1);
let expected = Rect::new(0, 1, 0, 0);
let block = Block::new().title(Title::from("Test").alignment(alignment));
assert_eq!(block.inner(area), expected);
}
#[test]
fn inner_takes_into_account_border_and_title() {
let test_rect = Rect::new(0, 0, 0, 2);
let top_top = Block::new()
.borders(Borders::TOP)
.title(Title::from("Test").position(Position::Top));
assert_eq!(top_top.inner(test_rect), Rect::new(0, 1, 0, 1));
let top_bot = Block::new()
.borders(Borders::BOTTOM)
.title(Title::from("Test").position(Position::Top));
assert_eq!(top_bot.inner(test_rect), Rect::new(0, 1, 0, 0));
let bot_top = Block::new()
.borders(Borders::TOP)
.title(Title::from("Test").position(Position::Bottom));
assert_eq!(bot_top.inner(test_rect), Rect::new(0, 1, 0, 0));
let bot_bot = Block::new()
.borders(Borders::BOTTOM)
.title(Title::from("Test").position(Position::Bottom));
assert_eq!(bot_bot.inner(test_rect), Rect::new(0, 0, 0, 1));
#[rstest]
#[case::top_top(Borders::TOP, Position::Top, Rect::new(0, 1, 0, 1))]
#[case::top_bot(Borders::BOTTOM, Position::Top, Rect::new(0, 1, 0, 0))]
#[case::bot_top(Borders::TOP, Position::Bottom, Rect::new(0, 1, 0, 0))]
#[case::top_top(Borders::BOTTOM, Position::Bottom, Rect::new(0, 0, 0, 1))]
fn inner_takes_into_account_border_and_title(
#[case] borders: Borders,
#[case] position: Position,
#[case] expected: Rect,
) {
let area = Rect::new(0, 0, 0, 2);
let block = Block::new()
.borders(borders)
.title(Title::from("Test").position(position));
assert_eq!(block.inner(area), expected);
}
#[test]
@ -1191,7 +1077,7 @@ mod tests {
fn title() {
use Alignment::*;
use Position::*;
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
Block::bordered()
.title(Title::from("A").position(Top).alignment(Left))
.title(Title::from("B").position(Top).alignment(Center))
@ -1200,19 +1086,18 @@ mod tests {
.title(Title::from("E").position(Bottom).alignment(Center))
.title(Title::from("F").position(Bottom).alignment(Right))
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┌A─────B─────C┐",
"│ │",
"└D─────E─────F┘",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌A───B───C┐",
"│ │",
"└D───E───F┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn title_top_bottom() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
Block::bordered()
.title_top(Line::raw("A").left_aligned())
.title_top(Line::raw("B").centered())
@ -1221,14 +1106,13 @@ mod tests {
.title_bottom(Line::raw("E").centered())
.title_bottom(Line::raw("F").right_aligned())
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┌A─────B─────C┐",
"│ │",
"└D─────E─────F┘",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌A───B───C┐",
"│ │",
"└D───E───F┘",
]);
assert_eq!(buffer, expected);
}
#[test]
@ -1244,7 +1128,7 @@ mod tests {
.title_alignment(alignment)
.title("test")
.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![expected]));
assert_eq!(buffer, Buffer::with_lines([expected]));
}
}
@ -1261,7 +1145,7 @@ mod tests {
.title_alignment(block_title_alignment)
.title(Title::from("test").alignment(alignment))
.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![expected]));
assert_eq!(buffer, Buffer::with_lines([expected]));
}
}
@ -1273,14 +1157,7 @@ mod tests {
.title_alignment(Alignment::Right)
.title("")
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ",
" ",
])
);
assert_eq!(buffer, Buffer::with_lines([" "; 3]));
}
#[test]
@ -1290,7 +1167,7 @@ mod tests {
.title_position(Position::Bottom)
.title("test")
.render(buffer.area, &mut buffer);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ", "test"]));
assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
}
#[test]
@ -1301,11 +1178,7 @@ mod tests {
.title_alignment(alignment)
.title("test".yellow())
.render(buffer.area, &mut buffer);
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow());
assert_buffer_eq!(buffer, expected_buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
}
}
@ -1318,11 +1191,7 @@ mod tests {
.title_style(Style::new().yellow())
.title("test")
.render(buffer.area, &mut buffer);
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow());
assert_buffer_eq!(buffer, expected_buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
}
}
@ -1335,31 +1204,26 @@ mod tests {
.title_style(Style::new().green().on_red())
.title("test".yellow())
.render(buffer.area, &mut buffer);
let mut expected_buffer = Buffer::with_lines(vec!["test"]);
expected_buffer.set_style(Rect::new(0, 0, 4, 1), Style::new().yellow().on_red());
assert_buffer_eq!(buffer, expected_buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
}
}
#[test]
fn title_border_style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.title("test")
.border_style(Style::new().yellow())
.render(buffer.area, &mut buffer);
let mut expected_buffer = Buffer::with_lines(vec![
"┌test─────────",
" ",
"└─────────────",
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"┌test────",
"",
"└────────",
]);
expected_buffer.set_style(Rect::new(0, 0, 15, 3), Style::new().yellow());
expected_buffer.set_style(Rect::new(1, 1, 13, 1), Style::reset());
assert_buffer_eq!(buffer, expected_buffer);
expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
assert_eq!(buffer, expected);
}
#[test]
@ -1381,103 +1245,97 @@ mod tests {
#[test]
fn render_plain_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Plain)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┌─────────────┐",
"│ │",
"└─────────────┘"
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌────────┐",
"│ │",
"└────────┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_rounded_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Rounded)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"╭─────────────╮",
"│ │",
"╰─────────────╯"
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"╭────────╮",
"│ │",
"╰────────╯",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_double_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Double)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"╔═════════════╗",
"║ ║",
"╚═════════════╝"
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"╔════════╗",
"║ ║",
"╚════════╝",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_quadrant_inside() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::QuadrantInside)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"▗▄▄▄▄▄▄▄▄▄▄▄▄▄▖",
"▐ ▌",
"▝▀▀▀▀▀▀▀▀▀▀▀▀▀▘",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"▗▄▄▄▄▄▄▄▄▖",
"▐ ▌",
"▝▀▀▀▀▀▀▀▀▘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_border_quadrant_outside() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::QuadrantOutside)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"▛▀▀▀▀▀▀▀▀▀▀▀▀▀▜",
"▌ ▐",
"▙▄▄▄▄▄▄▄▄▄▄▄▄▄▟",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"▛▀▀▀▀▀▀▀▀▜",
"▌ ▐",
"▙▄▄▄▄▄▄▄▄▟",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_solid_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Thick)
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"┏━━━━━━━━━━━━━┓",
"┃ ┃",
"┗━━━━━━━━━━━━━┛"
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┏━━━━━━━━┓",
"┃ ┃",
"┗━━━━━━━━┛",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_custom_border_set() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_set(border::Set {
top_left: "1",
@ -1490,13 +1348,12 @@ mod tests {
horizontal_bottom: "B",
})
.render(buffer.area, &mut buffer);
assert_buffer_eq!(
buffer,
Buffer::with_lines(vec![
"1TTTTTTTTTTTTT2",
"L R",
"3BBBBBBBBBBBBB4",
])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"1TTTTTTTT2",
"L R",
"3BBBBBBBB4",
]);
assert_eq!(buffer, expected);
}
}

View file

@ -1,4 +1,4 @@
use std::fmt::{self, Debug};
use std::fmt;
use bitflags::bitflags;
@ -24,7 +24,7 @@ bitflags! {
/// Implement the `Debug` trait for the `Borders` bitflags. This is a manual implementation to
/// display the flags in a more readable way. The default implementation would display the
/// flags as 'Border(0x0)' for `Borders::NONE` for example.
impl Debug for Borders {
impl fmt::Debug for Borders {
/// Display the Borders bitflags as a list of names. For example, `Borders::NONE` will be
/// displayed as `NONE` and `Borders::ALL` will be displayed as `ALL`. If multiple flags are
/// set, they will be displayed separated by a pipe character.

View file

@ -19,7 +19,7 @@ mod points;
mod rectangle;
mod world;
use std::{fmt::Debug, iter::zip};
use std::{fmt, iter::zip};
use itertools::Itertools;
@ -70,7 +70,7 @@ struct Layer {
/// resolution of the grid might exceed the number of rows and columns. For example, a grid of
/// Braille patterns will have a resolution of 2x4 dots per cell. This means that a grid of 10x10
/// cells will have a resolution of 20x40 dots.
trait Grid: Debug {
trait Grid: fmt::Debug {
/// Get the resolution of the grid in number of dots.
///
/// This doesn't have to be the same as the number of rows and columns of the grid. For example,

View file

@ -58,7 +58,7 @@ mod tests {
.x_bounds([-10.0, 10.0])
.y_bounds([-10.0, 10.0]);
canvas.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ⢀⣠⢤⣀ ",
" ⢰⠋ ⠈⣇",
" ⠘⣆⡀ ⣠⠇",

View file

@ -112,154 +112,111 @@ fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: us
#[cfg(test)]
mod tests {
use super::Line;
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
use rstest::rstest;
#[allow(clippy::needless_pass_by_value)]
#[track_caller]
fn test(line: Line, expected_lines: Vec<&str>) {
use super::{super::*, *};
use crate::{buffer::Buffer, layout::Rect};
#[rstest]
#[case::off_grid(&Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red), [" "; 10])]
#[case::off_grid(&Line::new(0.0, 0.0, 11.0, 11.0, Color::Red), [" "; 10])]
#[case::horizontal(&Line::new(0.0, 0.0, 10.0, 0.0, Color::Red), [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
"••••••••••",
])]
#[case::horizontal(&Line::new(10.0, 10.0, 0.0, 10.0, Color::Red), [
"••••••••••",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
])]
#[case::vertical(&Line::new(0.0, 0.0, 0.0, 10.0, Color::Red), [""; 10])]
#[case::vertical(&Line::new(10.0, 10.0, 10.0, 0.0, Color::Red), [""; 10])]
// dy < dx, x1 < x2
#[case::diagonal(&Line::new(0.0, 0.0, 10.0, 5.0, Color::Red), [
" ",
" ",
" ",
" ",
"",
" •• ",
" •• ",
" •• ",
" •• ",
"",
])]
// dy < dx, x1 > x2
#[case::diagonal(&Line::new(10.0, 0.0, 0.0, 5.0, Color::Red), [
" ",
" ",
" ",
" ",
"",
" •• ",
" •• ",
" •• ",
" •• ",
"",
])]
// dy > dx, y1 < y2
#[case::diagonal(&Line::new(0.0, 0.0, 5.0, 10.0, Color::Red), [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
])]
// dy > dx, y1 > y2
#[case::diagonal(&Line::new(0.0, 10.0, 5.0, 0.0, Color::Red), [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
])]
fn tests<'expected_line, ExpectedLines>(#[case] line: &Line, #[case] expected: ExpectedLines)
where
ExpectedLines: IntoIterator,
ExpectedLines::Item: Into<crate::text::Line<'expected_line>>,
{
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
let canvas = Canvas::default()
.marker(Marker::Dot)
.x_bounds([0.0, 10.0])
.y_bounds([0.0, 10.0])
.paint(|context| {
context.draw(&line);
});
.paint(|context| context.draw(line));
canvas.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(expected_lines);
let mut expected = Buffer::with_lines(expected);
for cell in &mut expected.content {
if cell.symbol() == "" {
cell.set_style(Style::new().red());
}
}
assert_buffer_eq!(buffer, expected);
}
#[test]
fn off_grid() {
test(
Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red),
vec![" "; 10],
);
test(
Line::new(0.0, 0.0, 11.0, 11.0, Color::Red),
vec![" "; 10],
);
}
#[test]
fn horizontal() {
test(
Line::new(0.0, 0.0, 10.0, 0.0, Color::Red),
vec![
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
"••••••••••",
],
);
test(
Line::new(10.0, 10.0, 0.0, 10.0, Color::Red),
vec![
"••••••••••",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
],
);
}
#[test]
fn vertical() {
test(
Line::new(0.0, 0.0, 0.0, 10.0, Color::Red),
vec![""; 10],
);
test(
Line::new(10.0, 10.0, 10.0, 0.0, Color::Red),
vec![""; 10],
);
}
#[test]
fn diagonal() {
// dy < dx, x1 < x2
test(
Line::new(0.0, 0.0, 10.0, 5.0, Color::Red),
vec![
" ",
" ",
" ",
" ",
"",
" •• ",
" •• ",
" •• ",
" •• ",
"",
],
);
// dy < dx, x1 > x2
test(
Line::new(10.0, 0.0, 0.0, 5.0, Color::Red),
vec![
" ",
" ",
" ",
" ",
"",
" •• ",
" •• ",
" •• ",
" •• ",
"",
],
);
// dy > dx, y1 < y2
test(
Line::new(0.0, 0.0, 5.0, 10.0, Color::Red),
vec![
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
],
);
// dy > dx, y1 > y2
test(
Line::new(0.0, 10.0, 5.0, 0.0, Color::Red),
vec![
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
],
);
assert_eq!(buffer, expected);
}
}

View file

@ -65,7 +65,7 @@ mod tests {
use strum::ParseError;
use super::*;
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
use crate::{prelude::*, widgets::canvas::Canvas};
#[test]
fn map_resolution_to_string() {
@ -101,7 +101,7 @@ mod tests {
context.draw(&Map::default());
});
canvas.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ",
" ••••••• •• •• •• • ",
" •••••••••••••• ••• •••• ••• •• •••• ",
@ -143,7 +143,7 @@ mod tests {
"",
" ",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -160,7 +160,7 @@ mod tests {
});
});
canvas.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ",
" ⢀⣠⠤⠤⠤⠔⢤⣤⡄⠤⡠⣄⠢⠂⢢⠰⣠⡄⣀⡀ ⣀ ",
" ⢀⣀⡤⣦⠲⢶⣿⣮⣿⡉⣰⢶⢏⡂ ⢀⣟⠁ ⢺⣻⢿⠏ ⠈⠉⠁ ⢀⣀ ⠈⠓⢳⣢⣂⡀ ",
@ -202,6 +202,6 @@ mod tests {
"⠶⠔⠲⠤⠠⠜⢗⠤⠄ ⠘⠉ ⠁ ⠈⠉⠒⠔⠤",
" ",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
}

View file

@ -66,7 +66,7 @@ impl Shape for Rectangle {
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_buffer_eq, prelude::*, widgets::canvas::Canvas};
use crate::{prelude::*, widgets::canvas::Canvas};
#[test]
fn draw_block_lines() {
@ -85,7 +85,7 @@ mod tests {
});
});
canvas.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"██████████",
"█ █",
"█ █",
@ -99,7 +99,7 @@ mod tests {
]);
expected.set_style(buffer.area, Style::new().red());
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -119,7 +119,7 @@ mod tests {
});
});
canvas.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"█▀▀▀▀▀▀▀▀█",
"█ █",
"█ █",
@ -134,7 +134,7 @@ mod tests {
expected.set_style(buffer.area, Style::new().red().on_red());
expected.set_style(buffer.area.inner(&Margin::new(1, 0)), Style::reset().red());
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::reset());
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
@ -163,7 +163,7 @@ mod tests {
});
});
canvas.render(buffer.area, &mut buffer);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"⡏⠉⠉⠉⠉⠉⠉⠉⠉⢹",
"⡇⢠⠤⠤⠤⠤⠤⠤⡄⢸",
"⡇⢸ ⡇⢸",
@ -178,6 +178,6 @@ mod tests {
expected.set_style(buffer.area, Style::new().red());
expected.set_style(buffer.area.inner(&Margin::new(1, 1)), Style::new().green());
expected.set_style(buffer.area.inner(&Margin::new(2, 2)), Style::reset());
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
}

View file

@ -1111,10 +1111,10 @@ impl<'a> Styled for Chart<'a> {
#[cfg(test)]
mod tests {
use rstest::rstest;
use strum::ParseError;
use super::*;
use crate::assert_buffer_eq;
struct LegendTestCase {
chart_area: Rect,
@ -1209,7 +1209,6 @@ mod tests {
.x_axis(Axis::default().title("xxxxxxxxxxxxxxxx"));
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 4));
widget.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(vec![" ".repeat(8); 4]));
}
@ -1244,30 +1243,25 @@ mod tests {
let widget = Chart::new(vec![long_dataset_name, short_dataset])
.hidden_legend_constraints((100.into(), 100.into()));
let mut buffer = Buffer::empty(Rect::new(0, 0, 20, 5));
widget.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ┌──────────────┐",
" │Very long name│",
" │ Short name│",
" └──────────────┘",
" ",
]);
assert_buffer_eq!(buffer, expected);
assert_eq!(buffer, expected);
}
#[test]
fn test_chart_have_a_topleft_legend() {
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
.legend_position(Some(LegendPosition::TopLeft));
let area = Rect::new(0, 0, 30, 20);
let mut buffer = Buffer::empty(area);
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"┌───┐ ",
"│Ds1│ ",
"└───┘ ",
@ -1289,7 +1283,6 @@ mod tests {
" ",
" ",
]);
assert_eq!(buffer, expected);
}
@ -1297,13 +1290,10 @@ mod tests {
fn test_chart_have_a_long_y_axis_title_overlapping_legend() {
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
.y_axis(Axis::default().title("The title overlap a legend."));
let area = Rect::new(0, 0, 30, 20);
let mut buffer = Buffer::empty(area);
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
"The title overlap a legend. ",
" ┌───┐",
" │Ds1│",
@ -1325,7 +1315,6 @@ mod tests {
" ",
" ",
]);
assert_eq!(buffer, expected);
}
@ -1333,13 +1322,10 @@ mod tests {
fn test_chart_have_overflowed_y_axis() {
let chart = Chart::new(vec![Dataset::default().name("Ds1")])
.y_axis(Axis::default().title("The title overlap a legend."));
let area = Rect::new(0, 0, 10, 10);
let mut buffer = Buffer::empty(area);
chart.render(buffer.area, &mut buffer);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" ",
" ",
" ",
@ -1351,7 +1337,6 @@ mod tests {
" ",
" ",
]);
assert_eq!(buffer, expected);
}
@ -1360,12 +1345,8 @@ mod tests {
let name = "Data";
let chart = Chart::new(vec![Dataset::default().name(name)])
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
let area = Rect::new(0, 0, name.len() as u16 + 2, 3);
let mut buffer = Buffer::empty(area);
let expected = Buffer::with_lines(vec!["┌────┐", "│Data│", "└────┘"]);
for position in [
LegendPosition::TopLeft,
LegendPosition::Top,
@ -1379,171 +1360,103 @@ mod tests {
let chart = chart.clone().legend_position(Some(position));
buffer.reset();
chart.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌────┐",
"│Data│",
"└────┘",
]);
assert_eq!(buffer, expected);
}
}
#[allow(clippy::too_many_lines)]
#[test]
fn test_legend_of_chart_have_odd_margin_size() {
#[rstest]
#[case(Some(LegendPosition::TopLeft), [
"┌────┐ ",
"│Data│ ",
"└────┘ ",
" ",
" ",
" ",
])]
#[case(Some(LegendPosition::Top), [
" ┌────┐ ",
" │Data│ ",
" └────┘ ",
" ",
" ",
" ",
])]
#[case(Some(LegendPosition::TopRight), [
" ┌────┐",
" │Data│",
" └────┘",
" ",
" ",
" ",
])]
#[case(Some(LegendPosition::Left), [
" ",
"┌────┐ ",
"│Data│ ",
"└────┘ ",
" ",
" ",
])]
#[case(Some(LegendPosition::Right), [
" ",
" ┌────┐",
" │Data│",
" └────┘",
" ",
" ",
])]
#[case(Some(LegendPosition::BottomLeft), [
" ",
" ",
" ",
"┌────┐ ",
"│Data│ ",
"└────┘ ",
])]
#[case(Some(LegendPosition::Bottom), [
" ",
" ",
" ",
" ┌────┐ ",
" │Data│ ",
" └────┘ ",
])]
#[case(Some(LegendPosition::BottomRight), [
" ",
" ",
" ",
" ┌────┐",
" │Data│",
" └────┘",
])]
#[case(None, [
" ",
" ",
" ",
" ",
" ",
" ",
])]
fn test_legend_of_chart_have_odd_margin_size<'line, Lines>(
#[case] legend_position: Option<LegendPosition>,
#[case] expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
{
let name = "Data";
let base_chart = Chart::new(vec![Dataset::default().name(name)])
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
let area = Rect::new(0, 0, name.len() as u16 + 2 + 3, 3 + 3);
let mut buffer = Buffer::empty(area);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::TopLeft));
buffer.reset();
let chart = Chart::new(vec![Dataset::default().name(name)])
.legend_position(legend_position)
.hidden_legend_constraints((Constraint::Percentage(100), Constraint::Percentage(100)));
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
"┌────┐ ",
"│Data│ ",
"└────┘ ",
" ",
" ",
" ",
])
);
buffer.reset();
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::Top));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ┌────┐ ",
" │Data│ ",
" └────┘ ",
" ",
" ",
" ",
])
);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::TopRight));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ┌────┐",
" │Data│",
" └────┘",
" ",
" ",
" ",
])
);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::Left));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
"┌────┐ ",
"│Data│ ",
"└────┘ ",
" ",
" ",
])
);
buffer.reset();
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::Right));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ┌────┐",
" │Data│",
" └────┘",
" ",
" ",
])
);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::BottomLeft));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ",
" ",
"┌────┐ ",
"│Data│ ",
"└────┘ ",
])
);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::Bottom));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ",
" ",
" ┌────┐ ",
" │Data│ ",
" └────┘ ",
])
);
let chart = base_chart
.clone()
.legend_position(Some(LegendPosition::BottomRight));
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ",
" ",
" ┌────┐",
" │Data│",
" └────┘",
])
);
let chart = base_chart.clone().legend_position(None);
buffer.reset();
chart.render(buffer.area, &mut buffer);
assert_eq!(
buffer,
Buffer::with_lines(vec![
" ",
" ",
" ",
" ",
" ",
" ",
])
);
assert_eq!(buffer, Buffer::with_lines(expected));
}
}

View file

@ -43,24 +43,21 @@ impl WidgetRef for Clear {
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_buffer_eq;
#[test]
fn render() {
let mut buf = Buffer::with_lines(vec!["xxxxxxxxxxxxxxx"; 7]);
let mut buffer = Buffer::with_lines(["xxxxxxxxxxxxxxx"; 7]);
let clear = Clear;
clear.render(Rect::new(1, 2, 3, 4), &mut buf);
assert_buffer_eq!(
buf,
Buffer::with_lines(vec![
"xxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"xxxxxxxxxxxxxxx",
])
);
clear.render(Rect::new(1, 2, 3, 4), &mut buffer);
let expected = Buffer::with_lines([
"xxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"x xxxxxxxxxxx",
"xxxxxxxxxxxxxxx",
]);
assert_eq!(buffer, expected);
}
}

View file

@ -30,6 +30,7 @@ use crate::{prelude::*, widgets::Block};
/// # See also
///
/// - [`LineGauge`] for a thin progress bar
#[allow(clippy::struct_field_names)] // gauge_style needs to be differentiated to style
#[derive(Debug, Clone, PartialEq)]
pub struct Gauge<'a> {
block: Option<Block<'a>>,

File diff suppressed because it is too large Load diff

View file

@ -414,73 +414,65 @@ mod test {
/// area and comparing the rendered and expected content.
/// This can be used for easy testing of varying configured paragraphs with the same expected
/// buffer or any other test case really.
#[allow(clippy::needless_pass_by_value)]
fn test_case(paragraph: &Paragraph, expected: Buffer) {
#[track_caller]
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.clone(), size);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer(expected);
}
#[test]
fn zero_width_char_at_end_of_line() {
let line = "foo\0";
let paragraphs = vec![
for paragraph in [
Paragraph::new(line),
Paragraph::new(line).wrap(Wrap { trim: false }),
Paragraph::new(line).wrap(Wrap { trim: true }),
];
for paragraph in paragraphs {
test_case(&paragraph, Buffer::with_lines(vec!["foo"]));
test_case(&paragraph, Buffer::with_lines(vec!["foo "]));
test_case(&paragraph, Buffer::with_lines(vec!["foo ", " "]));
test_case(&paragraph, Buffer::with_lines(vec!["foo", " "]));
] {
test_case(&paragraph, &Buffer::with_lines(["foo"]));
test_case(&paragraph, &Buffer::with_lines(["foo "]));
test_case(&paragraph, &Buffer::with_lines(["foo ", " "]));
test_case(&paragraph, &Buffer::with_lines(["foo", " "]));
}
}
#[test]
fn test_render_empty_paragraph() {
let paragraphs = vec![
for paragraph in [
Paragraph::new(""),
Paragraph::new("").wrap(Wrap { trim: false }),
Paragraph::new("").wrap(Wrap { trim: true }),
];
for paragraph in paragraphs {
test_case(&paragraph, Buffer::with_lines(vec![" "]));
test_case(&paragraph, Buffer::with_lines(vec![" "]));
test_case(&paragraph, Buffer::with_lines(vec![" "; 10]));
test_case(&paragraph, Buffer::with_lines(vec![" ", " "]));
] {
test_case(&paragraph, &Buffer::with_lines([" "]));
test_case(&paragraph, &Buffer::with_lines([" "]));
test_case(&paragraph, &Buffer::with_lines([" "; 10]));
test_case(&paragraph, &Buffer::with_lines([" ", " "]));
}
}
#[test]
fn test_render_single_line_paragraph() {
let text = "Hello, world!";
let truncated_paragraph = Paragraph::new(text);
let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
test_case(paragraph, Buffer::with_lines(vec!["Hello, world! "]));
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
for paragraph in [
Paragraph::new(text),
Paragraph::new(text).wrap(Wrap { trim: false }),
Paragraph::new(text).wrap(Wrap { trim: true }),
] {
test_case(&paragraph, &Buffer::with_lines(["Hello, world! "]));
test_case(&paragraph, &Buffer::with_lines(["Hello, world!"]));
test_case(
paragraph,
Buffer::with_lines(vec!["Hello, world! ", " "]),
&paragraph,
&Buffer::with_lines(["Hello, world! ", " "]),
);
test_case(
paragraph,
Buffer::with_lines(vec!["Hello, world!", " "]),
&paragraph,
&Buffer::with_lines(["Hello, world!", " "]),
);
}
}
@ -488,29 +480,22 @@ mod test {
#[test]
fn test_render_multi_line_paragraph() {
let text = "This is a\nmultiline\nparagraph.";
let paragraphs = vec![
for paragraph in [
Paragraph::new(text),
Paragraph::new(text).wrap(Wrap { trim: false }),
Paragraph::new(text).wrap(Wrap { trim: true }),
];
for paragraph in paragraphs {
] {
test_case(
&paragraph,
Buffer::with_lines(vec!["This is a ", "multiline ", "paragraph."]),
&Buffer::with_lines(["This is a ", "multiline ", "paragraph."]),
);
test_case(
&paragraph,
Buffer::with_lines(vec![
"This is a ",
"multiline ",
"paragraph. ",
]),
&Buffer::with_lines(["This is a ", "multiline ", "paragraph. "]),
);
test_case(
&paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"This is a ",
"multiline ",
"paragraph. ",
@ -530,12 +515,11 @@ mod test {
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
#[rustfmt::skip]
test_case(
paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title─────────┐",
"│Hello, worlds!│",
"└──────────────┘",
@ -543,7 +527,7 @@ mod test {
);
test_case(
paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title───────────┐",
"│Hello, worlds! │",
"└────────────────┘",
@ -551,7 +535,7 @@ mod test {
);
test_case(
paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title────────────┐",
"│Hello, worlds! │",
"│ │",
@ -562,7 +546,7 @@ mod test {
test_case(
&truncated_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title───────┐",
"│Hello, world│",
"│ │",
@ -571,7 +555,7 @@ mod test {
);
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title──────┐",
"│Hello, │",
"│worlds! │",
@ -580,7 +564,7 @@ mod test {
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌Title──────┐",
"│Hello, │",
"│worlds! │",
@ -598,29 +582,29 @@ mod test {
let paragraph = Paragraph::new(vec![l0, l1, l2, l3]);
let mut expected =
Buffer::with_lines(vec!["unformatted", "bold text", "cyan text", "dim text"]);
Buffer::with_lines(["unformatted", "bold text", "cyan text", "dim text"]);
expected.set_style(Rect::new(0, 1, 9, 1), Style::new().bold());
expected.set_style(Rect::new(0, 2, 9, 1), Style::new().cyan());
expected.set_style(Rect::new(0, 3, 8, 1), Style::new().dim());
test_case(&paragraph, expected);
test_case(&paragraph, &expected);
}
#[test]
fn test_render_line_spans_styled() {
let l0 = Line::default().spans(vec![
let l0 = Line::default().spans([
Span::styled("bold", Style::new().bold()),
Span::raw(" and "),
Span::styled("cyan", Style::new().cyan()),
]);
let l1 = Line::default().spans(vec![Span::raw("unformatted")]);
let l1 = Line::default().spans([Span::raw("unformatted")]);
let paragraph = Paragraph::new(vec![l0, l1]);
let mut expected = Buffer::with_lines(vec!["bold and cyan", "unformatted"]);
let mut expected = Buffer::with_lines(["bold and cyan", "unformatted"]);
expected.set_style(Rect::new(0, 0, 4, 1), Style::new().bold());
expected.set_style(Rect::new(9, 0, 4, 1), Style::new().cyan());
test_case(&paragraph, expected);
test_case(&paragraph, &expected);
}
#[test]
@ -630,10 +614,9 @@ mod test {
.title_position(Position::Bottom)
.title("Title");
let paragraph = Paragraph::new("Hello, world!").block(block);
test_case(
&paragraph,
Buffer::with_lines(vec!["Hello, world! ", "Title──────────"]),
&Buffer::with_lines(["Hello, world! ", "Title──────────"]),
);
}
@ -645,7 +628,7 @@ mod test {
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"This is a long line",
"of text that should",
"wrap and ",
@ -656,7 +639,7 @@ mod test {
);
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"This is a ",
"long line of",
"text that ",
@ -671,7 +654,7 @@ mod test {
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"This is a long line",
"of text that should",
"wrap and ",
@ -682,7 +665,7 @@ mod test {
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"This is a ",
"long line of",
"text that ",
@ -703,19 +686,19 @@ mod test {
test_case(
&truncated_paragraph,
Buffer::with_lines(vec!["This is a long line of"]),
&Buffer::with_lines(["This is a long line of"]),
);
test_case(
&truncated_paragraph,
Buffer::with_lines(vec!["This is a long line of te"]),
&Buffer::with_lines(["This is a long line of te"]),
);
test_case(
&truncated_paragraph,
Buffer::with_lines(vec!["This is a long line of "]),
&Buffer::with_lines(["This is a long line of "]),
);
test_case(
&truncated_paragraph.clone().scroll((0, 2)),
Buffer::with_lines(vec!["is is a long line of te"]),
&Buffer::with_lines(["is is a long line of te"]),
);
}
@ -726,21 +709,19 @@ mod test {
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
test_case(paragraph, Buffer::with_lines(vec!["Hello, world! "]));
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
test_case(paragraph, &Buffer::with_lines(["Hello, world! "]));
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
}
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec!["Hello, ", "world! "]),
&Buffer::with_lines(["Hello, ", "world! "]),
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec!["Hello, ", "world! "]),
&Buffer::with_lines(["Hello, ", "world! "]),
);
}
@ -751,23 +732,21 @@ mod test {
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
test_case(paragraph, Buffer::with_lines(vec![" Hello, world! "]));
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
}
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec![" Hello, ", " world! "]),
&Buffer::with_lines([" Hello, ", " world! "]),
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec![" Hello, ", " world! "]),
&Buffer::with_lines([" Hello, ", " world! "]),
);
}
@ -778,21 +757,19 @@ mod test {
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
test_case(paragraph, Buffer::with_lines(vec![" Hello, world!"]));
test_case(paragraph, Buffer::with_lines(vec!["Hello, world!"]));
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
test_case(paragraph, &Buffer::with_lines([" Hello, world!"]));
test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
}
test_case(&truncated_paragraph, Buffer::with_lines(vec!["Hello, wor"]));
test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec![" Hello,", " world!"]),
&Buffer::with_lines([" Hello,", " world!"]),
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec![" Hello,", " world!"]),
&Buffer::with_lines([" Hello,", " world!"]),
);
}
@ -803,57 +780,51 @@ mod test {
let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
test_case(
paragraph,
Buffer::with_lines(vec!["multiline ", "paragraph. ", " "]),
&Buffer::with_lines(["multiline ", "paragraph. ", " "]),
);
test_case(paragraph, Buffer::with_lines(vec!["multiline "]));
test_case(paragraph, &Buffer::with_lines(["multiline "]));
}
test_case(
&truncated_paragraph.clone().scroll((2, 4)),
Buffer::with_lines(vec!["iline ", "graph. "]),
&Buffer::with_lines(["iline ", "graph. "]),
);
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec!["cool ", "multili", "ne "]),
&Buffer::with_lines(["cool ", "multili", "ne "]),
);
}
#[test]
fn test_render_paragraph_with_zero_width_area() {
let text = "Hello, world!";
let area = Rect::new(0, 0, 0, 3);
let paragraphs = vec![
for paragraph in [
Paragraph::new(text),
Paragraph::new(text).wrap(Wrap { trim: false }),
Paragraph::new(text).wrap(Wrap { trim: true }),
];
let area = Rect::new(0, 0, 0, 3);
for paragraph in paragraphs {
test_case(&paragraph, Buffer::empty(area));
test_case(&paragraph.clone().scroll((2, 4)), Buffer::empty(area));
] {
test_case(&paragraph, &Buffer::empty(area));
test_case(&paragraph.clone().scroll((2, 4)), &Buffer::empty(area));
}
}
#[test]
fn test_render_paragraph_with_zero_height_area() {
let text = "Hello, world!";
let area = Rect::new(0, 0, 10, 0);
let paragraphs = vec![
for paragraph in [
Paragraph::new(text),
Paragraph::new(text).wrap(Wrap { trim: false }),
Paragraph::new(text).wrap(Wrap { trim: true }),
];
let area = Rect::new(0, 0, 10, 0);
for paragraph in paragraphs {
test_case(&paragraph, Buffer::empty(area));
test_case(&paragraph.clone().scroll((2, 4)), Buffer::empty(area));
] {
test_case(&paragraph, &Buffer::empty(area));
test_case(&paragraph.clone().scroll((2, 4)), &Buffer::empty(area));
}
}
@ -864,13 +835,7 @@ mod test {
Span::styled("world!", Style::default().fg(Color::Blue)),
]);
let paragraphs = vec![
Paragraph::new(text.clone()),
Paragraph::new(text.clone()).wrap(Wrap { trim: false }),
Paragraph::new(text.clone()).wrap(Wrap { trim: true }),
];
let mut expected_buffer = Buffer::with_lines(vec!["Hello, world!"]);
let mut expected_buffer = Buffer::with_lines(["Hello, world!"]);
expected_buffer.set_style(
Rect::new(0, 0, 7, 1),
Style::default().fg(Color::Red).bg(Color::Green),
@ -879,10 +844,15 @@ mod test {
Rect::new(7, 0, 6, 1),
Style::default().fg(Color::Blue).bg(Color::Green),
);
for paragraph in paragraphs {
for paragraph in [
Paragraph::new(text.clone()),
Paragraph::new(text.clone()).wrap(Wrap { trim: false }),
Paragraph::new(text.clone()).wrap(Wrap { trim: true }),
] {
test_case(
&paragraph.style(Style::default().bg(Color::Green)),
expected_buffer.clone(),
&expected_buffer,
);
}
}
@ -890,22 +860,20 @@ mod test {
#[test]
fn test_render_paragraph_with_special_characters() {
let text = "Hello, <world>!";
let paragraphs = vec![
for paragraph in [
Paragraph::new(text),
Paragraph::new(text).wrap(Wrap { trim: false }),
Paragraph::new(text).wrap(Wrap { trim: true }),
];
for paragraph in paragraphs {
test_case(&paragraph, Buffer::with_lines(vec!["Hello, <world>!"]));
test_case(&paragraph, Buffer::with_lines(vec!["Hello, <world>! "]));
] {
test_case(&paragraph, &Buffer::with_lines(["Hello, <world>!"]));
test_case(&paragraph, &Buffer::with_lines(["Hello, <world>! "]));
test_case(
&paragraph,
Buffer::with_lines(vec!["Hello, <world>! ", " "]),
&Buffer::with_lines(["Hello, <world>! ", " "]),
);
test_case(
&paragraph,
Buffer::with_lines(vec!["Hello, <world>!", " "]),
&Buffer::with_lines(["Hello, <world>!", " "]),
);
}
}
@ -917,27 +885,25 @@ mod test {
let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
let paragraphs = vec![&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph];
for paragraph in paragraphs {
test_case(paragraph, Buffer::with_lines(vec!["こんにちは, 世界! 😃"]));
for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
test_case(paragraph, &Buffer::with_lines(["こんにちは, 世界! 😃"]));
test_case(
paragraph,
Buffer::with_lines(vec!["こんにちは, 世界! 😃 "]),
&Buffer::with_lines(["こんにちは, 世界! 😃 "]),
);
}
test_case(
&truncated_paragraph,
Buffer::with_lines(vec!["こんにちは, 世 "]),
&Buffer::with_lines(["こんにちは, 世 "]),
);
test_case(
&wrapped_paragraph,
Buffer::with_lines(vec!["こんにちは, ", "世界! 😃 "]),
&Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
);
test_case(
&trimmed_paragraph,
Buffer::with_lines(vec!["こんにちは, ", "世界! 😃 "]),
&Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
);
}
@ -1025,13 +991,12 @@ mod test {
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
paragraph.render(Rect::new(0, 0, 20, 3), &mut buf);
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"┌──────────────────┐",
"│Styled text │",
"└──────────────────┘",
]);
expected.set_style(Rect::new(1, 1, 11, 1), Style::default().fg(Color::Green));
assert_eq!(buf, expected);
}
}

View file

@ -674,113 +674,107 @@ mod tests {
}
#[rstest]
#[case("#-", 0, 2, "area 2, position_0")]
#[case("-#", 1, 2, "area 2, position_1")]
#[case::area_2_position_0("#-", 0, 2)]
#[case::area_2_position_1("-#", 1, 2)]
fn render_scrollbar_simplest(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let mut buffer = Buffer::empty(Rect::new(0, 0, expected.width() as u16, 1));
let mut state = ScrollbarState::new(content_length).position(position);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("#####-----", 0, 10, "position_0")]
#[case("-#####----", 1, 10, "position_1")]
#[case("-#####----", 2, 10, "position_2")]
#[case("--#####---", 3, 10, "position_3")]
#[case("--#####---", 4, 10, "position_4")]
#[case("---#####--", 5, 10, "position_5")]
#[case("---#####--", 6, 10, "position_6")]
#[case("----#####-", 7, 10, "position_7")]
#[case("----#####-", 8, 10, "position_8")]
#[case("-----#####", 9, 10, "position_9")]
#[case::position_0("#####-----", 0, 10)]
#[case::position_1("-#####----", 1, 10)]
#[case::position_2("-#####----", 2, 10)]
#[case::position_3("--#####---", 3, 10)]
#[case::position_4("--#####---", 4, 10)]
#[case::position_5("---#####--", 5, 10)]
#[case::position_6("---#####--", 6, 10)]
#[case::position_7("----#####-", 7, 10)]
#[case::position_8("----#####-", 8, 10)]
#[case::position_9("-----#####", 9, 10)]
fn render_scrollbar_simple(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let mut buffer = Buffer::empty(Rect::new(0, 0, expected.width() as u16, 1));
let mut state = ScrollbarState::new(content_length).position(position);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case(" ", 0, 0, "position_0")]
#[case::position_0(" ", 0, 0)]
fn render_scrollbar_nobar(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let size = expected.width();
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
let mut state = ScrollbarState::new(content_length).position(position);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("##########", 0, 1, "fullbar position 0")]
#[case("#########-", 0, 2, "almost fullbar position 0")]
#[case("-#########", 1, 2, "almost fullbar position 1")]
#[case::fullbar_position_0("##########", 0, 1)]
#[case::almost_fullbar_position_0("#########-", 0, 2)]
#[case::almost_fullbar_position_1("-#########", 1, 2)]
fn render_scrollbar_fullbar(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let size = expected.width();
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
let mut state = ScrollbarState::new(content_length).position(position);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("#########-", 0, 2, "position_0")]
#[case("-#########", 1, 2, "position_1")]
#[case::position_0("#########-", 0, 2)]
#[case::position_1("-#########", 1, 2)]
fn render_scrollbar_almost_fullbar(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let size = expected.width();
let mut buffer = Buffer::empty(Rect::new(0, 0, size as u16, 1));
let mut state = ScrollbarState::new(content_length).position(position);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}",);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("█████═════", 0, 10, "position_0")]
#[case("═█████════", 1, 10, "position_1")]
#[case("═█████════", 2, 10, "position_2")]
#[case("══█████═══", 3, 10, "position_3")]
#[case("══█████═══", 4, 10, "position_4")]
#[case("═══█████══", 5, 10, "position_5")]
#[case("═══█████══", 6, 10, "position_6")]
#[case("════█████═", 7, 10, "position_7")]
#[case("════█████═", 8, 10, "position_8")]
#[case("═════█████", 9, 10, "position_9")]
#[case("═════█████", 100, 10, "position_out_of_bounds")]
#[case::position_0("█████═════", 0, 10)]
#[case::position_1("═█████════", 1, 10)]
#[case::position_2("═█████════", 2, 10)]
#[case::position_3("══█████═══", 3, 10)]
#[case::position_4("══█████═══", 4, 10)]
#[case::position_5("═══█████══", 5, 10)]
#[case::position_6("═══█████══", 6, 10)]
#[case::position_7("════█████═", 7, 10)]
#[case::position_8("════█████═", 8, 10)]
#[case::position_9("═════█████", 9, 10)]
#[case::position_out_of_bounds("═════█████", 100, 10)]
fn render_scrollbar_without_symbols(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] assertion_message: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
@ -789,30 +783,25 @@ mod tests {
.begin_symbol(None)
.end_symbol(None)
.render(buffer.area, &mut buffer, &mut state);
assert_eq!(
buffer,
Buffer::with_lines(vec![expected]),
"{assertion_message}",
);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("█████ ", 0, 10, "position_0")]
#[case(" █████ ", 1, 10, "position_1")]
#[case(" █████ ", 2, 10, "position_2")]
#[case(" █████ ", 3, 10, "position_3")]
#[case(" █████ ", 4, 10, "position_4")]
#[case(" █████ ", 5, 10, "position_5")]
#[case(" █████ ", 6, 10, "position_6")]
#[case(" █████ ", 7, 10, "position_7")]
#[case(" █████ ", 8, 10, "position_8")]
#[case(" █████", 9, 10, "position_9")]
#[case(" █████", 100, 10, "position_out_of_bounds")]
#[case::position_0("█████ ", 0, 10)]
#[case::position_1(" █████ ", 1, 10)]
#[case::position_2(" █████ ", 2, 10)]
#[case::position_3(" █████ ", 3, 10)]
#[case::position_4(" █████ ", 4, 10)]
#[case::position_5(" █████ ", 5, 10)]
#[case::position_6(" █████ ", 6, 10)]
#[case::position_7(" █████ ", 7, 10)]
#[case::position_8(" █████ ", 8, 10)]
#[case::position_9(" █████", 9, 10)]
#[case::position_out_of_bounds(" █████", 100, 10)]
fn render_scrollbar_without_track_symbols(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] assertion_message: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
@ -822,30 +811,25 @@ mod tests {
.begin_symbol(None)
.end_symbol(None)
.render(buffer.area, &mut buffer, &mut state);
assert_eq!(
buffer,
Buffer::with_lines(vec![expected]),
"{assertion_message}",
);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("█████-----", 0, 10, "position_0")]
#[case("-█████----", 1, 10, "position_1")]
#[case("-█████----", 2, 10, "position_2")]
#[case("--█████---", 3, 10, "position_3")]
#[case("--█████---", 4, 10, "position_4")]
#[case("---█████--", 5, 10, "position_5")]
#[case("---█████--", 6, 10, "position_6")]
#[case("----█████-", 7, 10, "position_7")]
#[case("----█████-", 8, 10, "position_8")]
#[case("-----█████", 9, 10, "position_9")]
#[case("-----█████", 100, 10, "position_out_of_bounds")]
#[case::position_0("█████-----", 0, 10)]
#[case::position_1("-█████----", 1, 10)]
#[case::position_2("-█████----", 2, 10)]
#[case::position_3("--█████---", 3, 10)]
#[case::position_4("--█████---", 4, 10)]
#[case::position_5("---█████--", 5, 10)]
#[case::position_6("---█████--", 6, 10)]
#[case::position_7("----█████-", 7, 10)]
#[case::position_8("----█████-", 8, 10)]
#[case::position_9("-----█████", 9, 10)]
#[case::position_out_of_bounds("-----█████", 100, 10)]
fn render_scrollbar_without_track_symbols_over_content(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] assertion_message: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
@ -858,32 +842,27 @@ mod tests {
.begin_symbol(None)
.end_symbol(None)
.render(buffer.area, &mut buffer, &mut state);
assert_eq!(
buffer,
Buffer::with_lines(vec![expected]),
"{assertion_message}",
);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("<####---->", 0, 10, "position_0")]
#[case("<#####--->", 1, 10, "position_1")]
#[case("<-####--->", 2, 10, "position_2")]
#[case("<-####--->", 3, 10, "position_3")]
#[case("<--####-->", 4, 10, "position_4")]
#[case("<--####-->", 5, 10, "position_5")]
#[case("<---####->", 6, 10, "position_6")]
#[case("<---####->", 7, 10, "position_7")]
#[case("<---#####>", 8, 10, "position_8")]
#[case("<----####>", 9, 10, "position_9")]
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
#[case("<----####>", 15, 10, "position_few_out_of_bounds")]
#[case("<----####>", 500, 10, "position_very_many_out_of_bounds")]
#[case::position_0("<####---->", 0, 10)]
#[case::position_1("<#####--->", 1, 10)]
#[case::position_2("<-####--->", 2, 10)]
#[case::position_3("<-####--->", 3, 10)]
#[case::position_4("<--####-->", 4, 10)]
#[case::position_5("<--####-->", 5, 10)]
#[case::position_6("<---####->", 6, 10)]
#[case::position_7("<---####->", 7, 10)]
#[case::position_8("<---#####>", 8, 10)]
#[case::position_9("<----####>", 9, 10)]
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
#[case::position_few_out_of_bounds("<----####>", 15, 10)]
#[case::position_very_many_out_of_bounds("<----####>", 500, 10)]
fn render_scrollbar_with_symbols(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] assertion_message: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 1));
@ -894,30 +873,25 @@ mod tests {
.track_symbol(Some("-"))
.thumb_symbol("#")
.render(buffer.area, &mut buffer, &mut state);
assert_eq!(
buffer,
Buffer::with_lines(vec![expected]),
"{assertion_message}",
);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
#[rstest]
#[case("█████═════", 0, 10, "position_0")]
#[case("═█████════", 1, 10, "position_1")]
#[case("═█████════", 2, 10, "position_2")]
#[case("══█████═══", 3, 10, "position_3")]
#[case("══█████═══", 4, 10, "position_4")]
#[case("═══█████══", 5, 10, "position_5")]
#[case("═══█████══", 6, 10, "position_6")]
#[case("════█████═", 7, 10, "position_7")]
#[case("════█████═", 8, 10, "position_8")]
#[case("═════█████", 9, 10, "position_9")]
#[case("═════█████", 100, 10, "position_out_of_bounds")]
#[case::position_0("█████═════", 0, 10)]
#[case::position_1("═█████════", 1, 10)]
#[case::position_2("═█████════", 2, 10)]
#[case::position_3("══█████═══", 3, 10)]
#[case::position_4("══█████═══", 4, 10)]
#[case::position_5("═══█████══", 5, 10)]
#[case::position_6("═══█████══", 6, 10)]
#[case::position_7("════█████═", 7, 10)]
#[case::position_8("════█████═", 8, 10)]
#[case::position_9("═════█████", 9, 10)]
#[case::position_out_of_bounds("═════█████", 100, 10)]
fn render_scrollbar_horizontal_bottom(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 2));
@ -927,30 +901,25 @@ mod tests {
.end_symbol(None)
.render(buffer.area, &mut buffer, &mut state);
let empty_string = " ".repeat(size as usize);
assert_eq!(
buffer,
Buffer::with_lines(vec![&empty_string, expected]),
"{description}",
);
assert_eq!(buffer, Buffer::with_lines([&empty_string, expected]));
}
#[rstest]
#[case("█████═════", 0, 10, "position_0")]
#[case("═█████════", 1, 10, "position_1")]
#[case("═█████════", 2, 10, "position_2")]
#[case("══█████═══", 3, 10, "position_3")]
#[case("══█████═══", 4, 10, "position_4")]
#[case("═══█████══", 5, 10, "position_5")]
#[case("═══█████══", 6, 10, "position_6")]
#[case("════█████═", 7, 10, "position_7")]
#[case("════█████═", 8, 10, "position_8")]
#[case("═════█████", 9, 10, "position_9")]
#[case("═════█████", 100, 10, "position_out_of_bounds")]
#[case::position_0("█████═════", 0, 10)]
#[case::position_1("═█████════", 1, 10)]
#[case::position_2("═█████════", 2, 10)]
#[case::position_3("══█████═══", 3, 10)]
#[case::position_4("══█████═══", 4, 10)]
#[case::position_5("═══█████══", 5, 10)]
#[case::position_6("═══█████══", 6, 10)]
#[case::position_7("════█████═", 7, 10)]
#[case::position_8("════█████═", 8, 10)]
#[case::position_9("═════█████", 9, 10)]
#[case::position_out_of_bounds("═════█████", 100, 10)]
fn render_scrollbar_horizontal_top(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, size, 2));
@ -960,30 +929,25 @@ mod tests {
.end_symbol(None)
.render(buffer.area, &mut buffer, &mut state);
let empty_string = " ".repeat(size as usize);
assert_eq!(
buffer,
Buffer::with_lines(vec![expected, &empty_string]),
"{description}",
);
assert_eq!(buffer, Buffer::with_lines([expected, &empty_string]));
}
#[rstest]
#[case("<####---->", 0, 10, "position_0")]
#[case("<#####--->", 1, 10, "position_1")]
#[case("<-####--->", 2, 10, "position_2")]
#[case("<-####--->", 3, 10, "position_3")]
#[case("<--####-->", 4, 10, "position_4")]
#[case("<--####-->", 5, 10, "position_5")]
#[case("<---####->", 6, 10, "position_6")]
#[case("<---####->", 7, 10, "position_7")]
#[case("<---#####>", 8, 10, "position_8")]
#[case("<----####>", 9, 10, "position_9")]
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
#[case::position_0("<####---->", 0, 10)]
#[case::position_1("<#####--->", 1, 10)]
#[case::position_2("<-####--->", 2, 10)]
#[case::position_3("<-####--->", 3, 10)]
#[case::position_4("<--####-->", 4, 10)]
#[case::position_5("<--####-->", 5, 10)]
#[case::position_6("<---####->", 6, 10)]
#[case::position_7("<---####->", 7, 10)]
#[case::position_8("<---#####>", 8, 10)]
#[case::position_9("<----####>", 9, 10)]
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
fn render_scrollbar_vertical_left(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, size));
@ -995,26 +959,25 @@ mod tests {
.thumb_symbol("#")
.render(buffer.area, &mut buffer, &mut state);
let bar = expected.chars().map(|c| format!("{c} "));
assert_eq!(buffer, Buffer::with_lines(bar), "{description}");
assert_eq!(buffer, Buffer::with_lines(bar));
}
#[rstest]
#[case("<####---->", 0, 10, "position_0")]
#[case("<#####--->", 1, 10, "position_1")]
#[case("<-####--->", 2, 10, "position_2")]
#[case("<-####--->", 3, 10, "position_3")]
#[case("<--####-->", 4, 10, "position_4")]
#[case("<--####-->", 5, 10, "position_5")]
#[case("<---####->", 6, 10, "position_6")]
#[case("<---####->", 7, 10, "position_7")]
#[case("<---#####>", 8, 10, "position_8")]
#[case("<----####>", 9, 10, "position_9")]
#[case("<----####>", 10, 10, "position_one_out_of_bounds")]
#[case::position_0("<####---->", 0, 10)]
#[case::position_1("<#####--->", 1, 10)]
#[case::position_2("<-####--->", 2, 10)]
#[case::position_3("<-####--->", 3, 10)]
#[case::position_4("<--####-->", 4, 10)]
#[case::position_5("<--####-->", 5, 10)]
#[case::position_6("<---####->", 6, 10)]
#[case::position_7("<---####->", 7, 10)]
#[case::position_8("<---#####>", 8, 10)]
#[case::position_9("<----####>", 9, 10)]
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
fn render_scrollbar_vertical_rightl(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
) {
let size = expected.width() as u16;
let mut buffer = Buffer::empty(Rect::new(0, 0, 5, size));
@ -1026,26 +989,25 @@ mod tests {
.thumb_symbol("#")
.render(buffer.area, &mut buffer, &mut state);
let bar = expected.chars().map(|c| format!(" {c}"));
assert_eq!(buffer, Buffer::with_lines(bar), "{description}");
assert_eq!(buffer, Buffer::with_lines(bar));
}
#[rstest]
#[case("##--------", 0, 10, "position_0")]
#[case("-##-------", 1, 10, "position_1")]
#[case("--##------", 2, 10, "position_2")]
#[case("---##-----", 3, 10, "position_3")]
#[case("----#-----", 4, 10, "position_4")]
#[case("-----#----", 5, 10, "position_5")]
#[case("-----##---", 6, 10, "position_6")]
#[case("------##--", 7, 10, "position_7")]
#[case("-------##-", 8, 10, "position_8")]
#[case("--------##", 9, 10, "position_9")]
#[case("--------##", 10, 10, "position_one_out_of_bounds")]
#[case::position_0("##--------", 0, 10)]
#[case::position_1("-##-------", 1, 10)]
#[case::position_2("--##------", 2, 10)]
#[case::position_3("---##-----", 3, 10)]
#[case::position_4("----#-----", 4, 10)]
#[case::position_5("-----#----", 5, 10)]
#[case::position_6("-----##---", 6, 10)]
#[case::position_7("------##--", 7, 10)]
#[case::position_8("-------##-", 8, 10)]
#[case::position_9("--------##", 9, 10)]
#[case::position_one_out_of_bounds("--------##", 10, 10)]
fn custom_viewport_length(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let size = expected.width() as u16;
@ -1054,28 +1016,27 @@ mod tests {
.position(position)
.viewport_content_length(2);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}");
assert_eq!(buffer, Buffer::with_lines([expected]));
}
/// Fixes <https://github.com/ratatui-org/ratatui/pull/959> which was a bug that would not
/// render a thumb when the viewport was very small in comparison to the content length.
#[rstest]
#[case("#----", 0, 100, "position_0")]
#[case("#----", 10, 100, "position_10")]
#[case("-#---", 20, 100, "position_20")]
#[case("-#---", 30, 100, "position_30")]
#[case("--#--", 40, 100, "position_40")]
#[case("--#--", 50, 100, "position_50")]
#[case("---#-", 60, 100, "position_60")]
#[case("---#-", 70, 100, "position_70")]
#[case("----#", 80, 100, "position_80")]
#[case("----#", 90, 100, "position_90")]
#[case("----#", 100, 100, "position_one_out_of_bounds")]
#[case::position_0("#----", 0, 100)]
#[case::position_10("#----", 10, 100)]
#[case::position_20("-#---", 20, 100)]
#[case::position_30("-#---", 30, 100)]
#[case::position_40("--#--", 40, 100)]
#[case::position_50("--#--", 50, 100)]
#[case::position_60("---#-", 60, 100)]
#[case::position_70("---#-", 70, 100)]
#[case::position_80("----#", 80, 100)]
#[case::position_90("----#", 90, 100)]
#[case::position_one_out_of_bounds("----#", 100, 100)]
fn thumb_visible_on_very_small_track(
#[case] expected: &str,
#[case] position: usize,
#[case] content_length: usize,
#[case] description: &str,
scrollbar_no_arrows: Scrollbar,
) {
let size = expected.width() as u16;
@ -1084,6 +1045,6 @@ mod tests {
.position(position)
.viewport_content_length(2);
scrollbar_no_arrows.render(buffer.area, &mut buffer, &mut state);
assert_eq!(buffer, Buffer::with_lines(vec![expected]), "{description}");
assert_eq!(buffer, Buffer::with_lines([expected]));
}
}

View file

@ -225,7 +225,7 @@ mod tests {
use strum::ParseError;
use super::*;
use crate::{assert_buffer_eq, buffer::Cell};
use crate::buffer::Cell;
#[test]
fn render_direction_to_string() {
@ -264,21 +264,21 @@ mod tests {
fn it_does_not_panic_if_max_is_zero() {
let widget = Sparkline::default().data(&[0, 0, 0]);
let buffer = render(widget, 6);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"]));
assert_eq!(buffer, Buffer::with_lines([" xxx"]));
}
#[test]
fn it_does_not_panic_if_max_is_set_to_zero() {
let widget = Sparkline::default().data(&[0, 1, 2]).max(0);
let buffer = render(widget, 6);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" xxx"]));
assert_eq!(buffer, Buffer::with_lines([" xxx"]));
}
#[test]
fn it_draws() {
let widget = Sparkline::default().data(&[0, 1, 2, 3, 4, 5, 6, 7, 8]);
let buffer = render(widget, 12);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"]));
assert_eq!(buffer, Buffer::with_lines([" ▁▂▃▄▅▆▇█xxx"]));
}
#[test]
@ -287,7 +287,7 @@ mod tests {
.data(&[0, 1, 2, 3, 4, 5, 6, 7, 8])
.direction(RenderDirection::LeftToRight);
let buffer = render(widget, 12);
assert_buffer_eq!(buffer, Buffer::with_lines(vec![" ▁▂▃▄▅▆▇█xxx"]));
assert_eq!(buffer, Buffer::with_lines([" ▁▂▃▄▅▆▇█xxx"]));
}
#[test]
@ -296,7 +296,7 @@ mod tests {
.data(&[0, 1, 2, 3, 4, 5, 6, 7, 8])
.direction(RenderDirection::RightToLeft);
let buffer = render(widget, 12);
assert_buffer_eq!(buffer, Buffer::with_lines(vec!["xxx█▇▆▅▄▃▂▁ "]));
assert_eq!(buffer, Buffer::with_lines(["xxx█▇▆▅▄▃▂▁ "]));
}
#[test]

View file

@ -210,7 +210,7 @@ pub struct Table<'a> {
/// Style used to render the selected row
highlight_style: Style,
/// Symbol in front of the selected rom
/// Symbol in front of the selected row
highlight_symbol: Text<'a>,
/// Decides when to allocate spacing for the row selection
@ -1001,7 +1001,6 @@ mod tests {
#[cfg(test)]
mod render {
use super::*;
use crate::assert_buffer_eq;
#[test]
fn render_empty_area() {
@ -1009,7 +1008,7 @@ mod tests {
let rows = vec![Row::new(vec!["Cell1", "Cell2"])];
let table = Table::new(rows, vec![Constraint::Length(5); 2]);
Widget::render(table, Rect::new(0, 0, 0, 0), &mut buf);
assert_buffer_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
}
#[test]
@ -1017,7 +1016,7 @@ mod tests {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
let table = Table::default();
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
assert_buffer_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
}
#[test]
@ -1030,12 +1029,13 @@ mod tests {
let block = Block::bordered().title("Block");
let table = Table::new(rows, vec![Constraint::Length(5); 2]).block(block);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌Block────────┐",
"│Cell1 Cell2 │",
"└─────────────┘",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1048,12 +1048,13 @@ mod tests {
];
let table = Table::new(rows, [Constraint::Length(5); 2]).header(header);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Head1 Head2 ",
"Cell1 Cell2 ",
"Cell3 Cell4 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1066,12 +1067,13 @@ mod tests {
];
let table = Table::new(rows, [Constraint::Length(5); 2]).footer(footer);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Cell1 Cell2 ",
"Cell3 Cell4 ",
"Foot1 Foot2 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1084,12 +1086,13 @@ mod tests {
.header(header)
.footer(footer);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Head1 Head2 ",
"Cell1 Cell2 ",
"Foot1 Foot2 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1102,12 +1105,13 @@ mod tests {
];
let table = Table::new(rows, [Constraint::Length(5); 2]).header(header);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Head1 Head2 ",
" ",
"Cell1 Cell2 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1117,12 +1121,13 @@ mod tests {
let rows = vec![Row::new(vec!["Cell1", "Cell2"])];
let table = Table::new(rows, [Constraint::Length(5); 2]).footer(footer);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Cell1 Cell2 ",
" ",
"Foot1 Foot2 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
@ -1134,30 +1139,27 @@ mod tests {
];
let table = Table::new(rows, [Constraint::Length(5); 2]);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let expected = Buffer::with_lines([
"Cell1 Cell2 ",
" ",
"Cell3 Cell4 ",
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
#[test]
fn render_with_alignment() {
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 3));
let rows = vec![
Row::new(vec![Line::from("Left").alignment(Alignment::Left)]),
Row::new(vec![Line::from("Center").alignment(Alignment::Center)]),
Row::new(vec![Line::from("Right").alignment(Alignment::Right)]),
];
let table = Table::new(rows, [Percentage(100)]);
Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
let expected = Buffer::with_lines(vec![
"Left ",
" Center ",
" Right",
]);
assert_buffer_eq!(buf, expected);
Widget::render(table, Rect::new(0, 0, 10, 3), &mut buf);
let expected = Buffer::with_lines(["Left ", " Center ", " Right"]);
assert_eq!(buf, expected);
}
#[test]
@ -1181,19 +1183,18 @@ mod tests {
.highlight_symbol(">>");
let mut state = TableState::new().with_selected(0);
StatefulWidget::render(table, Rect::new(0, 0, 15, 3), &mut buf, &mut state);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
">>Cell1 Cell2 ".red(),
" Cell3 Cell4 ".into(),
" ".into(),
]);
assert_buffer_eq!(buf, expected);
assert_eq!(buf, expected);
}
}
// test how constraints interact with table column width allocation
mod column_widths {
use super::*;
use crate::assert_buffer_eq;
#[test]
fn length_constraint() {
@ -1387,12 +1388,17 @@ mod tests {
assert_eq!(table.get_columns_widths(10, 0), [(0, 5), (5, 5)]);
}
fn test_table_with_selection(
#[track_caller]
fn test_table_with_selection<'line, Lines>(
highlight_spacing: HighlightSpacing,
columns: u16,
spacing: u16,
selection: Option<usize>,
) -> Buffer {
expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
{
let table = Table::default()
.rows(vec![Row::new(vec!["ABCDE", "12345"])])
.highlight_spacing(highlight_spacing)
@ -1402,26 +1408,24 @@ mod tests {
let mut buf = Buffer::empty(area);
let mut state = TableState::default().with_selected(selection);
StatefulWidget::render(table, area, &mut buf, &mut state);
buf
assert_eq!(buf, Buffer::with_lines(expected));
}
#[test]
fn excess_area_highlight_symbol_and_column_spacing_allocation() {
// no highlight_symbol rendered ever
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
15, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
15, // width
0, // spacing
None, // selection
[
"ABCDE 12345 ", /* default layout is Flex::Start but columns length
* constraints are calculated as `max_area / n_columns`,
* i.e. they are distributed amongst available space */
" ", // row 2
" ", // row 3
])
],
);
let table = Table::default()
@ -1431,88 +1435,76 @@ mod tests {
let area = Rect::new(0, 0, 15, 3);
let mut buf = Buffer::empty(area);
Widget::render(table, area, &mut buf);
assert_buffer_eq!(
buf,
Buffer::with_lines(vec![
"ABCDE12345 ", /* As reference, this is what happens when you manually
* specify widths */
" ", // row 2
" ", // row 3
])
);
let expected = Buffer::with_lines([
"ABCDE12345 ", /* As reference, this is what happens when you manually
* specify widths */
" ", // row 2
" ", // row 3
]);
assert_eq!(buf, expected);
// no highlight_symbol rendered ever
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
15, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
15, // width
0, // spacing
Some(0), // selection
[
"ABCDE 12345 ", // row 1
" ", // row 2
" ", // row 3
])
],
);
// no highlight_symbol rendered because no selection is made
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
None, // selection
[
"ABCDE 12345 ", // row 1
" ", // row 2
" ", // row 3
])
],
);
// highlight_symbol rendered because selection is made
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
15, // width
0, // spacing
Some(0), // selection
[
">>>ABCDE 12345 ", // row 1
" ", // row 2
" ", // row 3
])
],
);
// highlight_symbol always rendered even no selection is made
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
15, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
15, // width
0, // spacing
None, // selection
[
" ABCDE 12345 ", // row 1
" ", // row 2
" ", // row 3
])
],
);
// no highlight_symbol rendered because no selection is made
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
15, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
15, // width
0, // spacing
Some(0), // selection
[
">>>ABCDE 12345 ", // row 1
" ", // row 2
" ", // row 3
])
],
);
}
@ -1520,31 +1512,27 @@ mod tests {
#[test]
fn insufficient_area_highlight_symbol_and_column_spacing_allocation() {
// column spacing is prioritized over every other constraint
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
10, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
10, // width
1, // spacing
None, // selection
[
"ABCDE 1234", // spacing is prioritized and column is cut
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
1, // spacing
None, // selection
[
"ABCDE 1234", // spacing is prioritized and column is cut
" ", // row 2
" ", // row 3
])
],
);
// this test checks that space for highlight_symbol space is always allocated.
@ -1555,59 +1543,51 @@ mod tests {
// Then in a separate step, column widths are calculated.
// column spacing is prioritized when column widths are calculated and last column here
// ends up with just 1 wide
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
10, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
10, // width
1, // spacing
None, // selection
[
" ABC 123", // highlight_symbol and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
// the following are specification tests
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
9, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
9, // width
1, // spacing
None, // selection
[
" ABC 12", // highlight_symbol and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
8, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
8, // width
1, // spacing
None, // selection
[
" AB 12", // highlight_symbol and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
7, // width
1, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
7, // width
1, // spacing
None, // selection
[
" AB 1", // highlight_symbol and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
let table = Table::default()
@ -1620,10 +1600,13 @@ mod tests {
let mut buf = Buffer::empty(area);
Widget::render(table, area, &mut buf);
// highlight_symbol and spacing are prioritized but columns are evenly distributed
assert_buffer_eq!(
buf,
Buffer::with_lines(vec![" ABCDE 1", " ", " ",])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
" ABCDE 1",
" ",
" ",
]);
assert_eq!(buf, expected);
let table = Table::default()
.rows(vec![Row::new(vec!["ABCDE", "12345"])])
@ -1635,137 +1618,122 @@ mod tests {
let mut buf = Buffer::empty(area);
Widget::render(table, area, &mut buf);
// highlight_symbol and spacing are prioritized but columns are evenly distributed
assert_buffer_eq!(
buf,
Buffer::with_lines(vec![" ABC 123", " ", " ",])
);
#[rustfmt::skip]
let expected = Buffer::with_lines([
" ABC 123",
" ",
" ",
]);
assert_eq!(buf, expected);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
10, // width
1, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
10, // width
1, // spacing
Some(0), // selection
[
"ABCDE 1234", // spacing is prioritized
" ",
" ",
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
1, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
1, // spacing
Some(0), // selection
[
">>>ABC 123", // row 1
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
10, // width
1, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
10, // width
1, // spacing
Some(0), // selection
[
">>>ABC 123", // highlight column and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
}
#[test]
fn insufficient_area_highlight_symbol_allocation_with_no_column_spacing() {
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
10, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
10, // width
0, // spacing
None, // selection
[
"ABCDE12345", // row 1
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
0, // spacing
None, // selection
[
"ABCDE12345", // row 1
" ", // row 2
" ", // row 3
])
],
);
// highlight symbol spacing is prioritized over all constraints
// even if the constraints are fixed length
// this is because highlight_symbol column is separated _before_ any of the constraint
// widths are calculated
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
10, // width
0, // spacing
None, // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
10, // width
0, // spacing
None, // selection
[
" ABCD123", // highlight column and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Never,
10, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Never,
10, // width
0, // spacing
Some(0), // selection
[
"ABCDE12345", // row 1
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::WhenSelected,
10, // width
0, // spacing
Some(0), // selection
[
">>>ABCD123", // highlight column and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
assert_buffer_eq!(
test_table_with_selection(
HighlightSpacing::Always,
10, // width
0, // spacing
Some(0), // selection
),
Buffer::with_lines(vec![
test_table_with_selection(
HighlightSpacing::Always,
10, // width
0, // spacing
Some(0), // selection
[
">>>ABCD123", // highlight column and spacing are prioritized
" ", // row 2
" ", // row 3
])
],
);
}
}

View file

@ -333,7 +333,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_buffer_eq;
#[test]
fn new() {
@ -379,60 +378,61 @@ mod tests {
);
}
fn render(tabs: Tabs, area: Rect) -> Buffer {
#[track_caller]
fn test_case(tabs: Tabs, area: Rect, expected: &Buffer) {
let mut buffer = Buffer::empty(area);
tabs.render(area, &mut buffer);
buffer
assert_eq!(&buffer, expected);
}
#[test]
fn render_default() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
let mut expected = Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
// first tab selected
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]
fn render_no_padding() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding("", "");
let mut expected = Buffer::with_lines(vec!["Tab1│Tab2│Tab3│Tab4 "]);
let mut expected = Buffer::with_lines(["Tab1│Tab2│Tab3│Tab4 "]);
// first tab selected
expected.set_style(Rect::new(0, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]
fn render_more_padding() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding("---", "++");
let mut expected = Buffer::with_lines(vec!["---Tab1++│---Tab2++│---Tab3++│"]);
let mut expected = Buffer::with_lines(["---Tab1++│---Tab2++│---Tab3++│"]);
// first tab selected
expected.set_style(Rect::new(3, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]
fn render_with_block() {
let tabs =
Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).block(Block::bordered().title("Tabs"));
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"┌Tabs────────────────────────┐",
"│ Tab1 │ Tab2 │ Tab3 │ Tab4 │",
"└────────────────────────────┘",
]);
// first tab selected
expected.set_style(Rect::new(2, 1, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 3)), expected);
test_case(tabs, Rect::new(0, 0, 30, 3), &expected);
}
#[test]
fn render_style() {
let tabs =
Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).style(Style::default().fg(Color::Red));
let mut expected = Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 ".red()]);
let mut expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 ".red()]);
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE.red());
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]
@ -440,40 +440,32 @@ mod tests {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]);
// first tab selected
assert_buffer_eq!(
render(tabs.clone().select(0), Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![Line::from(vec![
" ".into(),
"Tab1".reversed(),
" │ Tab2 │ Tab3 │ Tab4 ".into(),
])])
);
let expected = Buffer::with_lines([Line::from(vec![
" ".into(),
"Tab1".reversed(),
" │ Tab2 │ Tab3 │ Tab4 ".into(),
])]);
test_case(tabs.clone().select(0), Rect::new(0, 0, 30, 1), &expected);
// second tab selected
assert_buffer_eq!(
render(tabs.clone().select(1), Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![Line::from(vec![
" Tab1 │ ".into(),
"Tab2".reversed(),
" │ Tab3 │ Tab4 ".into(),
])])
);
let expected = Buffer::with_lines([Line::from(vec![
" Tab1 │ ".into(),
"Tab2".reversed(),
" │ Tab3 │ Tab4 ".into(),
])]);
test_case(tabs.clone().select(1), Rect::new(0, 0, 30, 1), &expected);
// last tab selected
assert_buffer_eq!(
render(tabs.clone().select(3), Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![Line::from(vec![
" Tab1 │ Tab2 │ Tab3 │ ".into(),
"Tab4".reversed(),
" ".into(),
])])
);
let expected = Buffer::with_lines([Line::from(vec![
" Tab1 │ Tab2 │ Tab3 │ ".into(),
"Tab4".reversed(),
" ".into(),
])]);
test_case(tabs.clone().select(3), Rect::new(0, 0, 30, 1), &expected);
// out of bounds selects no tab
assert_buffer_eq!(
render(tabs.clone().select(4), Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │ Tab4 "])
);
let expected = Buffer::with_lines([" Tab1 │ Tab2 │ Tab3 │ Tab4 "]);
test_case(tabs.clone().select(4), Rect::new(0, 0, 30, 1), &expected);
}
#[test]
@ -482,23 +474,21 @@ mod tests {
.style(Style::new().red())
.highlight_style(Style::new().underlined())
.select(0);
assert_buffer_eq!(
render(tabs, Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![Line::from(vec![
" ".red(),
"Tab1".red().underlined(),
" │ Tab2 │ Tab3 │ Tab4 ".red(),
])])
);
let expected = Buffer::with_lines([Line::from(vec![
" ".red(),
"Tab1".red().underlined(),
" │ Tab2 │ Tab3 │ Tab4 ".red(),
])]);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]
fn render_divider() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).divider("--");
let mut expected = Buffer::with_lines(vec![" Tab1 -- Tab2 -- Tab3 -- Tab4 "]);
let mut expected = Buffer::with_lines([" Tab1 -- Tab2 -- Tab3 -- Tab4 "]);
// first tab selected
expected.set_style(Rect::new(1, 0, 4, 1), DEFAULT_HIGHLIGHT_STYLE);
assert_buffer_eq!(render(tabs, Rect::new(0, 0, 30, 1)), expected);
test_case(tabs, Rect::new(0, 0, 30, 1), &expected);
}
#[test]

View file

@ -42,7 +42,11 @@ impl AppState {
/// Renders the list to a `TestBackend` and asserts that the result matches the expected buffer.
#[track_caller]
fn assert_buffer(state: &mut AppState, expected: &Buffer) {
fn assert_buffer<'line, Lines>(state: &mut AppState, expected: Lines)
where
Lines: IntoIterator,
Lines::Item: Into<Line<'line>>,
{
let backend = TestBackend::new(21, 5);
let mut terminal = Terminal::new(backend).unwrap();
terminal
@ -76,7 +80,7 @@ fn assert_buffer(state: &mut AppState, expected: &Buffer) {
f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar);
})
.unwrap();
terminal.backend().assert_buffer(expected);
terminal.backend().assert_buffer_lines(expected);
}
const DEFAULT_STATE_BUFFER: [&str; 5] = [
@ -106,19 +110,15 @@ const DEFAULT_STATE_REPR: &str = r#"{
#[test]
fn default_state_serialize() {
let mut state = AppState::default();
let expected = Buffer::with_lines(DEFAULT_STATE_BUFFER);
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, DEFAULT_STATE_BUFFER);
let state = serde_json::to_string_pretty(&state).unwrap();
assert_eq!(state, DEFAULT_STATE_REPR);
}
#[test]
fn default_state_deserialize() {
let expected = Buffer::with_lines(DEFAULT_STATE_BUFFER);
let mut state: AppState = serde_json::from_str(DEFAULT_STATE_REPR).unwrap();
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, DEFAULT_STATE_BUFFER);
}
const SELECTED_STATE_BUFFER: [&str; 5] = [
@ -148,19 +148,15 @@ const SELECTED_STATE_REPR: &str = r#"{
fn selected_state_serialize() {
let mut state = AppState::default();
state.select(1);
let expected = Buffer::with_lines(SELECTED_STATE_BUFFER);
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, SELECTED_STATE_BUFFER);
let state = serde_json::to_string_pretty(&state).unwrap();
assert_eq!(state, SELECTED_STATE_REPR);
}
#[test]
fn selected_state_deserialize() {
let expected = Buffer::with_lines(SELECTED_STATE_BUFFER);
let mut state: AppState = serde_json::from_str(SELECTED_STATE_REPR).unwrap();
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, SELECTED_STATE_BUFFER);
}
const SCROLLED_STATE_BUFFER: [&str; 5] = [
@ -191,17 +187,13 @@ const SCROLLED_STATE_REPR: &str = r#"{
fn scrolled_state_serialize() {
let mut state = AppState::default();
state.select(8);
let expected = Buffer::with_lines(SCROLLED_STATE_BUFFER);
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, SCROLLED_STATE_BUFFER);
let state = serde_json::to_string_pretty(&state).unwrap();
assert_eq!(state, SCROLLED_STATE_REPR);
}
#[test]
fn scrolled_state_deserialize() {
let expected = Buffer::with_lines(SCROLLED_STATE_BUFFER);
let mut state: AppState = serde_json::from_str(SCROLLED_STATE_REPR).unwrap();
assert_buffer(&mut state, &expected);
assert_buffer(&mut state, SCROLLED_STATE_BUFFER);
}

View file

@ -28,7 +28,7 @@ fn barchart_can_be_stylized() {
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
" ██ ",
" ▅▅ ██ ",
"▂▂ ██ ██ ",
@ -55,7 +55,6 @@ fn barchart_can_be_stylized() {
expected.get_mut(x * 3, 4).set_fg(Color::Blue);
expected.get_mut(x * 3 + 1, 4).set_fg(Color::Reset);
}
terminal.backend().assert_buffer(&expected);
}
@ -72,7 +71,8 @@ fn block_can_be_stylized() -> io::Result<()> {
f.render_widget(block, area);
})?;
let mut expected = Buffer::with_lines(vec![
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"┌Title─┐ ",
"│ │ ",
"└──────┘ ",
@ -89,7 +89,6 @@ fn block_can_be_stylized() -> io::Result<()> {
for x in 1..=5 {
expected.get_mut(x, 0).set_fg(Color::LightBlue);
}
terminal.backend().assert_buffer(&expected);
Ok(())
}
@ -104,7 +103,7 @@ fn paragraph_can_be_stylized() -> io::Result<()> {
f.render_widget(paragraph, area);
})?;
let mut expected = Buffer::with_lines(vec!["Text "]);
let mut expected = Buffer::with_lines(["Text "]);
for x in 0..4 {
expected.get_mut(x, 0).set_fg(Color::Cyan);
}

View file

@ -1,10 +1,8 @@
use std::error::Error;
use ratatui::{
assert_buffer_eq,
backend::{Backend, TestBackend},
layout::Rect,
prelude::Buffer,
widgets::{Paragraph, Widget},
Terminal, TerminalOptions, Viewport,
};
@ -105,16 +103,13 @@ fn terminal_insert_before_moves_viewport() -> Result<(), Box<dyn Error>> {
f.render_widget(paragraph, f.size());
})?;
assert_buffer_eq!(
terminal.backend().buffer().clone(),
Buffer::with_lines(vec![
"------ Line 1 ------",
"------ Line 2 ------",
"[---- Viewport ----]",
" ",
" ",
])
);
terminal.backend().assert_buffer_lines([
"------ Line 1 ------",
"------ Line 2 ------",
"[---- Viewport ----]",
" ",
" ",
]);
Ok(())
}
@ -150,16 +145,13 @@ fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box<dyn Error>>
f.render_widget(paragraph, f.size());
})?;
assert_buffer_eq!(
terminal.backend().buffer().clone(),
Buffer::with_lines(vec![
"------ Line 2 ------",
"------ Line 3 ------",
"------ Line 4 ------",
"------ Line 5 ------",
"[---- Viewport ----]",
])
);
terminal.backend().assert_buffer_lines([
"------ Line 2 ------",
"------ Line 3 ------",
"------ Line 4 ------",
"------ Line 5 ------",
"[---- Viewport ----]",
]);
Ok(())
}
@ -205,16 +197,13 @@ fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box<dyn Error>
f.render_widget(paragraph, f.size());
})?;
assert_buffer_eq!(
terminal.backend().buffer().clone(),
Buffer::with_lines(vec![
"------ Line 2 ------",
"------ Line 3 ------",
"------ Line 4 ------",
"------ Line 5 ------",
"[---- Viewport ----]",
])
);
terminal.backend().assert_buffer_lines([
"------ Line 2 ------",
"------ Line 3 ------",
"------ Line 4 ------",
"------ Line 5 ------",
"[---- Viewport ----]",
]);
Ok(())
}

View file

@ -6,29 +6,24 @@ use ratatui::{
Terminal,
};
// check that bars fill up correctly up to max value
#[test]
fn widgets_barchart_not_full_below_max_value() {
let test_case = |expected| {
let backend = TestBackend::new(30, 10);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let barchart = BarChart::default()
.block(Block::bordered())
.data(&[("empty", 0), ("half", 50), ("almost", 99), ("full", 100)])
.max(100)
.bar_width(7)
.bar_gap(0);
f.render_widget(barchart, size);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
// check that bars fill up correctly up to max value
test_case(Buffer::with_lines(vec![
let backend = TestBackend::new(30, 10);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let barchart = BarChart::default()
.block(Block::bordered())
.data(&[("empty", 0), ("half", 50), ("almost", 99), ("full", 100)])
.max(100)
.bar_width(7)
.bar_gap(0);
f.render_widget(barchart, size);
})
.unwrap();
terminal.backend().assert_buffer_lines([
"┌────────────────────────────┐",
"│ ▇▇▇▇▇▇▇███████│",
"│ ██████████████│",
@ -39,47 +34,43 @@ fn widgets_barchart_not_full_below_max_value() {
"│ ██50█████99█████100██│",
"│ empty half almost full │",
"└────────────────────────────┘",
]));
]);
}
#[test]
fn widgets_barchart_group() {
const TERMINAL_HEIGHT: u16 = 11;
let test_case = |expected| {
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
let mut terminal = Terminal::new(backend).unwrap();
let backend = TestBackend::new(35, TERMINAL_HEIGHT);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let barchart = BarChart::default()
.block(Block::bordered())
.data(
BarGroup::default().label("Mar".into()).bars(&[
Bar::default()
.value(10)
.label("C1".into())
.style(Style::default().fg(Color::Red))
.value_style(Style::default().fg(Color::Blue)),
Bar::default()
.value(20)
.style(Style::default().fg(Color::Green))
.text_value("20M".to_string()),
]),
)
.data(&vec![("C1", 50), ("C2", 40)])
.data(&[("C1", 60), ("C2", 90)])
.data(&[("xx", 10), ("xx", 10)])
.group_gap(2)
.bar_width(4)
.bar_gap(1);
f.render_widget(barchart, size);
})
.unwrap();
terminal
.draw(|f| {
let size = f.size();
let barchart = BarChart::default()
.block(Block::bordered())
.data(
BarGroup::default().label("Mar".into()).bars(&[
Bar::default()
.value(10)
.label("C1".into())
.style(Style::default().fg(Color::Red))
.value_style(Style::default().fg(Color::Blue)),
Bar::default()
.value(20)
.style(Style::default().fg(Color::Green))
.text_value("20M".to_string()),
]),
)
.data(&vec![("C1", 50), ("C2", 40)])
.data(&[("C1", 60), ("C2", 90)])
.data(&[("xx", 10), ("xx", 10)])
.group_gap(2)
.bar_width(4)
.bar_gap(1);
f.render_widget(barchart, size);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"┌─────────────────────────────────┐",
"│ ████│",
"│ ████│",
@ -92,16 +83,13 @@ fn widgets_barchart_group() {
"│Mar │",
"└─────────────────────────────────┘",
]);
for y in 1..(TERMINAL_HEIGHT - 3) {
for x in 1..5 {
expected.get_mut(x, y).set_fg(Color::Red);
expected.get_mut(x + 5, y).set_fg(Color::Green);
}
}
expected.get_mut(2, 7).set_fg(Color::Blue);
expected.get_mut(3, 7).set_fg(Color::Blue);
test_case(expected);
terminal.backend().assert_buffer(&expected);
}

File diff suppressed because it is too large Load diff

View file

@ -12,10 +12,9 @@ use ratatui::{
use time::{Date, Month};
#[track_caller]
fn test_render<W: Widget>(widget: W, expected: &Buffer, width: u16, height: u16) {
fn test_render<W: Widget>(widget: W, width: u16, height: u16, expected: &Buffer) {
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| f.render_widget(widget, f.size()))
.unwrap();
@ -28,14 +27,14 @@ fn days_layout() {
Date::from_calendar_date(2023, Month::January, 1).unwrap(),
CalendarEventStore::default(),
);
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" 1 2 3 4 5 6 7",
" 8 9 10 11 12 13 14",
" 15 16 17 18 19 20 21",
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, &expected, 21, 5);
test_render(c, 21, 5, &expected);
}
#[test]
@ -45,7 +44,7 @@ fn days_layout_show_surrounding() {
CalendarEventStore::default(),
)
.show_surrounding(Style::default());
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" 26 27 28 29 30 1 2",
" 3 4 5 6 7 8 9",
" 10 11 12 13 14 15 16",
@ -53,7 +52,7 @@ fn days_layout_show_surrounding() {
" 24 25 26 27 28 29 30",
" 31 1 2 3 4 5 6",
]);
test_render(c, &expected, 21, 6);
test_render(c, 21, 6, &expected);
}
#[test]
@ -63,7 +62,7 @@ fn show_month_header() {
CalendarEventStore::default(),
)
.show_month_header(Style::default());
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" January 2023 ",
" 1 2 3 4 5 6 7",
" 8 9 10 11 12 13 14",
@ -71,7 +70,7 @@ fn show_month_header() {
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, &expected, 21, 6);
test_render(c, 21, 6, &expected);
}
#[test]
@ -81,7 +80,7 @@ fn show_weekdays_header() {
CalendarEventStore::default(),
)
.show_weekdays_header(Style::default());
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" Su Mo Tu We Th Fr Sa",
" 1 2 3 4 5 6 7",
" 8 9 10 11 12 13 14",
@ -89,7 +88,7 @@ fn show_weekdays_header() {
" 22 23 24 25 26 27 28",
" 29 30 31",
]);
test_render(c, &expected, 21, 6);
test_render(c, 21, 6, &expected);
}
#[test]
@ -101,7 +100,7 @@ fn show_combo() {
.show_weekdays_header(Style::default())
.show_month_header(Style::default())
.show_surrounding(Style::default());
let expected = Buffer::with_lines(vec![
let expected = Buffer::with_lines([
" January 2023 ",
" Su Mo Tu We Th Fr Sa",
" 1 2 3 4 5 6 7",
@ -110,5 +109,5 @@ fn show_combo() {
" 22 23 24 25 26 27 28",
" 29 30 31 1 2 3 4",
]);
test_render(c, &expected, 21, 7);
test_render(c, 21, 7, &expected);
}

View file

@ -29,7 +29,7 @@ fn widgets_canvas_draw_labels() {
})
.unwrap();
let mut expected = Buffer::with_lines(vec![" ", " ", " ", " ", "test "]);
let mut expected = Buffer::with_lines(["", "", "", "", "test "]);
for row in 0..5 {
for col in 0..5 {
expected.get_mut(col, row).set_bg(Color::Yellow);

View file

@ -14,9 +14,16 @@ fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
labels.iter().map(|l| Span::from(*l)).collect()
}
fn axis_test_case<'a, S>(width: u16, height: u16, x_axis: Axis, y_axis: Axis, lines: Vec<S>)
where
S: Into<text::Line<'a>>,
#[track_caller]
fn axis_test_case<'line, Lines>(
width: u16,
height: u16,
x_axis: Axis,
y_axis: Axis,
expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<text::Line<'line>>,
{
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
@ -26,8 +33,7 @@ where
f.render_widget(chart, f.size());
})
.unwrap();
let expected = Buffer::with_lines(lines);
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer_lines(expected);
}
#[rstest]
@ -62,195 +68,192 @@ fn widgets_chart_can_render_on_small_areas(#[case] width: u16, #[case] height: u
.unwrap();
}
#[test]
fn widgets_chart_handles_long_labels() {
let test_case = |x_labels, y_labels, x_alignment, lines| {
let mut x_axis = Axis::default().bounds([0.0, 1.0]);
if let Some((left_label, right_label)) = x_labels {
x_axis = x_axis
.labels(vec![Span::from(left_label), Span::from(right_label)])
.labels_alignment(x_alignment);
}
let mut y_axis = Axis::default().bounds([0.0, 1.0]);
if let Some((left_label, right_label)) = y_labels {
y_axis = y_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
}
axis_test_case(10, 5, x_axis, y_axis, lines);
};
test_case(
Some(("AAAA", "B")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B",
],
);
test_case(
Some(("A", "BBBB")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ─────────",
"A BBBB",
],
);
test_case(
Some(("AAAAAAAAAAA", "B")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B",
],
);
test_case(
Some(("A", "B")),
Some(("CCCCCCC", "D")),
Alignment::Left,
vec![
"D │ ",
"",
"CCC│ ",
" └──────",
" A B",
],
);
test_case(
Some(("AAAAAAAAAA", "B")),
Some(("C", "D")),
Alignment::Center,
vec![
"D │ ",
"",
"C │ ",
" └──────",
"AAAAAAA B",
],
);
test_case(
Some(("AAAAAAA", "B")),
Some(("C", "D")),
Alignment::Right,
vec![
"D│ ",
"",
"C│ ",
" └────────",
" AAAAA B",
],
);
test_case(
Some(("AAAAAAA", "BBBBBBB")),
Some(("C", "D")),
Alignment::Right,
vec![
"D│ ",
"",
"C│ ",
" └────────",
" AAAAABBBB",
],
);
#[rstest]
#[case(
Some(("AAAA", "B")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B",
],
)]
#[case(
Some(("A", "BBBB")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ─────────",
"A BBBB",
],
)]
#[case(
Some(("AAAAAAAAAAA", "B")),
None,
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B",
],
)]
#[case(
Some(("A", "B")),
Some(("CCCCCCC", "D")),
Alignment::Left,
vec![
"D │ ",
"",
"CCC│ ",
" └──────",
" A B",
],
)]
#[case(
Some(("AAAAAAAAAA", "B")),
Some(("C", "D")),
Alignment::Center,
vec![
"D │ ",
"",
"C │ ",
" └──────",
"AAAAAAA B",
],
)]
#[case(
Some(("AAAAAAA", "B")),
Some(("C", "D")),
Alignment::Right,
vec![
"D│ ",
"",
"C│ ",
" └────────",
" AAAAA B",
],
)]
#[case(
Some(("AAAAAAA", "BBBBBBB")),
Some(("C", "D")),
Alignment::Right,
vec![
"D│ ",
"",
"C│ ",
" └────────",
" AAAAABBBB",
],
)]
fn widgets_chart_handles_long_labels<'line, Lines>(
#[case] x_labels: Option<(&str, &str)>,
#[case] y_labels: Option<(&str, &str)>,
#[case] x_alignment: Alignment,
#[case] expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<text::Line<'line>>,
{
let mut x_axis = Axis::default().bounds([0.0, 1.0]);
if let Some((left_label, right_label)) = x_labels {
x_axis = x_axis
.labels(vec![Span::from(left_label), Span::from(right_label)])
.labels_alignment(x_alignment);
}
let mut y_axis = Axis::default().bounds([0.0, 1.0]);
if let Some((left_label, right_label)) = y_labels {
y_axis = y_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
}
axis_test_case(10, 5, x_axis, y_axis, expected);
}
#[test]
fn widgets_chart_handles_x_axis_labels_alignments() {
let test_case = |y_alignment, lines| {
let x_axis = Axis::default()
.labels(vec![Span::from("AAAA"), Span::from("B"), Span::from("C")])
.labels_alignment(y_alignment);
let y_axis = Axis::default();
axis_test_case(10, 5, x_axis, y_axis, lines);
};
test_case(
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B C",
],
);
test_case(
Alignment::Center,
vec![
" ",
" ",
" ",
" ────────",
"AAAA B C",
],
);
test_case(
Alignment::Right,
vec![
" ",
" ",
" ",
"──────────",
"AAA B C",
],
);
#[rstest]
#[case::left(
Alignment::Left,
vec![
" ",
" ",
" ",
" ───────",
"AAA B C",
],
)]
#[case::center(
Alignment::Center,
vec![
" ",
" ",
" ",
" ────────",
"AAAA B C",
],
)]
#[case::right(
Alignment::Right,
vec![
" ",
" ",
" ",
"──────────",
"AAA B C",
],
)]
fn widgets_chart_handles_x_axis_labels_alignments<'line, Lines>(
#[case] y_alignment: Alignment,
#[case] expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<text::Line<'line>>,
{
let x_axis = Axis::default()
.labels(vec![Span::from("AAAA"), Span::from("B"), Span::from("C")])
.labels_alignment(y_alignment);
let y_axis = Axis::default();
axis_test_case(10, 5, x_axis, y_axis, expected);
}
#[test]
fn widgets_chart_handles_y_axis_labels_alignments() {
let test_case = |y_alignment, lines| {
let x_axis = Axis::default().labels(create_labels(&["AAAAA", "B"]));
let y_axis = Axis::default()
.labels(create_labels(&["C", "D"]))
.labels_alignment(y_alignment);
axis_test_case(20, 5, x_axis, y_axis, lines);
};
test_case(
Alignment::Left,
vec![
"D │ ",
"",
"C │ ",
" └───────────────",
"AAAAA B",
],
);
test_case(
Alignment::Center,
vec![
" D │ ",
"",
" C │ ",
" └───────────────",
"AAAAA B",
],
);
test_case(
Alignment::Right,
vec![
" D│ ",
"",
" C│ ",
" └───────────────",
"AAAAA B",
],
);
#[rstest]
#[case::left(Alignment::Left, [
"D │ ",
"",
"C │ ",
" └───────────────",
"AAAAA B",
])]
#[case::center(Alignment::Center, [
" D │ ",
"",
" C │ ",
" └───────────────",
"AAAAA B",
])]
#[case::right(Alignment::Right, [
" D│ ",
"",
" C│ ",
" └───────────────",
"AAAAA B",
])]
fn widgets_chart_handles_y_axis_labels_alignments<'line, Lines>(
#[case] y_alignment: Alignment,
#[case] expected: Lines,
) where
Lines: IntoIterator,
Lines::Item: Into<text::Line<'line>>,
{
let x_axis = Axis::default().labels(create_labels(&["AAAAA", "B"]));
let y_axis = Axis::default()
.labels(create_labels(&["C", "D"]))
.labels_alignment(y_alignment);
axis_test_case(20, 5, x_axis, y_axis, expected);
}
#[test]
@ -431,7 +434,7 @@ fn widgets_chart_can_have_a_legend() {
);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"┌Chart Test────────────────────────────────────────────────┐",
"│10.0│Y Axis ┌─────────┐│",
"│ │ •• │Dataset 1││",
@ -611,7 +614,6 @@ fn widgets_chart_can_have_a_legend() {
for (col, row) in x_axis_title {
expected.get_mut(col, row).set_fg(Color::Yellow);
}
terminal.backend().assert_buffer(&expected);
}
@ -641,7 +643,7 @@ fn widgets_chart_top_line_styling_is_correct() {
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"b│abc••••",
"",
"",

View file

@ -35,7 +35,7 @@ fn widgets_gauge_renders() {
f.render_widget(gauge, chunks[1]);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
" ",
" ",
" ┌Percentage────────────────────────┐ ",
@ -82,7 +82,7 @@ fn widgets_gauge_renders_no_unicode() {
f.render_widget(gauge, chunks[1]);
})
.unwrap();
let expected = Buffer::with_lines(vec![
terminal.backend().assert_buffer_lines([
" ",
" ",
" ┌Percentage────────────────────────┐ ",
@ -94,7 +94,6 @@ fn widgets_gauge_renders_no_unicode() {
" ",
" ",
]);
terminal.backend().assert_buffer(&expected);
}
#[test]
@ -119,7 +118,7 @@ fn widgets_gauge_applies_styles() {
f.render_widget(gauge, f.size());
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"┌Test──────┐",
"│████ │",
"│███43% │",
@ -171,8 +170,7 @@ fn widgets_gauge_supports_large_labels() {
f.render_widget(gauge, f.size());
})
.unwrap();
let expected = Buffer::with_lines(vec!["4333333333"]);
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer_lines(["4333333333"]);
}
#[test]
@ -209,7 +207,7 @@ fn widgets_line_gauge_renders() {
);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
"43% ────────────────",
"┌Gauge 2───────────┐",
"│21% ━━━━━━━━━━━━━━│",

View file

@ -8,6 +8,7 @@ use ratatui::{
widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState},
Terminal,
};
use rstest::rstest;
#[test]
fn list_should_shows_the_length() {
@ -45,7 +46,12 @@ fn widgets_list_should_highlight_the_selected_item() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![" Item 1 ", ">> Item 2 ", " Item 3 "]);
#[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);
}
@ -75,9 +81,12 @@ fn widgets_list_should_highlight_the_selected_item_wide_symbol() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![" Item 1 ", "▶ Item 2 ", " Item 3 "]);
#[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);
}
@ -95,7 +104,7 @@ fn widgets_list_should_truncate_items() {
let backend = TestBackend::new(10, 2);
let mut terminal = Terminal::new(backend).unwrap();
let cases = vec![
let cases = [
// An item is selected
TruncateTestCase {
selected: Some(0),
@ -103,7 +112,7 @@ fn widgets_list_should_truncate_items() {
ListItem::new("A very long line"),
ListItem::new("A very long line"),
],
expected: Buffer::with_lines(vec![
expected: Buffer::with_lines([
format!(">> A ve{} ", symbols::line::VERTICAL),
format!(" A ve{} ", symbols::line::VERTICAL),
]),
@ -115,7 +124,7 @@ fn widgets_list_should_truncate_items() {
ListItem::new("A very long line"),
ListItem::new("A very long line"),
],
expected: Buffer::with_lines(vec![
expected: Buffer::with_lines([
format!("A very {} ", symbols::line::VERTICAL),
format!("A very {} ", symbols::line::VERTICAL),
]),
@ -159,8 +168,12 @@ fn widgets_list_should_clamp_offset_if_items_are_removed() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let expected = Buffer::with_lines(vec![" Item 2 ", " Item 3 ", " Item 4 ", ">> Item 5 "]);
terminal.backend().assert_buffer(&expected);
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));
@ -172,8 +185,12 @@ fn widgets_list_should_clamp_offset_if_items_are_removed() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let expected = Buffer::with_lines(vec![" Item 3 ", " ", " ", " "]);
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer_lines([
" Item 3 ",
" ",
" ",
" ",
]);
}
#[test]
@ -196,7 +213,7 @@ fn widgets_list_should_display_multiline_items() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
" Item 1 ",
" Item 1a",
">> Item 2 ",
@ -232,7 +249,7 @@ fn widgets_list_should_repeat_highlight_symbol() {
f.render_stateful_widget(list, size, &mut state);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
let mut expected = Buffer::with_lines([
" Item 1 ",
" Item 1a",
">> Item 2 ",
@ -251,7 +268,6 @@ fn widgets_list_should_repeat_highlight_symbol() {
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![
@ -268,134 +284,98 @@ fn widget_list_should_not_ignore_empty_string_items() {
f.render_widget(list, f.size());
})
.unwrap();
let expected = Buffer::with_lines(vec!["Item 1", "", "", "Item 4"]);
terminal.backend().assert_buffer(&expected);
terminal
.backend()
.assert_buffer_lines(["Item 1", "", "", "Item 4"]);
}
#[allow(clippy::too_many_lines)]
#[test]
fn widgets_list_enable_always_highlight_spacing() {
let test_case = |state: &mut ListState, 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 = 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, state);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
assert_eq!(HighlightSpacing::default(), HighlightSpacing::WhenSelected);
let mut state = ListState::default();
// no selection, "WhenSelected" should only allocate if selected
test_case(
&mut state,
HighlightSpacing::default(),
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
// no selection, "Always" should allocate regardless if selected or not
test_case(
&mut state,
HighlightSpacing::Always,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│ Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// no selection, "Never" should never allocate regadless if selected or not
test_case(
&mut state,
HighlightSpacing::Never,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
// select first, "WhenSelected" should only allocate if selected
state.select(Some(0));
test_case(
&mut state,
HighlightSpacing::default(),
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// select first, "Always" should allocate regardless if selected or not
state.select(Some(0));
test_case(
&mut state,
HighlightSpacing::Always,
Buffer::with_lines(vec![
"┌────────────────────────────┐",
"│>> Item 1 │",
"│ Item 1a │",
"│ Item 2 │",
"│ Item 2b │",
"│ Item 3 │",
"│ Item 3c │",
"└────────────────────────────┘",
]),
);
// 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![
"┌────────────────────────────┐",
"│Item 1 │",
"│Item 1a │",
"│Item 2 │",
"│Item 2b │",
"│Item 3 │",
"│Item 3c │",
"└────────────────────────────┘",
]),
);
#[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));
}

View file

@ -9,19 +9,17 @@ use ratatui::{
/// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal
/// area and comparing the rendered and expected content.
#[allow(clippy::needless_pass_by_value)]
fn test_case(paragraph: Paragraph, expected: Buffer) {
#[track_caller]
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);
terminal.backend().assert_buffer(expected);
}
#[test]
@ -35,7 +33,7 @@ fn widgets_paragraph_renders_double_width_graphemes() {
test_case(
paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌────────┐",
"│コンピュ│",
"│ータ上で│",
@ -66,8 +64,7 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
f.render_widget(paragraph, size);
})
.unwrap();
let expected = Buffer::with_lines(vec![
terminal.backend().assert_buffer_lines([
// 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.
@ -77,7 +74,6 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
"│、 │",
"└────────┘",
]);
terminal.backend().assert_buffer(&expected);
}
#[test]
@ -88,7 +84,7 @@ fn widgets_paragraph_can_wrap_with_a_trailing_nbsp() {
test_case(
paragraph,
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│NBSP\u{00a0}",
"└──────────────────┘",
@ -104,7 +100,7 @@ fn widgets_paragraph_can_scroll_horizontally() {
test_case(
paragraph.clone().alignment(Alignment::Left).scroll((0, 7)),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│在可以水平滚动了!│",
"│ph can scroll hori│",
@ -120,7 +116,7 @@ fn widgets_paragraph_can_scroll_horizontally() {
// only support Alignment::Left
test_case(
paragraph.clone().alignment(Alignment::Right).scroll((0, 7)),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│段落现在可以水平滚│",
"│Paragraph can scro│",
@ -149,7 +145,7 @@ fn widgets_paragraph_can_wrap_its_content() {
test_case(
paragraph.clone().alignment(Alignment::Left),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│The library is │",
"│based on the │",
@ -164,7 +160,7 @@ fn widgets_paragraph_can_wrap_its_content() {
);
test_case(
paragraph.clone().alignment(Alignment::Center),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│ The library is │",
"│ based on the │",
@ -179,7 +175,7 @@ fn widgets_paragraph_can_wrap_its_content() {
);
test_case(
paragraph.clone().alignment(Alignment::Right),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│ The library is│",
"│ based on the│",
@ -208,7 +204,7 @@ fn widgets_paragraph_works_with_padding() {
test_case(
paragraph.clone().alignment(Alignment::Left),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌────────────────────┐",
"│ │",
"│ The library is │",
@ -224,8 +220,8 @@ fn widgets_paragraph_works_with_padding() {
]),
);
test_case(
paragraph.alignment(Alignment::Right),
Buffer::with_lines(vec![
paragraph.clone().alignment(Alignment::Right),
&Buffer::with_lines([
"┌────────────────────┐",
"│ │",
"│ The library is │",
@ -250,7 +246,7 @@ fn widgets_paragraph_works_with_padding() {
test_case(
paragraph.alignment(Alignment::Right),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌────────────────────┐",
"│ │",
"│ This is always │",
@ -284,7 +280,7 @@ fn widgets_paragraph_can_align_spans() {
test_case(
paragraph.clone().alignment(Alignment::Left),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│ This string will│",
"│ override the│",
@ -299,7 +295,7 @@ fn widgets_paragraph_can_align_spans() {
);
test_case(
paragraph.alignment(Alignment::Center),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│ This string will│",
"│ override the│",
@ -333,7 +329,7 @@ fn widgets_paragraph_can_align_spans() {
test_case(
paragraph.clone().alignment(Alignment::Right),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│This string │",
"│will override the │",
@ -348,7 +344,7 @@ fn widgets_paragraph_can_align_spans() {
);
test_case(
paragraph.alignment(Alignment::Left),
Buffer::with_lines(vec![
&Buffer::with_lines([
"┌──────────────────┐",
"│This string │",
"│will override the │",

File diff suppressed because it is too large Load diff

View file

@ -26,8 +26,7 @@ fn widgets_tabs_should_not_panic_on_narrow_areas() {
);
})
.unwrap();
let expected = Buffer::with_lines(vec![" "]);
terminal.backend().assert_buffer(&expected);
terminal.backend().assert_buffer_lines([" "]);
}
#[test]
@ -48,7 +47,7 @@ fn widgets_tabs_should_truncate_the_last_item() {
);
})
.unwrap();
let mut expected = Buffer::with_lines(vec![format!(" Tab1 {} T ", symbols::line::VERTICAL)]);
let mut expected = Buffer::with_lines([format!(" Tab1 {} T ", symbols::line::VERTICAL)]);
expected.set_style(Rect::new(1, 0, 4, 1), Style::new().reversed());
terminal.backend().assert_buffer(&expected);
}