use std::sync::Arc;
use super::*;
use crate::event::{Event, MouseButton};
use crate::geometry::Point;
use crate::widget::Widget;
const FONT_BYTES: &[u8] = include_bytes!("../../../../demo/assets/CascadiaCode.ttf");
fn font() -> Arc<Font> {
Arc::new(Font::from_slice(FONT_BYTES).expect("font"))
}
fn laid_out(text: &str, w: f64, h: f64) -> TextArea {
let mut ta = TextArea::new(font()).with_text(text);
ta.layout(Size::new(w, h));
ta
}
#[test]
fn double_click_selects_word() {
let mut ta = laid_out("hello world", 400.0, 120.0);
ta.begin_pointer_selection(8, 2, false);
assert_eq!(ta.selection(), Some((6, 11)));
assert_eq!(ta.selected_text(), "world");
}
#[test]
fn double_click_stops_at_punctuation() {
let mut ta = laid_out("foo.bar", 400.0, 120.0);
ta.begin_pointer_selection(1, 2, false);
assert_eq!(ta.selected_text(), "foo");
ta.begin_pointer_selection(5, 2, false);
assert_eq!(ta.selected_text(), "bar");
}
#[test]
fn triple_click_selects_logical_line() {
let text = "first line\nsecond line here\nthird";
let mut ta = laid_out(text, 400.0, 200.0);
ta.begin_pointer_selection(15, 3, false);
assert_eq!(ta.selected_text(), "second line here");
}
#[test]
fn word_drag_extends_by_whole_words() {
let text = "alpha beta gamma";
let mut ta = laid_out(text, 400.0, 120.0);
ta.begin_pointer_selection(7, 2, false);
assert_eq!(ta.selected_text(), "beta");
ta.extend_selection_drag(13);
assert_eq!(ta.selected_text(), "beta gamma");
ta.extend_selection_drag(2);
assert_eq!(ta.selected_text(), "alpha beta");
}
#[test]
fn double_mouse_down_event_selects_word() {
let mut ta = laid_out("hello world", 400.0, 120.0);
ta.on_event(&Event::FocusGained);
let p = ta.pos_for_cursor(8);
let click = Point::new(p.x + 1.0, p.y + 1.0);
let down = Event::MouseDown {
pos: click,
button: MouseButton::Left,
modifiers: Modifiers::default(),
};
ta.on_event(&down);
ta.on_event(&Event::MouseUp {
pos: click,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
ta.on_event(&down);
assert_eq!(ta.selected_text(), "world");
}
#[test]
fn backbuffer_mode_follows_lcd_setting() {
use crate::widget::BackbufferMode;
crate::device_scale::set_device_scale(1.0);
crate::ux_scale::set_ux_scale(1.0);
let ta = laid_out("hello", 400.0, 120.0);
crate::font_settings::set_lcd_enabled(true);
assert_eq!(ta.backbuffer_mode(), BackbufferMode::LcdCoverage);
crate::font_settings::set_lcd_enabled(false);
assert_eq!(ta.backbuffer_mode(), BackbufferMode::Rgba);
crate::font_settings::clear_lcd_enabled_override();
}
#[test]
fn line_baselines_snap_to_pixel_grid_when_hinting_on() {
let mut ta = TextArea::new(font())
.with_text("line one\nline two\nline three")
.with_font_size(13.0);
ta.layout(Size::new(400.0, 200.0));
crate::font_settings::set_hinting_enabled(true);
for i in 0..ta.visual_line_count() {
let y = ta.line_baseline_y(i);
assert_eq!(y.fract(), 0.0, "line {i} baseline {y} not on the pixel grid");
}
crate::font_settings::set_hinting_enabled(false);
}
fn key_mods(ta: &mut TextArea, k: Key, mods: Modifiers) {
ta.on_event(&Event::KeyDown {
key: k,
modifiers: mods,
});
}
fn ctrl() -> Modifiers {
Modifiers {
ctrl: true,
..Default::default()
}
}
fn ctrl_shift() -> Modifiers {
Modifiers {
ctrl: true,
shift: true,
..Default::default()
}
}
fn shift_mod() -> Modifiers {
Modifiers {
shift: true,
..Default::default()
}
}
fn place_cursor(ta: &TextArea, pos: usize) {
let mut st = ta.edit.borrow_mut();
st.cursor = pos;
st.anchor = pos;
}
fn place_selection(ta: &TextArea, anchor: usize, cursor: usize) {
let mut st = ta.edit.borrow_mut();
st.anchor = anchor;
st.cursor = cursor;
}
#[test]
fn ctrl_right_and_left_move_by_word() {
let mut ta = laid_out("hello world foo", 400.0, 120.0);
place_cursor(&ta, 0);
key_mods(&mut ta, Key::ArrowRight, ctrl());
assert_eq!(ta.cursor(), 5); key_mods(&mut ta, Key::ArrowRight, ctrl());
assert_eq!(ta.cursor(), 11); key_mods(&mut ta, Key::ArrowLeft, ctrl());
assert_eq!(ta.cursor(), 6); assert_eq!(ta.selection(), None, "plain word move collapses the selection");
}
#[test]
fn ctrl_shift_right_extends_selection_by_word() {
let mut ta = laid_out("hello world", 400.0, 120.0);
place_cursor(&ta, 0);
key_mods(&mut ta, Key::ArrowRight, ctrl_shift());
assert_eq!(ta.cursor(), 5);
assert_eq!(ta.selection(), Some((0, 5)), "Shift keeps the anchor put");
key_mods(&mut ta, Key::ArrowRight, ctrl_shift());
assert_eq!(ta.selection(), Some((0, 11)));
}
#[test]
fn ctrl_backspace_deletes_previous_word() {
let mut ta = laid_out("hello world", 400.0, 120.0);
place_cursor(&ta, 11);
key_mods(&mut ta, Key::Backspace, ctrl());
assert_eq!(ta.text(), "hello ");
assert_eq!(ta.cursor(), 6);
}
#[test]
fn ctrl_delete_deletes_next_word() {
let mut ta = laid_out("hello world", 400.0, 120.0);
place_cursor(&ta, 0);
key_mods(&mut ta, Key::Delete, ctrl());
assert_eq!(ta.text(), " world");
assert_eq!(ta.cursor(), 0);
}
#[test]
fn word_delete_removes_active_selection_first() {
let mut ta = laid_out("hello world", 400.0, 120.0);
place_selection(&ta, 0, 5); key_mods(&mut ta, Key::Backspace, ctrl());
assert_eq!(ta.text(), " world");
assert_eq!(ta.cursor(), 0);
assert_eq!(ta.selection(), None);
}
#[test]
fn trailing_space_advances_caret() {
let mut ta = laid_out("word", 400.0, 120.0);
place_cursor(&ta, 4);
let x0 = ta.pos_for_cursor(4).x;
ta.insert_str(" ");
ta.layout(Size::new(400.0, 120.0));
let x1 = ta.pos_for_cursor(5).x;
assert!(
x1 > x0 + 1.0,
"caret must advance past the trailing space: x0={x0} x1={x1}"
);
ta.insert_str(" ");
ta.layout(Size::new(400.0, 120.0));
let x2 = ta.pos_for_cursor(6).x;
assert!(
(x2 - x1) > 1.0 && ((x2 - x1) - (x1 - x0)).abs() < 0.5,
"each trailing space adds one uniform advance: x0={x0} x1={x1} x2={x2}"
);
}
#[test]
fn trailing_space_at_wrap_boundary_no_panic() {
let mut ta = laid_out("aaaa bbbb", 60.0, 120.0);
assert!(ta.visual_line_count() >= 2, "text should wrap");
place_cursor(&ta, 4);
ta.insert_str(" ");
ta.layout(Size::new(60.0, 120.0));
let p = ta.pos_for_cursor(5);
assert!(p.x.is_finite() && p.y.is_finite());
}
#[test]
fn tab_indents_every_line_of_a_multiline_selection() {
let mut ta = laid_out("aa\nbb\ncc", 400.0, 200.0);
place_selection(&ta, 0, 4);
key_mods(&mut ta, Key::Tab, Modifiers::default());
assert_eq!(ta.text(), " aa\n bb\ncc");
assert_eq!(ta.selection(), Some((0, 12)));
}
#[test]
fn shift_tab_outdents_every_line_of_a_multiline_selection() {
let mut ta = laid_out(" aa\n bb\ncc", 400.0, 200.0);
place_selection(&ta, 0, 12);
key_mods(&mut ta, Key::Tab, shift_mod());
assert_eq!(ta.text(), "aa\nbb\ncc");
assert_eq!(ta.selection(), Some((0, 4)), "same text stays selected");
}
#[test]
fn shift_tab_outdents_the_caret_line_without_a_selection() {
let mut ta = laid_out(" ab", 400.0, 120.0);
place_cursor(&ta, 6);
key_mods(&mut ta, Key::Tab, shift_mod());
assert_eq!(ta.text(), "ab");
assert_eq!(ta.cursor(), 2, "caret column shifts left by the removed indent");
}
#[test]
fn shift_tab_removes_only_the_spaces_present_when_fewer_than_a_tab() {
let mut ta = laid_out(" ab", 400.0, 120.0);
place_cursor(&ta, 4);
key_mods(&mut ta, Key::Tab, shift_mod());
assert_eq!(ta.text(), "ab");
assert_eq!(ta.cursor(), 2);
}
#[test]
fn shift_tab_removes_a_single_leading_tab() {
let mut ta = laid_out("\tab", 400.0, 120.0);
place_cursor(&ta, 3);
key_mods(&mut ta, Key::Tab, shift_mod());
assert_eq!(ta.text(), "ab");
assert_eq!(ta.cursor(), 2);
}
#[test]
fn tab_without_multiline_selection_inserts_spaces() {
let mut ta = laid_out("abcd", 400.0, 120.0);
place_selection(&ta, 1, 3); key_mods(&mut ta, Key::Tab, Modifiers::default());
assert_eq!(ta.text(), "a d");
assert_eq!(ta.cursor(), 5);
}