use crate::color::{apply_color_with_dim, apply_colors};
use crate::error::BoxenResult;
use crate::memory::pool::with_pooled_string;
use crate::options::{BoxenOptions, TitleAlignment};
use crate::text::text_width;
use crate::text::wrapping::wrap_text;
use std::fmt::Write;
use unicode_width::UnicodeWidthChar;
pub fn boxen<S: AsRef<str>>(text: S, options: Option<BoxenOptions>) -> BoxenResult<String> {
let text = text.as_ref();
let options = options.unwrap_or_default();
crate::error::validation::validate_all_options(text, &options).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Input validation failed: {e}"),
e.recommendations(),
)
})?;
let processed_content = process_content(text, &options).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Text processing failed: {e}"),
vec![crate::error::ErrorRecommendation::suggestion_only(
"Text processing error".to_string(),
"Check your text content and box dimensions".to_string(),
)],
)
})?;
let layout = options
.calculate_layout_dimensions(
processed_content.content_width,
processed_content.content_height,
)
.map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Layout calculation failed: {e}"),
e.recommendations(),
)
})?;
render_box(&processed_content, &options, &layout).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Box rendering failed: {e}"),
vec![crate::error::ErrorRecommendation::suggestion_only(
"Rendering error".to_string(),
"Check your configuration and try reducing complexity".to_string(),
)],
)
})
}
#[derive(Debug)]
struct ProcessedContent {
lines: Vec<String>,
content_width: usize,
content_height: usize,
}
fn process_content(text: &str, options: &BoxenOptions) -> BoxenResult<ProcessedContent> {
let max_content_width = options.calculate_max_content_width()?;
let max_content_height = options.calculate_max_content_height()?;
let wrapped_lines = if text.is_empty() {
vec![String::new()]
} else {
wrap_text(text, max_content_width)?
};
let natural_content_width = wrapped_lines
.iter()
.map(|line| text_width(line))
.max()
.unwrap_or(0);
let target_width = if options.fullscreen.is_some() || options.width.is_some() {
max_content_width
} else {
natural_content_width.min(max_content_width)
};
let height_constrained_lines = if let Some(max_height) = max_content_height {
crate::text::apply_height_constraints(&wrapped_lines, max_height)
} else {
wrapped_lines
};
let aligned_lines = crate::text::align_lines(
&height_constrained_lines,
options.text_alignment,
target_width,
);
let content_height = aligned_lines.len();
Ok(ProcessedContent {
lines: aligned_lines,
content_width: target_width,
content_height,
})
}
fn render_box(
content: &ProcessedContent,
options: &BoxenOptions,
layout: &crate::options::LayoutDimensions,
) -> BoxenResult<String> {
let border_chars = options.border_style.get_chars()?;
let has_border = options.border_style.is_visible();
let estimated_chars_per_line = layout.total_width + 10; let estimated_lines = layout.total_height + options.margin.vertical();
let estimated_capacity = estimated_chars_per_line * estimated_lines;
let mut result = String::with_capacity(estimated_capacity);
for _ in 0..options.margin.top {
writeln!(result).unwrap();
}
if has_border {
let top_border = render_top_border(&border_chars, options, layout.inner_width)?;
add_line_with_float_positioning(&mut result, &top_border, options, layout);
render_content_with_borders(&mut result, content, options, layout, &border_chars)?;
let bottom_border = render_bottom_border(&border_chars, layout.inner_width, options)?;
add_line_with_float_positioning(&mut result, &bottom_border, options, layout);
} else {
render_content_without_borders(&mut result, content, options, layout)?;
}
for _ in 0..options.margin.bottom {
result.push('\n');
}
if options.margin.bottom == 0 && result.ends_with('\n') {
result.pop();
}
Ok(result)
}
fn render_top_border(
border_chars: &crate::options::BorderChars,
options: &BoxenOptions,
inner_width: usize,
) -> BoxenResult<String> {
with_pooled_string(|border| {
border.reserve(inner_width + 2);
if let Some(title) = &options.title {
render_top_border_with_title_colored(
border,
title,
border_chars,
options,
inner_width,
)?;
} else {
border.push(border_chars.top_left);
for _ in 0..inner_width {
border.push(border_chars.top);
}
border.push(border_chars.top_right);
let styled_border = apply_color_with_dim(
border.as_str(),
options.border_color.as_ref(),
options.dim_border,
)?;
return Ok(styled_border.to_string());
}
Ok(border.as_str().to_string())
})
}
#[allow(clippy::too_many_lines)]
fn render_top_border_with_title_colored(
result: &mut impl Write,
title: &str,
border_chars: &crate::options::BorderChars,
options: &BoxenOptions,
inner_width: usize,
) -> BoxenResult<()> {
let title_width = text_width(title);
let effective_title = if title_width > inner_width {
with_pooled_string(|truncated| {
truncated.reserve(inner_width);
let mut current_width = 0;
for ch in title.chars() {
let char_width = UnicodeWidthChar::width(ch).unwrap_or(1);
if current_width + char_width > inner_width {
break;
}
truncated.push(ch);
current_width += char_width;
}
truncated.as_str().to_string()
})
} else {
title.to_string()
};
let effective_title_width = text_width(&effective_title);
let remaining_width = inner_width - effective_title_width;
let title_color = options
.title_color
.as_ref()
.or(options.border_color.as_ref());
let styled_title = if let Some(color) = title_color {
apply_colors(&effective_title, Some(color), None)?.to_string()
} else {
effective_title.clone()
};
let style_border_char = |ch: char| -> BoxenResult<String> {
let ch_str = ch.to_string();
let styled =
apply_color_with_dim(&ch_str, options.border_color.as_ref(), options.dim_border)?;
Ok(styled.to_string())
};
let style_border_str = |s: &str| -> BoxenResult<String> {
let styled = apply_color_with_dim(s, options.border_color.as_ref(), options.dim_border)?;
Ok(styled.to_string())
};
match options.title_alignment {
TitleAlignment::Left => {
write!(result, "{}", style_border_char(border_chars.top_left)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
write!(result, "{styled_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
let border_fill = border_chars.top.to_string().repeat(remaining_width);
write!(result, "{}", style_border_str(&border_fill)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
write!(result, "{}", style_border_char(border_chars.top_right)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
}
TitleAlignment::Right => {
write!(result, "{}", style_border_char(border_chars.top_left)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
let border_fill = border_chars.top.to_string().repeat(remaining_width);
write!(result, "{}", style_border_str(&border_fill)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
write!(result, "{styled_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
write!(result, "{}", style_border_char(border_chars.top_right)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
}
TitleAlignment::Center => {
let left_padding = remaining_width / 2;
let right_padding = remaining_width - left_padding;
write!(result, "{}", style_border_char(border_chars.top_left)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
let left_border_fill = border_chars.top.to_string().repeat(left_padding);
write!(result, "{}", style_border_str(&left_border_fill)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
write!(result, "{styled_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
let right_border_fill = border_chars.top.to_string().repeat(right_padding);
write!(result, "{}", style_border_str(&right_border_fill)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
write!(result, "{}", style_border_char(border_chars.top_right)?).map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write border: {e}"),
vec![],
)
})?;
}
}
Ok(())
}
fn render_bottom_border(
border_chars: &crate::options::BorderChars,
inner_width: usize,
options: &BoxenOptions,
) -> BoxenResult<String> {
with_pooled_string(|border| {
border.reserve(inner_width + 2);
border.push(border_chars.bottom_left);
for _ in 0..inner_width {
border.push(border_chars.bottom);
}
border.push(border_chars.bottom_right);
let styled_border = apply_color_with_dim(
border.as_str(),
options.border_color.as_ref(),
options.dim_border,
)?;
Ok(styled_border.to_string())
})
}
fn render_content_with_borders(
result: &mut String,
content: &ProcessedContent,
options: &BoxenOptions,
layout: &crate::options::LayoutDimensions,
border_chars: &crate::options::BorderChars,
) -> BoxenResult<()> {
for _ in 0..options.padding.top {
let padded_line = render_padded_empty_line(border_chars, layout.inner_width, options)?;
add_line_with_float_positioning(result, &padded_line, options, layout);
}
for line in &content.lines {
let content_line = render_content_line(line, border_chars, options, layout.inner_width)?;
add_line_with_float_positioning(result, &content_line, options, layout);
}
for _ in 0..options.padding.bottom {
let padded_line = render_padded_empty_line(border_chars, layout.inner_width, options)?;
add_line_with_float_positioning(result, &padded_line, options, layout);
}
Ok(())
}
fn render_content_without_borders(
result: &mut String,
content: &ProcessedContent,
options: &BoxenOptions,
layout: &crate::options::LayoutDimensions,
) -> BoxenResult<()> {
if let Some(title) = &options.title {
let title_line = render_title_without_border(title, options, layout.inner_width)?;
add_line_with_float_positioning(result, &title_line, options, layout);
}
for _ in 0..options.padding.top {
let empty_line = " ".repeat(layout.inner_width);
let styled_line = if let Some(bg_color) = &options.background_color {
apply_colors(&empty_line, None, Some(bg_color))?.to_string()
} else {
empty_line
};
add_line_with_float_positioning(result, &styled_line, options, layout);
}
for line in &content.lines {
let padded_line = with_pooled_string(|buffer| {
buffer.reserve(options.padding.left + line.len() + options.padding.right);
for _ in 0..options.padding.left {
buffer.push(' ');
}
buffer.push_str(line);
for _ in 0..options.padding.right {
buffer.push(' ');
}
buffer.as_str().to_string()
});
let styled_line = if let Some(bg_color) = &options.background_color {
apply_colors(&padded_line, None, Some(bg_color))?.to_string()
} else {
padded_line
};
add_line_with_float_positioning(result, &styled_line, options, layout);
}
for _ in 0..options.padding.bottom {
let empty_line = " ".repeat(layout.inner_width);
let styled_line = if let Some(bg_color) = &options.background_color {
apply_colors(&empty_line, None, Some(bg_color))?.to_string()
} else {
empty_line
};
add_line_with_float_positioning(result, &styled_line, options, layout);
}
Ok(())
}
fn render_content_line(
line: &str,
border_chars: &crate::options::BorderChars,
options: &BoxenOptions,
inner_width: usize,
) -> BoxenResult<String> {
with_pooled_string(|content_area| {
content_area.reserve(inner_width);
for _ in 0..options.padding.left {
content_area.push(' ');
}
content_area.push_str(line);
let current_content_width = text_width(content_area.as_str());
let remaining_width = inner_width - current_content_width;
for _ in 0..remaining_width {
content_area.push(' ');
}
let styled_content = if let Some(bg_color) = &options.background_color {
apply_colors(content_area.as_str(), None, Some(bg_color))?.to_string()
} else {
content_area.as_str().to_string()
};
let left_border = apply_color_with_dim(
&border_chars.left.to_string(),
options.border_color.as_ref(),
options.dim_border,
)?
.to_string();
let right_border = apply_color_with_dim(
&border_chars.right.to_string(),
options.border_color.as_ref(),
options.dim_border,
)?
.to_string();
with_pooled_string(|result| {
result.reserve(left_border.len() + styled_content.len() + right_border.len());
write!(result, "{left_border}{styled_content}{right_border}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write content line: {e}"),
vec![],
)
})?;
Ok(result.as_str().to_string())
})
})
}
fn render_padded_empty_line(
border_chars: &crate::options::BorderChars,
inner_width: usize,
options: &BoxenOptions,
) -> BoxenResult<String> {
with_pooled_string(|content_area| {
content_area.reserve(inner_width);
for _ in 0..inner_width {
content_area.push(' ');
}
let styled_content = if let Some(bg_color) = &options.background_color {
apply_colors(content_area.as_str(), None, Some(bg_color))?.to_string()
} else {
content_area.as_str().to_string()
};
let left_border = apply_color_with_dim(
&border_chars.left.to_string(),
options.border_color.as_ref(),
options.dim_border,
)?
.to_string();
let right_border = apply_color_with_dim(
&border_chars.right.to_string(),
options.border_color.as_ref(),
options.dim_border,
)?
.to_string();
with_pooled_string(|result| {
result.reserve(left_border.len() + styled_content.len() + right_border.len());
write!(result, "{left_border}{styled_content}{right_border}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write padded line: {e}"),
vec![],
)
})?;
Ok(result.as_str().to_string())
})
})
}
fn render_title_without_border(
title: &str,
options: &BoxenOptions,
inner_width: usize,
) -> BoxenResult<String> {
let title_width = text_width(title);
let effective_title = if title_width > inner_width {
with_pooled_string(|truncated| {
truncated.reserve(inner_width);
let mut current_width = 0;
for ch in title.chars() {
let char_width = UnicodeWidthChar::width(ch).unwrap_or(1);
if current_width + char_width > inner_width {
break;
}
truncated.push(ch);
current_width += char_width;
}
truncated.as_str().to_string()
})
} else {
title.to_string()
};
let effective_title_width = text_width(&effective_title);
let remaining_width = inner_width - effective_title_width;
let title_line = with_pooled_string(|buffer| {
buffer.reserve(inner_width);
match options.title_alignment {
TitleAlignment::Left => {
write!(buffer, "{effective_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
for _ in 0..remaining_width {
buffer.push(' ');
}
}
TitleAlignment::Right => {
for _ in 0..remaining_width {
buffer.push(' ');
}
write!(buffer, "{effective_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
}
TitleAlignment::Center => {
let left_padding = remaining_width / 2;
let right_padding = remaining_width - left_padding;
for _ in 0..left_padding {
buffer.push(' ');
}
write!(buffer, "{effective_title}").map_err(|e| {
crate::error::BoxenError::rendering_error(
format!("Failed to write title: {e}"),
vec![],
)
})?;
for _ in 0..right_padding {
buffer.push(' ');
}
}
}
Ok::<String, crate::error::BoxenError>(buffer.as_str().to_string())
})?;
let styled_title = if let Some(bg_color) = &options.background_color {
apply_colors(&title_line, None, Some(bg_color))?.to_string()
} else {
title_line
};
Ok(styled_title)
}
fn add_line_with_float_positioning(
result: &mut String,
line: &str,
options: &BoxenOptions,
layout: &crate::options::LayoutDimensions,
) {
use crate::options::Float;
use crate::terminal::get_terminal_width;
let terminal_width = get_terminal_width();
let box_width_without_margins = layout.total_width - options.margin.horizontal();
let left_spacing = match options.float {
Float::Left => {
options.margin.left
}
Float::Center => {
let available_space = terminal_width.saturating_sub(layout.total_width);
available_space / 2 + options.margin.left
}
Float::Right => {
if terminal_width > box_width_without_margins + options.margin.right {
terminal_width - box_width_without_margins - options.margin.right
} else {
options.margin.left }
}
};
for _ in 0..left_spacing {
result.push(' ');
}
result.push_str(line);
if matches!(options.float, Float::Left) {
for _ in 0..options.margin.right {
result.push(' ');
}
}
result.push('\n');
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::{
BorderStyle, BoxenOptions, Height, Spacing, TextAlignment, TitleAlignment, Width,
};
#[ctor::ctor]
fn init_colors() {
colored::control::set_override(true);
}
#[ctor::ctor]
fn init_terminal_size() {
unsafe {
std::env::set_var("COLUMNS", "80");
std::env::set_var("LINES", "24");
}
}
#[test]
fn test_basic_box_rendering() {
let result = boxen("Hello", None).unwrap();
assert!(result.contains("Hello"));
assert!(result.contains("┌")); assert!(result.contains("┐")); assert!(result.contains("└")); assert!(result.contains("┘")); assert!(result.contains("│")); assert!(result.contains("─"));
assert_eq!(result.lines().count(), 3);
}
#[test]
fn test_empty_text() {
let result = boxen("", None).unwrap();
assert!(result.contains("┌"));
assert!(result.contains("┐"));
assert!(result.contains("└"));
assert!(result.contains("┘"));
assert_eq!(result.lines().count(), 3);
}
#[test]
fn test_multiline_text() {
let result = boxen("Hello\nWorld", None).unwrap();
assert!(result.contains("Hello"));
assert!(result.contains("World"));
assert_eq!(result.lines().count(), 4);
}
#[test]
fn test_no_border_style() {
let options = BoxenOptions {
border_style: BorderStyle::None,
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(!result.contains("┌"));
assert!(!result.contains("│"));
assert_eq!(result.lines().count(), 1);
}
#[test]
fn test_different_border_styles() {
let styles = vec![
BorderStyle::Single,
BorderStyle::Double,
BorderStyle::Round,
BorderStyle::Bold,
BorderStyle::Classic,
];
for style in styles {
let options = BoxenOptions {
border_style: style,
..Default::default()
};
let result = boxen("Test", Some(options)).unwrap();
assert!(result.contains("Test"));
assert_eq!(result.lines().count(), 3);
}
}
#[test]
fn test_padding() {
let options = BoxenOptions {
padding: Spacing {
top: 1,
right: 2,
bottom: 1,
left: 2,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 5);
let lines: Vec<&str> = result.lines().collect();
let content_line = lines[2]; assert!(content_line.contains("Hello"));
assert!(content_line.starts_with("│ Hello"));
}
#[test]
fn test_margins() {
let options = BoxenOptions {
margin: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
..Default::default() };
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 5);
let lines: Vec<&str> = result.lines().collect();
assert_eq!(lines[0], "");
assert_eq!(lines[4], "");
assert!(lines[1].starts_with(" ┌"));
assert!(lines[3].starts_with(" └"));
}
#[test]
fn test_title_basic() {
let options = BoxenOptions {
title: Some("Title".to_string()),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Title"));
assert!(result.contains("Content"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.contains("Title"));
assert!(top_border.starts_with("┌"));
assert!(top_border.ends_with("┐"));
}
#[test]
fn test_title_alignment() {
let alignments = vec![
TitleAlignment::Left,
TitleAlignment::Center,
TitleAlignment::Right,
];
for alignment in alignments {
let options = BoxenOptions {
title: Some("Title".to_string()),
title_alignment: alignment,
width: Some(Width::Fixed(20)), ..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Title"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.contains("Title"));
}
}
#[test]
fn test_title_truncation() {
let options = BoxenOptions {
title: Some("Very Long Title That Should Be Truncated".to_string()),
width: Some(Width::Fixed(15)), ..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.starts_with("┌"));
assert!(top_border.ends_with("┐"));
assert_eq!(text_width(top_border), 15);
}
#[test]
fn test_text_alignment() {
let alignments = vec![
TextAlignment::Left,
TextAlignment::Center,
TextAlignment::Right,
];
for alignment in alignments {
let options = BoxenOptions {
text_alignment: alignment,
width: Some(Width::Fixed(20)), ..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert_eq!(result.lines().count(), 3);
}
}
#[test]
fn test_complex_configuration() {
let options = BoxenOptions {
border_style: BorderStyle::Double,
padding: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
margin: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
title: Some("Test".to_string()),
title_alignment: TitleAlignment::Center,
text_alignment: TextAlignment::Center,
..Default::default() };
let result = boxen("Hello World", Some(options)).unwrap();
assert!(result.contains("Test"));
assert!(result.contains("Hello World"));
assert!(result.contains("╔"));
assert!(result.contains("╗"));
assert!(result.contains("╚"));
assert!(result.contains("╝"));
assert_eq!(result.lines().count(), 7);
}
#[test]
fn test_width_constraint() {
let options = BoxenOptions {
width: Some(Width::Fixed(10)),
..Default::default()
};
let result = boxen("Hi", Some(options)).unwrap();
for line in result.lines() {
assert_eq!(text_width(line), 10);
}
}
#[test]
fn test_process_content() {
let options = BoxenOptions::default();
let content = process_content("Hello\nWorld", &options).unwrap();
assert_eq!(content.lines.len(), 2);
assert!(content.lines[0].contains("Hello"));
assert!(content.lines[1].contains("World"));
assert_eq!(content.content_width, 5);
assert_eq!(content.content_height, 2);
}
#[test]
fn test_render_top_border_no_title() {
use crate::options::BorderChars;
let border_chars = BorderChars::single();
let options = BoxenOptions::default();
let result = render_top_border(&border_chars, &options, 10).unwrap();
assert_eq!(result, "┌──────────┐");
assert_eq!(text_width(&result), 12); }
#[test]
fn test_render_bottom_border() {
use crate::options::BorderChars;
let border_chars = BorderChars::single();
let options = BoxenOptions::default();
let result = render_bottom_border(&border_chars, 10, &options).unwrap();
assert!(result.contains("└"));
assert!(result.contains("┘"));
}
#[test]
fn test_add_line_with_float_positioning() {
use crate::options::Float;
let mut result = String::new();
let options = BoxenOptions {
float: Float::Left,
margin: Spacing {
top: 0,
right: 2,
bottom: 0,
left: 3,
},
..Default::default()
};
let layout = crate::options::LayoutDimensions {
content_width: 4,
content_height: 1,
total_width: 10,
total_height: 3,
inner_width: 4,
inner_height: 1,
};
add_line_with_float_positioning(&mut result, "test", &options, &layout);
assert_eq!(result, " test \n");
}
#[test]
fn test_error_handling() {
let options = BoxenOptions {
width: Some(Width::Fixed(1)), padding: Spacing::from(2), ..Default::default()
};
let result = boxen("Hello", Some(options));
assert!(result.is_err());
}
#[test]
fn test_unicode_content() {
let result = boxen("你好世界", None).unwrap();
assert!(result.contains("你好世界"));
assert!(result.contains("┌"));
assert!(result.contains("┐"));
assert_eq!(result.lines().count(), 3);
}
#[test]
fn test_long_text_wrapping() {
let long_text = "This is a very long line of text that should be wrapped when it exceeds the available width";
let options = BoxenOptions {
width: Some(Width::Fixed(30)),
..Default::default()
};
let result = boxen(long_text, Some(options)).unwrap();
assert!(result.contains("This is a very long"));
assert!(result.lines().count() > 3); }
#[test]
fn test_width_calculation_fix() {
let options = BoxenOptions {
margin: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
width: Some(Width::Fixed(70)),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 5); }
#[test]
fn test_fullscreen_mode_auto() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(result.contains("┌"));
assert!(result.contains("┐"));
assert!(result.contains("└"));
assert!(result.contains("┘"));
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_with_margins() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
margin: Spacing {
top: 1,
right: 2,
bottom: 1,
left: 2,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
let lines: Vec<&str> = result.lines().collect();
assert_eq!(lines[0], "");
assert_eq!(lines[terminal_height - 1], "");
for line in lines.iter().take(terminal_height - 1).skip(1) {
assert!(line.starts_with(" "));
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_with_padding() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
padding: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
assert!(result.contains("Hello"));
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_custom() {
let custom_func =
|width: usize, height: usize| -> (usize, usize) { (width * 3 / 4, height * 3 / 4) };
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Custom(custom_func)),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(result.contains("┌"));
assert!(result.contains("┐"));
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
let expected_width = terminal_width * 3 / 4;
let expected_height = terminal_height * 3 / 4;
assert_eq!(result.lines().count(), expected_height);
for line in result.lines() {
assert_eq!(text_width(line), expected_width);
}
}
#[test]
fn test_fullscreen_mode_with_title() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
title: Some("Test Title".to_string()),
title_alignment: crate::options::TitleAlignment::Center,
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Test Title"));
assert!(result.contains("Content"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.contains("Test Title"));
let terminal_width = crate::terminal::get_terminal_width();
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_multiline_content() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
text_alignment: crate::options::TextAlignment::Center,
..Default::default()
};
let result = boxen("Line 1\nLine 2\nLine 3", Some(options)).unwrap();
assert!(result.contains("Line 1"));
assert!(result.contains("Line 2"));
assert!(result.contains("Line 3"));
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_overrides_width() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
width: Some(Width::Fixed(50)), ..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_no_border() {
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
border_style: BorderStyle::None,
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(!result.contains("┌"));
assert!(!result.contains("│"));
let terminal_width = crate::terminal::get_terminal_width();
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
assert_eq!(result.lines().count(), terminal_height);
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_fullscreen_mode_with_height_constraint() {
let terminal_height = crate::terminal::get_terminal_height().unwrap_or(24);
let options = BoxenOptions {
fullscreen: Some(crate::options::FullscreenMode::Auto),
padding: Spacing::from(1), ..Default::default()
};
let result = boxen("Small content", Some(options)).unwrap();
assert_eq!(result.lines().count(), terminal_height);
let terminal_width = crate::terminal::get_terminal_width();
for line in result.lines() {
assert_eq!(text_width(line), terminal_width);
}
}
#[test]
fn test_title_with_no_border_left_alignment() {
let options = BoxenOptions {
title: Some("Left Title".to_string()),
title_alignment: TitleAlignment::Left,
border_style: BorderStyle::None,
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Left Title"));
assert!(result.contains("Content"));
assert_eq!(result.lines().count(), 2);
let lines: Vec<&str> = result.lines().collect();
let title_line = lines[0];
assert!(title_line.starts_with("Left Title"));
assert_eq!(text_width(title_line), 20);
}
#[test]
fn test_title_with_no_border_center_alignment() {
let options = BoxenOptions {
title: Some("Center".to_string()),
title_alignment: TitleAlignment::Center,
border_style: BorderStyle::None,
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
let lines: Vec<&str> = result.lines().collect();
let title_line = lines[0];
assert!(title_line.contains("Center"));
assert_eq!(text_width(title_line), 20);
let title_start = title_line.find("Center").unwrap();
assert!(title_start > 5); }
#[test]
fn test_title_with_no_border_right_alignment() {
let options = BoxenOptions {
title: Some("Right Title".to_string()),
title_alignment: TitleAlignment::Right,
border_style: BorderStyle::None,
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
let lines: Vec<&str> = result.lines().collect();
let title_line = lines[0];
assert!(title_line.ends_with("Right Title"));
assert_eq!(text_width(title_line), 20);
}
#[test]
fn test_title_truncation_no_border() {
let options = BoxenOptions {
title: Some("This is a very long title that should be truncated".to_string()),
border_style: BorderStyle::None,
width: Some(Width::Fixed(15)),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
let lines: Vec<&str> = result.lines().collect();
let title_line = lines[0];
assert_eq!(text_width(title_line), 15);
assert!(title_line.starts_with("This is a very"));
}
#[test]
fn test_height_constraint_padding() {
let options = BoxenOptions {
height: Some(Height::Fixed(10)), ..Default::default()
};
let result = boxen("Hello\nWorld", Some(options)).unwrap();
assert_eq!(result.lines().count(), 10);
assert!(result.contains("Hello"));
assert!(result.contains("World"));
assert!(result.contains("┌"));
assert!(result.contains("└"));
}
#[test]
fn test_height_constraint_truncation() {
let long_text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8";
let options = BoxenOptions {
height: Some(Height::Fixed(5)), ..Default::default()
};
let result = boxen(long_text, Some(options)).unwrap();
assert_eq!(result.lines().count(), 5);
assert!(result.contains("Line 1"));
assert!(result.contains("Line 2"));
assert!(!result.contains("Line 7"));
assert!(!result.contains("Line 8"));
assert!(result.contains("┌"));
assert!(result.contains("└"));
}
#[test]
fn test_height_constraint_exact_fit() {
let options = BoxenOptions {
height: Some(Height::Fixed(3)), ..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 3);
assert!(result.contains("Hello"));
assert!(result.contains("┌"));
assert!(result.contains("└"));
}
#[test]
fn test_height_constraint_with_padding() {
let options = BoxenOptions {
height: Some(Height::Fixed(8)),
padding: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 8);
assert!(result.contains("Hello"));
let lines: Vec<&str> = result.lines().collect();
assert!(lines[0].contains("┌")); assert!(lines[7].contains("└")); }
#[test]
fn test_height_constraint_with_margins() {
let options = BoxenOptions {
height: Some(Height::Fixed(10)), margin: Spacing {
top: 1,
right: 0,
bottom: 1,
left: 0,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 10);
assert!(result.contains("Hello"));
let lines: Vec<&str> = result.lines().collect();
assert_eq!(lines[0], ""); assert_eq!(lines[9], ""); }
#[test]
fn test_height_constraint_no_border() {
let options = BoxenOptions {
border_style: BorderStyle::None,
height: Some(Height::Fixed(4)), ..Default::default()
};
let result = boxen("Line 1\nLine 2\nLine 3\nLine 4\nLine 5", Some(options)).unwrap();
assert_eq!(result.lines().count(), 4);
assert!(result.contains("Line 1"));
assert!(result.contains("Line 4"));
assert!(!result.contains("Line 5"));
assert!(!result.contains("┌"));
assert!(!result.contains("└"));
}
#[test]
fn test_height_constraint_minimum_height() {
let options = BoxenOptions {
height: Some(Height::Fixed(2)), ..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert_eq!(result.lines().count(), 2);
assert!(result.contains("┌"));
assert!(result.contains("└"));
}
#[test]
fn test_height_constraint_with_title() {
let options = BoxenOptions {
title: Some("Title".to_string()),
height: Some(Height::Fixed(5)),
..Default::default()
};
let result = boxen("Content\nMore content\nEven more", Some(options)).unwrap();
assert_eq!(result.lines().count(), 5);
assert!(result.contains("Title"));
assert!(result.contains("Content"));
let lines: Vec<&str> = result.lines().collect();
assert!(lines[0].contains("Title"));
}
#[test]
fn test_height_constraint_error_handling() {
let options = BoxenOptions {
height: Some(Height::Fixed(1)), padding: Spacing::from(1),
..Default::default()
};
let result = boxen("Hello", Some(options));
assert!(result.is_err());
}
#[test]
fn test_process_content_with_height_constraints() {
let options = BoxenOptions {
height: Some(Height::Fixed(8)), padding: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 1,
},
..Default::default()
};
let content = process_content("Line 1\nLine 2\nLine 3\nLine 4\nLine 5", &options).unwrap();
assert_eq!(content.content_height, 4);
assert_eq!(content.lines.len(), 4);
assert_eq!(content.lines[0], "Line 1");
assert_eq!(content.lines[1], "Line 2");
assert_eq!(content.lines[2], "Line 3");
assert_eq!(content.lines[3], "Line 4");
}
#[test]
fn test_title_with_no_border_and_padding() {
let options = BoxenOptions {
title: Some("Title".to_string()),
border_style: BorderStyle::None,
padding: Spacing {
top: 1,
right: 2,
bottom: 1,
left: 2,
},
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert_eq!(result.lines().count(), 4);
let lines: Vec<&str> = result.lines().collect();
assert!(lines[0].contains("Title")); assert_eq!(lines[1].trim(), ""); assert!(lines[2].contains("Content")); assert_eq!(lines[3].trim(), ""); }
#[test]
fn test_title_with_unicode_characters() {
let options = BoxenOptions {
title: Some("测试标题".to_string()),
title_alignment: TitleAlignment::Center,
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("内容", Some(options)).unwrap();
assert!(result.contains("测试标题"));
assert!(result.contains("内容"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.contains("测试标题"));
}
#[test]
fn test_title_edge_cases() {
let options = BoxenOptions {
title: Some(String::new()),
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Content"));
assert!(result.contains("┌"));
assert!(result.contains("┐"));
}
#[test]
fn test_float_left_positioning() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Left,
margin: Spacing {
top: 0,
right: 0,
bottom: 0,
left: 5, },
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
for line in result.lines() {
if !line.trim().is_empty() {
assert!(line.starts_with(" ")); }
}
}
#[test]
fn test_float_center_positioning() {
use crate::options::Float;
use crate::terminal::get_terminal_width;
let options = BoxenOptions {
float: Float::Center,
width: Some(Width::Fixed(20)), ..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let terminal_width = get_terminal_width();
let expected_left_spacing = (terminal_width - 20) / 2;
for line in result.lines() {
if !line.trim().is_empty() {
let leading_spaces = line.len() - line.trim_start().len();
assert!(
leading_spaces >= expected_left_spacing.saturating_sub(1)
&& leading_spaces <= expected_left_spacing + 1,
"Expected ~{expected_left_spacing} leading spaces, got {leading_spaces}"
);
}
}
}
#[test]
fn test_float_right_positioning() {
use crate::options::Float;
use crate::terminal::get_terminal_width;
let options = BoxenOptions {
float: Float::Right,
width: Some(Width::Fixed(20)), margin: Spacing {
top: 0,
right: 3, bottom: 0,
left: 0,
},
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
for line in result.lines() {
if !line.trim().is_empty() {
let leading_spaces = line.len() - line.trim_start().len();
let terminal_width = get_terminal_width();
let center_position = terminal_width / 2;
assert!(
leading_spaces > center_position,
"Right float should position box to the right of center. Got {leading_spaces} leading spaces, center is at {center_position}"
);
}
}
}
#[test]
fn test_float_positioning_with_different_terminal_widths() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Center,
width: Some(Width::Fixed(10)),
..Default::default()
};
let result = boxen("Test", Some(options)).unwrap();
assert!(result.contains("Test"));
assert_eq!(result.lines().count(), 3);
for line in result.lines() {
if line.contains("Test") || line.contains("─") {
let trimmed = line.trim_start();
if !trimmed.is_empty() {
assert_eq!(crate::text::text_width(trimmed), 10);
}
}
}
}
#[test]
fn test_float_positioning_with_no_border() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Center,
border_style: BorderStyle::None,
width: Some(Width::Fixed(15)),
..Default::default()
};
let result = boxen("No Border", Some(options)).unwrap();
assert!(result.contains("No Border"));
assert!(!result.contains("┌"));
assert!(!result.contains("│"));
assert_eq!(result.lines().count(), 1); }
#[test]
fn test_float_positioning_with_padding_and_margins() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Left,
padding: Spacing {
top: 1,
right: 2,
bottom: 1,
left: 2,
},
margin: Spacing {
top: 1,
right: 1,
bottom: 1,
left: 3, },
..Default::default()
};
let result = boxen("Padded", Some(options)).unwrap();
assert!(result.contains("Padded"));
assert_eq!(result.lines().count(), 7);
let lines: Vec<&str> = result.lines().collect();
for (i, line) in lines.iter().enumerate() {
if i != 0 && i != lines.len() - 1 && !line.trim().is_empty() {
assert!(line.starts_with(" ")); }
}
}
#[test]
fn test_float_positioning_edge_cases() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Right,
width: Some(Width::Fixed(50)), ..Default::default()
};
let result = boxen("Edge Case", Some(options));
assert!(result.is_ok());
let box_str = result.unwrap();
assert!(box_str.contains("Edge Case"));
}
#[test]
fn test_float_positioning_with_multiline_content() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Center,
width: Some(Width::Fixed(25)),
..Default::default()
};
let result = boxen("Line 1\nLine 2\nLine 3", Some(options)).unwrap();
assert!(result.contains("Line 1"));
assert!(result.contains("Line 2"));
assert!(result.contains("Line 3"));
let lines: Vec<&str> = result.lines().collect();
let mut content_line_positions = Vec::new();
for line in &lines {
if line.contains("Line") || line.contains("─") {
let leading_spaces = line.len() - line.trim_start().len();
content_line_positions.push(leading_spaces);
}
}
if content_line_positions.len() > 1 {
let first_pos = content_line_positions[0];
for pos in &content_line_positions {
assert_eq!(
*pos, first_pos,
"All content lines should be aligned consistently"
);
}
}
}
#[test]
fn test_float_positioning_with_title() {
use crate::options::Float;
let options = BoxenOptions {
float: Float::Right,
title: Some("My Title".to_string()),
width: Some(Width::Fixed(20)),
margin: Spacing {
top: 0,
right: 2,
bottom: 0,
left: 0,
},
..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("My Title"));
assert!(result.contains("Content"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
let content_line = lines[1];
let top_leading = top_border.len() - top_border.trim_start().len();
let content_leading = content_line.len() - content_line.trim_start().len();
assert_eq!(
top_leading, content_leading,
"Title and content should be aligned"
);
}
#[test]
fn test_border_color_integration() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Named("red".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let uncolored_result = boxen("Hello", None).unwrap();
assert!(result.len() > uncolored_result.len());
}
#[test]
fn test_background_color_integration() {
use crate::options::Color;
let options = BoxenOptions {
background_color: Some(Color::Named("blue".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let uncolored_result = boxen("Hello", None).unwrap();
assert!(result.len() > uncolored_result.len());
}
#[test]
fn test_dim_border_integration() {
let options = BoxenOptions {
dim_border: true,
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let normal_result = boxen("Hello", None).unwrap();
assert!(result.len() > normal_result.len());
}
#[test]
fn test_border_and_background_color_combination() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Named("red".to_string())),
background_color: Some(Color::Named("blue".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let uncolored_result = boxen("Hello", None).unwrap();
assert!(result.len() > uncolored_result.len() + 20); }
#[test]
fn test_color_with_padding() {
use crate::options::Color;
let options = BoxenOptions {
background_color: Some(Color::Named("green".to_string())),
padding: Spacing::from(1),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(result.lines().count() > 3);
}
#[test]
fn test_color_with_no_border() {
use crate::options::Color;
let options = BoxenOptions {
border_style: BorderStyle::None,
background_color: Some(Color::Named("yellow".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
assert!(!result.contains("┌"));
assert!(!result.contains("│"));
let uncolored_result = boxen(
"Hello",
Some(BoxenOptions {
border_style: BorderStyle::None,
..Default::default()
}),
)
.unwrap();
assert!(result.len() > uncolored_result.len());
}
#[test]
fn test_hex_color_integration() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Hex("#FF0000".to_string())),
background_color: Some(Color::Hex("#00FF00".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let uncolored_result = boxen("Hello", None).unwrap();
assert!(result.len() > uncolored_result.len());
}
#[test]
fn test_rgb_color_integration() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Rgb(255, 0, 0)),
background_color: Some(Color::Rgb(0, 255, 0)),
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let uncolored_result = boxen("Hello", None).unwrap();
assert!(result.len() > uncolored_result.len());
}
#[test]
fn test_invalid_color_error_handling() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Named("invalid_color".to_string())),
..Default::default()
};
let result = boxen("Hello", Some(options));
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.to_lowercase().contains("color"));
}
#[test]
fn test_color_with_multiline_content() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Named("cyan".to_string())),
background_color: Some(Color::Named("magenta".to_string())),
..Default::default()
};
let result = boxen("Line 1\nLine 2\nLine 3", Some(options)).unwrap();
assert!(result.contains("Line 1"));
assert!(result.contains("Line 2"));
assert!(result.contains("Line 3"));
assert!(result.lines().count() >= 5); }
#[test]
fn test_color_with_title() {
use crate::options::Color;
let options = BoxenOptions {
title: Some("Title".to_string()),
border_color: Some(Color::Named("blue".to_string())),
background_color: Some(Color::Named("white".to_string())),
width: Some(Width::Fixed(20)), ..Default::default()
};
let result = boxen("Content", Some(options)).unwrap();
assert!(result.contains("Title"));
assert!(result.contains("Content"));
let lines: Vec<&str> = result.lines().collect();
let top_border = lines[0];
assert!(top_border.contains("Title"));
}
#[test]
fn test_color_with_different_border_styles() {
use crate::options::Color;
let border_styles = vec![
BorderStyle::Single,
BorderStyle::Double,
BorderStyle::Round,
BorderStyle::Bold,
BorderStyle::Classic,
];
for style in border_styles {
let options = BoxenOptions {
border_style: style,
border_color: Some(Color::Named("green".to_string())),
background_color: Some(Color::Named("black".to_string())),
..Default::default()
};
let result = boxen("Test", Some(options)).unwrap();
assert!(result.contains("Test"));
let uncolored_result = boxen(
"Test",
Some(BoxenOptions {
border_style: style,
..Default::default()
}),
)
.unwrap();
assert!(result.len() > uncolored_result.len());
}
}
#[test]
fn test_dim_border_with_color() {
use crate::options::Color;
let options = BoxenOptions {
border_color: Some(Color::Named("red".to_string())),
dim_border: true,
..Default::default()
};
let result = boxen("Hello", Some(options)).unwrap();
assert!(result.contains("Hello"));
let normal_colored_result = boxen(
"Hello",
Some(BoxenOptions {
border_color: Some(Color::Named("red".to_string())),
dim_border: false,
..Default::default()
}),
)
.unwrap();
assert_ne!(result, normal_colored_result);
}
#[test]
fn test_color_with_float_positioning() {
use crate::options::{Color, Float};
let options = BoxenOptions {
float: Float::Center,
border_color: Some(Color::Named("blue".to_string())),
background_color: Some(Color::Named("yellow".to_string())),
width: Some(Width::Fixed(20)),
..Default::default()
};
let result = boxen("Centered", Some(options)).unwrap();
assert!(result.contains("Centered"));
let uncolored_result = boxen(
"Centered",
Some(BoxenOptions {
float: Float::Center,
width: Some(Width::Fixed(20)),
..Default::default()
}),
)
.unwrap();
assert!(result.len() > uncolored_result.len());
}
}