// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use super::*; use crate::render::Canvas; fn make_canvas() -> Canvas { Canvas::new( 800, 600 ) } #[ test ] fn new_uses_documented_defaults() { let t = Text::new( "hello" ); assert_eq!( t.content, "hello" ); assert_eq!( t.size, crate::Length::px( 16.0 ) ); assert_eq!( t.color, Color::WHITE ); assert_eq!( t.align, TextAlign::Left ); } #[ test ] fn size_builder_overrides_default() { let t = Text::new( "" ).size( 32.0 ); assert_eq!( t.size, crate::Length::px( 32.0 ) ); } #[ test ] fn size_builder_accepts_length() { let t = Text::new( "" ).size( crate::Length::vmin( 5.0 ) ); assert_eq!( t.size.base, crate::LengthBase::Vmin( 5.0 ) ); } #[ test ] fn color_builder_overrides_default() { let t = Text::new( "" ).color( Color::rgb( 1.0, 0.0, 0.0 ) ); assert_eq!( t.color, Color::rgb( 1.0, 0.0, 0.0 ) ); } #[ test ] fn align_builder_can_target_each_variant() { assert_eq!( Text::new( "" ).align( TextAlign::Left ).align, TextAlign::Left ); assert_eq!( Text::new( "" ).align( TextAlign::Center ).align, TextAlign::Center ); assert_eq!( Text::new( "" ).align( TextAlign::Right ).align, TextAlign::Right ); } #[ test ] fn align_center_shorthand_matches_explicit_align() { let a = Text::new( "" ).align_center(); let b = Text::new( "" ).align( TextAlign::Center ); assert_eq!( a.align, b.align ); } #[ test ] fn preferred_size_returns_non_negative_dimensions() { let canvas = make_canvas(); let ( w, h ) = Text::new( "ltk" ).preferred_size( 200.0, &canvas ); assert!( w >= 0.0 ); assert!( h > 0.0, "non-empty text reports a positive line height" ); } #[ test ] fn preferred_size_caps_width_at_max_width() { // A long string must not exceed the parent-supplied bound — text // elides at draw time, so the layout pass needs to see at most // `max_width`. let canvas = make_canvas(); let long = "the quick brown fox jumps over the lazy dog"; let ( w, _ ) = Text::new( long ).size( 24.0 ).preferred_size( 64.0, &canvas ); assert!( w <= 64.0, "preferred width must be clamped to max_width (got {w})" ); } #[ test ] fn preferred_size_height_scales_with_font_size() { let canvas = make_canvas(); let ( _, h_small ) = Text::new( "x" ).size( 12.0 ).preferred_size( 200.0, &canvas ); let ( _, h_large ) = Text::new( "x" ).size( 48.0 ).preferred_size( 200.0, &canvas ); assert!( h_large > h_small, "larger font must report taller line height" ); } #[ test ] fn empty_content_still_reports_a_line_height() { let canvas = make_canvas(); let ( _, h ) = Text::new( "" ).preferred_size( 200.0, &canvas ); // Even an empty string keeps the baseline metrics so a hidden field // keeps its row height inside a column. assert!( h > 0.0 ); } #[ test ] fn line_height_builder_defaults_to_one_and_clamps() { assert_eq!( Text::new( "" ).line_height, 1.0 ); assert_eq!( Text::new( "" ).line_height( 1.5 ).line_height, 1.5 ); // Floor keeps wrapped lines from overlapping even at absurd inputs. assert_eq!( Text::new( "" ).line_height( 0.1 ).line_height, 0.5 ); } #[ test ] fn line_height_multiplies_wrapped_text_height() { let canvas = make_canvas(); // Long enough to wrap onto several lines at this width. let long = "the quick brown fox jumps over the lazy dog"; let base = Text::new( long ).size( 24.0 ).wrap( true ); let doubled = Text::new( long ).size( 24.0 ).wrap( true ).line_height( 2.0 ); let h_base = base.preferred_size( 120.0, &canvas ).1; let h_doubled = doubled.preferred_size( 120.0, &canvas ).1; // Wrapping is width-driven, so both report the same line count; doubling // the line height therefore doubles the reported height. assert!( ( h_doubled - 2.0 * h_base ).abs() < 1e-3, "h_base={h_base} h_doubled={h_doubled}" ); } #[ test ] fn wrap_breaks_words_wider_than_the_line() { let canvas = make_canvas(); let word = "supercalifragilisticexpialidocious"; let lines = wrap_lines( &format!( "a {word} b" ), 24.0, 120.0, &canvas, None ); assert!( lines.len() > 3, "lines={lines:?}" ); assert_eq!( lines[ 0 ], "a" ); assert_eq!( lines.concat().replace( ' ', "" ), format!( "a{word}b" ) ); for line in &lines { assert!( canvas.measure_text( line, 24.0 ) <= 120.0, "line {line:?} overflows" ); } } #[ test ] fn text_align_enum_implements_partial_eq() { // Compile-time guard: TextAlign must keep deriving PartialEq so // downstream theming code can compare alignments. assert_eq!( TextAlign::Left, TextAlign::Left ); assert_ne!( TextAlign::Left, TextAlign::Right ); } fn rect( x: f32, y: f32, width: f32, height: f32 ) -> Rect { Rect { x, y, width, height } } #[ test ] fn without_fit_the_text_keeps_its_size() { let canvas = make_canvas(); let t = Text::new( "0123456789" ).size( 64.0 ); assert_eq!( t.fitted_size( rect( 0.0, 0.0, 40.0, 20.0 ), &canvas, None ), 64.0 ); } #[ test ] fn a_fitted_text_that_fits_keeps_its_size() { let canvas = make_canvas(); let t = Text::new( "0" ).size( 32.0 ).fit(); assert_eq!( t.fitted_size( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas, None ), 32.0 ); } #[ test ] fn a_fitted_line_shrinks_to_the_width_and_the_height() { let canvas = make_canvas(); let wide = Text::new( "0123456789" ).size( 64.0 ).fit(); let size = wide.fitted_size( rect( 0.0, 0.0, 120.0, 300.0 ), &canvas, None ); assert!( size < 64.0, "size={size}" ); assert!( wide.measure( &wide.content, size, &canvas, None ) + 8.0 <= 120.0 ); let low = Text::new( "0" ).size( 64.0 ).fit(); let size = low.fitted_size( rect( 0.0, 0.0, 400.0, 30.0 ), &canvas, None ); assert!( size < 64.0 && low.line_height_at( size, &canvas ) <= 30.0, "size={size}" ); } #[ test ] fn a_fitted_wrapped_text_keeps_all_its_lines_within_the_height() { let canvas = make_canvas(); let t = Text::new( "the quick brown fox jumps over the lazy dog" ).size( 48.0 ).wrap( true ).fit(); let area = rect( 0.0, 0.0, 160.0, 90.0 ); let size = t.fitted_size( area, &canvas, None ); let lines = wrap_lines( &t.content, size, area.width - WRAP_PAD, &canvas, None ); assert!( size < 48.0 ); assert!( lines.len() as f32 * t.line_height_at( size, &canvas ) <= area.height, "size={size} lines={lines:?}" ); } #[ test ] fn valign_places_the_lines_in_a_taller_rect() { let area = rect( 0.0, 10.0, 100.0, 100.0 ); assert_eq!( Text::new( "" ).top( area, 20.0 ), 10.0 ); assert_eq!( Text::new( "" ).valign( VAlign::Center ).top( area, 20.0 ), 50.0 ); assert_eq!( Text::new( "" ).valign( VAlign::Bottom ).top( area, 20.0 ), 90.0 ); assert_eq!( Text::new( "" ).valign( VAlign::Bottom ).top( area, 150.0 ), 10.0 ); }