Files
servo/tests/unit/script/textinput.rs
Martin Robinson 19e41ab9f9 libservo: Add a ClipboardDelegate and a default implementation (#35297)
Add a `ClipboardDelegate` to the `WebView` API and a default
implementation in libservo for this delegate that works on Mac, Windows,
and Linux. Support for Android will be added in the future. This means
that embedders do not need to do anything special to get clipboard
support, but can choose to override it or implement it for other
platforms.

In addition, this adds support for handling fetches of clipboard contents
and renames things to reflect that eventually other types of clipboard
content will be supported. Part of this is removing the string
argument from the `ClipboardEventType::Paste` enum because script will
need to get other types of content from the clipboard than just a
string. It now talks to the embedder to get this information directly.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2025-02-07 10:43:46 +00:00

859 lines
31 KiB
Rust

// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use keyboard_types::{Key, Modifiers};
use script::test::textinput::{
ClipboardProvider, Direction, Lines, Selection, SelectionDirection, TextInput, TextPoint,
UTF16CodeUnits, UTF8Bytes,
};
use script::test::DOMString;
pub struct DummyClipboardContext {
content: String,
}
impl DummyClipboardContext {
pub fn new(s: &str) -> DummyClipboardContext {
DummyClipboardContext {
content: s.to_owned(),
}
}
}
impl ClipboardProvider for DummyClipboardContext {
fn get_text(&mut self) -> Result<String, String> {
Ok(self.content.clone())
}
fn set_text(&mut self, s: String) {
self.content = s;
}
}
fn text_input(lines: Lines, s: &str) -> TextInput<DummyClipboardContext> {
TextInput::new(
lines,
DOMString::from(s),
DummyClipboardContext::new(""),
None,
None,
SelectionDirection::None,
)
}
#[test]
fn test_set_content_ignores_max_length() {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from(""),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits::one()),
None,
SelectionDirection::None,
);
textinput.set_content(DOMString::from("mozilla rocks"));
assert_eq!(textinput.get_content(), DOMString::from("mozilla rocks"));
}
#[test]
fn test_textinput_when_inserting_multiple_lines_over_a_selection_respects_max_length() {
let mut textinput = TextInput::new(
Lines::Multiple,
DOMString::from("hello\nworld"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(17)),
None,
SelectionDirection::None,
);
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
textinput.adjust_vertical(1, Selection::Selected);
// Selection is now "hello\n
// ------
// world"
// ----
textinput.insert_string("cruel\nterrible\nbad".to_string());
assert_eq!(textinput.get_content(), "hcruel\nterrible\nd");
}
#[test]
fn test_textinput_when_inserting_multiple_lines_still_respects_max_length() {
let mut textinput = TextInput::new(
Lines::Multiple,
DOMString::from("hello\nworld"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(17)),
None,
SelectionDirection::None,
);
textinput.adjust_vertical(1, Selection::NotSelected);
textinput.insert_string("cruel\nterrible".to_string());
assert_eq!(textinput.get_content(), "hello\ncruel\nworld");
}
#[test]
fn test_textinput_when_content_is_already_longer_than_max_length_and_theres_no_selection_dont_insert_anything(
) {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("abc"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits::one()),
None,
SelectionDirection::None,
);
textinput.insert_char('a');
assert_eq!(textinput.get_content(), "abc");
}
#[test]
fn test_multi_line_textinput_with_maxlength_doesnt_allow_appending_characters_when_input_spans_lines(
) {
let mut textinput = TextInput::new(
Lines::Multiple,
DOMString::from("abc\nd"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(5)),
None,
SelectionDirection::None,
);
textinput.insert_char('a');
assert_eq!(textinput.get_content(), "abc\nd");
}
#[test]
fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_when_replacing_a_selection(
) {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("abcde"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(5)),
None,
SelectionDirection::None,
);
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
// Selection is now "abcde"
// ---
textinput.replace_selection(DOMString::from("too long"));
assert_eq!(textinput.get_content(), "atooe");
}
#[test]
fn test_single_line_textinput_with_max_length_allows_deletion_when_replacing_a_selection() {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("abcde"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(1)),
None,
SelectionDirection::None,
);
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::Selected);
// Selection is now "abcde"
// --
textinput.replace_selection(DOMString::from("only deletion should be applied"));
assert_eq!(textinput.get_content(), "ade");
}
#[test]
fn test_single_line_textinput_with_max_length_multibyte() {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from(""),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(2)),
None,
SelectionDirection::None,
);
textinput.insert_char('á');
assert_eq!(textinput.get_content(), "á");
textinput.insert_char('é');
assert_eq!(textinput.get_content(), "áé");
textinput.insert_char('i');
assert_eq!(textinput.get_content(), "áé");
}
#[test]
fn test_single_line_textinput_with_max_length_multi_code_unit() {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from(""),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits(3)),
None,
SelectionDirection::None,
);
textinput.insert_char('\u{10437}');
assert_eq!(textinput.get_content(), "\u{10437}");
textinput.insert_char('\u{10437}');
assert_eq!(textinput.get_content(), "\u{10437}");
textinput.insert_char('x');
assert_eq!(textinput.get_content(), "\u{10437}x");
textinput.insert_char('x');
assert_eq!(textinput.get_content(), "\u{10437}x");
}
#[test]
fn test_single_line_textinput_with_max_length_inside_char() {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("\u{10437}"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits::one()),
None,
SelectionDirection::None,
);
textinput.insert_char('x');
assert_eq!(textinput.get_content(), "\u{10437}");
}
#[test]
fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_after_max_length_is_reached(
) {
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("a"),
DummyClipboardContext::new(""),
Some(UTF16CodeUnits::one()),
None,
SelectionDirection::None,
);
textinput.insert_char('b');
assert_eq!(textinput.get_content(), "a");
}
#[test]
fn test_textinput_delete_char() {
let mut textinput = text_input(Lines::Single, "abcdefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::NotSelected);
textinput.delete_char(Direction::Backward);
assert_eq!(textinput.get_content(), "acdefg");
textinput.delete_char(Direction::Forward);
assert_eq!(textinput.get_content(), "adefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::Selected);
textinput.delete_char(Direction::Forward);
assert_eq!(textinput.get_content(), "afg");
let mut textinput = text_input(Lines::Single, "a🌠b");
// Same as "Right" key
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
textinput.delete_char(Direction::Forward);
// Not splitting surrogate pairs.
assert_eq!(textinput.get_content(), "ab");
let mut textinput = text_input(Lines::Single, "abcdefg");
textinput.set_selection_range(2, 2, SelectionDirection::None);
textinput.delete_char(Direction::Backward);
assert_eq!(textinput.get_content(), "acdefg");
}
#[test]
fn test_textinput_insert_char() {
let mut textinput = text_input(Lines::Single, "abcdefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::NotSelected);
textinput.insert_char('a');
assert_eq!(textinput.get_content(), "abacdefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::Selected);
textinput.insert_char('b');
assert_eq!(textinput.get_content(), "ababefg");
let mut textinput = text_input(Lines::Single, "a🌠c");
// Same as "Right" key
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
textinput.insert_char('b');
// Not splitting surrogate pairs.
assert_eq!(textinput.get_content(), "a🌠bc");
}
#[test]
fn test_textinput_get_sorted_selection() {
let mut textinput = text_input(Lines::Single, "abcdefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::Selected);
let (start, end) = textinput.sorted_selection_bounds();
assert_eq!(start.index, UTF8Bytes(2));
assert_eq!(end.index, UTF8Bytes(4));
textinput.clear_selection();
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Backward, Selection::Selected);
let (start, end) = textinput.sorted_selection_bounds();
assert_eq!(start.index, UTF8Bytes(2));
assert_eq!(end.index, UTF8Bytes(4));
}
#[test]
fn test_textinput_replace_selection() {
let mut textinput = text_input(Lines::Single, "abcdefg");
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::Selected);
textinput.replace_selection(DOMString::from("xyz"));
assert_eq!(textinput.get_content(), "abxyzefg");
}
#[test]
fn test_textinput_replace_selection_multibyte_char() {
let mut textinput = text_input(Lines::Single, "é");
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::Selected);
textinput.replace_selection(DOMString::from("e"));
assert_eq!(textinput.get_content(), "e");
}
#[test]
fn test_textinput_current_line_length() {
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
assert_eq!(textinput.current_line_length(), UTF8Bytes(3));
textinput.adjust_vertical(1, Selection::NotSelected);
assert_eq!(textinput.current_line_length(), UTF8Bytes(2));
textinput.adjust_vertical(1, Selection::NotSelected);
assert_eq!(textinput.current_line_length(), UTF8Bytes::one());
}
#[test]
fn test_textinput_adjust_vertical() {
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::NotSelected);
textinput.adjust_vertical(1, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
textinput.adjust_vertical(-1, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
textinput.adjust_vertical(2, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 2);
assert_eq!(textinput.edit_point().index, UTF8Bytes(1));
textinput.adjust_vertical(-1, Selection::Selected);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(1));
}
#[test]
fn test_textinput_adjust_vertical_multibyte() {
let mut textinput = text_input(Lines::Multiple, "áé\nae");
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
textinput.adjust_vertical(1, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(1));
}
#[test]
fn test_textinput_adjust_horizontal() {
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(1));
textinput.adjust_horizontal(UTF8Bytes(2), Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 2);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.adjust_horizontal(
UTF8Bytes::one(),
Direction::Backward,
Selection::NotSelected,
);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
}
#[test]
fn test_textinput_adjust_horizontal_by_word() {
// Test basic case of movement word by word based on UAX#29 rules
let mut textinput = text_input(Lines::Single, "abc def");
textinput.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
textinput.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(7));
textinput.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(4));
textinput.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
// Test new line case of movement word by word based on UAX#29 rules
let mut textinput_2 = text_input(Lines::Multiple, "abc\ndef");
textinput_2.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
textinput_2.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 1);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes(3));
textinput_2.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 1);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes::zero());
textinput_2.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 0);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes::zero());
// Test non-standard sized characters case of movement word by word based on UAX#29 rules
let mut textinput_3 = text_input(Lines::Single, "áéc d🌠bc");
textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(5));
textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(7));
textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(13));
textinput_3.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(11));
textinput_3.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(6));
}
#[test]
fn test_textinput_adjust_horizontal_to_line_end() {
// Test standard case of movement to end based on UAX#29 rules
let mut textinput = text_input(Lines::Single, "abc def");
textinput.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(7));
// Test new line case of movement to end based on UAX#29 rules
let mut textinput_2 = text_input(Lines::Multiple, "abc\ndef");
textinput_2.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 0);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes(3));
textinput_2.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 0);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes(3));
textinput_2.adjust_horizontal_to_line_end(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_2.edit_point().line, 0);
assert_eq!(textinput_2.edit_point().index, UTF8Bytes::zero());
// Test non-standard sized characters case of movement to end based on UAX#29 rules
let mut textinput_3 = text_input(Lines::Single, "áéc d🌠bc");
textinput_3.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes(13));
textinput_3.adjust_horizontal_to_line_end(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput_3.edit_point().line, 0);
assert_eq!(textinput_3.edit_point().index, UTF8Bytes::zero());
}
#[test]
fn test_navigation_keyboard_shortcuts() {
let mut textinput = text_input(Lines::Multiple, "hello áéc");
// Test that CMD + Right moves to the end of the current line.
textinput.handle_keydown_aux(Key::ArrowRight, Modifiers::META, true);
assert_eq!(textinput.edit_point().index, UTF8Bytes(11));
// Test that CMD + Right moves to the beginning of the current line.
textinput.handle_keydown_aux(Key::ArrowLeft, Modifiers::META, true);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
// Test that CTRL + ALT + E moves to the end of the current line also.
textinput.handle_keydown_aux(
Key::Character("e".to_owned()),
Modifiers::CONTROL | Modifiers::ALT,
true,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes(11));
// Test that CTRL + ALT + A moves to the beginning of the current line also.
textinput.handle_keydown_aux(
Key::Character("a".to_owned()),
Modifiers::CONTROL | Modifiers::ALT,
true,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
// Test that ALT + Right moves to the end of the word.
textinput.handle_keydown_aux(Key::ArrowRight, Modifiers::ALT, true);
assert_eq!(textinput.edit_point().index, UTF8Bytes(5));
// Test that CTRL + ALT + F moves to the end of the word also.
textinput.handle_keydown_aux(
Key::Character("f".to_owned()),
Modifiers::CONTROL | Modifiers::ALT,
true,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes(11));
// Test that ALT + Left moves to the end of the word.
textinput.handle_keydown_aux(Key::ArrowLeft, Modifiers::ALT, true);
assert_eq!(textinput.edit_point().index, UTF8Bytes(6));
// Test that CTRL + ALT + B moves to the end of the word also.
textinput.handle_keydown_aux(
Key::Character("b".to_owned()),
Modifiers::CONTROL | Modifiers::ALT,
true,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
}
#[test]
fn test_textinput_handle_return() {
let mut single_line_textinput = text_input(Lines::Single, "abcdef");
single_line_textinput.adjust_horizontal(
UTF8Bytes(3),
Direction::Forward,
Selection::NotSelected,
);
single_line_textinput.handle_return();
assert_eq!(single_line_textinput.get_content(), "abcdef");
let mut multi_line_textinput = text_input(Lines::Multiple, "abcdef");
multi_line_textinput.adjust_horizontal(
UTF8Bytes(3),
Direction::Forward,
Selection::NotSelected,
);
multi_line_textinput.handle_return();
assert_eq!(multi_line_textinput.get_content(), "abc\ndef");
}
#[test]
fn test_textinput_select_all() {
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.select_all();
assert_eq!(textinput.edit_point().line, 2);
assert_eq!(textinput.edit_point().index, UTF8Bytes(1));
}
#[test]
fn test_textinput_get_content() {
let single_line_textinput = text_input(Lines::Single, "abcdefg");
assert_eq!(single_line_textinput.get_content(), "abcdefg");
let multi_line_textinput = text_input(Lines::Multiple, "abc\nde\nf");
assert_eq!(multi_line_textinput.get_content(), "abc\nde\nf");
}
#[test]
fn test_textinput_set_content() {
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
assert_eq!(textinput.get_content(), "abc\nde\nf");
textinput.set_content(DOMString::from("abc\nf"));
assert_eq!(textinput.get_content(), "abc\nf");
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(3));
textinput.set_content(DOMString::from("de"));
assert_eq!(textinput.get_content(), "de");
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
}
#[test]
fn test_clipboard_paste() {
#[cfg(target_os = "macos")]
const MODIFIERS: Modifiers = Modifiers::META;
#[cfg(not(target_os = "macos"))]
const MODIFIERS: Modifiers = Modifiers::CONTROL;
let mut textinput = TextInput::new(
Lines::Single,
DOMString::from("defg"),
DummyClipboardContext::new("abc"),
None,
None,
SelectionDirection::None,
);
assert_eq!(textinput.get_content(), "defg");
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.handle_keydown_aux(Key::Character("v".to_owned()), MODIFIERS, false);
assert_eq!(textinput.get_content(), "abcdefg");
}
#[test]
fn test_textinput_cursor_position_correct_after_clearing_selection() {
let mut textinput = text_input(Lines::Single, "abcdef");
// Single line - Forward
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes(3));
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes(3));
// Single line - Backward
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal(
UTF8Bytes::one(),
Direction::Backward,
Selection::NotSelected,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(3), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal_by_one(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
let mut textinput = text_input(Lines::Multiple, "abc\nde\nf");
// Multiline - Forward
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal(UTF8Bytes::one(), Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.edit_point().line, 1);
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.edit_point().line, 1);
// Multiline - Backward
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal(
UTF8Bytes::one(),
Direction::Backward,
Selection::NotSelected,
);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.edit_point().line, 0);
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Backward, Selection::NotSelected);
textinput.adjust_horizontal(UTF8Bytes(4), Direction::Forward, Selection::Selected);
textinput.adjust_horizontal_by_one(Direction::Backward, Selection::NotSelected);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.edit_point().line, 0);
}
#[test]
fn test_textinput_set_selection_with_direction() {
let mut textinput = text_input(Lines::Single, "abcdef");
textinput.set_selection_range(2, 6, SelectionDirection::Forward);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(6));
assert_eq!(textinput.selection_direction(), SelectionDirection::Forward);
assert!(textinput.selection_origin().is_some());
assert_eq!(textinput.selection_origin().unwrap().line, 0);
assert_eq!(textinput.selection_origin().unwrap().index, UTF8Bytes(2));
textinput.set_selection_range(2, 6, SelectionDirection::Backward);
assert_eq!(textinput.edit_point().line, 0);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
assert_eq!(
textinput.selection_direction(),
SelectionDirection::Backward
);
assert!(textinput.selection_origin().is_some());
assert_eq!(textinput.selection_origin().unwrap().line, 0);
assert_eq!(textinput.selection_origin().unwrap().index, UTF8Bytes(6));
textinput = text_input(Lines::Multiple, "\n\n");
textinput.set_selection_range(0, 1, SelectionDirection::Forward);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.selection_direction(), SelectionDirection::Forward);
assert!(textinput.selection_origin().is_some());
assert_eq!(textinput.selection_origin().unwrap().line, 0);
assert_eq!(
textinput.selection_origin().unwrap().index,
UTF8Bytes::zero()
);
textinput = text_input(Lines::Multiple, "\n");
textinput.set_selection_range(0, 1, SelectionDirection::Forward);
assert_eq!(textinput.edit_point().line, 1);
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
assert_eq!(textinput.selection_direction(), SelectionDirection::Forward);
assert!(textinput.selection_origin().is_some());
assert_eq!(textinput.selection_origin().unwrap().line, 0);
assert_eq!(
textinput.selection_origin().unwrap().index,
UTF8Bytes::zero()
);
}
#[test]
fn test_textinput_unicode_handling() {
let mut textinput = text_input(Lines::Single, "éèùµ$£");
assert_eq!(textinput.edit_point().index, UTF8Bytes::zero());
textinput.set_edit_point_index(1);
assert_eq!(textinput.edit_point().index, UTF8Bytes(2));
textinput.set_edit_point_index(4);
assert_eq!(textinput.edit_point().index, UTF8Bytes(8));
}
#[test]
fn test_selection_bounds() {
let mut textinput = text_input(Lines::Single, "abcdef");
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_origin_or_edit_point()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_start()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_end()
);
textinput.set_selection_range(2, 5, SelectionDirection::Forward);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(2)
},
textinput.selection_origin_or_edit_point()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(2)
},
textinput.selection_start()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(5)
},
textinput.selection_end()
);
assert_eq!(UTF8Bytes(2), textinput.selection_start_offset());
assert_eq!(UTF8Bytes(5), textinput.selection_end_offset());
textinput.set_selection_range(3, 6, SelectionDirection::Backward);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(6)
},
textinput.selection_origin_or_edit_point()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(3)
},
textinput.selection_start()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(6)
},
textinput.selection_end()
);
assert_eq!(UTF8Bytes(3), textinput.selection_start_offset());
assert_eq!(UTF8Bytes(6), textinput.selection_end_offset());
textinput = text_input(Lines::Multiple, "\n\n");
textinput.set_selection_range(0, 1, SelectionDirection::Forward);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_origin_or_edit_point()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_start()
);
assert_eq!(
TextPoint {
line: 1,
index: UTF8Bytes::zero()
},
textinput.selection_end()
);
}
#[test]
fn test_select_all() {
let mut textinput = text_input(Lines::Single, "abc");
textinput.set_selection_range(2, 3, SelectionDirection::Backward);
textinput.select_all();
assert_eq!(textinput.selection_direction(), SelectionDirection::Forward);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes::zero()
},
textinput.selection_start()
);
assert_eq!(
TextPoint {
line: 0,
index: UTF8Bytes(3)
},
textinput.selection_end()
);
}