use proptest::prelude::*;
use rnk::components::{Box as RnkBox, Text};
use rnk::core::{Dimension, Element, FlexDirection};
use rnk::layout::measure::measure_text_width;
use rnk::testing::{TestRenderer, display_width};
proptest! {
#[test]
fn unicode_width_consistency(s in "[ -~]{0,100}") {
let width = measure_text_width(&s);
prop_assert_eq!(width, s.len());
}
#[test]
fn cjk_width(s in "[一-龥]{1,10}") {
let width = measure_text_width(&s);
let char_count = s.chars().count();
prop_assert_eq!(width, char_count * 2);
}
#[test]
fn mixed_width_additive(ascii in "[a-z]{1,10}", cjk in "[一-龥]{1,5}") {
let combined = format!("{}{}", ascii, cjk);
let combined_width = measure_text_width(&combined);
let ascii_width = measure_text_width(&ascii);
let cjk_width = measure_text_width(&cjk);
prop_assert_eq!(combined_width, ascii_width + cjk_width);
}
}
proptest! {
#[test]
fn layout_dimensions_non_negative(
width in 1u16..200,
height in 1u16..100
) {
let element = RnkBox::new()
.width(Dimension::Points(width as f32))
.height(Dimension::Points(height as f32))
.into_element();
let renderer = TestRenderer::new(500, 200);
let layout = renderer.get_layout(&element).unwrap();
prop_assert!(layout.width >= 0.0, "Width should be non-negative");
prop_assert!(layout.height >= 0.0, "Height should be non-negative");
prop_assert!(layout.x >= 0.0, "X should be non-negative");
prop_assert!(layout.y >= 0.0, "Y should be non-negative");
}
#[test]
fn layout_validation_valid_elements(
width in 10u16..100,
height in 5u16..50,
term_width in 100u16..500,
term_height in 50u16..200
) {
prop_assume!(width < term_width);
prop_assume!(height < term_height);
let element = RnkBox::new()
.width(Dimension::Points(width as f32))
.height(Dimension::Points(height as f32))
.into_element();
let renderer = TestRenderer::new(term_width, term_height);
let result = renderer.validate_layout(&element);
prop_assert!(result.is_ok(), "Layout should be valid: {:?}", result);
}
#[test]
fn nested_boxes_valid(depth in 1usize..5) {
fn create_nested(depth: usize) -> Element {
if depth == 0 {
return Text::new("leaf").into_element();
}
RnkBox::new()
.padding(1)
.child(create_nested(depth - 1))
.into_element()
}
let element = create_nested(depth);
let renderer = TestRenderer::new(100, 50);
let result = renderer.validate_layout(&element);
prop_assert!(result.is_ok(), "Nested layout should be valid: {:?}", result);
}
}
proptest! {
#[test]
fn text_appears_in_output(s in "[a-zA-Z0-9]{1,30}") {
let element = Text::new(&s).into_element();
let renderer = TestRenderer::new(80, 24);
let output = renderer.render_to_plain(&element);
prop_assert!(
output.contains(&s),
"Output should contain text '{}', got: {}",
s, output
);
}
#[test]
fn render_within_bounds(
term_width in 40u16..200,
term_height in 10u16..100
) {
let element = RnkBox::new()
.width(Dimension::Percent(100.0))
.height(Dimension::Percent(100.0))
.child(Text::new("Content").into_element())
.into_element();
let renderer = TestRenderer::new(term_width, term_height);
let output = renderer.render_to_plain(&element);
for line in output.lines() {
let line_width = display_width(line);
prop_assert!(
line_width <= term_width as usize,
"Line width {} exceeds terminal width {}",
line_width, term_width
);
}
let line_count = output.lines().count();
prop_assert!(
line_count <= term_height as usize,
"Line count {} exceeds terminal height {}",
line_count, term_height
);
}
}
proptest! {
#[test]
fn box_children_order(children_count in 1usize..5) {
let texts: Vec<String> = (0..children_count)
.map(|i| format!("child{}", i))
.collect();
let mut builder = RnkBox::new()
.flex_direction(FlexDirection::Column);
for text in &texts {
builder = builder.child(Text::new(text).into_element());
}
let element = builder.into_element();
prop_assert_eq!(element.children.len(), children_count);
for (i, child) in element.children.iter().enumerate() {
let expected = format!("child{}", i);
prop_assert_eq!(
child.text_content.as_deref(),
Some(expected.as_str()),
"Child {} should have text '{}'",
i, expected
);
}
}
#[test]
fn text_styling_preserved(
use_bold in any::<bool>(),
use_italic in any::<bool>(),
use_underline in any::<bool>()
) {
let mut text = Text::new("styled");
if use_bold {
text = text.bold();
}
if use_italic {
text = text.italic();
}
if use_underline {
text = text.underline();
}
let element = text.into_element();
prop_assert_eq!(element.style.bold, use_bold);
prop_assert_eq!(element.style.italic, use_italic);
prop_assert_eq!(element.style.underline, use_underline);
}
}
proptest! {
#[test]
fn empty_string_safe(_dummy in Just(())) {
let element = Text::new("").into_element();
let renderer = TestRenderer::new(80, 24);
let _ = renderer.render_to_plain(&element);
let _ = renderer.get_layout(&element);
}
#[test]
fn long_string_safe(s in ".{100,500}") {
let element = Text::new(&s).into_element();
let renderer = TestRenderer::new(80, 24);
let _ = renderer.render_to_plain(&element);
let _ = renderer.get_layout(&element);
}
#[test]
fn zero_dimensions_safe(_dummy in Just(())) {
let element = RnkBox::new()
.width(Dimension::Points(0.0))
.height(Dimension::Points(0.0))
.into_element();
let renderer = TestRenderer::new(80, 24);
let _ = renderer.render_to_plain(&element);
let result = renderer.validate_layout(&element);
prop_assert!(result.is_ok());
}
#[test]
fn minimal_terminal_safe(_dummy in Just(())) {
let element = Text::new("x").into_element();
let renderer = TestRenderer::new(1, 1);
let _ = renderer.render_to_plain(&element);
}
}