use rich_rs::{Console, ConsoleOptions, Segment, Segments};
use textual::css::set_style_context;
use textual::prelude::*;
use textual::render::FrameBuffer;
use textual::style::{
Constrain, HorizontalAlign, KeylineType, OverlayMode, Scalar, Style, TextOverflow, TextWrap,
};
fn tree_render(
root: &mut dyn Widget,
w: usize,
h: usize,
) -> (WidgetTree, FrameBuffer, Vec<String>) {
let console = Console::new();
let mut tree = build_widget_tree_from_root(root).expect("tree should have children");
let frame = render_tree_to_frame(&mut tree, root, &console, w, h);
let lines = frame.as_plain_lines();
(tree, frame, lines)
}
struct BorderCaptionWidget {
title: &'static str,
subtitle: &'static str,
styles: WidgetStyles,
}
impl BorderCaptionWidget {
fn new(title: &'static str, subtitle: &'static str) -> Self {
let mut styles = WidgetStyles::default();
styles.style = styles
.style
.border_top(textual::style::Color::parse("white").unwrap())
.border_bottom(textual::style::Color::parse("white").unwrap())
.border_left(textual::style::Color::parse("white").unwrap())
.border_right(textual::style::Color::parse("white").unwrap());
styles.style.border_title_align = Some(HorizontalAlign::Center);
styles.style.border_subtitle_align = Some(HorizontalAlign::Right);
styles.style.border_title_color = Some(textual::style::Color::parse("yellow").unwrap());
styles.style.border_subtitle_color = Some(textual::style::Color::parse("cyan").unwrap());
Self {
title,
subtitle,
styles,
}
}
}
impl Widget for BorderCaptionWidget {
fn render(&self, _console: &Console, options: &ConsoleOptions) -> Segments {
let w = options.size.0.max(1);
let h = options.size.1.max(1);
let mut out = Segments::new();
for y in 0..h {
out.push(Segment::new(" ".repeat(w)));
if y + 1 < h {
out.push(Segment::line());
}
}
out
}
fn style_type(&self) -> &'static str {
"BorderCaptionWidget"
}
fn border_title(&self) -> Option<&str> {
Some(self.title)
}
fn border_subtitle(&self) -> Option<&str> {
Some(self.subtitle)
}
fn styles(&self) -> Option<&WidgetStyles> {
Some(&self.styles)
}
}
struct FillWidget {
style_type_name: &'static str,
styles: WidgetStyles,
}
impl FillWidget {
fn new(style_type_name: &'static str, style: Style) -> Self {
let mut styles = WidgetStyles::default();
styles.style = style;
Self {
style_type_name,
styles,
}
}
}
impl Widget for FillWidget {
fn render(&self, _console: &Console, options: &ConsoleOptions) -> Segments {
let w = options.size.0.max(1);
let h = options.size.1.max(1);
let mut out = Segments::new();
for y in 0..h {
out.push(Segment::new("x".repeat(w)));
if y + 1 < h {
out.push(Segment::line());
}
}
out
}
fn style_type(&self) -> &'static str {
self.style_type_name
}
fn styles(&self) -> Option<&WidgetStyles> {
Some(&self.styles)
}
}
#[test]
fn p2g28_outline_style_fields_wired() {
use textual::style::{BorderEdge, BorderType, Color};
let mut style = textual::style::Style::new();
let red = Color::parse("red").unwrap();
style.outline_top = BorderEdge::Edge {
border_type: BorderType::Solid,
color: red,
};
style.outline_right = BorderEdge::Edge {
border_type: BorderType::Solid,
color: red,
};
style.outline_bottom = BorderEdge::Edge {
border_type: BorderType::Block,
color: red,
};
style.outline_left = BorderEdge::Edge {
border_type: BorderType::Tall,
color: red,
};
assert!(style.outline_top.is_set());
assert!(style.outline_right.is_set());
assert!(style.outline_bottom.is_set());
assert!(style.outline_left.is_set());
assert_eq!(style.outline_top.color(), Some(red));
assert_eq!(style.outline_bottom.edge_type(), "block");
assert_eq!(style.outline_left.edge_type(), "tall");
}
#[test]
fn p2g28_outline_css_parses_all_edges() {
let css = "Label { outline: solid red; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("hi"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 5);
let all_text: String = lines.join("");
assert!(
all_text.contains("hi"),
"label text should appear in output: {all_text:?}"
);
}
#[test]
fn p2g28_outline_per_side_css_parses() {
let css = "Label { outline-top: solid green; outline-bottom: solid blue; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("ok"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 5);
let all_text: String = lines.join("");
assert!(all_text.contains("ok"), "label text should appear");
}
#[test]
fn p2g28_outline_does_not_affect_layout_rect() {
let css_no_outline = "Label { }";
let css_with_outline = "Label { outline: solid red; }";
let sheet1 = StyleSheet::parse(css_no_outline);
let _guard1 = set_style_context(sheet1);
let mut root1 = Container::new().with_child(Label::new("test"));
let (_tree1, _frame1, lines1) = tree_render(&mut root1, 20, 5);
let row1 = lines1.iter().position(|l| l.contains("test"));
let sheet2 = StyleSheet::parse(css_with_outline);
let _guard2 = set_style_context(sheet2);
let mut root2 = Container::new().with_child(Label::new("test"));
let (_tree2, _frame2, lines2) = tree_render(&mut root2, 20, 5);
let row2 = lines2.iter().position(|l| l.contains("test"));
assert_eq!(
row1, row2,
"outline should not shift layout: row_without={row1:?} row_with={row2:?}"
);
}
#[test]
fn p2g29_border_title_align_style_fields() {
let mut style = textual::style::Style::new();
style.border_title_align = Some(HorizontalAlign::Center);
style.border_subtitle_align = Some(HorizontalAlign::Right);
assert_eq!(style.border_title_align, Some(HorizontalAlign::Center));
assert_eq!(style.border_subtitle_align, Some(HorizontalAlign::Right));
}
#[test]
fn p2g29_border_title_color_style_fields() {
use textual::style::Color;
let mut style = textual::style::Style::new();
let red = Color::parse("red").unwrap();
let blue = Color::parse("blue").unwrap();
style.border_title_color = Some(red);
style.border_title_background = Some(blue);
style.border_subtitle_color = Some(blue);
style.border_subtitle_background = Some(red);
assert_eq!(style.border_title_color, Some(red));
assert_eq!(style.border_title_background, Some(blue));
assert_eq!(style.border_subtitle_color, Some(blue));
assert_eq!(style.border_subtitle_background, Some(red));
}
#[test]
fn p2g29_border_title_style_flags() {
use textual::style::TextStyleFlags;
let flags = TextStyleFlags {
bold: true,
italic: true,
dim: false,
underline: false,
reverse: false,
strike: false,
};
let mut style = textual::style::Style::new();
style.border_title_style = Some(flags);
let stored = style.border_title_style.unwrap();
assert!(stored.bold);
assert!(stored.italic);
assert!(!stored.underline);
}
#[test]
fn p2g29_border_title_css_parse_roundtrip() {
let css = "Label { border-title-align: center; border-subtitle-align: right; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("titled"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("titled"));
}
#[test]
fn p2g29_border_title_color_css_parse_roundtrip() {
let css = "Label { border-title-color: red; border-title-background: blue; border-title-style: bold italic; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("styled"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("styled"));
}
#[test]
fn p2g29_border_title_subtitle_render_on_edges() {
let mut root = Container::new().with_child(BorderCaptionWidget::new("TITLE", "sub"));
let (_tree, frame, lines) = tree_render(&mut root, 24, 6);
assert!(
lines[0].contains("TITLE"),
"top border should contain title"
);
assert!(
lines[5].contains("sub"),
"bottom border should contain subtitle"
);
let title_x = lines[0].find("TITLE").expect("title should be present");
let title_cell = frame.get(title_x, 0);
let title_fg = title_cell.style.and_then(|s| s.color).map(|c| c);
assert_eq!(
title_fg,
Some(
textual::style::Color::parse("yellow")
.unwrap()
.to_simple_opaque()
)
);
}
#[test]
fn p2g31_text_wrap_style_fields() {
let mut style = textual::style::Style::new();
style.text_wrap = Some(TextWrap::Wrap);
assert_eq!(style.text_wrap, Some(TextWrap::Wrap));
style.text_wrap = Some(TextWrap::NoWrap);
assert_eq!(style.text_wrap, Some(TextWrap::NoWrap));
}
#[test]
fn p2g31_text_overflow_style_fields() {
let mut style = textual::style::Style::new();
style.text_overflow = Some(TextOverflow::Clip);
assert_eq!(style.text_overflow, Some(TextOverflow::Clip));
style.text_overflow = Some(TextOverflow::Fold);
assert_eq!(style.text_overflow, Some(TextOverflow::Fold));
style.text_overflow = Some(TextOverflow::Ellipsis);
assert_eq!(style.text_overflow, Some(TextOverflow::Ellipsis));
}
#[test]
fn p2g31_text_wrap_css_parse_roundtrip() {
let css = "Label { text-wrap: nowrap; text-overflow: ellipsis; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("truncated"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("truncated"));
}
#[test]
fn p2g31_text_overflow_line_truncation_clip() {
use textual::runtime::apply_text_overflow_to_line;
let long_line = vec![rich_rs::Segment::new("abcdefghij")]; let clipped = apply_text_overflow_to_line(&long_line, 5, TextOverflow::Clip);
let clipped_text: String = clipped.iter().map(|s| s.text.to_string()).collect();
assert_eq!(
rich_rs::cell_len(&clipped_text),
5,
"clipped should be 5 cols wide"
);
}
#[test]
fn p2g31_text_overflow_line_truncation_ellipsis() {
use textual::runtime::apply_text_overflow_to_line;
let long_line = vec![rich_rs::Segment::new("abcdefghij")]; let ellipsised = apply_text_overflow_to_line(&long_line, 5, TextOverflow::Ellipsis);
let ell_text: String = ellipsised.iter().map(|s| s.text.to_string()).collect();
assert!(
ell_text.contains('…'),
"ellipsis should be present: {ell_text:?}"
);
assert!(
rich_rs::cell_len(&ell_text) <= 5,
"ellipsised should fit in 5 cols: {ell_text:?} (width={})",
rich_rs::cell_len(&ell_text)
);
}
#[test]
fn p2g31_text_overflow_line_fold_preserves_text() {
use textual::runtime::apply_text_overflow_to_line;
let long_line = vec![rich_rs::Segment::new("abcdefghij")];
let folded = apply_text_overflow_to_line(&long_line, 5, TextOverflow::Fold);
let fold_text: String = folded.iter().map(|s| s.text.to_string()).collect();
assert_eq!(fold_text, "abcdefghij", "fold should preserve full text");
}
#[test]
fn p2g31_text_overflow_mode_function() {
use textual::runtime::text_overflow_mode;
let style_default = textual::style::Style::new();
assert!(text_overflow_mode(&style_default).is_none());
let mut style_wrap = textual::style::Style::new();
style_wrap.text_wrap = Some(TextWrap::Wrap);
assert!(text_overflow_mode(&style_wrap).is_none());
let mut style_nowrap = textual::style::Style::new();
style_nowrap.text_wrap = Some(TextWrap::NoWrap);
assert_eq!(text_overflow_mode(&style_nowrap), Some(TextOverflow::Clip));
let mut style_ellipsis = textual::style::Style::new();
style_ellipsis.text_wrap = Some(TextWrap::NoWrap);
style_ellipsis.text_overflow = Some(TextOverflow::Ellipsis);
assert_eq!(
text_overflow_mode(&style_ellipsis),
Some(TextOverflow::Ellipsis)
);
}
#[test]
fn p2g34_hatch_style_fields() {
use textual::style::{Color, Hatch};
let hatch = Hatch {
character: '+',
color: Color::parse("red").unwrap(),
};
let mut style = textual::style::Style::new();
style.hatch = Some(hatch);
let stored = style.hatch.unwrap();
assert_eq!(stored.character, '+');
assert_eq!(stored.color, Color::parse("red").unwrap());
}
#[test]
fn p2g34_overlay_style_fields() {
let mut style = textual::style::Style::new();
style.overlay = Some(OverlayMode::None);
assert_eq!(style.overlay, Some(OverlayMode::None));
style.overlay = Some(OverlayMode::Screen);
assert_eq!(style.overlay, Some(OverlayMode::Screen));
}
#[test]
fn p2g34_keyline_style_fields() {
use textual::style::{Color, Keyline};
let keyline = Keyline {
keyline_type: KeylineType::Thin,
color: Color::parse("red").unwrap(),
};
let mut style = textual::style::Style::new();
style.keyline = Some(keyline);
let stored = style.keyline.unwrap();
assert_eq!(stored.keyline_type, KeylineType::Thin);
assert_eq!(stored.color, Color::parse("red").unwrap());
}
#[test]
fn p2g34_hatch_css_parse_renders_without_crash() {
let css = r#"Container { hatch: cross #ff0000; }"#;
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("X"));
let (_tree, _frame, lines) = tree_render(&mut root, 10, 3);
let all_text: String = lines.join("");
assert!(all_text.contains('X'), "label text should survive hatch");
}
#[test]
fn p2g34_overlay_screen_renders_without_crash() {
let css = "Label { overlay: screen; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("overlay"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("overlay"));
}
#[test]
fn p2g34_keyline_css_parse_renders_without_crash() {
let css = "Container { keyline: thin red; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new()
.with_child(Label::new("A"))
.with_child(Label::new("B"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 5);
let all_text: String = lines.join("");
assert!(all_text.contains('A'));
assert!(all_text.contains('B'));
}
#[test]
fn p2g34_keyline_heavy_css_parse() {
let css = "Container { keyline: heavy green; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("heavy"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("heavy"));
}
#[test]
fn p2g35_constrain_x_y_style_fields() {
let mut style = textual::style::Style::new();
style.constrain_x = Some(Constrain::Inflect);
style.constrain_y = Some(Constrain::Inside);
assert_eq!(style.constrain_x, Some(Constrain::Inflect));
assert_eq!(style.constrain_y, Some(Constrain::Inside));
}
#[test]
fn p2g35_expand_style_fields() {
let mut style = textual::style::Style::new();
style.expand = Some(true);
assert_eq!(style.expand, Some(true));
style.expand = Some(false);
assert_eq!(style.expand, Some(false));
}
#[test]
fn p2g35_constrain_x_y_css_parse_roundtrip() {
let css = "Label { constrain-x: inflect; constrain-y: inside; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("constrained"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("constrained"));
}
#[test]
fn p2g35_expand_css_parse_roundtrip() {
let css = "Label { expand: true; }";
let sheet = StyleSheet::parse(css);
let _guard = set_style_context(sheet);
let mut root = Container::new().with_child(Label::new("expanded"));
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let all_text: String = lines.join("");
assert!(all_text.contains("expanded"));
}
#[test]
fn p2g35_axis_constrain_resolution() {
use textual::runtime::resolve_axis_constrain;
let mut style = textual::style::Style::new();
style.constrain = Some(Constrain::Inside);
let (cx, cy) = resolve_axis_constrain(&style);
assert_eq!(cx, Constrain::Inside);
assert_eq!(cy, Constrain::Inside);
style.constrain_x = Some(Constrain::Inflect);
let (cx, cy) = resolve_axis_constrain(&style);
assert_eq!(cx, Constrain::Inflect);
assert_eq!(cy, Constrain::Inside);
}
#[test]
fn p2g35_constrain_overlay_position_clamps_inside() {
use textual::runtime::constrain_overlay_position;
let (x, y) =
constrain_overlay_position(90, 20, 20, 5, 100, 25, Constrain::Inside, Constrain::Inside);
assert!(x + 20 <= 100, "x should be clamped inside viewport: x={x}");
assert!(y + 5 <= 25, "y should be clamped inside viewport: y={y}");
assert_eq!(x, 80, "x should be clamped to 80 (100 - 20)");
assert_eq!(y, 20, "y should stay at 20 (fits in viewport)");
}
#[test]
fn p2g35_constrain_overlay_position_inflects() {
use textual::runtime::constrain_overlay_position;
let (x, _y) =
constrain_overlay_position(90, 5, 20, 3, 100, 25, Constrain::Inflect, Constrain::None);
assert_eq!(x, 70, "x should inflect to 70 (90 - 20)");
let (_x, y) =
constrain_overlay_position(5, 22, 10, 5, 100, 25, Constrain::None, Constrain::Inflect);
assert_eq!(y, 17, "y should inflect to 17 (22 - 5)");
}
#[test]
fn p2g35_constrain_none_does_not_clamp() {
use textual::runtime::constrain_overlay_position;
let (x, y) =
constrain_overlay_position(90, 22, 20, 5, 100, 25, Constrain::None, Constrain::None);
assert_eq!(x, 90, "x should be unchanged");
assert_eq!(y, 22, "y should be unchanged");
}
#[test]
fn p2_28_outline_paints_outside_border_box() {
use textual::style::{BorderEdge, BorderType, Color, Spacing};
let red = Color::parse("red").unwrap();
let outline_edge = BorderEdge::Edge {
border_type: BorderType::Solid,
color: red,
};
let mut label = Label::new("hi");
label.set_width(2);
label.styles_mut().unwrap().style.outline_top = outline_edge;
label.styles_mut().unwrap().style.outline_bottom = outline_edge;
label.styles_mut().unwrap().style.outline_left = outline_edge;
label.styles_mut().unwrap().style.outline_right = outline_edge;
let mut inner = Container::new().with_child(label);
inner.set_width(20);
inner.styles_mut().unwrap().style.padding = Some(Spacing::all(3));
let mut root = Container::new().with_child(inner);
let (_tree, frame, _lines) = tree_render(&mut root, 20, 8);
let top_cell = frame.get(3, 2);
assert_eq!(top_cell.text, "─", "top outline at (3,2)");
let bottom_cell = frame.get(3, 4);
assert_eq!(bottom_cell.text, "─", "bottom outline at (3,4)");
let left_cell = frame.get(2, 3);
assert_eq!(left_cell.text, "│", "left outline at (2,3)");
assert_eq!(frame.get(3, 3).text, "h", "label text at (3,3)");
assert_eq!(frame.get(4, 3).text, "i", "label text at (4,3)");
}
#[test]
fn p2_28_outline_clipped_at_viewport_edge() {
use textual::style::{BorderEdge, BorderType, Color};
let red = Color::parse("red").unwrap();
let outline_edge = BorderEdge::Edge {
border_type: BorderType::Solid,
color: red,
};
let mut label = Label::new("edge");
label.set_width(4);
label.styles_mut().unwrap().style.outline_top = outline_edge;
label.styles_mut().unwrap().style.outline_bottom = outline_edge;
label.styles_mut().unwrap().style.outline_left = outline_edge;
label.styles_mut().unwrap().style.outline_right = outline_edge;
let mut root = Container::new().with_child(label);
root.set_width(10);
let (_tree, frame, _lines) = tree_render(&mut root, 10, 3);
assert_eq!(frame.get(0, 1).text, "─", "bottom outline at (0,1)");
assert_eq!(frame.get(3, 1).text, "─", "bottom outline at (3,1)");
assert_eq!(frame.get(0, 0).text, "e", "label text at (0,0)");
}
#[test]
fn p2_34_hatch_fills_blank_cells_with_pattern() {
use textual::style::{Color, Hatch};
let mut label = Label::new("X "); label.styles_mut().unwrap().style.hatch = Some(Hatch {
character: '+',
color: Color::parse("#ff0000").unwrap(),
});
let mut root = Container::new().with_child(label);
let (_tree, frame, _lines) = tree_render(&mut root, 20, 3);
assert_eq!(frame.get(0, 0).text, "X", "text cell should be preserved");
assert_eq!(frame.get(1, 0).text, "+", "blank cell should be hatched");
assert_eq!(frame.get(5, 0).text, "+", "blank cell should be hatched");
assert_eq!(frame.get(9, 0).text, "+", "blank cell should be hatched");
}
#[test]
fn p2g34_overlay_screen_blends_with_underlay() {
let base_style = Style::new()
.width(Scalar::Percent(100.0))
.height(Scalar::Percent(100.0))
.bg(textual::style::Color::parse("#0000ff").unwrap());
let mut overlay_style = Style::new()
.width(Scalar::Percent(100.0))
.height(Scalar::Percent(100.0))
.bg(textual::style::Color::parse("#ff0000").unwrap());
overlay_style.position = Some(textual::style::Position::Absolute);
overlay_style.overlay = Some(OverlayMode::Screen);
let mut root = Container::new()
.with_child(FillWidget::new("BaseFill", base_style))
.with_child(FillWidget::new("OverlayFill", overlay_style));
let (_tree, frame, _lines) = tree_render(&mut root, 8, 3);
let bg = frame
.get(0, 0)
.style
.and_then(|s| s.bgcolor)
.map(|c| c)
.expect("blended background color should exist");
assert_eq!(
bg,
textual::style::Color::parse("#ff00ff")
.unwrap()
.to_simple_opaque()
);
}
#[test]
fn p2g34_keyline_draws_separator_between_children() {
let mut keylined = Container::new();
keylined.styles_mut().unwrap().style.layout = Some(textual::style::Layout::Vertical);
keylined.styles_mut().unwrap().style.keyline = Some(textual::style::Keyline {
keyline_type: KeylineType::Thin,
color: textual::style::Color::parse("red").unwrap(),
});
let mut a = Label::new("A");
a.styles_mut().unwrap().style.height = Some(Scalar::Cells(1));
let mut b = Label::new("B");
b.styles_mut().unwrap().style.height = Some(Scalar::Cells(1));
keylined = keylined.with_child(a).with_child(b);
let mut root = Container::new().with_child(keylined);
let (_tree, frame, lines) = tree_render(&mut root, 10, 4);
assert!(
lines.iter().any(|l| l.contains('─')),
"keyline separator should be painted; lines={lines:?}"
);
let sep_y = lines
.iter()
.position(|l| l.contains('─'))
.expect("separator row");
let cell = frame.get(0, sep_y);
let fg = cell.style.and_then(|s| s.color).map(|c| c);
assert_eq!(
fg,
Some(
textual::style::Color::parse("red")
.unwrap()
.to_simple_opaque()
)
);
}
struct WideRenderWidget {
text: String,
styles: WidgetStyles,
}
impl WideRenderWidget {
fn new(text: &str) -> Self {
Self {
text: text.to_string(),
styles: WidgetStyles::default(),
}
}
}
impl Widget for WideRenderWidget {
fn render(&self, _console: &Console, _options: &ConsoleOptions) -> rich_rs::Segments {
let mut out = rich_rs::Segments::new();
out.push(rich_rs::Segment::new(self.text.clone()));
out
}
fn content_width(&self) -> Option<usize> {
Some(8)
}
fn style_type(&self) -> &'static str {
"WideRenderWidget"
}
fn styles(&self) -> Option<&WidgetStyles> {
Some(&self.styles)
}
fn styles_mut(&mut self) -> Option<&mut WidgetStyles> {
Some(&mut self.styles)
}
}
#[test]
fn p2_31_nowrap_ellipsis_narrow_pipeline() {
let mut widget = WideRenderWidget::new("abcdefghijklmnop"); widget.set_width(8);
widget.styles.style.text_wrap = Some(TextWrap::NoWrap);
widget.styles.style.text_overflow = Some(TextOverflow::Ellipsis);
let mut root = Container::new().with_child(widget);
let (_tree, _frame, lines) = tree_render(&mut root, 20, 3);
let first_line = &lines[0];
assert!(
first_line.contains('…') || first_line.starts_with("abcdefgh"),
"tree-level text overflow should truncate to the widget width: {first_line:?}"
);
assert!(
rich_rs::cell_len(first_line.trim_end()) <= 8,
"truncated output should fit within 8 columns: {first_line:?} (width={})",
rich_rs::cell_len(first_line.trim_end())
);
}