use crate::math::Size;
use crate::style::{
FontColor, FontFamily, FontSize, HorizontalTextAlignment, LineHeight, TextStyle, TextWrap,
VerticalTextAlignment, Weight,
};
use crate::tests::mono_style_test;
use crate::{Action, Point, TextContext, TextState};
use cosmic_text::Color;
fn mono_style_with_alignment(
h_align: HorizontalTextAlignment,
v_align: VerticalTextAlignment,
) -> TextStyle {
TextStyle {
font_size: FontSize(14.0),
line_height: LineHeight(1.0),
font_color: FontColor(Color::rgb(0, 0, 0)),
horizontal_alignment: h_align,
vertical_alignment: v_align,
wrap: Some(TextWrap::NoWrap), font_family: FontFamily::Monospace,
weight: Weight::NORMAL,
letter_spacing: None,
}
}
fn mono_style_with_wrap(
h_align: HorizontalTextAlignment,
v_align: VerticalTextAlignment,
wrap: Option<TextWrap>,
) -> TextStyle {
TextStyle {
font_size: FontSize(14.0),
line_height: LineHeight(1.0),
font_color: FontColor(Color::rgb(0, 0, 0)),
horizontal_alignment: h_align,
vertical_alignment: v_align,
wrap,
font_family: FontFamily::Monospace,
weight: Weight::NORMAL,
letter_spacing: None,
}
}
#[test]
pub fn test_set_text() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(200.0, 25.0));
text_state.recalculate(&mut ctx);
assert_eq!(text_state.text(), "Hello World");
assert_eq!(text_state.text_char_len(), 11);
text_state.set_text("New text");
text_state.recalculate(&mut ctx);
assert_eq!(text_state.text(), "New text");
assert_eq!(text_state.text_char_len(), 8);
text_state.set_text("");
text_state.recalculate(&mut ctx);
assert_eq!(text_state.text(), "");
assert_eq!(text_state.text_char_len(), 0);
}
#[test]
pub fn test_set_style() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
let _initial_style = text_state.style().clone();
let new_style = TextStyle::new(20.0, Color::rgb(255, 0, 0))
.with_horizontal_alignment(HorizontalTextAlignment::Center)
.with_vertical_alignment(VerticalTextAlignment::Center);
text_state.set_style(&new_style);
let updated_style = text_state.style();
assert_eq!(updated_style.font_size.value(), 20.0);
assert_eq!(updated_style.font_color.0, Color::rgb(255, 0, 0));
assert!(matches!(
updated_style.horizontal_alignment,
HorizontalTextAlignment::Center
));
assert!(matches!(
updated_style.vertical_alignment,
VerticalTextAlignment::Center
));
}
#[test]
pub fn test_set_outer_size() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
assert_eq!(text_state.outer_size(), Size::ZERO);
let new_size = Size::new(300.0, 150.0);
text_state.set_outer_size(&new_size);
assert_eq!(text_state.outer_size(), new_size);
}
#[test]
pub fn test_inner_size() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(200.0, 25.0));
assert_eq!(text_state.inner_size(), Size::ZERO);
text_state.recalculate(&mut ctx);
let inner_size = text_state.inner_size();
assert!(inner_size.x > 0.0);
assert!(inner_size.y > 0.0);
text_state.set_text("Hello World with more text to increase the size");
text_state.recalculate(&mut ctx);
let new_inner_size = text_state.inner_size();
assert!(new_inner_size.x > inner_size.x);
}
#[test]
pub fn test_buffer_metadata() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
assert_eq!(text_state.buffer_metadata(), 0);
text_state.set_buffer_metadata(42);
assert_eq!(text_state.buffer_metadata(), 42);
}
#[test]
pub fn test_caret_width() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
assert_eq!(text_state.caret_width(), 3.0);
text_state.set_caret_width(5.0);
assert_eq!(text_state.caret_width(), 5.0);
}
#[test]
pub fn test_selection_methods() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(200.0, 25.0));
text_state.is_editable = true;
text_state.is_selectable = true;
text_state.are_actions_enabled = true;
text_state.recalculate(&mut ctx);
assert!(!text_state.is_text_selected());
assert!(text_state.selected_text().is_none());
text_state.apply_action(&mut ctx, &Action::SelectAll);
assert!(text_state.is_text_selected());
assert_eq!(text_state.selected_text(), Some("Hello World"));
text_state.reset_selection();
assert!(!text_state.is_text_selected());
assert!(text_state.selected_text().is_none());
}
#[test]
pub fn test_cursor_char_index() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(200.0, 25.0));
text_state.is_editable = true;
text_state.is_editing = true;
text_state.is_selectable = true;
text_state.are_actions_enabled = true;
text_state.recalculate(&mut ctx);
assert_eq!(text_state.cursor_char_index(), Some(0));
text_state.apply_action(&mut ctx, &Action::MoveCursorRight);
assert_eq!(text_state.cursor_char_index(), Some(1));
text_state.apply_action(&mut ctx, &Action::MoveCursorRight);
assert_eq!(text_state.cursor_char_index(), Some(2));
text_state.apply_action(&mut ctx, &Action::MoveCursorLeft);
assert_eq!(text_state.cursor_char_index(), Some(1));
}
#[test]
pub fn test_absolute_scroll() {
let mut ctx = TextContext::default();
let initial_text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::None,
));
text_state.set_outer_size(&Size::new(200.0, 50.0)); text_state.recalculate(&mut ctx);
let initial_scroll = text_state.absolute_scroll();
assert_eq!(initial_scroll.x, 0.0);
assert_eq!(initial_scroll.y, 0.0);
let new_scroll = Point::new(0.0, 20.0);
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert!(updated_scroll.y > 0.0); }
#[test]
pub fn test_handle_press() {
let mut ctx = TextContext::default();
let initial_text = "Hello World".to_string();
let mut text_state = TextState::new_with_text(initial_text, &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(200.0, 25.0));
text_state.is_editable = true;
text_state.is_selectable = true;
text_state.recalculate(&mut ctx);
assert_eq!(text_state.cursor_char_index(), Some(0));
text_state.handle_press(&mut ctx, Point::new(30.0, 10.0));
assert!(text_state.cursor_char_index().unwrap() > 0);
}
#[test]
pub fn test_horizontal_alignment() {
let mut ctx = TextContext::default();
let text = "Hello World".to_string();
let container_width = 200.0;
let container_height = 25.0;
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let first_glyph_x = text_state.first_glyph().unwrap().x;
assert!(
first_glyph_x < 5.0,
"Left-aligned text should start near the left edge"
);
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Center,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_width = text_state.inner_size().x;
let first_glyph_x = text_state.first_glyph().unwrap().x;
let expected_x = (container_width - text_width) / 2.0;
assert!(
(first_glyph_x - expected_x).abs() < 5.0,
"Center-aligned text should start near the center. Expected: {expected_x}, Actual: {first_glyph_x}"
);
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Right,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_width = text_state.inner_size().x;
let first_glyph_x = text_state.first_glyph().unwrap().x;
let expected_x = container_width - text_width;
assert!((first_glyph_x - expected_x).abs() < 5.0,
"Right-aligned text should start near the right edge minus text width. Expected: {expected_x}, Actual: {first_glyph_x}");
}
#[test]
pub fn test_vertical_alignment() {
let mut ctx = TextContext::default();
let text = "Hello World".to_string();
let container_width = 200.0;
let container_height = 100.0;
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
assert!(
matches!(
text_state.style().vertical_alignment,
VerticalTextAlignment::Start
),
"Vertical alignment should be Start"
);
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Center,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
assert!(
matches!(
text_state.style().vertical_alignment,
VerticalTextAlignment::Center
),
"Vertical alignment should be Center"
);
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::End,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
assert!(
matches!(
text_state.style().vertical_alignment,
VerticalTextAlignment::End
),
"Vertical alignment should be End"
);
}
#[test]
pub fn test_combined_alignment() {
let mut ctx = TextContext::default();
let text = "Hello World".to_string();
let container_width = 200.0;
let container_height = 100.0;
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Center,
VerticalTextAlignment::Center,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
assert!(
matches!(
text_state.style().horizontal_alignment,
HorizontalTextAlignment::Center
),
"Horizontal alignment should be Center"
);
assert!(
matches!(
text_state.style().vertical_alignment,
VerticalTextAlignment::Center
),
"Vertical alignment should be Center"
);
let text_width = text_state.inner_size().x;
let first_glyph_x = text_state.first_glyph().unwrap().x;
let expected_x = (container_width - text_width) / 2.0;
assert!(
(first_glyph_x - expected_x).abs() < 5.0,
"Horizontally centered text should start at the right position. Expected: {expected_x}, Actual: {first_glyph_x}"
);
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Right,
VerticalTextAlignment::End,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
assert!(
matches!(
text_state.style().horizontal_alignment,
HorizontalTextAlignment::Right
),
"Horizontal alignment should be Right"
);
assert!(
matches!(
text_state.style().vertical_alignment,
VerticalTextAlignment::End
),
"Vertical alignment should be End"
);
let text_width = text_state.inner_size().x;
let first_glyph_x = text_state.first_glyph().unwrap().x;
let expected_x = container_width - text_width;
assert!(
(first_glyph_x - expected_x).abs() < 5.0,
"Right-aligned text should start at the right position. Expected: {expected_x}, Actual: {first_glyph_x}"
);
}
#[test]
pub fn test_horizontal_overflow() {
let mut ctx = TextContext::default();
let long_text =
"This is a very long text that should overflow the container horizontally".to_string();
let container_width = 100.0; let container_height = 50.0;
let mut text_state = TextState::new_with_text(long_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_wrap(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
Some(TextWrap::NoWrap), ));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_width = text_state.inner_size().x;
assert!(
text_width > container_width,
"Text should overflow horizontally. Text width: {text_width}, Container width: {container_width}"
);
let mut text_state = TextState::new_with_text(long_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_wrap(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
Some(TextWrap::Wrap), ));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_width = text_state.inner_size().x;
assert!(text_width <= container_width + 2.0,
"Text should not overflow horizontally with wrapping by much. Text width: {text_width}, Container width: {container_width}");
}
#[test]
pub fn test_vertical_overflow() {
let mut ctx = TextContext::default();
let multi_line_text =
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10"
.to_string();
let container_width = 200.0;
let container_height = 50.0;
let mut text_state =
TextState::new_with_text(multi_line_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_test());
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_height = text_state.inner_size().y;
assert!(
text_height > container_height,
"Text should overflow vertically. Text height: {text_height}, Container height: {container_height}"
);
}
#[test]
pub fn test_both_overflow() {
let mut ctx = TextContext::default();
let long_multi_line_text =
"This is a very long line that should overflow horizontally\n".repeat(10);
let container_width = 100.0; let container_height = 50.0;
let mut text_state =
TextState::new_with_text(long_multi_line_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_wrap(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
Some(TextWrap::NoWrap), ));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let text_size = text_state.inner_size();
assert!(
text_size.x > container_width,
"Text should overflow horizontally. Text width: {}, Container width: {}",
text_size.x,
container_width
);
assert!(
text_size.y > container_height,
"Text should overflow vertically. Text height: {}, Container height: {}",
text_size.y,
container_height
);
}
#[test]
pub fn test_vertical_scroll_with_alignment() {
let mut ctx = TextContext::default();
let initial_text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5".to_string();
let container_width = 200.0;
let container_height = 50.0;
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::None,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let initial_scroll = text_state.absolute_scroll();
assert_eq!(initial_scroll.y, 0.0);
let new_scroll = Point::new(0.0, 20.0);
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert!(
updated_scroll.y > 0.0,
"Vertical scrolling should work with VerticalTextAlignment::None"
);
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
text_state.set_absolute_scroll(Point::new(0.0, 0.0));
let initial_scroll = text_state.absolute_scroll();
let new_scroll = Point::new(0.0, 20.0);
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert_eq!(
updated_scroll.y, initial_scroll.y,
"Vertical scrolling should not work with VerticalTextAlignment::Start"
);
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Center,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
text_state.set_absolute_scroll(Point::new(0.0, 0.0));
let initial_scroll = text_state.absolute_scroll();
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert_eq!(
updated_scroll.y, initial_scroll.y,
"Vertical scrolling should not work with VerticalTextAlignment::Center"
);
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::End,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
text_state.set_absolute_scroll(Point::new(0.0, 0.0));
let initial_scroll = text_state.absolute_scroll();
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert_eq!(
updated_scroll.y, initial_scroll.y,
"Vertical scrolling should not work with VerticalTextAlignment::End"
);
}
#[test]
pub fn test_combined_scroll_with_alignment() {
let mut ctx = TextContext::default();
let initial_text = "This is a very long text that should overflow horizontally\nLine 2\nLine 3\nLine 4\nLine 5".to_string();
let container_width = 100.0; let container_height = 50.0;
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::None,
VerticalTextAlignment::None,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
let initial_scroll = text_state.absolute_scroll();
assert_eq!(initial_scroll.x, 0.0);
assert_eq!(initial_scroll.y, 0.0);
let new_scroll = Point::new(20.0, 30.0);
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert!(
updated_scroll.x > 0.0,
"Horizontal scrolling should work with HorizontalTextAlignment::None"
);
assert!(
updated_scroll.y > 0.0,
"Vertical scrolling should work with VerticalTextAlignment::None"
);
let mut text_state = TextState::new_with_text(initial_text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::None,
VerticalTextAlignment::Start,
));
text_state.set_outer_size(&Size::new(container_width, container_height));
text_state.recalculate(&mut ctx);
text_state.set_absolute_scroll(new_scroll);
let updated_scroll = text_state.absolute_scroll();
assert!(
updated_scroll.x > 0.0,
"Horizontal scrolling should work with HorizontalTextAlignment::None"
);
assert_eq!(
updated_scroll.y, 0.0,
"Vertical scrolling should not work with VerticalTextAlignment::Start"
);
}
#[test]
pub fn test_scale_factor_consistency() {
let mut ctx = TextContext::default();
let text = "Line 1\nLine 2\nLine 3".to_string();
let mut text_state = TextState::new_with_text(text.clone(), &mut ctx.font_system, ());
text_state.set_style(&mono_style_with_alignment(
HorizontalTextAlignment::Left,
VerticalTextAlignment::Center,
));
text_state.set_outer_size(&Size::new(200.0, 100.0));
text_state.set_scale_factor(1.0);
text_state.recalculate(&mut ctx);
let scroll1 = text_state.absolute_scroll();
text_state.set_scale_factor(2.0);
text_state.recalculate(&mut ctx);
let scroll2 = text_state.absolute_scroll();
assert!(scroll1.approx_eq(&scroll2, 0.75), "Absolute scroll should be stable in logical pixels when scale changes. Before: {:?}, After: {:?}", scroll1, scroll2);
text_state.is_selectable = true;
text_state.is_editable = true;
text_state.are_actions_enabled = true;
text_state.handle_press(&mut ctx, Point::new(2.0, 5.0));
let cursor_idx_scale2 = text_state.cursor_char_index().unwrap_or(0);
text_state.set_scale_factor(1.0);
text_state.recalculate(&mut ctx);
text_state.handle_press(&mut ctx, Point::new(2.0, 5.0));
let cursor_idx_scale1 = text_state.cursor_char_index().unwrap_or(0);
assert_eq!(
cursor_idx_scale1, cursor_idx_scale2,
"Hit-test should map the same logical coords to the same character across scales"
);
text_state.apply_action(&mut ctx, &Action::SelectAll);
let lines_scale1 = text_state.selection().lines().to_vec();
text_state.set_scale_factor(2.0);
text_state.recalculate(&mut ctx);
let lines_scale2 = text_state.selection().lines().to_vec();
if let (Some(l1), Some(l2)) = (lines_scale1.first(), lines_scale2.first()) {
let h1 = (l1.end_y_pt.unwrap_or(0.0) - l1.start_y_pt.unwrap_or(0.0)).abs();
let h2 = (l2.end_y_pt.unwrap_or(0.0) - l2.start_y_pt.unwrap_or(0.0)).abs();
assert!(
(h1 - h2).abs() < 1.0,
"Selection line height should be stable in logical px across scales: h1={} h2={}",
h1,
h2
);
}
}