| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/textfield/textfield_unittest.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/format_macros.h" |
| #include "base/i18n/rtl.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/pickle.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "ui/accessibility/accessibility_features.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/accessibility/platform/ax_platform_for_test.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/base/clipboard/test/test_clipboard.h" |
| #include "ui/base/dragdrop/drag_drop_types.h" |
| #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h" |
| #include "ui/base/emoji/emoji_panel_helper.h" |
| #include "ui/base/ime/constants.h" |
| #include "ui/base/ime/ime_key_event_dispatcher.h" |
| #include "ui/base/ime/init/input_method_factory.h" |
| #include "ui/base/ime/input_method_base.h" |
| #include "ui/base/ime/text_edit_commands.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/base/ui_base_switches_util.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_processor.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/events/test/keyboard_layout.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/render_text.h" |
| #include "ui/gfx/render_text_test_api.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/touch_selection/touch_selection_metrics.h" |
| #include "ui/views/accessibility/atomic_view_ax_tree_manager.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/accessibility/view_ax_platform_node_delegate.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/textfield/textfield_model.h" |
| #include "ui/views/controls/textfield/textfield_test_api.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/style/platform_style.h" |
| #include "ui/views/test/ax_event_counter.h" |
| #include "ui/views/test/test_views_delegate.h" |
| #include "ui/views/test/widget_test.h" |
| #include "ui/views/views_features.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_utils.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/windows_version.h" |
| #endif |
| |
| #if BUILDFLAG(IS_LINUX) |
| #include "ui/linux/fake_linux_ui.h" |
| #include "ui/linux/linux_ui.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ui/aura/window.h" |
| #include "ui/wm/core/ime_util_chromeos.h" |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "ui/base/cocoa/secure_password_input.h" |
| #include "ui/menus/cocoa/text_services_context_menu.h" |
| #endif |
| |
| #if BUILDFLAG(IS_OZONE) |
| #include "ui/events/ozone/layout/keyboard_layout_engine_test_utils.h" |
| #endif |
| |
| namespace views::test { |
| |
| const char16_t kHebrewLetterSamekh = 0x05E1; |
| |
| // Convenience to make constructing a GestureEvent simpler. |
| ui::GestureEvent |
| CreateTestGestureEvent(int x, int y, const ui::GestureEventDetails& details) { |
| return ui::GestureEvent(x, y, ui::EF_NONE, base::TimeTicks(), details); |
| } |
| |
| // This controller will happily destroy the target field passed on |
| // construction when a key event is triggered. |
| class TextfieldDestroyerController : public TextfieldController { |
| public: |
| explicit TextfieldDestroyerController(Textfield* target) : target_(target) { |
| target_->set_controller(this); |
| } |
| |
| TextfieldDestroyerController(const TextfieldDestroyerController&) = delete; |
| TextfieldDestroyerController& operator=(const TextfieldDestroyerController&) = |
| delete; |
| |
| Textfield* target() { return target_.get(); } |
| |
| // TextfieldController: |
| bool HandleKeyEvent(Textfield* sender, |
| const ui::KeyEvent& key_event) override { |
| if (target_) { |
| target_->OnBlur(); |
| } |
| target_.reset(); |
| return false; |
| } |
| |
| private: |
| std::unique_ptr<Textfield> target_; |
| }; |
| |
| // Class that focuses a textfield when it sees a KeyDown event. |
| class TextfieldFocuser : public View { |
| METADATA_HEADER(TextfieldFocuser, View) |
| |
| public: |
| explicit TextfieldFocuser(Textfield* textfield) : textfield_(*textfield) { |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| } |
| |
| TextfieldFocuser(const TextfieldFocuser&) = delete; |
| TextfieldFocuser& operator=(const TextfieldFocuser&) = delete; |
| |
| void set_consume(bool consume) { consume_ = consume; } |
| |
| // View: |
| bool OnKeyPressed(const ui::KeyEvent& event) override { |
| textfield_->RequestFocus(); |
| return consume_; |
| } |
| |
| private: |
| bool consume_ = true; |
| const raw_ref<Textfield> textfield_; |
| }; |
| |
| BEGIN_METADATA(TextfieldFocuser) |
| END_METADATA |
| |
| class MockInputMethod : public ui::InputMethodBase { |
| public: |
| MockInputMethod(); |
| |
| MockInputMethod(const MockInputMethod&) = delete; |
| MockInputMethod& operator=(const MockInputMethod&) = delete; |
| |
| ~MockInputMethod() override; |
| |
| // InputMethod: |
| ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* key) override; |
| void OnTextInputTypeChanged(ui::TextInputClient* client) override; |
| void OnCaretBoundsChanged(const ui::TextInputClient* client) override {} |
| void CancelComposition(const ui::TextInputClient* client) override; |
| bool IsCandidatePopupOpen() const override; |
| void SetVirtualKeyboardVisibilityIfEnabled(bool visibility) override { |
| if (visibility) { |
| count_show_virtual_keyboard_++; |
| } |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| bool OnUntranslatedIMEMessage( |
| const CHROME_MSG event, |
| InputMethod::NativeEventResult* result) override { |
| return false; |
| } |
| void OnInputLocaleChanged() override {} |
| bool IsInputLocaleCJK() const override { return false; } |
| void OnUrlChanged() override {} |
| #endif |
| |
| bool untranslated_ime_message_called() const { |
| return untranslated_ime_message_called_; |
| } |
| bool text_input_type_changed() const { return text_input_type_changed_; } |
| bool cancel_composition_called() const { return cancel_composition_called_; } |
| int count_show_virtual_keyboard() const { |
| return count_show_virtual_keyboard_; |
| } |
| |
| // Clears all internal states and result. |
| void Clear(); |
| |
| void SetCompositionTextForNextKey(const ui::CompositionText& composition); |
| void SetResultTextForNextKey(const std::u16string& result); |
| |
| private: |
| // Overridden from InputMethodBase. |
| void OnWillChangeFocusedClient(ui::TextInputClient* focused_before, |
| ui::TextInputClient* focused) override; |
| |
| // Clears boolean states defined below. |
| void ClearStates(); |
| |
| // Whether a mock composition or result is scheduled for the next key event. |
| bool HasComposition(); |
| |
| // Clears only composition information and result text. |
| void ClearComposition(); |
| |
| // Composition information for the next key event. It'll be cleared |
| // automatically after dispatching the next key event. |
| ui::CompositionText composition_; |
| |
| // Result text for the next key event. It'll be cleared automatically after |
| // dispatching the next key event. |
| std::u16string result_text_; |
| |
| // Record call state of corresponding methods. They will be set to false |
| // automatically before dispatching a key event. |
| bool untranslated_ime_message_called_ = false; |
| bool text_input_type_changed_ = false; |
| bool cancel_composition_called_ = false; |
| |
| int count_show_virtual_keyboard_ = 0; |
| }; |
| |
| MockInputMethod::MockInputMethod() : ui::InputMethodBase(nullptr) {} |
| |
| MockInputMethod::~MockInputMethod() = default; |
| |
| ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(ui::KeyEvent* key) { |
| // On Mac, emulate InputMethodMac behavior for character events. Composition |
| // still needs to be mocked, since it's not possible to generate test events |
| // which trigger the appropriate NSResponder action messages for composition. |
| #if BUILDFLAG(IS_MAC) |
| if (key->is_char()) { |
| return DispatchKeyEventPostIME(key); |
| } |
| #endif |
| |
| // Checks whether the key event is from EventGenerator on Windows which will |
| // generate key event for WM_CHAR. |
| // The MockInputMethod will insert char on WM_KEYDOWN so ignore WM_CHAR here. |
| if (key->is_char() && key->HasNativeEvent()) { |
| key->SetHandled(); |
| return ui::EventDispatchDetails(); |
| } |
| |
| ui::EventDispatchDetails dispatch_details; |
| |
| bool handled = !IsTextInputTypeNone() && HasComposition(); |
| ClearStates(); |
| if (handled) { |
| DCHECK(!key->is_char()); |
| ui::KeyEvent mock_key(ui::EventType::kKeyPressed, ui::VKEY_PROCESSKEY, |
| key->flags()); |
| dispatch_details = DispatchKeyEventPostIME(&mock_key); |
| } else { |
| dispatch_details = DispatchKeyEventPostIME(key); |
| } |
| |
| if (key->handled() || dispatch_details.dispatcher_destroyed) { |
| return dispatch_details; |
| } |
| |
| ui::TextInputClient* client = GetTextInputClient(); |
| if (client) { |
| if (handled) { |
| if (result_text_.length()) { |
| client->InsertText(result_text_, |
| ui::TextInputClient::InsertTextCursorBehavior:: |
| kMoveCursorAfterText); |
| } |
| if (composition_.text.length()) { |
| client->SetCompositionText(composition_); |
| } else { |
| client->ClearCompositionText(); |
| } |
| } else if (key->type() == ui::EventType::kKeyPressed) { |
| char16_t ch = key->GetCharacter(); |
| if (ch) { |
| client->InsertChar(*key); |
| } |
| } |
| } |
| |
| ClearComposition(); |
| |
| return dispatch_details; |
| } |
| |
| void MockInputMethod::OnTextInputTypeChanged(ui::TextInputClient* client) { |
| if (IsTextInputClientFocused(client)) { |
| text_input_type_changed_ = true; |
| } |
| InputMethodBase::OnTextInputTypeChanged(client); |
| } |
| |
| void MockInputMethod::CancelComposition(const ui::TextInputClient* client) { |
| if (IsTextInputClientFocused(client)) { |
| cancel_composition_called_ = true; |
| ClearComposition(); |
| } |
| } |
| |
| bool MockInputMethod::IsCandidatePopupOpen() const { |
| return false; |
| } |
| |
| void MockInputMethod::OnWillChangeFocusedClient( |
| ui::TextInputClient* focused_before, |
| ui::TextInputClient* focused) { |
| ui::TextInputClient* client = GetTextInputClient(); |
| if (client && client->HasCompositionText()) { |
| client->ConfirmCompositionText(/* keep_selection */ false); |
| } |
| ClearComposition(); |
| } |
| |
| void MockInputMethod::Clear() { |
| ClearStates(); |
| ClearComposition(); |
| } |
| |
| void MockInputMethod::SetCompositionTextForNextKey( |
| const ui::CompositionText& composition) { |
| composition_ = composition; |
| } |
| |
| void MockInputMethod::SetResultTextForNextKey(const std::u16string& result) { |
| result_text_ = result; |
| } |
| |
| void MockInputMethod::ClearStates() { |
| untranslated_ime_message_called_ = false; |
| text_input_type_changed_ = false; |
| cancel_composition_called_ = false; |
| } |
| |
| bool MockInputMethod::HasComposition() { |
| return composition_.text.length() || result_text_.length(); |
| } |
| |
| void MockInputMethod::ClearComposition() { |
| composition_ = ui::CompositionText(); |
| result_text_.clear(); |
| } |
| |
| // A Textfield wrapper to intercept OnKey[Pressed|Released]() results. |
| class TestTextfield : public views::Textfield { |
| METADATA_HEADER(TestTextfield, views::Textfield) |
| |
| public: |
| TestTextfield() = default; |
| |
| TestTextfield(const TestTextfield&) = delete; |
| TestTextfield& operator=(const TestTextfield&) = delete; |
| |
| ~TestTextfield() override = default; |
| |
| // ui::TextInputClient: |
| void InsertChar(const ui::KeyEvent& e) override { |
| views::Textfield::InsertChar(e); |
| #if BUILDFLAG(IS_MAC) |
| // On Mac, characters are inserted directly rather than attempting to get a |
| // unicode character from the ui::KeyEvent (which isn't always possible). |
| key_received_ = true; |
| #endif |
| } |
| |
| bool key_handled() const { return key_handled_; } |
| bool key_received() const { return key_received_; } |
| int event_flags() const { return event_flags_; } |
| |
| void clear() { |
| key_received_ = key_handled_ = false; |
| event_flags_ = 0; |
| } |
| |
| void OnAccessibilityEvent(ax::mojom::Event event_type) override { |
| accessibility_events_.push_back(event_type); |
| } |
| |
| std::vector<ax::mojom::Event> GetAccessibilityEventsOfTypes( |
| const std::vector<ax::mojom::Event>& event_types) { |
| std::vector<ax::mojom::Event> filtered_events; |
| for (const auto& event : accessibility_events_) { |
| if (std::find(event_types.begin(), event_types.end(), event) != |
| event_types.end()) { |
| filtered_events.push_back(event); |
| } |
| } |
| return filtered_events; |
| } |
| |
| void ClearAccessibilityEvents() { accessibility_events_.clear(); } |
| |
| private: |
| // views::View: |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| key_received_ = true; |
| event_flags_ = event->flags(); |
| |
| // Since Textfield::OnKeyPressed() might destroy |this|, get a weak pointer |
| // and verify it isn't null before writing the bool value to key_handled_. |
| base::WeakPtr<TestTextfield> textfield(weak_ptr_factory_.GetWeakPtr()); |
| views::View::OnKeyEvent(event); |
| |
| if (!textfield) { |
| return; |
| } |
| |
| key_handled_ = event->handled(); |
| |
| // Currently, Textfield::OnKeyReleased always returns false. |
| if (event->type() == ui::EventType::kKeyReleased) { |
| EXPECT_FALSE(key_handled_); |
| } |
| } |
| |
| bool key_handled_ = false; |
| bool key_received_ = false; |
| int event_flags_ = 0; |
| std::vector<ax::mojom::Event> accessibility_events_; |
| |
| base::WeakPtrFactory<TestTextfield> weak_ptr_factory_{this}; |
| }; |
| |
| BEGIN_METADATA(TestTextfield) |
| END_METADATA |
| |
| TextfieldTest::TextfieldTest() { |
| ui::SetUpInputMethodForTesting(new MockInputMethod()); |
| } |
| |
| TextfieldTest::~TextfieldTest() = default; |
| |
| void TextfieldTest::SetUp() { |
| // OS clipboard is a global resource, which causes flakiness when unit tests |
| // run in parallel. So, use a per-instance test clipboard. |
| ui::Clipboard::SetClipboardForCurrentThread( |
| std::make_unique<ui::TestClipboard>()); |
| ViewsTestBase::SetUp(); |
| |
| #if BUILDFLAG(IS_OZONE) |
| // Setting up the keyboard layout engine depends on the implementation and may |
| // be asynchronous. We ensure that it is ready to use so that tests could |
| // handle key events properly. |
| ui::WaitUntilLayoutEngineIsReadyForTest(); |
| #endif |
| } |
| |
| void TextfieldTest::TearDown() { |
| textfield_ = nullptr; |
| event_target_ = nullptr; |
| if (widget_) { |
| widget_->Close(); |
| } |
| // Clear kill buffer used for "Yank" text editing command so that no state |
| // persists between tests. |
| TextfieldModel::ClearKillBuffer(); |
| ViewsTestBase::TearDown(); |
| } |
| |
| ui::ClipboardBuffer TextfieldTest::GetAndResetCopiedToClipboard() { |
| return std::exchange(copied_to_clipboard_, ui::ClipboardBuffer::kMaxValue); |
| } |
| |
| std::u16string TextfieldTest::GetClipboardText( |
| ui::ClipboardBuffer clipboard_buffer) { |
| std::u16string text; |
| ui::Clipboard::GetForCurrentThread()->ReadText( |
| clipboard_buffer, /* data_dst = */ nullptr, &text); |
| return text; |
| } |
| |
| void TextfieldTest::SetClipboardText(ui::ClipboardBuffer clipboard_buffer, |
| const std::u16string& text) { |
| ui::ScopedClipboardWriter(clipboard_buffer).WriteText(text); |
| } |
| |
| void TextfieldTest::ContentsChanged(Textfield* sender, |
| const std::u16string& new_contents) { |
| // Paste calls TextfieldController::ContentsChanged() explicitly even if the |
| // paste action did not change the content. So |new_contents| may match |
| // |last_contents_|. For more info, see http://crbug.com/79002 |
| last_contents_ = new_contents; |
| } |
| |
| void TextfieldTest::OnBeforeUserAction(Textfield* sender) { |
| ++on_before_user_action_; |
| } |
| |
| void TextfieldTest::OnAfterUserAction(Textfield* sender) { |
| ++on_after_user_action_; |
| } |
| |
| void TextfieldTest::OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_type) { |
| copied_to_clipboard_ = clipboard_type; |
| } |
| |
| bool TextfieldTest::HandleWriteTextToClipboard(ui::ClipboardBuffer, |
| const std::u16string_view&) { |
| return handle_write_to_clipboard_; |
| } |
| |
| bool TextfieldTest::AllowStartDragEvent(const std::u16string_view&) { |
| return allow_drag_event_; |
| } |
| |
| void TextfieldTest::InitTextfield(int count) { |
| ASSERT_FALSE(textfield_); |
| textfield_ = PrepareTextfields(count, std::make_unique<TestTextfield>(), |
| gfx::Rect(100, 100, 200, 200)); |
| } |
| |
| void TextfieldTest::PrepareTextfieldsInternal(int count, |
| Textfield* textfield, |
| View* container, |
| gfx::Rect bounds) { |
| input_method()->SetImeKeyEventDispatcher( |
| test::WidgetTest::GetImeKeyEventDispatcherForWidget(widget_.get())); |
| |
| textfield->set_controller(this); |
| textfield->SetBoundsRect(bounds); |
| textfield->SetID(1); |
| |
| for (int i = 1; i < count; ++i) { |
| Textfield* child = container->AddChildView(std::make_unique<Textfield>()); |
| child->SetID(i + 1); |
| } |
| |
| TextfieldTestApi(textfield).model()->ClearEditHistory(); |
| |
| // Since the window type is activatable, showing the widget will also |
| // activate it. Calling Activate directly is insufficient, since that does |
| // not also _focus_ an aura::Window (i.e. using the FocusClient). Both the |
| // widget and the textfield must have focus to properly handle input. |
| widget_->Show(); |
| textfield->RequestFocus(); |
| |
| event_generator_ = |
| std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_.get())); |
| event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW); |
| event_target_ = textfield; |
| } |
| |
| ui::MenuModel* TextfieldTest::GetContextMenuModel() { |
| GetTextfieldTestApi().UpdateContextMenu(); |
| return GetTextfieldTestApi().context_menu_contents(); |
| } |
| |
| void TextfieldTest::MockAXModeAdded() { |
| ui::AXMode mode = ui::AXPlatformForTest::GetInstance().GetAccessibilityMode(); |
| widget_->OnAXModeAdded(mode); |
| } |
| |
| bool TextfieldTest::TestingNativeMac() const { |
| #if BUILDFLAG(IS_MAC) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool TextfieldTest::TestingNativeCrOs() const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| return true; |
| #else |
| return false; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| void TextfieldTest::SendKeyPress(ui::KeyboardCode key_code, int flags) { |
| event_generator_->PressKey(key_code, flags); |
| } |
| |
| void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code, |
| bool alt, |
| bool shift, |
| bool control_or_command, |
| bool caps_lock) { |
| bool control = control_or_command; |
| bool command = false; |
| |
| // By default, swap control and command for native events on Mac. This |
| // handles most cases. |
| if (TestingNativeMac()) { |
| std::swap(control, command); |
| } |
| |
| int flags = |
| (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) | |
| (alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0) | |
| (caps_lock ? ui::EF_CAPS_LOCK_ON : 0); |
| |
| SendKeyPress(key_code, flags); |
| } |
| |
| void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code, |
| bool shift, |
| bool control_or_command) { |
| SendKeyEvent(key_code, false, shift, control_or_command, false); |
| } |
| |
| void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code) { |
| SendKeyEvent(key_code, false, false); |
| } |
| |
| void TextfieldTest::SendKeyEvent(char16_t ch) { |
| SendKeyEvent(ch, ui::EF_NONE, false); |
| } |
| |
| void TextfieldTest::SendKeyEvent(char16_t ch, int flags) { |
| SendKeyEvent(ch, flags, false); |
| } |
| |
| void TextfieldTest::SendKeyEvent(char16_t ch, int flags, bool from_vk) { |
| if (ch < 0x80) { |
| ui::KeyboardCode code = |
| ch == ' ' ? ui::VKEY_SPACE |
| : static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a'); |
| SendKeyPress(code, flags); |
| } else { |
| // For unicode characters, assume they come from IME rather than the |
| // keyboard. So they are dispatched directly to the input method. But on |
| // Mac, key events don't pass through InputMethod. Hence they are |
| // dispatched regularly. |
| ui::KeyEvent event = ui::KeyEvent::FromCharacter(ch, ui::VKEY_UNKNOWN, |
| ui::DomCode::NONE, flags); |
| if (from_vk) { |
| ui::Event::Properties properties; |
| properties[ui::kPropertyFromVK] = |
| std::vector<uint8_t>(ui::kPropertyFromVKSize); |
| event.SetProperties(properties); |
| } |
| #if BUILDFLAG(IS_MAC) |
| event_generator_->Dispatch(&event); |
| #else |
| input_method()->DispatchKeyEvent(&event); |
| #endif |
| } |
| } |
| |
| void TextfieldTest::DispatchMockInputMethodKeyEvent() { |
| // Send a key to trigger MockInputMethod::DispatchKeyEvent(). Note the |
| // specific VKEY isn't used (MockInputMethod will mock a ui::VKEY_PROCESSKEY |
| // whenever it has a test composition). However, on Mac, it can't be a letter |
| // (e.g. VKEY_A) since all native character events on Mac are unicode events |
| // and don't have a meaningful ui::KeyEvent that would trigger |
| // DispatchKeyEvent(). It also can't be VKEY_ENTER, since those key events may |
| // need to be suppressed when interacting with real system IME. |
| SendKeyEvent(ui::VKEY_INSERT); |
| } |
| |
| // Sends a platform-specific move (and select) to the logical start of line. |
| // Eg. this should move (and select) to the right end of line for RTL text. |
| void TextfieldTest::SendHomeEvent(bool shift) { |
| if (TestingNativeMac()) { |
| // [NSResponder moveToBeginningOfLine:] is the correct way to do this on |
| // Mac, but that doesn't have a default key binding. Since |
| // views::Textfield doesn't currently support multiple lines, the same |
| // effect can be achieved by Cmd+Up which maps to |
| // [NSResponder moveToBeginningOfDocument:]. |
| SendKeyEvent(ui::VKEY_UP, shift /* shift */, true /* command */); |
| return; |
| } |
| SendKeyEvent(ui::VKEY_HOME, shift /* shift */, false /* control */); |
| } |
| |
| // Sends a platform-specific move (and select) to the logical end of line. |
| void TextfieldTest::SendEndEvent(bool shift) { |
| if (TestingNativeMac()) { |
| SendKeyEvent(ui::VKEY_DOWN, shift, true); // Cmd+Down. |
| return; |
| } |
| SendKeyEvent(ui::VKEY_END, shift, false); |
| } |
| |
| // Sends {delete, move, select} word {forward, backward}. |
| void TextfieldTest::SendWordEvent(ui::KeyboardCode key, bool shift) { |
| bool alt = false; |
| bool control = true; |
| bool caps = false; |
| if (TestingNativeMac()) { |
| // Use Alt+Left/Right/Backspace on native Mac. |
| alt = true; |
| control = false; |
| } |
| SendKeyEvent(key, alt, shift, control, caps); |
| } |
| |
| // Sends Shift+Delete if supported, otherwise Cmd+X again. |
| void TextfieldTest::SendAlternateCut() { |
| if (TestingNativeMac()) { |
| SendKeyEvent(ui::VKEY_X, false, true); |
| } else { |
| SendKeyEvent(ui::VKEY_DELETE, true, false); |
| } |
| } |
| |
| // Sends Ctrl+Insert if supported, otherwise Cmd+C again. |
| void TextfieldTest::SendAlternateCopy() { |
| if (TestingNativeMac()) { |
| SendKeyEvent(ui::VKEY_C, false, true); |
| } else { |
| SendKeyEvent(ui::VKEY_INSERT, false, true); |
| } |
| } |
| |
| // Sends Shift+Insert if supported, otherwise Cmd+V again. |
| void TextfieldTest::SendAlternatePaste() { |
| if (TestingNativeMac()) { |
| SendKeyEvent(ui::VKEY_V, false, true); |
| } else { |
| SendKeyEvent(ui::VKEY_INSERT, true, false); |
| } |
| } |
| |
| View* TextfieldTest::GetFocusedView() { |
| return widget_->GetFocusManager()->GetFocusedView(); |
| } |
| |
| int TextfieldTest::GetCursorPositionX(int cursor_pos) { |
| return GetTextfieldTestApi() |
| .GetRenderText() |
| ->GetCursorBounds(gfx::SelectionModel(cursor_pos, gfx::CURSOR_FORWARD), |
| false) |
| .x(); |
| } |
| |
| int TextfieldTest::GetCursorYForTesting() { |
| return GetTextfieldTestApi().GetRenderText()->GetLineOffset(0).y() + 1; |
| } |
| |
| gfx::Rect TextfieldTest::GetCursorBounds() { |
| return GetTextfieldTestApi().GetRenderText()->GetUpdatedCursorBounds(); |
| } |
| |
| // Gets the cursor bounds of |sel|. |
| gfx::Rect TextfieldTest::GetCursorBounds(const gfx::SelectionModel& sel) { |
| return GetTextfieldTestApi().GetRenderText()->GetCursorBounds(sel, true); |
| } |
| |
| gfx::Rect TextfieldTest::GetDisplayRect() { |
| return GetTextfieldTestApi().GetRenderText()->display_rect(); |
| } |
| |
| gfx::Rect TextfieldTest::GetCursorViewRect() { |
| return GetTextfieldTestApi().GetCursorViewRect(); |
| } |
| |
| // Performs a mouse click on the point whose x-axis is |bound|'s x plus |
| // |x_offset| and y-axis is in the middle of |bound|'s vertical range. |
| void TextfieldTest::MouseClick(const gfx::Rect bound, int x_offset) { |
| gfx::Point point(bound.x() + x_offset, bound.y() + bound.height() / 2); |
| ui::MouseEvent click(ui::EventType::kMousePressed, point, point, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| event_target_->OnMousePressed(click); |
| ui::MouseEvent release(ui::EventType::kMouseReleased, point, point, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| event_target_->OnMouseReleased(release); |
| } |
| |
| // This is to avoid double/triple click. |
| void TextfieldTest::NonClientMouseClick() { |
| ui::MouseEvent click(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), |
| int{ui::EF_LEFT_MOUSE_BUTTON} | ui::EF_IS_NON_CLIENT, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| event_target_->OnMousePressed(click); |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| int{ui::EF_LEFT_MOUSE_BUTTON} | ui::EF_IS_NON_CLIENT, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| event_target_->OnMouseReleased(release); |
| } |
| |
| void TextfieldTest::VerifyTextfieldContextMenuContents( |
| bool textfield_has_selection, |
| bool can_undo, |
| ui::MenuModel* menu) { |
| const auto& text = textfield_->GetText(); |
| const bool is_all_selected = |
| !text.empty() && textfield_->GetSelectedRange().length() == text.length(); |
| |
| int menu_index = 0; |
| |
| #if BUILDFLAG(IS_MAC) |
| if (textfield_has_selection) { |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */)); |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); |
| } |
| #endif |
| |
| if (ui::IsEmojiPanelSupported()) { |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); |
| } |
| |
| EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */)); |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); |
| EXPECT_EQ(textfield_has_selection, menu->IsEnabledAt(menu_index++ /* CUT */)); |
| EXPECT_EQ(textfield_has_selection, |
| menu->IsEnabledAt(menu_index++ /* COPY */)); |
| EXPECT_NE(GetClipboardText(ui::ClipboardBuffer::kCopyPaste).empty(), |
| menu->IsEnabledAt(menu_index++ /* PASTE */)); |
| EXPECT_EQ(textfield_has_selection, |
| menu->IsEnabledAt(menu_index++ /* DELETE */)); |
| EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); |
| EXPECT_EQ(!is_all_selected, menu->IsEnabledAt(menu_index++ /* SELECT ALL */)); |
| } |
| |
| void TextfieldTest::PressMouseButton(ui::EventFlags mouse_button_flags) { |
| ui::MouseEvent press(ui::EventType::kMousePressed, mouse_position_, |
| mouse_position_, ui::EventTimeForNow(), |
| mouse_button_flags, mouse_button_flags); |
| event_target_->OnMousePressed(press); |
| } |
| |
| void TextfieldTest::ReleaseMouseButton(ui::EventFlags mouse_button_flags) { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, mouse_position_, |
| mouse_position_, ui::EventTimeForNow(), |
| mouse_button_flags, mouse_button_flags); |
| event_target_->OnMouseReleased(release); |
| } |
| |
| void TextfieldTest::PressLeftMouseButton() { |
| PressMouseButton(ui::EF_LEFT_MOUSE_BUTTON); |
| } |
| |
| void TextfieldTest::ReleaseLeftMouseButton() { |
| ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON); |
| } |
| |
| void TextfieldTest::ClickLeftMouseButton() { |
| PressLeftMouseButton(); |
| ReleaseLeftMouseButton(); |
| } |
| |
| void TextfieldTest::ClickRightMouseButton() { |
| PressMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); |
| ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); |
| } |
| |
| void TextfieldTest::DragMouseTo(const gfx::Point& where) { |
| mouse_position_ = where; |
| ui::MouseEvent drag(ui::EventType::kMouseDragged, where, where, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0); |
| event_target_->OnMouseDragged(drag); |
| } |
| |
| void TextfieldTest::MoveMouseTo(const gfx::Point& where) { |
| mouse_position_ = where; |
| } |
| |
| // Taps on the textfield. |
| void TextfieldTest::TapAtCursor(ui::EventPointerType pointer_type) { |
| ui::GestureEventDetails tap_down_details(ui::EventType::kGestureTapDown); |
| tap_down_details.set_primary_pointer_type(pointer_type); |
| ui::GestureEvent tap_down = |
| CreateTestGestureEvent(GetCursorPositionX(0), 0, tap_down_details); |
| textfield_->OnGestureEvent(&tap_down); |
| |
| ui::GestureEventDetails tap_up_details(ui::EventType::kGestureTap); |
| tap_up_details.set_primary_pointer_type(pointer_type); |
| ui::GestureEvent tap_up = |
| CreateTestGestureEvent(GetCursorPositionX(0), 0, tap_up_details); |
| textfield_->OnGestureEvent(&tap_up); |
| } |
| |
| TextfieldTestApi TextfieldTest::GetTextfieldTestApi() { |
| return TextfieldTestApi(textfield_); |
| } |
| |
| MockInputMethod* TextfieldTest::input_method() { |
| return static_cast<MockInputMethod*>(widget_->GetInputMethod()); |
| } |
| |
| TextfieldModel* TextfieldTest::model() { |
| return GetTextfieldTestApi().model(); |
| } |
| |
| TEST_F(TextfieldTest, ModelChangesTest) { |
| InitTextfield(); |
| |
| // TextfieldController::ContentsChanged() shouldn't be called when changing |
| // text programmatically. |
| last_contents_.clear(); |
| textfield_->SetText(u"this is"); |
| EXPECT_EQ(u"this is", model()->text()); |
| EXPECT_EQ(u"this is", textfield_->GetText()); |
| EXPECT_TRUE(last_contents_.empty()); |
| |
| textfield_->AppendText(u" a test"); |
| EXPECT_EQ(u"this is a test", model()->text()); |
| EXPECT_EQ(u"this is a test", textfield_->GetText()); |
| EXPECT_TRUE(last_contents_.empty()); |
| |
| EXPECT_EQ(std::u16string(), textfield_->GetSelectedText()); |
| textfield_->SelectAll(false); |
| EXPECT_EQ(u"this is a test", textfield_->GetSelectedText()); |
| EXPECT_TRUE(last_contents_.empty()); |
| |
| textfield_->SetTextWithoutCaretBoundsChangeNotification(u"another test", 3); |
| EXPECT_EQ(u"another test", model()->text()); |
| EXPECT_EQ(u"another test", textfield_->GetText()); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 3u); |
| EXPECT_TRUE(last_contents_.empty()); |
| } |
| |
| TEST_F(TextfieldTest, Scroll) { |
| InitTextfield(); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| gfx::test::RenderTextTestApi render_text_test_api( |
| GetTextfieldTestApi().GetRenderText()); |
| constexpr int kGlyphWidth = 10; |
| render_text_test_api.SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| |
| // Empty Scroll() call should have no effect. |
| textfield_->Scroll({}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), 0); |
| |
| // Selected range should scroll cursor into view. |
| textfield_->SetSelectedRange({0, 20}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| |
| // Selected range should override new cursor position. |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 30); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| |
| // Scroll positions should affect scroll. |
| textfield_->SetSelectedRange(gfx::Range()); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->Scroll({30}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -200); |
| |
| // Should scroll right no more than necessary. |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->Scroll({15}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -50); |
| |
| // Should scroll left no more than necessary. |
| GetTextfieldTestApi().SetDisplayOffsetX(-200); // Scroll all the way right. |
| textfield_->Scroll({15}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -150); |
| |
| // Should not scroll if position is already in view. |
| GetTextfieldTestApi().SetDisplayOffsetX( |
| -100); // Scroll the middle 10 chars into view. |
| textfield_->Scroll({15}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| |
| // With multiple scroll positions, the Last scroll position takes priority. |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->Scroll({30, 0}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), 0); |
| textfield_->Scroll({30, 0, 20}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| |
| // With multiple scroll positions, the previous scroll positions should be |
| // scrolled to anyways. |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->Scroll({30, 20}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -200); |
| |
| // Only the selection end should affect scrolling. |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->Scroll({20}); |
| textfield_->SetSelectedRange({30, 20}); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| } |
| |
| TEST_F(TextfieldTest, ScrollUpdatesScrollXAccessibilityAttribute) { |
| InitTextfield(); |
| // Size the textfield wide enough to hold 10 characters. |
| gfx::test::RenderTextTestApi render_text_test_api( |
| GetTextfieldTestApi().GetRenderText()); |
| constexpr int kGlyphWidth = 10; |
| render_text_test_api.SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| |
| ui::AXNodeData textfield_node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData( |
| &textfield_node_data); |
| int scroll_x = |
| textfield_node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollX); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), scroll_x); |
| |
| textfield_->SetSelectedRange({0, 20}); |
| textfield_->Scroll({20}); |
| textfield_node_data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData( |
| &textfield_node_data); |
| EXPECT_EQ( |
| GetTextfieldTestApi().GetDisplayOffsetX(), |
| textfield_node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollX)); |
| EXPECT_NE(scroll_x, textfield_node_data.GetIntAttribute( |
| ax::mojom::IntAttribute::kScrollX)); |
| } |
| |
| TEST_F(TextfieldTest, |
| SetTextWithoutCaretBoundsChangeNotification_ModelEditHistory) { |
| InitTextfield(); |
| |
| // The cursor and selected range should reflect the selected range. |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 20); |
| textfield_->SetSelectedRange({10, 15}); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 15u); |
| EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(10, 15)); |
| |
| // After undo, the cursor and selected range should reflect the state prior to |
| // the edit. |
| textfield_->InsertOrReplaceText(u"xyz"); // 2nd edit |
| SendKeyEvent(ui::VKEY_Z, false, true); // Undo 2nd edit |
| EXPECT_EQ(textfield_->GetCursorPosition(), 15u); |
| EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(10, 15)); |
| |
| // After redo, the cursor and selected range should reflect the |
| // |cursor_position| parameter. |
| SendKeyEvent(ui::VKEY_Z, false, true); // Undo 2nd edit |
| SendKeyEvent(ui::VKEY_Z, false, true); // Undo 1st edit |
| SendKeyEvent(ui::VKEY_Z, true, true); // Redo 1st edit |
| EXPECT_EQ(textfield_->GetCursorPosition(), 20u); |
| EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20, 20)); |
| |
| // After undo, the cursor and selected range should reflect the state prior to |
| // the edit, even if that differs than the state after the current (1st) edit. |
| textfield_->InsertOrReplaceText(u"xyz"); // (2')nd edit |
| SendKeyEvent(ui::VKEY_Z, false, true); // Undo (2')nd edit |
| EXPECT_EQ(textfield_->GetCursorPosition(), 20u); |
| EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20, 20)); |
| } |
| |
| TEST_F(TextfieldTest, KeyTest) { |
| InitTextfield(); |
| // Event flags: key, alt, shift, ctrl, caps-lock. |
| SendKeyEvent(ui::VKEY_T, false, true, false, false); |
| SendKeyEvent(ui::VKEY_E, false, false, false, false); |
| SendKeyEvent(ui::VKEY_X, false, true, false, true); |
| SendKeyEvent(ui::VKEY_T, false, false, false, true); |
| SendKeyEvent(ui::VKEY_1, false, true, false, false); |
| SendKeyEvent(ui::VKEY_1, false, false, false, false); |
| SendKeyEvent(ui::VKEY_1, false, true, false, true); |
| SendKeyEvent(ui::VKEY_1, false, false, false, true); |
| |
| // On Mac, Caps+Shift remains uppercase. |
| if (TestingNativeMac()) { |
| EXPECT_EQ(u"TeXT!1!1", textfield_->GetText()); |
| } else { |
| EXPECT_EQ(u"TexT!1!1", textfield_->GetText()); |
| } |
| } |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // Control key shouldn't generate a printable character on Linux. |
| TEST_F(TextfieldTest, KeyTestControlModifier) { |
| InitTextfield(); |
| // 0x0448 is for 'CYRILLIC SMALL LETTER SHA'. |
| SendKeyEvent(0x0448, 0); |
| SendKeyEvent(0x0448, ui::EF_CONTROL_DOWN); |
| // 0x044C is for 'CYRILLIC SMALL LETTER FRONT YER'. |
| SendKeyEvent(0x044C, 0); |
| SendKeyEvent(0x044C, ui::EF_CONTROL_DOWN); |
| SendKeyEvent('i', 0); |
| SendKeyEvent('i', ui::EF_CONTROL_DOWN); |
| SendKeyEvent('m', 0); |
| SendKeyEvent('m', ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ( |
| u"\x0448\x044C" |
| u"im", |
| textfield_->GetText()); |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| #define MAYBE_KeysWithModifiersTest KeysWithModifiersTest |
| #else |
| // TODO(crbug.com/41274325): Implement keyboard layout changing for other |
| // platforms. |
| #define MAYBE_KeysWithModifiersTest DISABLED_KeysWithModifiersTest |
| #endif |
| |
| TEST_F(TextfieldTest, MAYBE_KeysWithModifiersTest) { |
| // Activate U.S. English keyboard layout. Modifier keys in other layouts may |
| // change the text inserted into a texfield and cause this test to fail. |
| ui::ScopedKeyboardLayout keyboard_layout(ui::KEYBOARD_LAYOUT_ENGLISH_US); |
| |
| InitTextfield(); |
| const int ctrl = ui::EF_CONTROL_DOWN; |
| const int alt = ui::EF_ALT_DOWN; |
| const int command = ui::EF_COMMAND_DOWN; |
| const int altgr = ui::EF_ALTGR_DOWN; |
| const int shift = ui::EF_SHIFT_DOWN; |
| |
| SendKeyPress(ui::VKEY_T, shift | alt | altgr); |
| SendKeyPress(ui::VKEY_H, alt); |
| SendKeyPress(ui::VKEY_E, altgr); |
| SendKeyPress(ui::VKEY_T, shift); |
| SendKeyPress(ui::VKEY_E, shift | altgr); |
| SendKeyPress(ui::VKEY_X, 0); |
| SendKeyPress(ui::VKEY_T, ctrl); // This causes transpose on Mac. |
| SendKeyPress(ui::VKEY_1, alt); |
| SendKeyPress(ui::VKEY_2, command); |
| SendKeyPress(ui::VKEY_3, 0); |
| SendKeyPress(ui::VKEY_4, 0); |
| SendKeyPress(ui::VKEY_OEM_PLUS, ctrl); |
| SendKeyPress(ui::VKEY_OEM_PLUS, ctrl | shift); |
| SendKeyPress(ui::VKEY_OEM_MINUS, ctrl); |
| SendKeyPress(ui::VKEY_OEM_MINUS, ctrl | shift); |
| |
| if (TestingNativeCrOs()) { |
| EXPECT_EQ(u"TeTEx34", textfield_->GetText()); |
| } else if (TestingNativeMac()) { |
| EXPECT_EQ(u"TheTxE134", textfield_->GetText()); |
| } else { |
| EXPECT_EQ(u"TeTEx234", textfield_->GetText()); |
| } |
| } |
| |
| TEST_F(TextfieldTest, AccessibleTextSelectBound) { |
| InitTextfield(); |
| ui::AXNodeData data; |
| gfx::Range range(4, 8); |
| auto canvas = std::make_unique<gfx::Canvas>(gfx::Size(1, 1), 1.0, false); |
| |
| textfield_->SetText(u"SettingText"); |
| textfield_->OnPaint(canvas.get()); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 11); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 11); |
| |
| textfield_->SetSelectedRange(range); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 4); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 8); |
| |
| textfield_->ExtendSelectionAndDelete(2, 1); |
| textfield_->SelectAll(false); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 0); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 4); |
| |
| textfield_->SetText(u"SettingText"); |
| textfield_->SetSelectedRange(range); |
| SendAlternateCut(); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 4); |
| |
| textfield_->SetText(u"SettingText"); |
| textfield_->SetSelectedRange(range); |
| SendAlternateCopy(); |
| SendAlternatePaste(); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 8); |
| |
| textfield_->SetText(u"Setting text for test"); |
| textfield_->SetEditableSelectionRange(gfx::Range(0)); |
| textfield_->SelectWord(); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(u"Setting", textfield_->GetSelectedText()); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 0); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 7); |
| |
| textfield_->DeleteRange(textfield_->GetSelectedRange()); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 0); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 0); |
| |
| textfield_->SetText(u"SettingText : "); |
| ui::KeyEvent key_event = ui::KeyEvent::FromCharacter( |
| 0x5A, ui::VKEY_Z, ui::DomCode::NONE, ui::EF_NONE); |
| textfield_->InsertChar(key_event); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 15); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 15); |
| |
| textfield_->SetText(u"0123456789"); |
| textfield_->SetSelectedRange(gfx::Range(3, 5)); |
| textfield_->AddSecondarySelectedRange(gfx::Range(7, 9)); |
| textfield_->OnPaint(canvas.get()); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart), 3); |
| EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd), 5); |
| } |
| |
| TEST_F(TextfieldTest, ControlAndSelectTest) { |
| // Insert a test string in a textfield. |
| InitTextfield(); |
| textfield_->SetText(u"one two three"); |
| SendHomeEvent(false); |
| SendKeyEvent(ui::VKEY_RIGHT, true, false); |
| SendKeyEvent(ui::VKEY_RIGHT, true, false); |
| SendKeyEvent(ui::VKEY_RIGHT, true, false); |
| |
| EXPECT_EQ(u"one", textfield_->GetSelectedText()); |
| |
| // Test word select. |
| SendWordEvent(ui::VKEY_RIGHT, true); |
| #if BUILDFLAG(IS_WIN) // Windows breaks on word starts and includes spaces. |
| EXPECT_EQ(u"one ", textfield_->GetSelectedText()); |
| SendWordEvent(ui::VKEY_RIGHT, true); |
| EXPECT_EQ(u"one two ", textfield_->GetSelectedText()); |
| #else // Non-Windows breaks on word ends and does NOT include spaces. |
| EXPECT_EQ(u"one two", textfield_->GetSelectedText()); |
| #endif |
| SendWordEvent(ui::VKEY_RIGHT, true); |
| EXPECT_EQ(u"one two three", textfield_->GetSelectedText()); |
| SendWordEvent(ui::VKEY_LEFT, true); |
| EXPECT_EQ(u"one two ", textfield_->GetSelectedText()); |
| SendWordEvent(ui::VKEY_LEFT, true); |
| EXPECT_EQ(u"one ", textfield_->GetSelectedText()); |
| |
| // Replace the selected text. |
| SendKeyEvent(ui::VKEY_Z, true, false); |
| SendKeyEvent(ui::VKEY_E, true, false); |
| SendKeyEvent(ui::VKEY_R, true, false); |
| SendKeyEvent(ui::VKEY_O, true, false); |
| SendKeyEvent(ui::VKEY_SPACE, false, false); |
| EXPECT_EQ(u"ZERO two three", textfield_->GetText()); |
| |
| SendEndEvent(true); |
| EXPECT_EQ(u"two three", textfield_->GetSelectedText()); |
| SendHomeEvent(true); |
| |
| // On Mac, the existing selection should be extended. |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(u"ZERO two three", textfield_->GetSelectedText()); |
| #else |
| EXPECT_EQ(u"ZERO ", textfield_->GetSelectedText()); |
| #endif |
| } |
| |
| TEST_F(TextfieldTest, WordSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"12 34567 89"); |
| |
| // Place the cursor after "5". |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| // Select word towards right. |
| SendWordEvent(ui::VKEY_RIGHT, true); |
| #if BUILDFLAG(IS_WIN) // Select word right includes space/punctuation. |
| EXPECT_EQ(u"67 ", textfield_->GetSelectedText()); |
| #else // Non-Win: select word right does NOT include space/punctuation. |
| EXPECT_EQ(u"67", textfield_->GetSelectedText()); |
| #endif |
| SendWordEvent(ui::VKEY_RIGHT, true); |
| EXPECT_EQ(u"67 89", textfield_->GetSelectedText()); |
| |
| // Select word towards left. |
| SendWordEvent(ui::VKEY_LEFT, true); |
| EXPECT_EQ(u"67 ", textfield_->GetSelectedText()); |
| SendWordEvent(ui::VKEY_LEFT, true); |
| |
| // On Mac, the selection should reduce to a caret when the selection direction |
| // changes for a word selection. |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_EQ(u"345", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(6, 3), textfield_->GetSelectedRange()); |
| #endif |
| |
| SendWordEvent(ui::VKEY_LEFT, true); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(u"345", textfield_->GetSelectedText()); |
| #else |
| EXPECT_EQ(u"12 345", textfield_->GetSelectedText()); |
| #endif |
| EXPECT_TRUE(textfield_->GetSelectedRange().is_reversed()); |
| |
| SendWordEvent(ui::VKEY_LEFT, true); |
| EXPECT_EQ(u"12 345", textfield_->GetSelectedText()); |
| } |
| |
| TEST_F(TextfieldTest, LineSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"12 34567 89"); |
| |
| // Place the cursor after "5". |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| // Select line towards right. |
| SendEndEvent(true); |
| EXPECT_EQ(u"67 89", textfield_->GetSelectedText()); |
| |
| // Select line towards left. On Mac, the existing selection should be extended |
| // to cover the whole line. |
| SendHomeEvent(true); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(textfield_->GetText(), textfield_->GetSelectedText()); |
| #else |
| EXPECT_EQ(u"12 345", textfield_->GetSelectedText()); |
| #endif |
| EXPECT_TRUE(textfield_->GetSelectedRange().is_reversed()); |
| |
| // Select line towards right. |
| SendEndEvent(true); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(textfield_->GetText(), textfield_->GetSelectedText()); |
| #else |
| EXPECT_EQ(u"67 89", textfield_->GetSelectedText()); |
| #endif |
| EXPECT_FALSE(textfield_->GetSelectedRange().is_reversed()); |
| } |
| |
| TEST_F(TextfieldTest, MoveUpDownAndModifySelection) { |
| InitTextfield(); |
| textfield_->SetText(u"12 34567 89"); |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| // Up/Down keys won't be handled except on Mac where they map to move |
| // commands. |
| SendKeyEvent(ui::VKEY_UP); |
| EXPECT_TRUE(textfield_->key_received()); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(gfx::Range(0), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_FALSE(textfield_->key_handled()); |
| #endif |
| textfield_->clear(); |
| |
| SendKeyEvent(ui::VKEY_DOWN); |
| EXPECT_TRUE(textfield_->key_received()); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(gfx::Range(11), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_FALSE(textfield_->key_handled()); |
| #endif |
| textfield_->clear(); |
| |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| // Shift+[Up/Down] should select the text to the beginning and end of the |
| // line, respectively. |
| SendKeyEvent(ui::VKEY_UP, true /* shift */, false /* command */); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange()); |
| textfield_->clear(); |
| |
| SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange()); |
| textfield_->clear(); |
| } |
| |
| TEST_F(TextfieldTest, MovePageUpDownAndModifySelection) { |
| InitTextfield(); |
| |
| // MOVE_PAGE_[UP/DOWN] and the associated selection commands should only be |
| // enabled on Mac. |
| #if BUILDFLAG(IS_MAC) |
| textfield_->SetText(u"12 34567 89"); |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| EXPECT_TRUE( |
| textfield_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_UP)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_DOWN)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION)); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PAGE_UP); |
| EXPECT_EQ(gfx::Range(0), textfield_->GetSelectedRange()); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PAGE_DOWN); |
| EXPECT_EQ(gfx::Range(11), textfield_->GetSelectedRange()); |
| |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION); |
| EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange()); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION); |
| EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_FALSE( |
| textfield_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_UP)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_DOWN)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION)); |
| #endif |
| } |
| |
| TEST_F(TextfieldTest, MoveParagraphForwardBackwardAndModifySelection) { |
| InitTextfield(); |
| textfield_->SetText(u"12 34567 89"); |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION); |
| EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange()); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION); |
| // On Mac, the selection should reduce to a caret when the selection direction |
| // is reversed for MOVE_PARAGRAPH_[FORWARD/BACKWARD]_AND_MODIFY_SELECTION. |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange()); |
| #endif |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION); |
| EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange()); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION); |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange()); |
| #else |
| EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange()); |
| #endif |
| } |
| |
| TEST_F(TextfieldTest, ModifySelectionWithMultipleSelections) { |
| InitTextfield(); |
| textfield_->SetText(u"0123456 89"); |
| textfield_->SetSelectedRange(gfx::Range(3, 5)); |
| textfield_->AddSecondarySelectedRange(gfx::Range(8, 9)); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION); |
| EXPECT_EQ(gfx::Range(3, 6), textfield_->GetSelectedRange()); |
| EXPECT_EQ(6U, textfield_->GetCursorPosition()); |
| EXPECT_EQ(0U, textfield_->GetSelectionModel().secondary_selections().size()); |
| } |
| |
| TEST_F(TextfieldTest, InsertionDeletionTest) { |
| // Insert a test string in a textfield. |
| InitTextfield(); |
| for (size_t i = 0; i < 10; ++i) { |
| SendKeyEvent(static_cast<ui::KeyboardCode>(ui::VKEY_A + i)); |
| } |
| EXPECT_EQ(u"abcdefghij", textfield_->GetText()); |
| |
| // Test the delete and backspace keys. |
| textfield_->SetSelectedRange(gfx::Range(5)); |
| for (size_t i = 0; i < 3; ++i) { |
| SendKeyEvent(ui::VKEY_BACK); |
| } |
| EXPECT_EQ(u"abfghij", textfield_->GetText()); |
| for (size_t i = 0; i < 3; ++i) { |
| SendKeyEvent(ui::VKEY_DELETE); |
| } |
| EXPECT_EQ(u"abij", textfield_->GetText()); |
| |
| // Select all and replace with "k". |
| textfield_->SelectAll(false); |
| SendKeyEvent(ui::VKEY_K); |
| EXPECT_EQ(u"k", textfield_->GetText()); |
| |
| // Delete the previous word from cursor. |
| bool shift = false; |
| textfield_->SetText(u"one two three four"); |
| SendEndEvent(shift); |
| SendWordEvent(ui::VKEY_BACK, shift); |
| EXPECT_EQ(u"one two three ", textfield_->GetText()); |
| |
| // Delete to a line break on Linux and ChromeOS, to a word break on Windows |
| // and Mac. |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| shift = true; |
| SendWordEvent(ui::VKEY_BACK, shift); |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| EXPECT_EQ(u"three ", textfield_->GetText()); |
| #else |
| EXPECT_EQ(u"one three ", textfield_->GetText()); |
| #endif |
| |
| // Delete the next word from cursor. |
| textfield_->SetText(u"one two three four"); |
| shift = false; |
| SendHomeEvent(shift); |
| SendWordEvent(ui::VKEY_DELETE, shift); |
| #if BUILDFLAG(IS_WIN) // Delete word incldes space/punctuation. |
| EXPECT_EQ(u"two three four", textfield_->GetText()); |
| #else // Non-Windows: delete word does NOT include space/punctuation. |
| EXPECT_EQ(u" two three four", textfield_->GetText()); |
| #endif |
| // Delete to a line break on Linux and ChromeOS, to a word break on Windows |
| // and Mac. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| shift = true; |
| SendWordEvent(ui::VKEY_DELETE, shift); |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| EXPECT_EQ(u" two", textfield_->GetText()); |
| #elif BUILDFLAG(IS_WIN) |
| EXPECT_EQ(u"two four", textfield_->GetText()); |
| #else |
| EXPECT_EQ(u" two four", textfield_->GetText()); |
| #endif |
| } |
| |
| // Test that deletion operations behave correctly with an active selection. |
| TEST_F(TextfieldTest, DeletionWithSelection) { |
| struct TestCase { |
| ui::KeyboardCode key; |
| bool shift; |
| }; |
| |
| constexpr auto kTestCases = std::to_array<TestCase>({ |
| {ui::VKEY_BACK, false}, |
| {ui::VKEY_BACK, true}, |
| {ui::VKEY_DELETE, false}, |
| {ui::VKEY_DELETE, true}, |
| }); |
| |
| InitTextfield(); |
| // [Ctrl] ([Alt] on Mac) + [Delete]/[Backspace] should delete the active |
| // selection, regardless of [Shift]. |
| for (size_t i = 0; i < kTestCases.size(); ++i) { |
| SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); |
| textfield_->SetText(u"one two three"); |
| textfield_->SetSelectedRange(gfx::Range(2, 6)); |
| // Make selection as - on|e tw|o three. |
| SendWordEvent(kTestCases[i].key, kTestCases[i].shift); |
| // Verify state is on|o three. |
| EXPECT_EQ(u"ono three", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(2), textfield_->GetSelectedRange()); |
| } |
| } |
| |
| // Test that deletion operations behave correctly with multiple selections. |
| TEST_F(TextfieldTest, DeletionWithMultipleSelections) { |
| struct TestCase { |
| ui::KeyboardCode key; |
| bool shift; |
| }; |
| |
| constexpr auto kTestCases = std::to_array<TestCase>({ |
| {ui::VKEY_BACK, false}, |
| {ui::VKEY_BACK, true}, |
| {ui::VKEY_DELETE, false}, |
| {ui::VKEY_DELETE, true}, |
| }); |
| |
| InitTextfield(); |
| // [Ctrl] ([Alt] on Mac) + [Delete]/[Backspace] should delete the active |
| // selection, regardless of [Shift]. |
| for (size_t i = 0; i < kTestCases.size(); ++i) { |
| SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); |
| textfield_->SetText(u"one two three"); |
| // Select: o[ne] [two] th[re]e |
| textfield_->SetSelectedRange(gfx::Range(4, 7)); |
| textfield_->AddSecondarySelectedRange(gfx::Range(10, 12)); |
| textfield_->AddSecondarySelectedRange(gfx::Range(1, 3)); |
| SendWordEvent(kTestCases[i].key, kTestCases[i].shift); |
| EXPECT_EQ(u"o the", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(2), textfield_->GetSelectedRange()); |
| EXPECT_EQ(0U, |
| textfield_->GetSelectionModel().secondary_selections().size()); |
| } |
| } |
| |
| // Test deletions not covered by other tests with key events. |
| TEST_F(TextfieldTest, DeletionWithEditCommands) { |
| struct TestCase { |
| ui::TextEditCommand command; |
| const char16_t* expected; |
| }; |
| |
| constexpr auto kTestCases = std::to_array<TestCase>({ |
| {ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE, u"two three"}, |
| {ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH, u"two three"}, |
| {ui::TextEditCommand::DELETE_TO_END_OF_LINE, u"one "}, |
| {ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH, u"one "}, |
| }); |
| |
| InitTextfield(); |
| for (size_t i = 0; i < kTestCases.size(); ++i) { |
| SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); |
| textfield_->SetText(u"one two three"); |
| textfield_->SetSelectedRange(gfx::Range(4)); |
| GetTextfieldTestApi().ExecuteTextEditCommand(kTestCases[i].command); |
| EXPECT_EQ(kTestCases[i].expected, textfield_->GetText()); |
| } |
| } |
| |
| TEST_F(TextfieldTest, PasswordTest) { |
| InitTextfield(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); |
| EXPECT_TRUE(textfield_->GetEnabled()); |
| EXPECT_TRUE(textfield_->IsFocusable()); |
| |
| last_contents_.clear(); |
| textfield_->SetText(u"password"); |
| // Ensure GetText() and the callback returns the actual text instead of "*". |
| EXPECT_EQ(u"password", textfield_->GetText()); |
| EXPECT_TRUE(last_contents_.empty()); |
| model()->SelectAll(false); |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"foo"); |
| |
| // Cut and copy should be disabled. |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kCut)); |
| textfield_->ExecuteCommand(Textfield::kCut, 0); |
| SendKeyEvent(ui::VKEY_X, false, true); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kCopy)); |
| textfield_->ExecuteCommand(Textfield::kCopy, 0); |
| SendKeyEvent(ui::VKEY_C, false, true); |
| SendAlternateCopy(); |
| EXPECT_EQ(u"foo", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"password", textfield_->GetText()); |
| // [Shift]+[Delete] should just delete without copying text to the clipboard. |
| textfield_->SelectAll(false); |
| SendKeyEvent(ui::VKEY_DELETE, true, false); |
| |
| // Paste should work normally. |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kPaste)); |
| textfield_->ExecuteCommand(Textfield::kPaste, 0); |
| SendKeyEvent(ui::VKEY_V, false, true); |
| SendAlternatePaste(); |
| EXPECT_EQ(u"foo", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"foofoofoo", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, PasswordSelectWordTest) { |
| InitTextfield(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| textfield_->SetText(u"password word test"); |
| |
| // Select word command should be disabled. |
| textfield_->SetEditableSelectionRange(gfx::Range(2)); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| textfield_->ExecuteCommand(Textfield::kPaste, 0); |
| EXPECT_EQ(u"", textfield_->GetSelectedText()); |
| |
| // Select word should select whole text instead of the nearest word. |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"password word test", textfield_->GetSelectedText()); |
| } |
| |
| // Check that text insertion works appropriately for password and read-only |
| // textfields. |
| TEST_F(TextfieldTest, TextInputType_InsertionTest) { |
| InitTextfield(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); |
| |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_FALSE(textfield_->GetPasswordCharRevealIndex().has_value()); |
| SendKeyEvent(kHebrewLetterSamekh, ui::EF_NONE, true /* from_vk */); |
| #if !BUILDFLAG(IS_MAC) |
| // Don't verifies the password character reveal on MacOS, because on MacOS, |
| // the text insertion is not done through TextInputClient::InsertChar(). |
| EXPECT_EQ(1u, textfield_->GetPasswordCharRevealIndex()); |
| #endif |
| SendKeyEvent(ui::VKEY_B); |
| EXPECT_FALSE(textfield_->GetPasswordCharRevealIndex().has_value()); |
| |
| EXPECT_EQ( |
| u"a\x05E1" |
| u"b", |
| textfield_->GetText()); |
| |
| textfield_->SetReadOnly(true); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, textfield_->GetTextInputType()); |
| SendKeyEvent(ui::VKEY_C); |
| |
| // No text should be inserted for read only textfields. |
| EXPECT_EQ( |
| u"a\x05E1" |
| u"b", |
| textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, ShouldDoLearning) { |
| InitTextfield(); |
| |
| // Defaults to false. |
| EXPECT_EQ(false, textfield_->ShouldDoLearning()); |
| |
| // The value can be set. |
| textfield_->SetShouldDoLearning(true); |
| EXPECT_EQ(true, textfield_->ShouldDoLearning()); |
| } |
| |
| TEST_F(TextfieldTest, TextInputType) { |
| InitTextfield(); |
| |
| // Defaults to TEXT |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, textfield_->GetTextInputType()); |
| |
| // And can be set. |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_URL); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_URL, textfield_->GetTextInputType()); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); |
| |
| // Readonly textfields have type NONE |
| textfield_->SetReadOnly(true); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, textfield_->GetTextInputType()); |
| |
| textfield_->SetReadOnly(false); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); |
| |
| // As do disabled textfields |
| textfield_->SetEnabled(false); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, textfield_->GetTextInputType()); |
| |
| textfield_->SetEnabled(true); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); |
| } |
| |
| TEST_F(TextfieldTest, NumberInputType_FiltersNonDigitCharacters) { |
| InitTextfield(); |
| |
| // Set the textfield to accept number input. |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_NUMBER); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_NUMBER, textfield_->GetTextInputType()); |
| |
| // Test inserting digits. They should be accepted. |
| SendKeyEvent(ui::VKEY_1); |
| EXPECT_EQ(u"1", textfield_->GetText()); |
| |
| // Test inserting a non-digit character. It should be ignored. |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_EQ(u"1", textfield_->GetText()); |
| |
| // Test inserting a string with only digits. It should be accepted. |
| textfield_->InsertText( |
| u"234", |
| ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); |
| EXPECT_EQ(u"1234", textfield_->GetText()); |
| |
| // Test inserting a string with mixed characters. Only digits should be kept. |
| textfield_->InsertText( |
| u"5a6b7", |
| ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); |
| EXPECT_EQ(u"1234567", textfield_->GetText()); |
| |
| // Test inserting a string with only non-digits. It should be ignored. |
| textfield_->InsertText( |
| u"abc", |
| ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); |
| EXPECT_EQ(u"1234567", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, OnKeyPress) { |
| InitTextfield(); |
| |
| // Character keys are handled by the input method. |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_FALSE(textfield_->key_handled()); |
| textfield_->clear(); |
| |
| // Arrow keys and home/end are handled by the textfield. |
| SendKeyEvent(ui::VKEY_LEFT); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| textfield_->clear(); |
| |
| SendKeyEvent(ui::VKEY_RIGHT); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| textfield_->clear(); |
| |
| const bool shift = false; |
| SendHomeEvent(shift); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| textfield_->clear(); |
| |
| SendEndEvent(shift); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| textfield_->clear(); |
| |
| // F20 key won't be handled. |
| SendKeyEvent(ui::VKEY_F20); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_FALSE(textfield_->key_handled()); |
| textfield_->clear(); |
| } |
| |
| // Tests that default key bindings are handled even with a delegate installed. |
| TEST_F(TextfieldTest, OnKeyPressBinding) { |
| InitTextfield(); |
| |
| #if BUILDFLAG(IS_LINUX) |
| // Install a TextEditKeyBindingsDelegateAuraLinux that does nothing. |
| class TestDelegate : public ui::FakeLinuxUi { |
| public: |
| TestDelegate() = default; |
| |
| TestDelegate(const TestDelegate&) = delete; |
| TestDelegate& operator=(const TestDelegate&) = delete; |
| |
| ~TestDelegate() override = default; |
| |
| ui::TextEditCommand GetTextEditCommandForEvent(const ui::Event& event, |
| int text_flags) override { |
| return ui::TextEditCommand::INVALID_COMMAND; |
| } |
| }; |
| |
| auto test_delegate = std::make_unique<TestDelegate>(); |
| auto* old_linux_ui = ui::LinuxUi::SetInstance(test_delegate.get()); |
| #endif |
| |
| SendKeyEvent(ui::VKEY_A, false, false); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| textfield_->clear(); |
| |
| // Undo/Redo command keys are handled by the textfield. |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_TRUE(textfield_->GetText().empty()); |
| textfield_->clear(); |
| |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| textfield_->clear(); |
| |
| #if BUILDFLAG(IS_LINUX) |
| ui::LinuxUi::SetInstance(old_linux_ui); |
| #endif |
| } |
| |
| TEST_F(TextfieldTest, CursorMovement) { |
| InitTextfield(); |
| |
| // Test with trailing whitespace. |
| textfield_->SetText(u"one two hre "); |
| |
| // Send the cursor at the end. |
| SendKeyEvent(ui::VKEY_END); |
| |
| // Ctrl+Left should move the cursor just before the last word. |
| const bool shift = false; |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| SendKeyEvent(ui::VKEY_T); |
| EXPECT_EQ(u"one two thre ", textfield_->GetText()); |
| EXPECT_EQ(u"one two thre ", last_contents_); |
| |
| #if BUILDFLAG(IS_WIN) // Move right by word includes space/punctuation. |
| // Ctrl+Right should move the cursor to the end of the last word. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| SendKeyEvent(ui::VKEY_E); |
| EXPECT_EQ(u"one two thre e", textfield_->GetText()); |
| EXPECT_EQ(u"one two thre e", last_contents_); |
| |
| // Ctrl+Right again should not move the cursor, because |
| // it is aleady at the end. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| SendKeyEvent(ui::VKEY_BACK); |
| EXPECT_EQ(u"one two thre ", textfield_->GetText()); |
| EXPECT_EQ(u"one two thre ", last_contents_); |
| #else // Non-Windows: move right by word does NOT include space/punctuation. |
| // Ctrl+Right should move the cursor to the end of the last word. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| SendKeyEvent(ui::VKEY_E); |
| EXPECT_EQ(u"one two three ", textfield_->GetText()); |
| EXPECT_EQ(u"one two three ", last_contents_); |
| |
| // Ctrl+Right again should move the cursor to the end. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| SendKeyEvent(ui::VKEY_BACK); |
| EXPECT_EQ(u"one two three", textfield_->GetText()); |
| EXPECT_EQ(u"one two three", last_contents_); |
| #endif |
| // Test with leading whitespace. |
| textfield_->SetText(u" ne two"); |
| |
| // Send the cursor at the beginning. |
| SendHomeEvent(shift); |
| |
| // Ctrl+Right, then Ctrl+Left should move the cursor to the beginning of the |
| // first word. |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| #if BUILDFLAG(IS_WIN) // Windows breaks on word start, move further to pass |
| // "ne". |
| SendWordEvent(ui::VKEY_RIGHT, shift); |
| #endif |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| SendKeyEvent(ui::VKEY_O); |
| EXPECT_EQ(u" one two", textfield_->GetText()); |
| EXPECT_EQ(u" one two", last_contents_); |
| |
| // Ctrl+Left to move the cursor to the beginning of the first word. |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| // Ctrl+Left again should move the cursor back to the very beginning. |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| SendKeyEvent(ui::VKEY_DELETE); |
| EXPECT_EQ(u"one two", textfield_->GetText()); |
| EXPECT_EQ(u"one two", last_contents_); |
| } |
| |
| TEST_F(TextfieldTest, CursorMovementWithMultipleSelections) { |
| InitTextfield(); |
| textfield_->SetText(u"012 456 890 234 678"); |
| // [p] [s] |
| textfield_->SetSelectedRange({4, 7}); |
| textfield_->AddSecondarySelectedRange({12, 15}); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand(ui::TextEditCommand::MOVE_LEFT); |
| EXPECT_EQ(gfx::Range(4, 4), textfield_->GetSelectedRange()); |
| EXPECT_EQ(0U, textfield_->GetSelectionModel().secondary_selections().size()); |
| |
| textfield_->SetSelectedRange({4, 7}); |
| textfield_->AddSecondarySelectedRange({12, 15}); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand(ui::TextEditCommand::MOVE_RIGHT); |
| EXPECT_EQ(gfx::Range(7, 7), textfield_->GetSelectedRange()); |
| EXPECT_EQ(0U, textfield_->GetSelectionModel().secondary_selections().size()); |
| } |
| |
| TEST_F(TextfieldTest, ShouldShowCursor) { |
| InitTextfield(); |
| textfield_->SetText(u"word1 word2"); |
| |
| // should show cursor when there's no primary selection |
| textfield_->SetSelectedRange({4, 4}); |
| EXPECT_TRUE(GetTextfieldTestApi().ShouldShowCursor()); |
| textfield_->AddSecondarySelectedRange({1, 3}); |
| EXPECT_TRUE(GetTextfieldTestApi().ShouldShowCursor()); |
| |
| // should not show cursor when there's a primary selection |
| textfield_->SetSelectedRange({4, 7}); |
| EXPECT_FALSE(GetTextfieldTestApi().ShouldShowCursor()); |
| textfield_->AddSecondarySelectedRange({1, 3}); |
| EXPECT_FALSE(GetTextfieldTestApi().ShouldShowCursor()); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| TEST_F(TextfieldTest, MacCursorAlphaTest) { |
| InitTextfield(); |
| |
| const int cursor_y = GetCursorYForTesting(); |
| MoveMouseTo(gfx::Point(GetCursorPositionX(0), cursor_y)); |
| ClickRightMouseButton(); |
| EXPECT_TRUE(textfield_->HasFocus()); |
| |
| const float kOpaque = 1.0; |
| EXPECT_FLOAT_EQ(kOpaque, GetTextfieldTestApi().CursorLayerOpacity()); |
| |
| GetTextfieldTestApi().FlashCursor(); |
| |
| const float kAlmostTransparent = 1.0 / 255.0; |
| EXPECT_FLOAT_EQ(kAlmostTransparent, |
| GetTextfieldTestApi().CursorLayerOpacity()); |
| |
| GetTextfieldTestApi().FlashCursor(); |
| |
| EXPECT_FLOAT_EQ(kOpaque, GetTextfieldTestApi().CursorLayerOpacity()); |
| |
| const float kTransparent = 0.0; |
| GetTextfieldTestApi().SetCursorLayerOpacity(kTransparent); |
| ASSERT_FLOAT_EQ(kTransparent, GetTextfieldTestApi().CursorLayerOpacity()); |
| |
| GetTextfieldTestApi().UpdateCursorVisibility(); |
| EXPECT_FLOAT_EQ(kOpaque, GetTextfieldTestApi().CursorLayerOpacity()); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, FocusTraversalTest) { |
| InitTextfield(3); |
| textfield_->RequestFocus(); |
| |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| widget_->GetFocusManager()->AdvanceFocus(false); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| widget_->GetFocusManager()->AdvanceFocus(false); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| // Cycle back to the first textfield. |
| widget_->GetFocusManager()->AdvanceFocus(false); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| |
| widget_->GetFocusManager()->AdvanceFocus(true); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| widget_->GetFocusManager()->AdvanceFocus(true); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| widget_->GetFocusManager()->AdvanceFocus(true); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| // Cycle back to the last textfield. |
| widget_->GetFocusManager()->AdvanceFocus(true); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| |
| // Request focus should still work. |
| textfield_->RequestFocus(); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| |
| // Test if clicking on textfield view sets the focus. |
| widget_->GetFocusManager()->AdvanceFocus(true); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| MoveMouseTo(gfx::Point(0, GetCursorYForTesting())); |
| ClickLeftMouseButton(); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| |
| // Tab/Shift+Tab should also cycle focus, not insert a tab character. |
| SendKeyEvent(ui::VKEY_TAB, false, false); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| SendKeyEvent(ui::VKEY_TAB, false, false); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| // Cycle back to the first textfield. |
| SendKeyEvent(ui::VKEY_TAB, false, false); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| |
| SendKeyEvent(ui::VKEY_TAB, true, false); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| SendKeyEvent(ui::VKEY_TAB, true, false); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| SendKeyEvent(ui::VKEY_TAB, true, false); |
| EXPECT_EQ(1, GetFocusedView()->GetID()); |
| // Cycle back to the last textfield. |
| SendKeyEvent(ui::VKEY_TAB, true, false); |
| EXPECT_EQ(3, GetFocusedView()->GetID()); |
| } |
| |
| TEST_F(TextfieldTest, ContextMenuDisplayTest) { |
| InitTextfield(); |
| |
| EXPECT_TRUE(textfield_->context_menu_controller()); |
| textfield_->SetText(u"hello world"); |
| ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste); |
| textfield_->ClearEditHistory(); |
| EXPECT_TRUE(GetContextMenuModel()); |
| VerifyTextfieldContextMenuContents(false, false, GetContextMenuModel()); |
| |
| textfield_->SelectAll(false); |
| VerifyTextfieldContextMenuContents(true, false, GetContextMenuModel()); |
| |
| SendKeyEvent(ui::VKEY_T); |
| VerifyTextfieldContextMenuContents(false, true, GetContextMenuModel()); |
| |
| textfield_->SelectAll(false); |
| VerifyTextfieldContextMenuContents(true, true, GetContextMenuModel()); |
| |
| // Exercise the "paste enabled?" check in the verifier. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"Test"); |
| VerifyTextfieldContextMenuContents(true, true, GetContextMenuModel()); |
| } |
| |
| TEST_F(TextfieldTest, DoubleAndTripleClickTest) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| |
| // Test for double click. |
| MoveMouseTo(gfx::Point(0, GetCursorYForTesting())); |
| ClickLeftMouseButton(); |
| EXPECT_TRUE(textfield_->GetSelectedText().empty()); |
| ClickLeftMouseButton(); |
| EXPECT_EQ(u"hello", textfield_->GetSelectedText()); |
| |
| // Test for triple click. |
| ClickLeftMouseButton(); |
| EXPECT_EQ(u"hello world", textfield_->GetSelectedText()); |
| |
| // Another click should reset back to double click. |
| ClickLeftMouseButton(); |
| EXPECT_EQ(u"hello", textfield_->GetSelectedText()); |
| } |
| |
| // Tests text selection behavior on a right click. |
| TEST_F(TextfieldTest, SelectionOnRightClick) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| |
| // Verify right clicking within the selection does not alter the selection. |
| textfield_->SetSelectedRange(gfx::Range(1, 5)); |
| EXPECT_EQ(u"ello", textfield_->GetSelectedText()); |
| const int cursor_y = GetCursorYForTesting(); |
| MoveMouseTo(gfx::Point(GetCursorPositionX(3), cursor_y)); |
| ClickRightMouseButton(); |
| EXPECT_EQ(u"ello", textfield_->GetSelectedText()); |
| |
| // Verify right clicking outside the selection, selects the word under the |
| // cursor on platforms where this is expected. |
| MoveMouseTo(gfx::Point(GetCursorPositionX(8), cursor_y)); |
| const char16_t* expected_right_click_word = |
| PlatformStyle::kSelectWordOnRightClick ? u"world" : u"ello"; |
| ClickRightMouseButton(); |
| EXPECT_EQ(expected_right_click_word, textfield_->GetSelectedText()); |
| |
| // Verify right clicking inside an unfocused textfield selects all the text on |
| // platforms where this is expected. Else the older selection is retained. |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_FALSE(textfield_->HasFocus()); |
| MoveMouseTo(gfx::Point(GetCursorPositionX(0), cursor_y)); |
| ClickRightMouseButton(); |
| EXPECT_TRUE(textfield_->HasFocus()); |
| const char16_t* expected_right_click_unfocused = |
| PlatformStyle::kSelectAllOnRightClickWhenUnfocused |
| ? u"hello world" |
| : expected_right_click_word; |
| EXPECT_EQ(expected_right_click_unfocused, textfield_->GetSelectedText()); |
| } |
| |
| TEST_F(TextfieldTest, DragToSelect) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int kStart = GetCursorPositionX(5); |
| const int kEnd = 500; |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Point start_point(kStart, cursor_y); |
| gfx::Point end_point(kEnd, cursor_y); |
| |
| MoveMouseTo(start_point); |
| PressLeftMouseButton(); |
| EXPECT_TRUE(textfield_->GetSelectedText().empty()); |
| |
| // Check that dragging left selects the beginning of the string. |
| DragMouseTo(gfx::Point(0, cursor_y)); |
| EXPECT_EQ(u"hello", textfield_->GetSelectedText()); |
| |
| // Check that dragging right selects the rest of the string. |
| DragMouseTo(end_point); |
| std::u16string text_right(textfield_->GetSelectedText()); |
| EXPECT_EQ(u" world", text_right); |
| |
| // Check that releasing in the same location does not alter the selection. |
| ReleaseLeftMouseButton(); |
| EXPECT_EQ(text_right, textfield_->GetSelectedText()); |
| |
| // Check that dragging from beyond the text length works too. |
| MoveMouseTo(end_point); |
| PressLeftMouseButton(); |
| DragMouseTo(gfx::Point(0, cursor_y)); |
| ReleaseLeftMouseButton(); |
| EXPECT_EQ(textfield_->GetText(), textfield_->GetSelectedText()); |
| } |
| |
| // Ensures dragging above or below the textfield extends a selection to either |
| // end, depending on the relative x offsets of the text and mouse cursors. |
| TEST_F(TextfieldTest, DragUpOrDownSelectsToEnd) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const std::u16string expected_left = |
| gfx::RenderText::kDragToEndIfOutsideVerticalBounds ? u"hello" : u"lo"; |
| const std::u16string expected_right = |
| gfx::RenderText::kDragToEndIfOutsideVerticalBounds ? u" world" : u" w"; |
| const int right_x = GetCursorPositionX(7); |
| const int left_x = GetCursorPositionX(3); |
| |
| // All drags start from here. |
| MoveMouseTo(gfx::Point(GetCursorPositionX(5), GetCursorYForTesting())); |
| PressLeftMouseButton(); |
| |
| // Perform one continuous drag, checking the selection at various points. |
| DragMouseTo(gfx::Point(left_x, -500)); |
| EXPECT_EQ(expected_left, textfield_->GetSelectedText()); // NW. |
| DragMouseTo(gfx::Point(right_x, -500)); |
| EXPECT_EQ(expected_right, textfield_->GetSelectedText()); // NE. |
| DragMouseTo(gfx::Point(right_x, 500)); |
| EXPECT_EQ(expected_right, textfield_->GetSelectedText()); // SE. |
| DragMouseTo(gfx::Point(left_x, 500)); |
| EXPECT_EQ(expected_left, textfield_->GetSelectedText()); // SW. |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| TEST_F(TextfieldTest, DragAndDrop_AcceptDrop) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| |
| ui::OSExchangeData data; |
| std::u16string string(u"string "); |
| data.SetString(string); |
| int formats = 0; |
| std::set<ui::ClipboardFormatType> format_types; |
| |
| // Ensure that disabled textfields do not accept drops. |
| textfield_->SetEnabled(false); |
| EXPECT_FALSE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(0, formats); |
| EXPECT_TRUE(format_types.empty()); |
| EXPECT_FALSE(textfield_->CanDrop(data)); |
| textfield_->SetEnabled(true); |
| |
| // Ensure that read-only textfields do not accept drops. |
| textfield_->SetReadOnly(true); |
| EXPECT_FALSE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(0, formats); |
| EXPECT_TRUE(format_types.empty()); |
| EXPECT_FALSE(textfield_->CanDrop(data)); |
| textfield_->SetReadOnly(false); |
| |
| // Ensure that enabled and editable textfields do accept drops. |
| EXPECT_TRUE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(ui::OSExchangeData::STRING, formats); |
| EXPECT_TRUE(format_types.empty()); |
| EXPECT_TRUE(textfield_->CanDrop(data)); |
| gfx::PointF drop_point(GetCursorPositionX(6), 0); |
| ui::DropTargetEvent drop( |
| data, drop_point, drop_point, |
| ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE, |
| textfield_->OnDragUpdated(drop)); |
| ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone; |
| auto cb = textfield_->GetDropCallback(drop); |
| std::move(cb).Run(drop, output_drag_op, /*drag_image_layer_owner=*/nullptr); |
| EXPECT_EQ(ui::mojom::DragOperation::kCopy, output_drag_op); |
| EXPECT_EQ(u"hello string world", textfield_->GetText()); |
| |
| // Ensure that textfields do not accept non-OSExchangeData::STRING types. |
| ui::OSExchangeData bad_data; |
| bad_data.SetFilename(base::FilePath(FILE_PATH_LITERAL("x"))); |
| ui::ClipboardFormatType fmt = ui::ClipboardFormatType::BitmapType(); |
| bad_data.SetPickledData(fmt, base::Pickle()); |
| bad_data.SetFileContents(base::FilePath(L"x"), "x"); |
| bad_data.SetHtml(std::u16string(u"x"), GURL("x.org")); |
| ui::DownloadFileInfo download(base::FilePath(), nullptr); |
| bad_data.provider().SetDownloadFileInfo(&download); |
| EXPECT_FALSE(textfield_->CanDrop(bad_data)); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, DragAndDrop_InitiateDrag) { |
| InitTextfield(); |
| textfield_->SetText(u"hello string world"); |
| |
| // Ensure the textfield will provide selected text for drag data. |
| ui::OSExchangeData data; |
| const gfx::Range kStringRange(6, 12); |
| textfield_->SetSelectedRange(kStringRange); |
| const gfx::Point kStringPoint(GetCursorPositionX(9), GetCursorYForTesting()); |
| textfield_->WriteDragDataForView(nullptr, kStringPoint, &data); |
| std::optional<std::u16string> string = data.GetString(); |
| EXPECT_EQ(textfield_->GetSelectedText(), string); |
| |
| // Ensure that disabled textfields do not support drag operations. |
| textfield_->SetEnabled(false); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, |
| textfield_->GetDragOperationsForView(nullptr, kStringPoint)); |
| textfield_->SetEnabled(true); |
| // Ensure that textfields without selections do not support drag operations. |
| textfield_->ClearSelection(); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, |
| textfield_->GetDragOperationsForView(nullptr, kStringPoint)); |
| textfield_->SetSelectedRange(kStringRange); |
| // Ensure that password textfields do not support drag operations. |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, |
| textfield_->GetDragOperationsForView(nullptr, kStringPoint)); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_TEXT); |
| MoveMouseTo(kStringPoint); |
| PressLeftMouseButton(); |
| // Ensure that textfields only initiate drag operations inside the selection. |
| EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, |
| textfield_->GetDragOperationsForView(nullptr, gfx::Point())); |
| EXPECT_FALSE( |
| textfield_->CanStartDragForView(nullptr, gfx::Point(), gfx::Point())); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, |
| textfield_->GetDragOperationsForView(nullptr, kStringPoint)); |
| EXPECT_TRUE( |
| textfield_->CanStartDragForView(nullptr, kStringPoint, gfx::Point())); |
| allow_drag_event_ = false; |
| EXPECT_FALSE( |
| textfield_->CanStartDragForView(nullptr, kStringPoint, kStringPoint)); |
| allow_drag_event_ = true; |
| // Ensure that textfields support local moves. |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY, |
| textfield_->GetDragOperationsForView(textfield_, kStringPoint)); |
| } |
| |
| TEST_F(TextfieldTest, DragAndDrop_ToTheRight) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| |
| ui::OSExchangeData data; |
| int formats = 0; |
| int operations = 0; |
| std::set<ui::ClipboardFormatType> format_types; |
| |
| // Start dragging "ello". |
| textfield_->SetSelectedRange(gfx::Range(1, 5)); |
| gfx::Point point(GetCursorPositionX(3), cursor_y); |
| MoveMouseTo(point); |
| PressLeftMouseButton(); |
| EXPECT_TRUE(textfield_->CanStartDragForView(textfield_, point, point)); |
| operations = textfield_->GetDragOperationsForView(textfield_, point); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY, |
| operations); |
| textfield_->WriteDragDataForView(nullptr, point, &data); |
| std::optional<std::u16string> string = data.GetString(); |
| EXPECT_EQ(textfield_->GetSelectedText(), string); |
| EXPECT_TRUE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(ui::OSExchangeData::STRING, formats); |
| EXPECT_TRUE(format_types.empty()); |
| |
| // Drop "ello" after "w". |
| const gfx::PointF kDropPoint(GetCursorPositionX(7), cursor_y); |
| EXPECT_TRUE(textfield_->CanDrop(data)); |
| ui::DropTargetEvent drop_a(data, kDropPoint, kDropPoint, operations); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a)); |
| ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone; |
| auto cb = textfield_->GetDropCallback(drop_a); |
| std::move(cb).Run(drop_a, output_drag_op, /*drag_image_layer_owner=*/nullptr); |
| EXPECT_EQ(ui::mojom::DragOperation::kMove, output_drag_op); |
| EXPECT_EQ(u"h welloorld", textfield_->GetText()); |
| textfield_->OnDragDone(); |
| |
| // Undo/Redo the drag&drop change. |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"hello world", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"hello world", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"h welloorld", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"h welloorld", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, DragAndDrop_ToTheLeft) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| |
| ui::OSExchangeData data; |
| int formats = 0; |
| int operations = 0; |
| std::set<ui::ClipboardFormatType> format_types; |
| |
| // Start dragging " worl". |
| textfield_->SetSelectedRange(gfx::Range(5, 10)); |
| gfx::Point point(GetCursorPositionX(7), cursor_y); |
| MoveMouseTo(point); |
| PressLeftMouseButton(); |
| EXPECT_TRUE(textfield_->CanStartDragForView(textfield_, point, gfx::Point())); |
| operations = textfield_->GetDragOperationsForView(textfield_, point); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY, |
| operations); |
| textfield_->WriteDragDataForView(nullptr, point, &data); |
| std::optional<std::u16string> string = data.GetString(); |
| EXPECT_EQ(textfield_->GetSelectedText(), string); |
| EXPECT_TRUE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(ui::OSExchangeData::STRING, formats); |
| EXPECT_TRUE(format_types.empty()); |
| |
| // Drop " worl" after "h". |
| EXPECT_TRUE(textfield_->CanDrop(data)); |
| gfx::PointF drop_point(GetCursorPositionX(1), cursor_y); |
| ui::DropTargetEvent drop_a(data, drop_point, drop_point, operations); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a)); |
| ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone; |
| auto cb = textfield_->GetDropCallback(drop_a); |
| std::move(cb).Run(drop_a, output_drag_op, /*drag_image_layer_owner=*/nullptr); |
| EXPECT_EQ(ui::mojom::DragOperation::kMove, output_drag_op); |
| EXPECT_EQ(u"h worlellod", textfield_->GetText()); |
| textfield_->OnDragDone(); |
| |
| // Undo/Redo the drag&drop change. |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"hello world", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"hello world", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"h worlellod", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"h worlellod", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, DropCallbackCancelled) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| |
| ui::OSExchangeData data; |
| int formats = 0; |
| int operations = 0; |
| std::set<ui::ClipboardFormatType> format_types; |
| |
| // Start dragging "hello". |
| textfield_->SetSelectedRange(gfx::Range(0, 5)); |
| gfx::Point point(GetCursorPositionX(3), cursor_y); |
| MoveMouseTo(point); |
| PressLeftMouseButton(); |
| EXPECT_TRUE(textfield_->CanStartDragForView(textfield_, point, gfx::Point())); |
| operations = textfield_->GetDragOperationsForView(textfield_, point); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE | ui::DragDropTypes::DRAG_COPY, |
| operations); |
| textfield_->WriteDragDataForView(nullptr, point, &data); |
| std::optional<std::u16string> string = data.GetString(); |
| EXPECT_EQ(textfield_->GetSelectedText(), string); |
| EXPECT_TRUE(textfield_->GetDropFormats(&formats, &format_types)); |
| EXPECT_EQ(ui::OSExchangeData::STRING, formats); |
| EXPECT_TRUE(format_types.empty()); |
| |
| // Drop "hello" after "d". The drop callback should do nothing because |
| // `textfield_` is mutated before the callback is run. |
| EXPECT_TRUE(textfield_->CanDrop(data)); |
| gfx::PointF drop_point(GetCursorPositionX(11), cursor_y); |
| ui::DropTargetEvent drop_a(data, drop_point, drop_point, operations); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a)); |
| ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone; |
| auto cb = textfield_->GetDropCallback(drop_a); |
| textfield_->AppendText(u"new text"); |
| std::move(cb).Run(drop_a, output_drag_op, /*drag_image_layer_owner=*/nullptr); |
| EXPECT_EQ(ui::mojom::DragOperation::kNone, output_drag_op); |
| EXPECT_EQ(u"hello worldnew text", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, DragAndDrop_Canceled) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| |
| // Start dragging "worl". |
| textfield_->SetSelectedRange(gfx::Range(6, 10)); |
| gfx::Point point(GetCursorPositionX(8), cursor_y); |
| MoveMouseTo(point); |
| PressLeftMouseButton(); |
| ui::OSExchangeData data; |
| textfield_->WriteDragDataForView(nullptr, point, &data); |
| EXPECT_TRUE(textfield_->CanDrop(data)); |
| // Drag the text over somewhere valid, outside the current selection. |
| gfx::PointF drop_point(GetCursorPositionX(2), cursor_y); |
| ui::DropTargetEvent drop(data, drop_point, drop_point, |
| ui::DragDropTypes::DRAG_MOVE); |
| EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop)); |
| // "Cancel" the drag, via move and release over the selection, and OnDragDone. |
| gfx::Point drag_point(GetCursorPositionX(9), cursor_y); |
| DragMouseTo(drag_point); |
| ReleaseLeftMouseButton(); |
| EXPECT_EQ(u"hello world", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, ReadOnlyTest) { |
| InitTextfield(); |
| textfield_->SetText(u"read only"); |
| textfield_->SetReadOnly(true); |
| EXPECT_TRUE(textfield_->GetEnabled()); |
| EXPECT_TRUE(textfield_->IsFocusable()); |
| |
| bool shift = false; |
| SendHomeEvent(shift); |
| EXPECT_EQ(0U, textfield_->GetCursorPosition()); |
| SendEndEvent(shift); |
| EXPECT_EQ(9U, textfield_->GetCursorPosition()); |
| |
| SendKeyEvent(ui::VKEY_LEFT, shift, false); |
| EXPECT_EQ(8U, textfield_->GetCursorPosition()); |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| EXPECT_EQ(5U, textfield_->GetCursorPosition()); |
| shift = true; |
| SendWordEvent(ui::VKEY_LEFT, shift); |
| EXPECT_EQ(0U, textfield_->GetCursorPosition()); |
| EXPECT_EQ(u"read ", textfield_->GetSelectedText()); |
| textfield_->SelectAll(false); |
| EXPECT_EQ(u"read only", textfield_->GetSelectedText()); |
| |
| // Cut should be disabled. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"Test"); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kCut)); |
| textfield_->ExecuteCommand(Textfield::kCut, 0); |
| SendKeyEvent(ui::VKEY_X, false, true); |
| SendAlternateCut(); |
| EXPECT_EQ(u"Test", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"read only", textfield_->GetText()); |
| |
| // Paste should be disabled. |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kPaste)); |
| textfield_->ExecuteCommand(Textfield::kPaste, 0); |
| SendKeyEvent(ui::VKEY_V, false, true); |
| SendAlternatePaste(); |
| EXPECT_EQ(u"read only", textfield_->GetText()); |
| |
| // Copy should work normally. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"Test"); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kCopy)); |
| textfield_->ExecuteCommand(Textfield::kCopy, 0); |
| EXPECT_EQ(u"read only", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"Test"); |
| SendKeyEvent(ui::VKEY_C, false, true); |
| EXPECT_EQ(u"read only", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"Test"); |
| SendAlternateCopy(); |
| EXPECT_EQ(u"read only", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| |
| // SetText should work even in read only mode. |
| textfield_->SetText(u" four five six "); |
| EXPECT_EQ(u" four five six ", textfield_->GetText()); |
| |
| textfield_->SelectAll(false); |
| EXPECT_EQ(u" four five six ", textfield_->GetSelectedText()); |
| |
| // Text field is unmodifiable and selection shouldn't change. |
| SendKeyEvent(ui::VKEY_DELETE); |
| EXPECT_EQ(u" four five six ", textfield_->GetSelectedText()); |
| SendKeyEvent(ui::VKEY_BACK); |
| EXPECT_EQ(u" four five six ", textfield_->GetSelectedText()); |
| SendKeyEvent(ui::VKEY_T); |
| EXPECT_EQ(u" four five six ", textfield_->GetSelectedText()); |
| } |
| |
| TEST_F(TextfieldTest, TextInputClientTest) { |
| InitTextfield(); |
| ui::TextInputClient* client = textfield_; |
| EXPECT_TRUE(client); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, client->GetTextInputType()); |
| |
| textfield_->SetText(u"0123456789"); |
| gfx::Range range; |
| EXPECT_TRUE(client->GetTextRange(&range)); |
| EXPECT_EQ(0U, range.start()); |
| EXPECT_EQ(10U, range.end()); |
| |
| EXPECT_TRUE(client->SetEditableSelectionRange(gfx::Range(1, 4))); |
| EXPECT_TRUE(client->GetEditableSelectionRange(&range)); |
| EXPECT_EQ(gfx::Range(1, 4), range); |
| |
| std::u16string substring; |
| EXPECT_TRUE(client->GetTextFromRange(range, &substring)); |
| EXPECT_EQ(u"123", substring); |
| |
| #if BUILDFLAG(IS_MAC) |
| EXPECT_TRUE(client->DeleteRange(range)); |
| EXPECT_EQ(u"0456789", textfield_->GetText()); |
| #endif |
| |
| ui::CompositionText composition; |
| composition.text = u"321"; |
| // Set composition through input method. |
| input_method()->Clear(); |
| input_method()->SetCompositionTextForNextKey(composition); |
| textfield_->clear(); |
| |
| on_before_user_action_ = on_after_user_action_ = 0; |
| DispatchMockInputMethodKeyEvent(); |
| |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_FALSE(textfield_->key_handled()); |
| EXPECT_TRUE(client->HasCompositionText()); |
| EXPECT_TRUE(client->GetCompositionTextRange(&range)); |
| EXPECT_EQ(u"0321456789", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(1, 4), range); |
| EXPECT_EQ(1, on_before_user_action_); |
| EXPECT_EQ(1, on_after_user_action_); |
| |
| input_method()->SetResultTextForNextKey(u"123"); |
| on_before_user_action_ = on_after_user_action_ = 0; |
| textfield_->clear(); |
| DispatchMockInputMethodKeyEvent(); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_FALSE(textfield_->key_handled()); |
| EXPECT_FALSE(client->HasCompositionText()); |
| EXPECT_FALSE(input_method()->cancel_composition_called()); |
| EXPECT_EQ(u"0123456789", textfield_->GetText()); |
| EXPECT_EQ(1, on_before_user_action_); |
| EXPECT_EQ(1, on_after_user_action_); |
| |
| input_method()->Clear(); |
| input_method()->SetCompositionTextForNextKey(composition); |
| textfield_->clear(); |
| DispatchMockInputMethodKeyEvent(); |
| EXPECT_TRUE(client->HasCompositionText()); |
| EXPECT_EQ(u"0123321456789", textfield_->GetText()); |
| |
| on_before_user_action_ = on_after_user_action_ = 0; |
| textfield_->clear(); |
| SendKeyEvent(ui::VKEY_RIGHT); |
| EXPECT_FALSE(client->HasCompositionText()); |
| EXPECT_TRUE(input_method()->cancel_composition_called()); |
| EXPECT_TRUE(textfield_->key_received()); |
| EXPECT_TRUE(textfield_->key_handled()); |
| EXPECT_EQ(u"0123321456789", textfield_->GetText()); |
| EXPECT_EQ(8U, textfield_->GetCursorPosition()); |
| EXPECT_EQ(1, on_before_user_action_); |
| EXPECT_EQ(1, on_after_user_action_); |
| |
| textfield_->clear(); |
| textfield_->SetText(u"0123456789"); |
| EXPECT_TRUE(client->SetEditableSelectionRange(gfx::Range(5, 5))); |
| client->ExtendSelectionAndDelete(4, 2); |
| EXPECT_EQ(u"0789", textfield_->GetText()); |
| |
| // On{Before,After}UserAction should be called by whatever user action |
| // triggers clearing or setting a selection if appropriate. |
| on_before_user_action_ = on_after_user_action_ = 0; |
| textfield_->clear(); |
| textfield_->ClearSelection(); |
| textfield_->SelectAll(false); |
| EXPECT_EQ(0, on_before_user_action_); |
| EXPECT_EQ(0, on_after_user_action_); |
| |
| input_method()->Clear(); |
| |
| // Changing the Textfield to readonly shouldn't change the input client, since |
| // it's still required for selections and clipboard copy. |
| ui::TextInputClient* text_input_client = textfield_; |
| EXPECT_TRUE(text_input_client); |
| EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, text_input_client->GetTextInputType()); |
| textfield_->SetReadOnly(true); |
| EXPECT_TRUE(input_method()->text_input_type_changed()); |
| EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, text_input_client->GetTextInputType()); |
| |
| input_method()->Clear(); |
| textfield_->SetReadOnly(false); |
| EXPECT_TRUE(input_method()->text_input_type_changed()); |
| EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, text_input_client->GetTextInputType()); |
| |
| input_method()->Clear(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| EXPECT_TRUE(input_method()->text_input_type_changed()); |
| } |
| |
| TEST_F(TextfieldTest, UndoRedoTest) { |
| InitTextfield(); |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| |
| // AppendText |
| textfield_->AppendText(u"b"); |
| last_contents_.clear(); // AppendText doesn't call ContentsChanged. |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| |
| // SetText |
| SendKeyEvent(ui::VKEY_C); |
| // Undo'ing append moves the cursor to the end for now. |
| // A no-op SetText won't add a new edit; see TextfieldModel::SetText. |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| textfield_->SetText(u"abc"); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| textfield_->SetText(u"123"); |
| textfield_->SetText(u"123"); |
| EXPECT_EQ(u"123", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_END, false, false); |
| SendKeyEvent(ui::VKEY_4, false, false); |
| EXPECT_EQ(u"1234", textfield_->GetText()); |
| last_contents_.clear(); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"123", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| // the insert edit "c" and set edit "123" are merged to single edit, |
| // so text becomes "ab" after undo. |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"123", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"1234", textfield_->GetText()); |
| |
| // Undoing to the same text shouldn't call ContentsChanged. |
| SendKeyEvent(ui::VKEY_A, false, true); // select all |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_B); |
| SendKeyEvent(ui::VKEY_C); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"1234", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| |
| // Delete/Backspace |
| SendKeyEvent(ui::VKEY_BACK); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendHomeEvent(false); |
| SendKeyEvent(ui::VKEY_DELETE); |
| EXPECT_EQ(u"b", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_A, false, true); |
| SendKeyEvent(ui::VKEY_DELETE); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"b", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"b", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| } |
| |
| // Most platforms support Ctrl+Y as an alternative to Ctrl+Shift+Z, but on Mac |
| // Ctrl+Y is bound to "Yank" and Cmd+Y is bound to "Show full history". So, on |
| // Mac, Cmd+Shift+Z is sent for the tests above and the Ctrl+Y test below is |
| // skipped. |
| #if !BUILDFLAG(IS_MAC) |
| |
| // Test that Ctrl+Y works for Redo, as well as Ctrl+Shift+Z. |
| TEST_F(TextfieldTest, RedoWithCtrlY) { |
| InitTextfield(); |
| SendKeyEvent(ui::VKEY_A); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Y, false, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_Z, true, true); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| } |
| |
| #endif // !BUILDFLAG(IS_MAC) |
| |
| // Non-Mac platforms don't have a key binding for Yank. Since this test is only |
| // run on Mac, it uses some Mac specific key bindings. |
| #if BUILDFLAG(IS_MAC) |
| |
| TEST_F(TextfieldTest, Yank) { |
| InitTextfield(2); |
| textfield_->SetText(u"abcdef"); |
| textfield_->SetSelectedRange(gfx::Range(2, 4)); |
| |
| // Press Ctrl+Y to yank. |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| |
| // Initially the kill buffer should be empty. Hence yanking should delete the |
| // selected text. |
| EXPECT_EQ(u"abef", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(2), textfield_->GetSelectedRange()); |
| |
| // Press Ctrl+K to delete to end of paragraph. This should place the deleted |
| // text in the kill buffer. |
| SendKeyPress(ui::VKEY_K, ui::EF_CONTROL_DOWN); |
| |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(2), textfield_->GetSelectedRange()); |
| |
| // Yank twice. |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(u"abefef", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange()); |
| |
| // Verify pressing backspace does not modify the kill buffer. |
| SendKeyEvent(ui::VKEY_BACK); |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(u"abefeef", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(7), textfield_->GetSelectedRange()); |
| |
| // Move focus to next textfield. |
| widget_->GetFocusManager()->AdvanceFocus(false); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| Textfield* textfield2 = static_cast<Textfield*>(GetFocusedView()); |
| EXPECT_TRUE(textfield2->GetText().empty()); |
| |
| // Verify yanked text persists across multiple textfields and that yanking |
| // into a password textfield works. |
| textfield2->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(u"ef", textfield2->GetText()); |
| EXPECT_EQ(gfx::Range(2), textfield2->GetSelectedRange()); |
| |
| // Verify deletion in a password textfield does not modify the kill buffer. |
| textfield2->SetText(u"hello"); |
| textfield2->SetSelectedRange(gfx::Range(0)); |
| SendKeyPress(ui::VKEY_K, ui::EF_CONTROL_DOWN); |
| EXPECT_TRUE(textfield2->GetText().empty()); |
| |
| textfield_->RequestFocus(); |
| textfield_->SetSelectedRange(gfx::Range(0)); |
| SendKeyPress(ui::VKEY_Y, ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(u"efabefeef", textfield_->GetText()); |
| } |
| |
| #endif // BUILDFLAG(IS_MAC) |
| |
| TEST_F(TextfieldTest, CutCopyPaste) { |
| InitTextfield(); |
| // Ensure kCut cuts. |
| textfield_->SetText(u"123"); |
| textfield_->SelectAll(false); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kCut)); |
| textfield_->ExecuteCommand(Textfield::kCut, 0); |
| EXPECT_EQ(u"123", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Ensure [Ctrl]+[x] cuts and [Ctrl]+[Alt][x] does nothing. |
| textfield_->SetText(u"456"); |
| textfield_->SelectAll(false); |
| SendKeyEvent(ui::VKEY_X, true, false, true, false); |
| EXPECT_EQ(u"123", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"456", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| SendKeyEvent(ui::VKEY_X, false, true); |
| EXPECT_EQ(u"456", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Ensure [Shift]+[Delete] cuts. |
| textfield_->SetText(u"123"); |
| textfield_->SelectAll(false); |
| SendAlternateCut(); |
| EXPECT_EQ(u"123", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Reset clipboard text. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u""); |
| |
| // Ensure [Shift]+[Delete] is a no-op in case there is no selection. |
| textfield_->SetText(u"123"); |
| textfield_->SetSelectedRange(gfx::Range(0)); |
| SendAlternateCut(); |
| EXPECT_EQ(u"", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"123", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| // Ensure kCopy copies. |
| textfield_->SetText(u"789"); |
| textfield_->SelectAll(false); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kCopy)); |
| textfield_->ExecuteCommand(Textfield::kCopy, 0); |
| EXPECT_EQ(u"789", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Ensure [Ctrl]+[c] copies and [Ctrl]+[Alt][c] does nothing. |
| textfield_->SetText(u"012"); |
| textfield_->SelectAll(false); |
| SendKeyEvent(ui::VKEY_C, true, false, true, false); |
| EXPECT_EQ(u"789", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| SendKeyEvent(ui::VKEY_C, false, true); |
| EXPECT_EQ(u"012", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Ensure [Ctrl]+[Insert] copies. |
| textfield_->SetText(u"345"); |
| textfield_->SelectAll(false); |
| SendAlternateCopy(); |
| EXPECT_EQ(u"345", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"345", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kCopyPaste, GetAndResetCopiedToClipboard()); |
| |
| // Ensure kPaste, [Ctrl]+[V], and [Shift]+[Insert] pastes; |
| // also ensure that [Ctrl]+[Alt]+[V] does nothing. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"abc"); |
| textfield_->SetText(std::u16string()); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kPaste)); |
| textfield_->ExecuteCommand(Textfield::kPaste, 0); |
| EXPECT_EQ(u"abc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_V, false, true); |
| EXPECT_EQ(u"abcabc", textfield_->GetText()); |
| SendAlternatePaste(); |
| EXPECT_EQ(u"abcabcabc", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_V, true, false, true, false); |
| EXPECT_EQ(u"abcabcabc", textfield_->GetText()); |
| |
| // Ensure [Ctrl]+[Shift]+[Insert] is a no-op. |
| textfield_->SelectAll(false); |
| SendKeyEvent(ui::VKEY_INSERT, true, true); |
| EXPECT_EQ(u"abc", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| EXPECT_EQ(u"abcabcabc", textfield_->GetText()); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| // Ensure clipboard buffer is unchanged if override is enabled |
| textfield_->SetText(u"345"); |
| textfield_->SelectAll(false); |
| SendAlternateCopy(); |
| EXPECT_EQ(u"345", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| handle_write_to_clipboard_ = true; |
| textfield_->SetText(u"4242"); |
| textfield_->SelectAll(false); |
| SendAlternateCopy(); |
| EXPECT_EQ(u"345", GetClipboardText(ui::ClipboardBuffer::kCopyPaste)); |
| } |
| |
| TEST_F(TextfieldTest, CutCopyPasteWithEditCommand) { |
| InitTextfield(); |
| // Target the "WIDGET". This means that, on Mac, keystrokes will be sent to a |
| // dummy 'Edit' menu which will dispatch into the responder chain as a "cut:" |
| // selector rather than a keydown. This has no effect on other platforms |
| // (events elsewhere always dispatch via a ui::EventProcessor, which is |
| // responsible for finding targets). |
| event_generator_->set_target(ui::test::EventGenerator::Target::WIDGET); |
| |
| SendKeyEvent(ui::VKEY_O, false, false); // Type "o". |
| SendKeyEvent(ui::VKEY_A, false, true); // Select it. |
| SendKeyEvent(ui::VKEY_C, false, true); // Copy it. |
| SendKeyEvent(ui::VKEY_RIGHT, false, false); // Deselect and navigate to end. |
| EXPECT_EQ(u"o", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_V, false, true); // Paste it. |
| EXPECT_EQ(u"oo", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_H, false, false); // Type "h". |
| EXPECT_EQ(u"ooh", textfield_->GetText()); |
| SendKeyEvent(ui::VKEY_LEFT, true, false); // Select "h". |
| SendKeyEvent(ui::VKEY_X, false, true); // Cut it. |
| EXPECT_EQ(u"oo", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, SelectWordFromEmptySelection) { |
| InitTextfield(); |
| textfield_->SetText(u"ab cde.123 4"); |
| |
| // Place the cursor at the beginning of the text. |
| textfield_->SetEditableSelectionRange(gfx::Range(0)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"ab", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(0, 2), textfield_->GetSelectedRange()); |
| |
| // Place the cursor after "c". |
| textfield_->SetEditableSelectionRange(gfx::Range(4)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"cde", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(3, 6), textfield_->GetSelectedRange()); |
| |
| // Place the cursor after "2". |
| textfield_->SetEditableSelectionRange(gfx::Range(9)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"123", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(7, 10), textfield_->GetSelectedRange()); |
| |
| // Place the cursor at the end of the text. |
| textfield_->SetEditableSelectionRange(gfx::Range(12)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"4", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(11, 12), textfield_->GetSelectedRange()); |
| } |
| |
| TEST_F(TextfieldTest, SelectWordFromNonEmptySelection) { |
| InitTextfield(); |
| textfield_->SetText(u"ab cde.123 4"); |
| |
| // Select "b". |
| textfield_->SetEditableSelectionRange(gfx::Range(1, 2)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"ab", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(0, 2), textfield_->GetSelectedRange()); |
| |
| // Select "b c" |
| textfield_->SetEditableSelectionRange(gfx::Range(1, 4)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"ab cde", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(0, 6), textfield_->GetSelectedRange()); |
| |
| // Select "e." |
| textfield_->SetEditableSelectionRange(gfx::Range(5, 7)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"cde.", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(3, 7), textfield_->GetSelectedRange()); |
| |
| // Select "e.1" |
| textfield_->SetEditableSelectionRange(gfx::Range(5, 8)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u"cde.123", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(3, 10), textfield_->GetSelectedRange()); |
| } |
| |
| TEST_F(TextfieldTest, SelectWordFromNonAlphaNumericFragment) { |
| InitTextfield(); |
| textfield_->SetText(u" HELLO !! WO RLD"); |
| |
| // Place the cursor within " !! ". |
| textfield_->SetEditableSelectionRange(gfx::Range(8)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u" !! ", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(7, 13), textfield_->GetSelectedRange()); |
| |
| textfield_->SetEditableSelectionRange(gfx::Range(10)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u" !! ", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(7, 13), textfield_->GetSelectedRange()); |
| } |
| |
| TEST_F(TextfieldTest, SelectWordFromWhitespaceFragment) { |
| InitTextfield(); |
| textfield_->SetText(u" HELLO !! WO RLD"); |
| textfield_->SetEditableSelectionRange(gfx::Range(17)); |
| textfield_->SelectWord(); |
| EXPECT_EQ(u" ", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(15, 20), textfield_->GetSelectedRange()); |
| } |
| |
| TEST_F(TextfieldTest, SelectCommands) { |
| InitTextfield(); |
| textfield_->SetText(u"hello string world"); |
| |
| // Select all and select word commands should both be enabled when there is no |
| // selection. |
| textfield_->SetEditableSelectionRange(gfx::Range(8)); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Select word at current position. Select word command should now be disabled |
| // since there is already a selection. |
| textfield_->ExecuteCommand(Textfield::kSelectWord, 0); |
| EXPECT_EQ(u"string", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(6, 12), textfield_->GetSelectedRange()); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Select all text. Select all and select word commands should now both be |
| // disabled. |
| textfield_->ExecuteCommand(Textfield::kSelectAll, 0); |
| EXPECT_EQ(u"hello string world", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(0, 18), textfield_->GetSelectedRange()); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| // No touch on desktop Mac. |
| #if !BUILDFLAG(IS_MAC) |
| TEST_F(TextfieldTest, SelectCommandsFromTouchEvent) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"hello string world"); |
| |
| // Select all and select word commands should both be enabled when there is no |
| // selection. |
| textfield_->SetEditableSelectionRange(gfx::Range(8)); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Select word at current position. Select word command should now be disabled |
| // since there is already a selection. |
| textfield_->ExecuteCommand(Textfield::kSelectWord, ui::EF_FROM_TOUCH); |
| EXPECT_EQ(u"string", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(6, 12), textfield_->GetSelectedRange()); |
| EXPECT_TRUE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Select all text. Select all and select word commands should now both be |
| // disabled. |
| textfield_->ExecuteCommand(Textfield::kSelectAll, ui::EF_FROM_TOUCH); |
| EXPECT_EQ(u"hello string world", textfield_->GetSelectedText()); |
| EXPECT_EQ(gfx::Range(0, 18), textfield_->GetSelectedRange()); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectAll)); |
| EXPECT_FALSE(textfield_->IsCommandIdEnabled(Textfield::kSelectWord)); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, OvertypeMode) { |
| InitTextfield(); |
| // Overtype mode should be disabled (no-op [Insert]). |
| textfield_->SetText(u"2"); |
| const bool shift = false; |
| SendHomeEvent(shift); |
| // Note: On Mac, there is no insert key. Insert sends kVK_Help. Currently, |
| // since there is no overtype on toolkit-views, the behavior happens to match. |
| // However, there's no enable-overtype equivalent key combination on OSX. |
| SendKeyEvent(ui::VKEY_INSERT); |
| SendKeyEvent(ui::VKEY_1, false, false); |
| EXPECT_EQ(u"12", textfield_->GetText()); |
| } |
| |
| TEST_F(TextfieldTest, TextCursorDisplayTest) { |
| InitTextfield(); |
| // LTR-RTL string in LTR context. |
| SendKeyEvent('a'); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| int x = GetCursorBounds().x(); |
| int prev_x = x; |
| |
| SendKeyEvent('b'); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_LT(prev_x, x); |
| prev_x = x; |
| |
| SendKeyEvent(0x05E1); |
| EXPECT_EQ(u"ab\x05E1", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| |
| SendKeyEvent(0x05E2); |
| EXPECT_EQ(u"ab\x05E1\x5E2", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| |
| // Clear text. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| SendKeyEvent(ui::VKEY_DELETE); |
| |
| // RTL-LTR string in LTR context. |
| SendKeyEvent(0x05E1); |
| EXPECT_EQ(u"\x05E1", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_EQ(GetDisplayRect().x(), x); |
| prev_x = x; |
| |
| SendKeyEvent(0x05E2); |
| EXPECT_EQ(u"\x05E1\x05E2", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| |
| SendKeyEvent('a'); |
| EXPECT_EQ( |
| u"\x05E1\x5E2" |
| u"a", |
| textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_LT(prev_x, x); |
| prev_x = x; |
| |
| SendKeyEvent('b'); |
| EXPECT_EQ( |
| u"\x05E1\x5E2" |
| u"ab", |
| textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_LT(prev_x, x); |
| } |
| |
| TEST_F(TextfieldTest, TextCursorDisplayInRTLTest) { |
| std::string locale = base::i18n::GetConfiguredLocale(); |
| base::i18n::SetICUDefaultLocale("he"); |
| |
| InitTextfield(); |
| // LTR-RTL string in RTL context. |
| SendKeyEvent('a'); |
| EXPECT_EQ(u"a", textfield_->GetText()); |
| int x = GetCursorBounds().x(); |
| EXPECT_EQ(GetDisplayRect().right() - 1, x); |
| int prev_x = x; |
| |
| SendKeyEvent('b'); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| |
| SendKeyEvent(0x05E1); |
| EXPECT_EQ(u"ab\x05E1", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GT(prev_x, x); |
| prev_x = x; |
| |
| SendKeyEvent(0x05E2); |
| EXPECT_EQ(u"ab\x05E1\x5E2", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GT(prev_x, x); |
| |
| // Clear text. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| SendKeyEvent(ui::VKEY_DELETE); |
| |
| // RTL-LTR string in RTL context. |
| SendKeyEvent(0x05E1); |
| EXPECT_EQ(u"\x05E1", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| prev_x = x; |
| |
| SendKeyEvent(0x05E2); |
| EXPECT_EQ(u"\x05E1\x05E2", textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GT(prev_x, x); |
| prev_x = x; |
| |
| SendKeyEvent('a'); |
| EXPECT_EQ( |
| u"\x05E1\x5E2" |
| u"a", |
| textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| prev_x = x; |
| |
| SendKeyEvent('b'); |
| EXPECT_EQ( |
| u"\x05E1\x5E2" |
| u"ab", |
| textfield_->GetText()); |
| x = GetCursorBounds().x(); |
| EXPECT_GE(1, std::abs(x - prev_x)); |
| |
| // Reset locale. |
| base::i18n::SetICUDefaultLocale(locale); |
| } |
| |
| TEST_F(TextfieldTest, TextCursorPositionInRTLTest) { |
| std::string locale = base::i18n::GetConfiguredLocale(); |
| base::i18n::SetICUDefaultLocale("he"); |
| |
| InitTextfield(); |
| // LTR-RTL string in RTL context. |
| int text_cursor_position_prev = GetTextfieldTestApi().GetCursorViewRect().x(); |
| SendKeyEvent('a'); |
| SendKeyEvent('b'); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| int text_cursor_position_new = GetTextfieldTestApi().GetCursorViewRect().x(); |
| // Text cursor stays at same place after inserting new characters in RTL mode. |
| EXPECT_EQ(text_cursor_position_prev, text_cursor_position_new); |
| |
| // Reset locale. |
| base::i18n::SetICUDefaultLocale(locale); |
| } |
| |
| TEST_F(TextfieldTest, TextCursorPositionInLTRTest) { |
| InitTextfield(); |
| |
| // LTR-RTL string in LTR context. |
| int text_cursor_position_prev = GetTextfieldTestApi().GetCursorViewRect().x(); |
| SendKeyEvent('a'); |
| SendKeyEvent('b'); |
| EXPECT_EQ(u"ab", textfield_->GetText()); |
| int text_cursor_position_new = GetTextfieldTestApi().GetCursorViewRect().x(); |
| // Text cursor moves to right after inserting new characters in LTR mode. |
| EXPECT_LT(text_cursor_position_prev, text_cursor_position_new); |
| } |
| |
| TEST_F(TextfieldTest, HitInsideTextAreaTest) { |
| InitTextfield(); |
| textfield_->SetText(u"ab\x05E1\x5E2"); |
| std::vector<gfx::Rect> cursor_bounds; |
| |
| // Save each cursor bound. |
| gfx::SelectionModel sel(0, gfx::CURSOR_FORWARD); |
| cursor_bounds.push_back(GetCursorBounds(sel)); |
| |
| sel = gfx::SelectionModel(1, gfx::CURSOR_BACKWARD); |
| gfx::Rect bound = GetCursorBounds(sel); |
| sel = gfx::SelectionModel(1, gfx::CURSOR_FORWARD); |
| EXPECT_EQ(bound.x(), GetCursorBounds(sel).x()); |
| cursor_bounds.push_back(bound); |
| |
| // Check that a cursor at the end of the Latin portion of the text is at the |
| // same position as a cursor placed at the end of the RTL Hebrew portion. |
| sel = gfx::SelectionModel(2, gfx::CURSOR_BACKWARD); |
| bound = GetCursorBounds(sel); |
| sel = gfx::SelectionModel(4, gfx::CURSOR_BACKWARD); |
| EXPECT_EQ(bound.x(), GetCursorBounds(sel).x()); |
| cursor_bounds.push_back(bound); |
| |
| sel = gfx::SelectionModel(3, gfx::CURSOR_BACKWARD); |
| bound = GetCursorBounds(sel); |
| sel = gfx::SelectionModel(3, gfx::CURSOR_FORWARD); |
| EXPECT_EQ(bound.x(), GetCursorBounds(sel).x()); |
| cursor_bounds.push_back(bound); |
| |
| sel = gfx::SelectionModel(2, gfx::CURSOR_FORWARD); |
| bound = GetCursorBounds(sel); |
| sel = gfx::SelectionModel(4, gfx::CURSOR_FORWARD); |
| EXPECT_EQ(bound.x(), GetCursorBounds(sel).x()); |
| cursor_bounds.push_back(bound); |
| |
| // Expected cursor position when clicking left and right of each character. |
| constexpr auto cursor_pos_expected = |
| std::to_array<size_t>({0, 1, 1, 2, 4, 3, 3, 2}); |
| |
| int index = 0; |
| for (size_t i = 0; i < cursor_bounds.size() - 1; ++i) { |
| int half_width = (cursor_bounds[i + 1].x() - cursor_bounds[i].x()) / 2; |
| MouseClick(cursor_bounds[i], half_width / 2); |
| EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition()); |
| |
| // To avoid trigger double click. Not using sleep() since it takes longer |
| // for the test to run if using sleep(). |
| NonClientMouseClick(); |
| |
| MouseClick(cursor_bounds[i + 1], -(half_width / 2)); |
| EXPECT_EQ(cursor_pos_expected[index++], textfield_->GetCursorPosition()); |
| |
| NonClientMouseClick(); |
| } |
| } |
| |
| TEST_F(TextfieldTest, HitOutsideTextAreaTest) { |
| InitTextfield(); |
| |
| // LTR-RTL string in LTR context. |
| textfield_->SetText(u"ab\x05E1\x5E2"); |
| |
| const bool shift = false; |
| SendHomeEvent(shift); |
| gfx::Rect bound = GetCursorBounds(); |
| MouseClick(bound, -10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| SendEndEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, 10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| NonClientMouseClick(); |
| |
| // RTL-LTR string in LTR context. |
| textfield_->SetText( |
| u"\x05E1\x5E2" |
| u"ab"); |
| |
| SendHomeEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, 10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| SendEndEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, -10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| } |
| |
| TEST_F(TextfieldTest, HitOutsideTextAreaInRTLTest) { |
| std::string locale = base::i18n::GetConfiguredLocale(); |
| base::i18n::SetICUDefaultLocale("he"); |
| |
| InitTextfield(); |
| |
| // RTL-LTR string in RTL context. |
| textfield_->SetText( |
| u"\x05E1\x5E2" |
| u"ab"); |
| const bool shift = false; |
| SendHomeEvent(shift); |
| gfx::Rect bound = GetCursorBounds(); |
| MouseClick(bound, 10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| SendEndEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, -10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| NonClientMouseClick(); |
| |
| // LTR-RTL string in RTL context. |
| textfield_->SetText(u"ab\x05E1\x5E2"); |
| SendHomeEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, -10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| SendEndEvent(shift); |
| bound = GetCursorBounds(); |
| MouseClick(bound, 10); |
| EXPECT_EQ(bound, GetCursorBounds()); |
| |
| // Reset locale. |
| base::i18n::SetICUDefaultLocale(locale); |
| } |
| |
| // TODO(https://crbug.com/361276581, https://crbug.com/361247468): Flakes on |
| // Fuschia cast Debug bots. |
| #if BUILDFLAG(IS_FUCHSIA) && !defined(NDEBUG) |
| #define MAYBE_OverflowTest DISABLED_OverflowTest |
| #define MAYBE_OverflowInRTLTest DISABLED_OverflowInRTLTest |
| #else |
| #define MAYBE_OverflowTest OverflowTest |
| #define MAYBE_OverflowInRTLTest OverflowInRTLTest |
| #endif |
| TEST_F(TextfieldTest, MAYBE_OverflowTest) { |
| InitTextfield(); |
| |
| std::u16string str; |
| for (size_t i = 0; i < 500; ++i) { |
| SendKeyEvent('a'); |
| } |
| SendKeyEvent(kHebrewLetterSamekh); |
| EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); |
| |
| // Test mouse pointing. |
| MouseClick(GetCursorBounds(), -1); |
| EXPECT_EQ(500U, textfield_->GetCursorPosition()); |
| |
| // Clear text. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| SendKeyEvent(ui::VKEY_DELETE); |
| |
| for (size_t i = 0; i < 500; ++i) { |
| SendKeyEvent(kHebrewLetterSamekh); |
| } |
| SendKeyEvent('a'); |
| EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); |
| |
| MouseClick(GetCursorBounds(), -1); |
| EXPECT_EQ(501U, textfield_->GetCursorPosition()); |
| } |
| |
| TEST_F(TextfieldTest, MAYBE_OverflowInRTLTest) { |
| std::string locale = base::i18n::GetConfiguredLocale(); |
| base::i18n::SetICUDefaultLocale("he"); |
| |
| InitTextfield(); |
| |
| std::u16string str; |
| for (size_t i = 0; i < 500; ++i) { |
| SendKeyEvent('a'); |
| } |
| SendKeyEvent(kHebrewLetterSamekh); |
| EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); |
| |
| MouseClick(GetCursorBounds(), 1); |
| EXPECT_EQ(501U, textfield_->GetCursorPosition()); |
| |
| // Clear text. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| SendKeyEvent(ui::VKEY_DELETE); |
| |
| for (size_t i = 0; i < 500; ++i) { |
| SendKeyEvent(kHebrewLetterSamekh); |
| } |
| SendKeyEvent('a'); |
| EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds())); |
| |
| MouseClick(GetCursorBounds(), 1); |
| EXPECT_EQ(500U, textfield_->GetCursorPosition()); |
| |
| // Reset locale. |
| base::i18n::SetICUDefaultLocale(locale); |
| } |
| |
| TEST_F(TextfieldTest, PasswordProtected) { |
| InitTextfield(); |
| ui::AXNodeData data; |
| |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kProtected)); |
| |
| data = ui::AXNodeData(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_NONE); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_FALSE(data.HasState(ax::mojom::State::kProtected)); |
| } |
| |
| TEST_F(TextfieldTest, CommitComposingTextTest) { |
| InitTextfield(); |
| ui::CompositionText composition; |
| composition.text = u"abc123"; |
| textfield_->SetCompositionText(composition); |
| size_t composed_text_length = |
| textfield_->ConfirmCompositionText(/* keep_selection */ false); |
| |
| EXPECT_EQ(composed_text_length, 6u); |
| } |
| |
| TEST_F(TextfieldTest, CommitEmptyComposingTextTest) { |
| InitTextfield(); |
| ui::CompositionText composition; |
| composition.text = u""; |
| textfield_->SetCompositionText(composition); |
| size_t composed_text_length = |
| textfield_->ConfirmCompositionText(/* keep_selection */ false); |
| |
| EXPECT_EQ(composed_text_length, 0u); |
| } |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // SetCompositionFromExistingText is only available on Windows and Chrome OS. |
| TEST_F(TextfieldTest, SetCompositionFromExistingTextTest) { |
| InitTextfield(); |
| textfield_->SetText(u"abc"); |
| |
| textfield_->SetCompositionFromExistingText(gfx::Range(1, 3), {}); |
| |
| gfx::Range actual_range; |
| EXPECT_TRUE(textfield_->HasCompositionText()); |
| EXPECT_TRUE(textfield_->GetCompositionTextRange(&actual_range)); |
| EXPECT_EQ(actual_range, gfx::Range(1, 3)); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, GetCompositionCharacterBoundsTest) { |
| InitTextfield(); |
| ui::CompositionText composition; |
| composition.text = u"abc123"; |
| const uint32_t char_count = static_cast<uint32_t>(composition.text.length()); |
| |
| // Compare the composition character bounds with surrounding cursor bounds. |
| for (uint32_t i = 0; i < char_count; ++i) { |
| composition.selection = gfx::Range(i); |
| textfield_->SetCompositionText(composition); |
| gfx::Point cursor_origin = GetCursorBounds().origin(); |
| views::View::ConvertPointToScreen(textfield_, &cursor_origin); |
| |
| composition.selection = gfx::Range(i + 1); |
| textfield_->SetCompositionText(composition); |
| gfx::Point next_cursor_bottom_left = GetCursorBounds().bottom_left(); |
| views::View::ConvertPointToScreen(textfield_, &next_cursor_bottom_left); |
| |
| gfx::Rect character; |
| EXPECT_TRUE(textfield_->GetCompositionCharacterBounds(i, &character)); |
| EXPECT_EQ(character.origin(), cursor_origin) << " i=" << i; |
| EXPECT_EQ(character.bottom_right(), next_cursor_bottom_left) << " i=" << i; |
| } |
| |
| // Return false if the index is out of range. |
| gfx::Rect rect; |
| EXPECT_FALSE(textfield_->GetCompositionCharacterBounds(char_count, &rect)); |
| EXPECT_FALSE( |
| textfield_->GetCompositionCharacterBounds(char_count + 1, &rect)); |
| EXPECT_FALSE( |
| textfield_->GetCompositionCharacterBounds(char_count + 100, &rect)); |
| } |
| |
| TEST_F(TextfieldTest, GetCompositionCharacterBounds_ComplexText) { |
| InitTextfield(); |
| |
| constexpr auto kUtf16Chars = std::to_array<char16_t>({ |
| // U+0020 SPACE |
| 0x0020, |
| // U+1F408 (CAT) as surrogate pair |
| 0xd83d, |
| 0xdc08, |
| // U+5642 as Ideographic Variation Sequences |
| 0x5642, |
| 0xDB40, |
| 0xDD00, |
| // U+260E (BLACK TELEPHONE) as Emoji Variation Sequences |
| 0x260E, |
| 0xFE0F, |
| // U+0020 SPACE |
| 0x0020, |
| }); |
| |
| ui::CompositionText composition; |
| composition.text.assign(kUtf16Chars.data(), kUtf16Chars.size()); |
| textfield_->SetCompositionText(composition); |
| |
| // Make sure GetCompositionCharacterBounds never fails for index. |
| std::array<gfx::Rect, kUtf16Chars.size()> rects; |
| for (uint32_t i = 0; i < kUtf16Chars.size(); ++i) { |
| EXPECT_TRUE(textfield_->GetCompositionCharacterBounds(i, &rects[i])); |
| } |
| |
| // Here we might expect the following results but it actually depends on how |
| // Uniscribe or HarfBuzz treats them with given font. |
| // - rects[1] == rects[2] |
| // - rects[3] == rects[4] == rects[5] |
| // - rects[6] == rects[7] |
| } |
| |
| // The word we select by double clicking should remain selected regardless of |
| // where we drag the mouse afterwards without releasing the left button. |
| TEST_F(TextfieldTest, KeepInitiallySelectedWord) { |
| InitTextfield(); |
| |
| textfield_->SetText(u"abc def ghi"); |
| |
| textfield_->SetSelectedRange(gfx::Range(5, 5)); |
| const gfx::Rect middle_cursor = GetCursorBounds(); |
| textfield_->SetSelectedRange(gfx::Range(0, 0)); |
| const gfx::Point beginning = GetCursorBounds().origin(); |
| |
| // Double click, but do not release the left button. |
| MouseClick(middle_cursor, 0); |
| const gfx::Point middle(middle_cursor.x(), |
| middle_cursor.y() + middle_cursor.height() / 2); |
| MoveMouseTo(middle); |
| PressLeftMouseButton(); |
| EXPECT_EQ(gfx::Range(4, 7), textfield_->GetSelectedRange()); |
| |
| // Drag the mouse to the beginning of the textfield. |
| DragMouseTo(beginning); |
| EXPECT_EQ(gfx::Range(7, 0), textfield_->GetSelectedRange()); |
| } |
| |
| #if BUILDFLAG(IS_LINUX) |
| TEST_F(TextfieldTest, SelectionClipboard) { |
| InitTextfield(); |
| textfield_->SetText(u"0123"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Point point_1(GetCursorPositionX(1), cursor_y); |
| gfx::Point point_2(GetCursorPositionX(2), cursor_y); |
| gfx::Point point_3(GetCursorPositionX(3), cursor_y); |
| gfx::Point point_4(GetCursorPositionX(4), cursor_y); |
| |
| // Text selected by the mouse should be placed on the selection clipboard. |
| ui::MouseEvent press(ui::EventType::kMousePressed, point_1, point_1, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMousePressed(press); |
| ui::MouseEvent drag(ui::EventType::kMouseDragged, point_3, point_3, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMouseDragged(drag); |
| ui::MouseEvent release(ui::EventType::kMouseReleased, point_3, point_3, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMouseReleased(release); |
| EXPECT_EQ(gfx::Range(1, 3), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"12", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| |
| // Select-all should update the selection clipboard. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| EXPECT_EQ(gfx::Range(0, 4), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"0123", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| |
| // Shift-click selection modifications should update the clipboard. |
| NonClientMouseClick(); |
| ui::MouseEvent press_2(ui::EventType::kMousePressed, point_2, point_2, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| press_2.SetFlags(press_2.flags() | ui::EF_SHIFT_DOWN); |
| textfield_->OnMousePressed(press_2); |
| ui::MouseEvent release_2(ui::EventType::kMouseReleased, point_2, point_2, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMouseReleased(release_2); |
| EXPECT_EQ(gfx::Range(0, 2), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"01", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| |
| // Shift-Left/Right should update the selection clipboard. |
| SendKeyEvent(ui::VKEY_RIGHT, true, false); |
| EXPECT_EQ(u"012", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| SendKeyEvent(ui::VKEY_LEFT, true, false); |
| EXPECT_EQ(u"01", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| SendKeyEvent(ui::VKEY_RIGHT, true, true); |
| EXPECT_EQ(u"0123", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| |
| // Moving the cursor without a selection should not change the clipboard. |
| SendKeyEvent(ui::VKEY_LEFT, false, false); |
| EXPECT_EQ(gfx::Range(0, 0), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"0123", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| // Middle clicking should paste at the mouse (not cursor) location. |
| // The cursor should be placed at the end of the pasted text. |
| ui::MouseEvent middle(ui::EventType::kMousePressed, point_4, point_4, |
| ui::EventTimeForNow(), ui::EF_MIDDLE_MOUSE_BUTTON, |
| ui::EF_MIDDLE_MOUSE_BUTTON); |
| textfield_->OnMousePressed(middle); |
| EXPECT_EQ(u"01230123", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(8, 8), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"0123", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| |
| // Middle clicking on an unfocused textfield should focus it and paste. |
| textfield_->GetFocusManager()->ClearFocus(); |
| EXPECT_FALSE(textfield_->HasFocus()); |
| textfield_->OnMousePressed(middle); |
| EXPECT_TRUE(textfield_->HasFocus()); |
| EXPECT_EQ(u"012301230123", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(8, 8), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"0123", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| |
| // Middle clicking with an empty selection clipboard should still focus. |
| SetClipboardText(ui::ClipboardBuffer::kSelection, std::u16string()); |
| textfield_->GetFocusManager()->ClearFocus(); |
| EXPECT_FALSE(textfield_->HasFocus()); |
| textfield_->OnMousePressed(middle); |
| EXPECT_TRUE(textfield_->HasFocus()); |
| EXPECT_EQ(u"012301230123", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(4, 4), textfield_->GetSelectedRange()); |
| EXPECT_TRUE(GetClipboardText(ui::ClipboardBuffer::kSelection).empty()); |
| |
| // Middle clicking in the selection should insert the selection clipboard |
| // contents into the middle of the selection, and move the cursor to the end |
| // of the pasted content. |
| SetClipboardText(ui::ClipboardBuffer::kCopyPaste, u"foo"); |
| textfield_->SetSelectedRange(gfx::Range(2, 6)); |
| textfield_->OnMousePressed(middle); |
| EXPECT_EQ(u"0123foo01230123", textfield_->GetText()); |
| EXPECT_EQ(gfx::Range(7, 7), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"foo", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| |
| // Double and triple clicking should update the clipboard contents. |
| textfield_->SetText(u"ab cd ef"); |
| gfx::Point word(GetCursorPositionX(4), cursor_y); |
| ui::MouseEvent press_word(ui::EventType::kMousePressed, word, word, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMousePressed(press_word); |
| ui::MouseEvent release_word(ui::EventType::kMouseReleased, word, word, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMouseReleased(release_word); |
| ui::MouseEvent double_click(ui::EventType::kMousePressed, word, word, |
| ui::EventTimeForNow(), |
| ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_DOUBLE_CLICK, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMousePressed(double_click); |
| textfield_->OnMouseReleased(release_word); |
| EXPECT_EQ(gfx::Range(3, 5), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"cd", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| textfield_->OnMousePressed(press_word); |
| textfield_->OnMouseReleased(release_word); |
| EXPECT_EQ(gfx::Range(0, 8), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"ab cd ef", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| |
| // Selecting a range of text without any user interaction should not change |
| // the clipboard content. |
| textfield_->SetSelectedRange(gfx::Range(0, 3)); |
| EXPECT_EQ(u"ab ", textfield_->GetSelectedText()); |
| EXPECT_EQ(u"ab cd ef", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| SetClipboardText(ui::ClipboardBuffer::kSelection, u"other"); |
| textfield_->SelectAll(false); |
| EXPECT_EQ(u"other", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| } |
| |
| // Verify that the selection clipboard is not updated for selections on a |
| // password textfield. |
| TEST_F(TextfieldTest, SelectionClipboard_Password) { |
| InitTextfield(2); |
| textfield_->SetText(u"abcd"); |
| |
| // Select-all should update the selection clipboard for a non-password |
| // textfield. |
| SendKeyEvent(ui::VKEY_A, false, true); |
| EXPECT_EQ(gfx::Range(0, 4), textfield_->GetSelectedRange()); |
| EXPECT_EQ(u"abcd", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kSelection, GetAndResetCopiedToClipboard()); |
| |
| // Move focus to the next textfield. |
| widget_->GetFocusManager()->AdvanceFocus(false); |
| EXPECT_EQ(2, GetFocusedView()->GetID()); |
| Textfield* textfield2 = static_cast<Textfield*>(GetFocusedView()); |
| |
| // Select-all should not modify the selection clipboard for a password |
| // textfield. |
| textfield2->SetText(u"1234"); |
| textfield2->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| SendKeyEvent(ui::VKEY_A, false, true); |
| EXPECT_EQ(gfx::Range(0, 4), textfield2->GetSelectedRange()); |
| EXPECT_EQ(u"abcd", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| // Shift-Left/Right should not modify the selection clipboard for a password |
| // textfield. |
| SendKeyEvent(ui::VKEY_LEFT, true, false); |
| EXPECT_EQ(gfx::Range(0, 3), textfield2->GetSelectedRange()); |
| EXPECT_EQ(u"abcd", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| |
| SendKeyEvent(ui::VKEY_RIGHT, true, false); |
| EXPECT_EQ(gfx::Range(0, 4), textfield2->GetSelectedRange()); |
| EXPECT_EQ(u"abcd", GetClipboardText(ui::ClipboardBuffer::kSelection)); |
| EXPECT_EQ(ui::ClipboardBuffer::kMaxValue, GetAndResetCopiedToClipboard()); |
| } |
| #endif |
| |
| // Long_Press gesture in Textfield can initiate a drag and drop now. |
| TEST_F(TextfieldTest, TestLongPressInitiatesDragDrop) { |
| // Enable touch-drag-drop to make long press effective. |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(::features::kTouchDragAndDrop); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Ensure the textfield will provide selected text for drag data. |
| textfield_->SetSelectedRange(gfx::Range(6, 12)); |
| const gfx::Point kStringPoint(GetCursorPositionX(9), GetCursorYForTesting()); |
| |
| // Create a long press event in the selected region should start a drag. |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kStringPoint.x(), kStringPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| textfield_->OnGestureEvent(&long_press); |
| EXPECT_TRUE( |
| textfield_->CanStartDragForView(nullptr, kStringPoint, kStringPoint)); |
| } |
| |
| // No touch on desktop Mac. |
| #if !BUILDFLAG(IS_MAC) |
| TEST_F(TextfieldTest, ScrollToAdjustDisplayOffset) { |
| InitTextfield(); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| constexpr int kGlyphWidth = 10; |
| gfx::test::RenderTextTestApi(GetTextfieldTestApi().GetRenderText()) |
| .SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| constexpr gfx::Range kSelectionRange(2, 7); |
| textfield_->SetEditableSelectionRange(kSelectionRange); |
| |
| // Scroll starting at a vertical angle to adjust the display offset. |
| constexpr int kDisplayOffsetXAdjustment = -30; |
| const gfx::Point kScrollStart = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(5), GetCursorYForTesting()}); |
| const gfx::Point kScrollEnd = |
| kScrollStart + gfx::Vector2d(kDisplayOffsetXAdjustment, 60); |
| event_generator_->GestureScrollSequence(kScrollStart, kScrollEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| |
| // Display offset should have updated without the selection changing. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, kSelectionRange); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), |
| kDisplayOffsetXAdjustment); |
| } |
| |
| TEST_F(TextfieldTest, TwoFingerScroll) { |
| InitTextfield(); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| constexpr int kGlyphWidth = 10; |
| gfx::test::RenderTextTestApi(GetTextfieldTestApi().GetRenderText()) |
| .SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| constexpr gfx::Range kSelectionRange(2, 7); |
| textfield_->SetEditableSelectionRange(kSelectionRange); |
| |
| // Scroll with two fingers to adjust the display offset. |
| constexpr int kDisplayOffsetXAdjustment = -30; |
| const gfx::Point kStart1 = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(5), GetCursorYForTesting()}); |
| const gfx::Point kStart2 = kStart1 + gfx::Vector2d(20, 0); |
| const gfx::Point kStart[] = {kStart1, kStart2}; |
| event_generator_->GestureMultiFingerScroll( |
| /*count=*/2, kStart, |
| /*event_separation_time_ms=*/50, |
| /*steps=*/5, /*move_x=*/kDisplayOffsetXAdjustment, |
| /*move_y=*/0); |
| |
| // Display offset should have updated without the selection changing. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, kSelectionRange); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), |
| kDisplayOffsetXAdjustment); |
| } |
| |
| TEST_F(TextfieldTest, ScrollToPlaceCursor) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Scroll in a horizontal direction to move the cursor. |
| constexpr int kCursorStartPos = 2; |
| constexpr int kCursorEndPos = 15; |
| const gfx::Point kScrollStart = views::View::ConvertPointToScreen( |
| textfield_, |
| {GetCursorPositionX(kCursorStartPos), GetCursorYForTesting()}); |
| const gfx::Point kScrollEnd = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(kCursorEndPos), GetCursorYForTesting()}); |
| event_generator_->GestureScrollSequence(kScrollStart, kScrollEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(kCursorEndPos)); |
| } |
| |
| TEST_F(TextfieldTest, ScrollToPlaceCursorAdjustsDisplayOffset) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| gfx::test::RenderTextTestApi render_text_test_api( |
| GetTextfieldTestApi().GetRenderText()); |
| constexpr int kGlyphWidth = 10; |
| render_text_test_api.SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| |
| // Scroll in a horizontal direction to move the cursor. |
| const gfx::Point kScrollStart = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| const gfx::Point kScrollEnd = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(30), GetCursorYForTesting()}); |
| event_generator_->GestureScrollSequence(kScrollStart, kScrollEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| |
| // Cursor should have moved and display should be offset so that the cursor is |
| // visible in the textfield. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(30)); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -200); |
| } |
| |
| TEST_F(TextfieldTest, TwoFingerScrollUpdate) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| gfx::test::RenderTextTestApi render_text_test_api( |
| GetTextfieldTestApi().GetRenderText()); |
| constexpr int kGlyphWidth = 10; |
| render_text_test_api.SetGlyphWidth(kGlyphWidth); |
| constexpr int kCursorWidth = 1; |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10 + kCursorWidth, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"0123456789_123456789_123456789", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| textfield_->SetEditableSelectionRange(gfx::Range(2, 7)); |
| |
| // Perform a scroll which starts with one finger then adds another finger |
| // after a delay. |
| const gfx::Point kStart1 = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(8), GetCursorYForTesting()}); |
| const gfx::Point kStart2 = kStart1 + gfx::Vector2d(20, 0); |
| const gfx::Point kStart[] = {kStart1, kStart2}; |
| constexpr gfx::Vector2d kDelta[] = { |
| gfx::Vector2d(-50, 0), |
| gfx::Vector2d(-30, 0), |
| }; |
| constexpr int kDelayAddingFingerMs[] = {0, 40}; |
| constexpr int kDelayReleasingFingerMs[] = {150, 150}; |
| event_generator_->GestureMultiFingerScrollWithDelays( |
| /*count=*/2, kStart, kDelta, kDelayAddingFingerMs, |
| kDelayReleasingFingerMs, /*event_separation_time_ms=*/20, /*steps=*/5); |
| |
| // Since the scroll started with one finger, the cursor should have moved. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(6)); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| // Since a second finger was added, the display should also be slightly |
| // offset. |
| EXPECT_LT(GetTextfieldTestApi().GetDisplayOffsetX(), 0); |
| } |
| |
| // TODO(crbug.com/40276114): Rewrite these long press tests when EventGenerator |
| // can generate long press gestures. |
| TEST_F(TextfieldTest, LongPressSelection) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a long press. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| |
| // Check that the nearest word is selected, but that the touch selection |
| // controller is not activated yet. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 5)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello"); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Check that touch selection is activated after the long press is released. |
| ui::GestureEvent long_tap = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongTap)); |
| event_generator_->Dispatch(&long_tap); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, LongPressDragSelectionLTRForward) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a forwards long press and drag movement. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->PressTouch(kLongPressPoint); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| event_generator_->MoveTouchBy(25, 0); |
| event_generator_->ReleaseTouch(); |
| |
| // Check that text is selected between the word boundaries around the start |
| // and end of the drag movement. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 12)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello string"); |
| } |
| |
| TEST_F(TextfieldTest, LongPressDragSelectionLTRBackward) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a backwards long press and drag movement. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(9), GetCursorYForTesting()}); |
| event_generator_->PressTouch(kLongPressPoint); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| event_generator_->MoveTouchBy(-25, 0); |
| event_generator_->ReleaseTouch(); |
| |
| // Check that text is selected between the word boundaries around the start |
| // and end of the drag movement. The selection range is reversed since the |
| // left endpoint moved while the right endpoint was fixed. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(12, 0)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello string"); |
| } |
| |
| TEST_F(TextfieldTest, LongPressDragSelectionRTLForward) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"مرحبا بالعالم مرحبا"); |
| |
| // Perform a forwards long press and drag movement. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(9), GetCursorYForTesting()}); |
| event_generator_->PressTouch(kLongPressPoint); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| event_generator_->MoveTouchBy(-25, 0); |
| event_generator_->ReleaseTouch(); |
| |
| // Check that text is selected between the word boundaries around the start |
| // and end of the drag movement. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(6, 19)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"بالعالم مرحبا"); |
| } |
| |
| TEST_F(TextfieldTest, LongPressDragSelectionRTLBackward) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"مرحبا بالعالم مرحبا"); |
| |
| // Perform a backwards long press and drag movement. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(9), GetCursorYForTesting()}); |
| event_generator_->PressTouch(kLongPressPoint); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| event_generator_->MoveTouchBy(25, 0); |
| event_generator_->ReleaseTouch(); |
| |
| // Check that text is selected between the word boundaries around the start |
| // and end of the drag movement. The selection range is reversed since the |
| // right endpoint moved while the left endpoint was fixed. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(13, 0)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"مرحبا بالعالم"); |
| } |
| |
| TEST_F(TextfieldTest, DoubleTapSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a double tap. |
| const gfx::Point kTapPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kTapPoint); |
| event_generator_->GestureTapAt(kTapPoint); |
| |
| // Check that nearest word is selected and that touch selection has been |
| // activated. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 5)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello"); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, TripleTapSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a triple tap. |
| const gfx::Point kTapPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kTapPoint); |
| event_generator_->GestureTapAt(kTapPoint); |
| event_generator_->GestureTapAt(kTapPoint); |
| |
| // Check that all text is selected and that touch selection has been |
| // activated. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 18)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello string world"); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, DoublePressDragSelection) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| |
| // Perform a double press and drag movement. |
| const gfx::Point kDragStart = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| const gfx::Point kDragEnd = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(10), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kDragStart); |
| event_generator_->GestureScrollSequence(kDragStart, kDragEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| |
| // Check that text is selected between the word boundaries around the start |
| // and end of the drag movement. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 12)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"Hello string"); |
| } |
| |
| TEST_F(TextfieldTest, TouchSelectionDraggingMetrics) { |
| base::HistogramTester histogram_tester; |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"Text in a textfield"); |
| const gfx::Point kDragStart = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| const gfx::Point kDragEnd = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(10), GetCursorYForTesting()}); |
| |
| // Double press drag selection. |
| event_generator_->GestureTapAt(kDragStart); |
| event_generator_->GestureScrollSequence(kDragStart, kDragEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| histogram_tester.ExpectBucketCount( |
| ui::kTouchSelectionDragTypeHistogramName, |
| ui::TouchSelectionDragType::kDoublePressDrag, 1); |
| histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, |
| 1); |
| |
| // Swipe to move cursor. |
| event_generator_->GestureScrollSequence(kDragStart, kDragEnd, |
| /*duration=*/base::Milliseconds(50), |
| /*steps=*/5); |
| histogram_tester.ExpectBucketCount(ui::kTouchSelectionDragTypeHistogramName, |
| ui::TouchSelectionDragType::kCursorDrag, |
| 1); |
| histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, |
| 2); |
| |
| // Long press drag selection. |
| event_generator_->PressTouch(kDragStart); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kDragStart.x(), kDragStart.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| event_generator_->MoveTouchBy(25, 0); |
| event_generator_->ReleaseTouch(); |
| histogram_tester.ExpectBucketCount(ui::kTouchSelectionDragTypeHistogramName, |
| ui::TouchSelectionDragType::kLongPressDrag, |
| 1); |
| histogram_tester.ExpectTotalCount(ui::kTouchSelectionDragTypeHistogramName, |
| 3); |
| } |
| #endif // !BUILDFLAG(IS_MAC) |
| |
| TEST_F(TextfieldTest, GetTextfieldBaseline_FontFallbackTest) { |
| InitTextfield(); |
| textfield_->SetText(u"abc"); |
| const int old_baseline = textfield_->GetBaseline(); |
| |
| // Set text which may fall back to a font which has taller baseline than |
| // the default font. |
| textfield_->SetText(u"๑"); |
| const int new_baseline = textfield_->GetBaseline(); |
| |
| // Regardless of the text, the baseline must be the same. |
| EXPECT_EQ(new_baseline, old_baseline); |
| } |
| |
| // Tests that a textfield view can be destroyed from OnKeyEvent() on its |
| // controller and it does not crash. |
| TEST_F(TextfieldTest, DestroyingTextfieldFromOnKeyEvent) { |
| InitTextfield(); |
| |
| // The controller assumes ownership of the textfield. |
| TextfieldDestroyerController controller(textfield_); |
| EXPECT_TRUE(controller.target()); |
| |
| // These two members point to the textfield that is to be destroyed - |
| // therefore null them. |
| textfield_ = nullptr; |
| event_target_ = nullptr; |
| |
| // Send a key to trigger OnKeyEvent(). |
| SendKeyEvent(ui::VKEY_RETURN); |
| |
| EXPECT_FALSE(controller.target()); |
| } |
| |
| TEST_F(TextfieldTest, CursorBlinkRestartsOnInsertOrReplace) { |
| InitTextfield(); |
| textfield_->SetText(u"abc"); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorBlinkTimerRunning()); |
| textfield_->SetSelectedRange(gfx::Range(1, 2)); |
| EXPECT_FALSE(GetTextfieldTestApi().IsCursorBlinkTimerRunning()); |
| textfield_->InsertOrReplaceText(u"foo"); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorBlinkTimerRunning()); |
| } |
| |
| TEST_F(TextfieldTest, InitialAccessibilityProperties) { |
| InitTextfield(); |
| ui::AXNodeData data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.role, ax::mojom::Role::kTextField); |
| EXPECT_TRUE(textfield_->GetViewAccessibility().IsLeaf()); |
| EXPECT_TRUE(data.HasState(ax::mojom::State::kEditable)); |
| } |
| |
| // Verifies setting the accessible name will call NotifyAccessibilityEvent. |
| TEST_F(TextfieldTest, SetAccessibleNameNotifiesAccessibilityEvent) { |
| InitTextfield(); |
| std::u16string test_tooltip_text = u"Test Accessible Name"; |
| test::AXEventCounter counter(views::AXUpdateNotifier::Get()); |
| EXPECT_EQ(0, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| textfield_->GetViewAccessibility().SetName(test_tooltip_text); |
| EXPECT_EQ(1, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| EXPECT_EQ(test_tooltip_text, |
| textfield_->GetViewAccessibility().GetCachedName()); |
| ui::AXNodeData data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| const std::string& name = |
| data.GetStringAttribute(ax::mojom::StringAttribute::kName); |
| EXPECT_EQ(test_tooltip_text, base::ASCIIToUTF16(name)); |
| |
| // `NameFrom::kAttribute` is appropriate when the name is explicitly set to |
| // a developer-provided string (rather than a label, tooltip, or placeholder |
| // for which there are other `NameFrom` values). `NameFrom::kContents` is |
| // typically not an appropriate value. |
| EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kAttribute); |
| } |
| |
| // Changing the value of the textfield should trigger a kTextChanged event. |
| TEST_F(TextfieldTest, SetValueAccessibilityEvents) { |
| InitTextfield(); |
| std::u16string value = u"hello world"; |
| test::AXEventCounter counter(views::AXUpdateNotifier::Get()); |
| EXPECT_EQ(0, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| textfield_->GetViewAccessibility().SetValue(value); |
| EXPECT_EQ(1, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| EXPECT_EQ(value, textfield_->GetViewAccessibility().GetValue()); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| TEST_F(TextfieldTest, AccessibilityAttributes) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| |
| ViewAXPlatformNodeDelegate* delegate = |
| static_cast<ViewAXPlatformNodeDelegate*>( |
| &textfield_->GetViewAccessibility()); |
| |
| textfield_->GetViewAccessibility().EnsureAtomicViewAXTreeManager(); |
| textfield_->SetText(u"this is the textfield"); |
| textfield_->SetBounds(1, 2, 3, 4); |
| |
| ui::AXNodeData actual = |
| delegate->GetAtomicViewAXTreeManagerForTesting()->GetRoot()->data(); |
| |
| EXPECT_EQ(ax::mojom::Role::kTextField, actual.role); |
| EXPECT_TRUE(actual.HasState(ax::mojom::State::kEditable) && |
| actual.HasState(ax::mojom::State::kFocusable)); |
| EXPECT_EQ(textfield_->GetAccessibleName(), |
| actual.GetString16Attribute(ax::mojom::StringAttribute::kName)); |
| EXPECT_EQ(textfield_->GetText(), |
| actual.GetString16Attribute(ax::mojom::StringAttribute::kValue)); |
| EXPECT_EQ( |
| textfield_->GetPlaceholderText(), |
| actual.GetString16Attribute(ax::mojom::StringAttribute::kPlaceholder)); |
| |
| EXPECT_EQ(static_cast<const int>(textfield_->GetSelectedRange().start()), |
| actual.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart)); |
| EXPECT_EQ(static_cast<const int>(textfield_->GetSelectedRange().end()), |
| actual.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd)); |
| |
| EXPECT_EQ(gfx::Rect(1, 2, 3, 4), |
| gfx::ToEnclosingRect(actual.relative_bounds.bounds)); |
| |
| EXPECT_EQ( |
| textfield_->GetBoundsInScreen(), |
| delegate->GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, |
| ui::AXClippingBehavior::kUnclipped, nullptr)); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, AccessiblePlaceholderTest) { |
| InitTextfield(); |
| |
| ui::AXNodeData data; |
| textfield_->SetPlaceholderText(u"Some placeholder"); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kPlaceholder), |
| u"Some placeholder"); |
| |
| data = ui::AXNodeData(); |
| textfield_->SetPlaceholderText(u"Updated placeholder"); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kPlaceholder), |
| u"Updated placeholder"); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleNameFromLabel) { |
| InitTextfield(); |
| |
| const std::u16string label_text = u"Some label"; |
| View label; |
| label.GetViewAccessibility().SetRole(ax::mojom::Role::kStaticText); |
| label.GetViewAccessibility().SetName(label_text); |
| textfield_->GetViewAccessibility().SetName(label); |
| |
| // Use `ViewAccessibility::GetAccessibleNodeData` so that we can get the |
| // label's accessible id to compare with the textfield's labelled-by id. |
| ui::AXNodeData label_data; |
| label.GetViewAccessibility().GetAccessibleNodeData(&label_data); |
| |
| ui::AXNodeData textfield_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&textfield_data); |
| EXPECT_EQ( |
| textfield_data.GetString16Attribute(ax::mojom::StringAttribute::kName), |
| label_text); |
| EXPECT_EQ(textfield_->GetViewAccessibility().GetCachedName(), label_text); |
| EXPECT_EQ(textfield_data.GetNameFrom(), ax::mojom::NameFrom::kRelatedElement); |
| EXPECT_EQ(textfield_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kLabelledbyIds)[0], |
| label_data.id); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Check that when accessibility virtual keyboard is enabled, windows are |
| // shifted up when focused and restored when focus is lost. |
| TEST_F(TextfieldTest, VirtualKeyboardFocusEnsureCaretNotInRect) { |
| InitTextfield(); |
| |
| aura::Window* root_window = GetRootWindow(widget_.get()); |
| int keyboard_height = 200; |
| gfx::Rect root_bounds = root_window->bounds(); |
| gfx::Rect orig_widget_bounds = gfx::Rect(0, 300, 400, 200); |
| gfx::Rect shifted_widget_bounds = gfx::Rect(0, 200, 400, 200); |
| gfx::Rect keyboard_view_bounds = |
| gfx::Rect(0, root_bounds.height() - keyboard_height, root_bounds.width(), |
| keyboard_height); |
| |
| // Focus the window. |
| widget_->SetBounds(orig_widget_bounds); |
| input_method()->SetFocusedTextInputClient(textfield_); |
| EXPECT_EQ(widget_->GetNativeView()->bounds(), orig_widget_bounds); |
| |
| // Simulate virtual keyboard. |
| input_method()->SetVirtualKeyboardBounds(keyboard_view_bounds); |
| |
| // Window should be shifted. |
| EXPECT_EQ(widget_->GetNativeView()->bounds(), shifted_widget_bounds); |
| |
| // Detach the textfield from the IME |
| input_method()->DetachTextInputClient(textfield_); |
| wm::RestoreWindowBoundsOnClientFocusLost( |
| widget_->GetNativeView()->GetToplevelWindow()); |
| |
| // Window should be restored. |
| EXPECT_EQ(widget_->GetNativeView()->bounds(), orig_widget_bounds); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // No touch on desktop Mac. Tracked in http://crbug.com/445520. |
| #if !BUILDFLAG(IS_MAC) |
| TEST_F(TextfieldTest, TapActivatesTouchSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Tapping in the textfield should activate touch selection. |
| const gfx::Point kPointInTextfield = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kPointInTextfield); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, ClearingFocusDeactivatesTouchSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Tap textfield to activate touch selection. |
| const gfx::Point kPointInTextfield = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kPointInTextfield); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| |
| // Clearing focus should deactivate touch selection. |
| textfield_->GetFocusManager()->ClearFocus(); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, TapOnSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| |
| // Select a range and check that touch selection handles are not present and |
| // that the correct range is selected. |
| constexpr gfx::Range kSelectionRange(2, 7); |
| textfield_->SetEditableSelectionRange(kSelectionRange); |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| EXPECT_EQ(range, kSelectionRange); |
| |
| // Tap on the selection and check that touch selection handles are shown, but |
| // the selection range is not modified. |
| constexpr gfx::Range kTapRange(5, 5); |
| const gfx::Rect kTapRect = |
| GetCursorBounds(gfx::SelectionModel(kTapRange, gfx::CURSOR_FORWARD)); |
| const gfx::Point kTapPoint = |
| views::View::ConvertPointToScreen(textfield_, kTapRect.CenterPoint()); |
| event_generator_->GestureTapAt(kTapPoint); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| EXPECT_EQ(range, kSelectionRange); |
| |
| // Tap again on the selection and check that touch selection handles are still |
| // present and that the selection is changed to a cursor at the tap location. |
| // We advance the clock before tapping again to avoid the tap being treated as |
| // a double tap. |
| event_generator_->AdvanceClock(base::Milliseconds(1000)); |
| event_generator_->GestureTapAt(kTapPoint); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_TRUE(GetTextfieldTestApi().touch_selection_controller()); |
| EXPECT_EQ(range, kTapRange); |
| } |
| |
| // When touch drag drop is enabled, long pressing on selected text initiates |
| // drag-drop behaviour. So, long pressing on selected text should preserve the |
| // selection rather than selecting the nearest word and activating touch |
| // selection. |
| TEST_F(TextfieldTest, LongPressOnSelection) { |
| InitTextfield(); |
| textfield_->SetText(u"Hello string world"); |
| constexpr gfx::Range kSelectionRange(2, 7); |
| textfield_->SetEditableSelectionRange(kSelectionRange); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"llo s"); |
| |
| // Long press on the selected text. |
| const gfx::Point kLongPressPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(3), GetCursorYForTesting()}); |
| event_generator_->PressTouch(kLongPressPoint); |
| ui::GestureEvent long_press = CreateTestGestureEvent( |
| kLongPressPoint.x(), kLongPressPoint.y(), |
| ui::GestureEventDetails(ui::EventType::kGestureLongPress)); |
| event_generator_->Dispatch(&long_press); |
| |
| // Check that the selection has not changed and that touch selection is not |
| // activated. |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, kSelectionRange); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"llo s"); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| } |
| |
| TEST_F(TextfieldTest, TouchSelectionInUnfocusableTextfield) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| |
| // Disable textfield and tap on it. Touch text selection should not get |
| // activated. |
| textfield_->SetEnabled(false); |
| const gfx::Point kTapPoint = views::View::ConvertPointToScreen( |
| textfield_, {GetCursorPositionX(2), GetCursorYForTesting()}); |
| event_generator_->GestureTapAt(kTapPoint); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| textfield_->SetEnabled(true); |
| |
| // Make textfield unfocusable and tap on it. Touch text selection should not |
| // get activated. |
| textfield_->SetFocusBehavior(View::FocusBehavior::NEVER); |
| event_generator_->GestureTapAt(kTapPoint); |
| EXPECT_FALSE(textfield_->HasFocus()); |
| EXPECT_FALSE(GetTextfieldTestApi().touch_selection_controller()); |
| textfield_->SetFocusBehavior(View::FocusBehavior::ALWAYS); |
| } |
| #endif |
| |
| TEST_F(TextfieldTest, MoveCaret) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Range range; |
| |
| textfield_->MoveCaret(gfx::Point(GetCursorPositionX(3), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(3)); |
| |
| textfield_->MoveCaret(gfx::Point(GetCursorPositionX(0), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0)); |
| |
| textfield_->MoveCaret(gfx::Point(GetCursorPositionX(11), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(11)); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtent) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Range range; |
| |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(5), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 5)); |
| |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(0), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 0)); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentToTextEnd) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world a"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Range range; |
| |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(13), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 13)); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentByCharacter) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{}, |
| /*disabled_features=*/{::features::kTouchTextEditingRedesign}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Range range; |
| |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(4), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 4)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"ll"); |
| |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(8), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 8)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"llo wo"); |
| |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(1), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 1)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"e"); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentExpandByWord) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"some textfield text"); |
| const int cursor_y = GetCursorYForTesting(); |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| |
| // Expand the selection. The end of the selection should move to the nearest |
| // word boundary. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(11), cursor_y)); |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 14)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfield"); |
| |
| // Shrink then expand the selection again. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(8), cursor_y)); |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(18), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 19)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfield text"); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentShrinkByCharacter) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"some textfield text"); |
| const int cursor_y = GetCursorYForTesting(); |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(12), cursor_y)); |
| |
| // Shrink the selection. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(11), cursor_y)); |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 11)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfi"); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentOffset) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"some textfield text"); |
| const int cursor_y = GetCursorYForTesting(); |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| |
| // Expand the selection. The end of the selection should move to the nearest |
| // word boundary. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(11), cursor_y)); |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 14)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfield"); |
| |
| // Shrink the selection. The offset between the selection extent and the end |
| // of the selection should be preserved. |
| const int offset = GetCursorPositionX(14) - GetCursorPositionX(11); |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(12) - offset, cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 12)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfie"); |
| |
| // Move the extent past the end of the selection. The offset should be reset |
| // and the selection should expand. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(13), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 13)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"me textfiel"); |
| } |
| |
| TEST_F(TextfieldTest, MoveRangeSelectionExtentNonEmptySelection) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| /*enabled_features=*/{::features::kTouchTextEditingRedesign}, |
| /*disabled_features=*/{}); |
| |
| InitTextfield(); |
| textfield_->SetText(u"some textfield text"); |
| const int cursor_y = GetCursorYForTesting(); |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(2), cursor_y), |
| gfx::Point(GetCursorPositionX(12), cursor_y)); |
| |
| // Shrink the selection. Selection should not become empty. |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(3), cursor_y)); |
| gfx::Range range; |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 3)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"m"); |
| |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(2), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 3)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"m"); |
| |
| textfield_->MoveRangeSelectionExtent( |
| gfx::Point(GetCursorPositionX(1), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(2, 1)); |
| EXPECT_EQ(textfield_->GetSelectedText(), u"o"); |
| } |
| |
| TEST_F(TextfieldTest, SelectBetweenCoordinates) { |
| InitTextfield(); |
| textfield_->SetText(u"hello world"); |
| const int cursor_y = GetCursorYForTesting(); |
| gfx::Range range; |
| |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(1), cursor_y), |
| gfx::Point(GetCursorPositionX(2), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(1, 2)); |
| |
| textfield_->SelectBetweenCoordinates( |
| gfx::Point(GetCursorPositionX(0), cursor_y), |
| gfx::Point(GetCursorPositionX(11), cursor_y)); |
| textfield_->GetEditableSelectionRange(&range); |
| EXPECT_EQ(range, gfx::Range(0, 11)); |
| } |
| |
| TEST_F(TextfieldTest, AccessiblePasswordTest) { |
| InitTextfield(); |
| textfield_->SetText(u"password"); |
| |
| ui::AXNodeData node_data_regular; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data_regular); |
| EXPECT_EQ(ax::mojom::Role::kTextField, node_data_regular.role); |
| EXPECT_EQ(u"password", node_data_regular.GetString16Attribute( |
| ax::mojom::StringAttribute::kValue)); |
| EXPECT_FALSE(node_data_regular.HasState(ax::mojom::State::kProtected)); |
| |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| ui::AXNodeData node_data_protected; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData( |
| &node_data_protected); |
| EXPECT_EQ(ax::mojom::Role::kTextField, node_data_protected.role); |
| EXPECT_EQ(u"••••••••", node_data_protected.GetString16Attribute( |
| ax::mojom::StringAttribute::kValue)); |
| EXPECT_TRUE(node_data_protected.HasState(ax::mojom::State::kProtected)); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleRole) { |
| InitTextfield(); |
| |
| ui::AXNodeData data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.role, ax::mojom::Role::kTextField); |
| EXPECT_EQ(textfield_->GetViewAccessibility().GetCachedRole(), |
| ax::mojom::Role::kTextField); |
| |
| textfield_->GetViewAccessibility().SetRole(ax::mojom::Role::kSearchBox); |
| |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.role, ax::mojom::Role::kSearchBox); |
| EXPECT_EQ(textfield_->GetViewAccessibility().GetCachedRole(), |
| ax::mojom::Role::kSearchBox); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleReadOnly) { |
| InitTextfield(); |
| |
| textfield_->SetReadOnly(true); |
| |
| ui::AXNodeData data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kReadOnly); |
| |
| textfield_->SetReadOnly(false); |
| |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_NE(data.GetRestriction(), ax::mojom::Restriction::kReadOnly); |
| |
| // We should not override the disabled restriction with a readonly one. |
| textfield_->SetEnabled(false); |
| textfield_->SetReadOnly(true); |
| |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kDisabled); |
| |
| // If we re-enable the textfield, the readonly restriction should be applied. |
| textfield_->SetEnabled(true); |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kReadOnly); |
| |
| // If we start out with a disabled textfield and then set it to readonly and |
| // then enable it again, the readonly restriction should be applied. |
| textfield_->SetEnabled(false); |
| textfield_->SetReadOnly(false); |
| |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kDisabled); |
| |
| textfield_->SetReadOnly(true); |
| textfield_->SetEnabled(true); |
| |
| data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kReadOnly); |
| } |
| |
| // Verify that cursor visibility is controlled by SetCursorEnabled. |
| TEST_F(TextfieldTest, CursorVisibility) { |
| InitTextfield(); |
| |
| textfield_->SetCursorEnabled(false); |
| EXPECT_FALSE(GetTextfieldTestApi().IsCursorVisible()); |
| |
| textfield_->SetCursorEnabled(true); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorVisible()); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleValue) { |
| InitTextfield(); |
| textfield_->SetText(u"password"); |
| |
| ui::AXNodeData node_data_regular; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data_regular); |
| EXPECT_EQ(ax::mojom::Role::kTextField, node_data_regular.role); |
| EXPECT_EQ(u"password", node_data_regular.GetString16Attribute( |
| ax::mojom::StringAttribute::kValue)); |
| EXPECT_FALSE(node_data_regular.HasState(ax::mojom::State::kProtected)); |
| |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| ui::AXNodeData node_data_protected; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData( |
| &node_data_protected); |
| EXPECT_EQ(ax::mojom::Role::kTextField, node_data_protected.role); |
| EXPECT_EQ(u"••••••••", node_data_protected.GetString16Attribute( |
| ax::mojom::StringAttribute::kValue)); |
| EXPECT_TRUE(node_data_protected.HasState(ax::mojom::State::kProtected)); |
| |
| textfield_->SetText(u"password"); |
| node_data_protected = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData( |
| &node_data_protected); |
| EXPECT_EQ(u"••••••••", node_data_protected.GetString16Attribute( |
| ax::mojom::StringAttribute::kValue)); |
| } |
| |
| // Tests that Textfield::FitToLocalBounds() sets the RenderText's display rect |
| // to the view's bounds, taking the border into account. |
| TEST_F(TextfieldTest, FitToLocalBounds) { |
| const int kDisplayRectWidth = 100; |
| const int kBorderWidth = 5; |
| InitTextfield(); |
| textfield_->SetBounds(0, 0, kDisplayRectWidth, 100); |
| textfield_->SetBorder(views::CreateEmptyBorder(kBorderWidth)); |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect(gfx::Rect(20, 20)); |
| ASSERT_EQ(20, GetTextfieldTestApi().GetRenderText()->display_rect().width()); |
| textfield_->FitToLocalBounds(); |
| EXPECT_EQ(kDisplayRectWidth - 2 * kBorderWidth, |
| GetTextfieldTestApi().GetRenderText()->display_rect().width()); |
| } |
| |
| // Verify that cursor view height does not exceed the textfield height. |
| TEST_F(TextfieldTest, CursorViewHeight) { |
| InitTextfield(); |
| textfield_->SetBounds(0, 0, 100, 100); |
| textfield_->SetCursorEnabled(true); |
| SendKeyEvent('a'); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorVisible()); |
| EXPECT_GT(textfield_->GetVisibleBounds().height(), |
| GetTextfieldTestApi().GetCursorViewRect().height()); |
| EXPECT_LE(GetTextfieldTestApi().GetCursorViewRect().height(), |
| GetCursorBounds().height()); |
| |
| // set the cursor height to be higher than the textfield height, verify that |
| // UpdateCursorViewPosition update cursor view height correctly. |
| gfx::Rect cursor_bound(GetTextfieldTestApi().GetCursorViewRect()); |
| cursor_bound.set_height(150); |
| GetTextfieldTestApi().SetCursorViewRect(cursor_bound); |
| SendKeyEvent('b'); |
| EXPECT_GT(textfield_->GetVisibleBounds().height(), |
| GetTextfieldTestApi().GetCursorViewRect().height()); |
| EXPECT_LE(GetTextfieldTestApi().GetCursorViewRect().height(), |
| GetCursorBounds().height()); |
| } |
| |
| // Verify that cursor view height is independent of its parent view height. |
| TEST_F(TextfieldTest, CursorViewHeightAtDiffDSF) { |
| InitTextfield(); |
| textfield_->SetBounds(0, 0, 100, 100); |
| textfield_->SetCursorEnabled(true); |
| SendKeyEvent('a'); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorVisible()); |
| int height = GetTextfieldTestApi().GetCursorViewRect().height(); |
| |
| // update the size of its parent view size and verify that the height of the |
| // cursor view stays the same. |
| View* parent = textfield_->parent(); |
| parent->SetBounds(0, 0, 50, height - 2); |
| SendKeyEvent('b'); |
| EXPECT_EQ(height, GetTextfieldTestApi().GetCursorViewRect().height()); |
| } |
| |
| // Check if the text cursor is always at the end of the textfield after the |
| // text overflows from the textfield. If the textfield size changes, check if |
| // the text cursor's location is updated accordingly. |
| TEST_F(TextfieldTest, TextfieldBoundsChangeTest) { |
| InitTextfield(); |
| gfx::Size new_size = gfx::Size(30, 100); |
| textfield_->SetSize(new_size); |
| |
| // Insert chars in |textfield_| to make it overflow. |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| SendKeyEvent('a'); |
| |
| // Check if the cursor continues pointing to the end of the textfield. |
| int prev_x = GetCursorBounds().x(); |
| SendKeyEvent('a'); |
| EXPECT_EQ(prev_x, GetCursorBounds().x()); |
| EXPECT_TRUE(GetTextfieldTestApi().IsCursorVisible()); |
| |
| // Increase the textfield size and check if the cursor moves to the new end. |
| textfield_->SetSize(gfx::Size(40, 100)); |
| EXPECT_LT(prev_x, GetCursorBounds().x()); |
| |
| prev_x = GetCursorBounds().x(); |
| // Decrease the textfield size and check if the cursor moves to the new end. |
| textfield_->SetSize(gfx::Size(30, 100)); |
| EXPECT_GT(prev_x, GetCursorBounds().x()); |
| } |
| |
| // Verify that after creating a new Textfield, the Textfield doesn't |
| // automatically receive focus and the text cursor is not visible. |
| TEST_F(TextfieldTest, TextfieldInitialization) { |
| std::unique_ptr<Widget> widget = |
| CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET); |
| { |
| View* container = widget->SetContentsView(std::make_unique<View>()); |
| TestTextfield* new_textfield = |
| container->AddChildView(std::make_unique<TestTextfield>()); |
| new_textfield->SetBoundsRect(gfx::Rect(100, 100, 100, 100)); |
| new_textfield->SetID(1); |
| widget->Show(); |
| EXPECT_FALSE(new_textfield->HasFocus()); |
| EXPECT_FALSE(TextfieldTestApi(new_textfield).IsCursorVisible()); |
| new_textfield->RequestFocus(); |
| EXPECT_TRUE(TextfieldTestApi(new_textfield).IsCursorVisible()); |
| } |
| widget->Close(); |
| } |
| |
| // Verify that if a textfield gains focus during key dispatch that an edit |
| // command only results when the event is not consumed. |
| TEST_F(TextfieldTest, SwitchFocusInKeyDown) { |
| InitTextfield(); |
| TextfieldFocuser* focuser = widget_->GetContentsView()->AddChildView( |
| std::make_unique<TextfieldFocuser>(textfield_)); |
| |
| focuser->RequestFocus(); |
| EXPECT_EQ(focuser, GetFocusedView()); |
| SendKeyPress(ui::VKEY_SPACE, 0); |
| EXPECT_EQ(textfield_, GetFocusedView()); |
| EXPECT_EQ(std::u16string(), textfield_->GetText()); |
| |
| focuser->set_consume(false); |
| focuser->RequestFocus(); |
| EXPECT_EQ(focuser, GetFocusedView()); |
| SendKeyPress(ui::VKEY_SPACE, 0); |
| EXPECT_EQ(textfield_, GetFocusedView()); |
| EXPECT_EQ(u" ", textfield_->GetText()); |
| // Remove to ensure that the pointer in the focuser does not become dangling. |
| widget_->GetContentsView()->RemoveChildViewT(std::exchange(focuser, nullptr)); |
| } |
| |
| TEST_F(TextfieldTest, SendingDeletePreservesShiftFlag) { |
| InitTextfield(); |
| SendKeyPress(ui::VKEY_DELETE, 0); |
| EXPECT_EQ(0, textfield_->event_flags()); |
| textfield_->clear(); |
| |
| // Ensure the shift modifier propagates for keys that may be subject to native |
| // key mappings. E.g., on Mac, Delete and Shift+Delete are both |
| // deleteForward:, but the shift modifier should propagate. |
| SendKeyPress(ui::VKEY_DELETE, ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(ui::EF_SHIFT_DOWN, textfield_->event_flags()); |
| } |
| |
| TEST_F(TextfieldTest, EmojiItem_EmptyField) { |
| InitTextfield(); |
| EXPECT_TRUE(textfield_->context_menu_controller()); |
| |
| // A normal empty field may show the Emoji option (if supported). |
| ui::MenuModel* context_menu = GetContextMenuModel(); |
| EXPECT_TRUE(context_menu); |
| EXPECT_GT(context_menu->GetItemCount(), 0u); |
| // Not all OS/versions support the emoji menu. |
| EXPECT_EQ(ui::IsEmojiPanelSupported(), |
| context_menu->GetLabelAt(0) == |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); |
| } |
| |
| TEST_F(TextfieldTest, EmojiItem_ReadonlyField) { |
| InitTextfield(); |
| EXPECT_TRUE(textfield_->context_menu_controller()); |
| |
| textfield_->SetReadOnly(true); |
| // In no case is the emoji option showing on a read-only field. |
| ui::MenuModel* context_menu = GetContextMenuModel(); |
| EXPECT_TRUE(context_menu); |
| EXPECT_GT(context_menu->GetItemCount(), 0u); |
| EXPECT_NE(context_menu->GetLabelAt(0), |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); |
| } |
| |
| TEST_F(TextfieldTest, EmojiItem_FieldWithText) { |
| InitTextfield(); |
| EXPECT_TRUE(textfield_->context_menu_controller()); |
| |
| #if BUILDFLAG(IS_MAC) |
| // On Mac, when there is text, the "Look up" item (+ separator) takes the top |
| // position, and emoji comes after. |
| constexpr int kExpectedEmojiIndex = 2; |
| #else |
| constexpr int kExpectedEmojiIndex = 0; |
| #endif |
| |
| // A field with text may still show the Emoji option (if supported). |
| textfield_->SetText(u"some text"); |
| textfield_->SelectAll(false); |
| ui::MenuModel* context_menu = GetContextMenuModel(); |
| EXPECT_TRUE(context_menu); |
| EXPECT_GT(context_menu->GetItemCount(), 0u); |
| // Not all OS/versions support the emoji menu. |
| EXPECT_EQ(ui::IsEmojiPanelSupported(), |
| context_menu->GetLabelAt(kExpectedEmojiIndex) == |
| l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // Tests to see if the BiDi submenu items are updated correctly when the |
| // textfield's text direction is changed. |
| TEST_F(TextfieldTest, TextServicesContextMenuTextDirectionTest) { |
| InitTextfield(); |
| EXPECT_TRUE(textfield_->context_menu_controller()); |
| |
| EXPECT_TRUE(GetContextMenuModel()); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::LEFT_TO_RIGHT); |
| GetTextfieldTestApi().UpdateContextMenu(); |
| |
| EXPECT_FALSE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionDefault)); |
| EXPECT_TRUE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionLtr)); |
| EXPECT_FALSE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionRtl)); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| GetTextfieldTestApi().UpdateContextMenu(); |
| |
| EXPECT_FALSE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionDefault)); |
| EXPECT_FALSE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionLtr)); |
| EXPECT_TRUE(textfield_->IsCommandIdChecked( |
| ui::TextServicesContextMenu::kWritingDirectionRtl)); |
| } |
| |
| // Tests to see if the look up item is hidden for password fields. |
| TEST_F(TextfieldTest, LookUpPassword) { |
| InitTextfield(); |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| |
| const std::u16string kText = u"Willie Wagtail"; |
| |
| textfield_->SetText(kText); |
| textfield_->SelectAll(false); |
| |
| ui::MenuModel* context_menu = GetContextMenuModel(); |
| EXPECT_TRUE(context_menu); |
| EXPECT_GT(context_menu->GetItemCount(), 0u); |
| EXPECT_NE(context_menu->GetCommandIdAt(0), IDS_CONTENT_CONTEXT_LOOK_UP); |
| EXPECT_NE(context_menu->GetLabelAt(0), |
| l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kText)); |
| } |
| |
| TEST_F(TextfieldTest, SecurePasswordInput) { |
| InitTextfield(); |
| ASSERT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled()); |
| |
| // Shouldn't enable secure input if it's not a password textfield. |
| textfield_->OnFocus(); |
| EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled()); |
| |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| |
| // Single matched calls immediately update IsPasswordInputEnabled(). |
| textfield_->OnFocus(); |
| EXPECT_TRUE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled()); |
| |
| textfield_->OnBlur(); |
| EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled()); |
| } |
| #endif // BUILDFLAG(IS_MAC) |
| |
| TEST_F(TextfieldTest, AccessibilitySelectionEvents) { |
| const std::u16string kText = u"abcdef"; |
| InitTextfield(); |
| textfield_->SetText(kText); |
| EXPECT_TRUE(textfield_->HasFocus()); |
| |
| std::vector<ax::mojom::Event> event_type = { |
| ax::mojom::Event::kTextSelectionChanged}; |
| std::vector<ax::mojom::Event> previous_selection_events = |
| textfield_->GetAccessibilityEventsOfTypes(event_type); |
| |
| textfield_->SelectAll(false); |
| |
| std::vector<ax::mojom::Event> selection_events = |
| textfield_->GetAccessibilityEventsOfTypes(event_type); |
| EXPECT_LT(previous_selection_events.size(), selection_events.size()); |
| previous_selection_events = selection_events; |
| |
| // Validate that there's no selection event fired when the textfield blurred, |
| // even though the text lost selection. |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_FALSE(textfield_->HasFocus()); |
| textfield_->ClearSelection(); |
| EXPECT_FALSE(textfield_->HasSelection()); |
| |
| selection_events = textfield_->GetAccessibilityEventsOfTypes(event_type); |
| // Has not changed. |
| EXPECT_EQ(previous_selection_events.size(), selection_events.size()); |
| } |
| |
| TEST_F(TextfieldTest, AccessibilitySelectionEventsOnInitialFocus) { |
| // Initialize the textfield so we have text to select. |
| const std::u16string kText = u"abcdef"; |
| InitTextfield(); |
| textfield_->SetText(kText); |
| |
| // Ensure focus isn't on the textfield yet. |
| widget_->GetFocusManager()->ClearFocus(); |
| // Clear all the accessibility events we got so far. |
| textfield_->ClearAccessibilityEvents(); |
| |
| // Setting the focus should fire a focus event and a text selection event, in |
| // that order. |
| textfield_->RequestFocus(); |
| std::vector<ax::mojom::Event> events = |
| textfield_->GetAccessibilityEventsOfTypes( |
| {ax::mojom::Event::kFocus, ax::mojom::Event::kTextSelectionChanged}); |
| |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(ax::mojom::Event::kFocus, events[0]); |
| EXPECT_EQ(ax::mojom::Event::kTextSelectionChanged, events[1]); |
| } |
| |
| TEST_F(TextfieldTest, FocusReasonMouse) { |
| InitTextfield(); |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, |
| textfield_->GetFocusReason()); |
| |
| const auto& bounds = textfield_->bounds(); |
| MouseClick(bounds, 10); |
| |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_MOUSE, |
| textfield_->GetFocusReason()); |
| } |
| |
| TEST_F(TextfieldTest, FocusReasonTouchTap) { |
| InitTextfield(); |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, |
| textfield_->GetFocusReason()); |
| |
| TapAtCursor(ui::EventPointerType::kTouch); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_TOUCH, |
| textfield_->GetFocusReason()); |
| } |
| |
| TEST_F(TextfieldTest, FocusReasonPenTap) { |
| InitTextfield(); |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, |
| textfield_->GetFocusReason()); |
| |
| TapAtCursor(ui::EventPointerType::kPen); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN, |
| textfield_->GetFocusReason()); |
| } |
| |
| TEST_F(TextfieldTest, FocusReasonMultipleEvents) { |
| InitTextfield(); |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, |
| textfield_->GetFocusReason()); |
| |
| // Pen tap, followed by a touch tap. |
| TapAtCursor(ui::EventPointerType::kPen); |
| TapAtCursor(ui::EventPointerType::kTouch); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN, |
| textfield_->GetFocusReason()); |
| } |
| |
| TEST_F(TextfieldTest, FocusReasonFocusBlurFocus) { |
| InitTextfield(); |
| widget_->GetFocusManager()->ClearFocus(); |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, |
| textfield_->GetFocusReason()); |
| |
| // Pen tap, blur, then programmatic focus. |
| TapAtCursor(ui::EventPointerType::kPen); |
| widget_->GetFocusManager()->ClearFocus(); |
| textfield_->RequestFocus(); |
| |
| EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_OTHER, |
| textfield_->GetFocusReason()); |
| } |
| |
| TEST_F(TextfieldTest, KeyboardObserverForPenInput) { |
| InitTextfield(); |
| |
| TapAtCursor(ui::EventPointerType::kPen); |
| EXPECT_EQ(1, input_method()->count_show_virtual_keyboard()); |
| } |
| |
| TEST_F(TextfieldTest, ChangeTextDirectionAndLayoutAlignmentTest) { |
| InitTextfield(); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| EXPECT_EQ(textfield_->GetTextDirection(), |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| EXPECT_EQ(textfield_->GetHorizontalAlignment(), |
| gfx::HorizontalAlignment::ALIGN_RIGHT); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| base::i18n::TextDirection text_direction = |
| base::i18n::GetFirstStrongCharacterDirection( |
| GetTextfieldTestApi().GetRenderText()->GetDisplayText()); |
| EXPECT_EQ(textfield_->GetTextDirection(), text_direction); |
| EXPECT_EQ(textfield_->GetHorizontalAlignment(), |
| gfx::HorizontalAlignment::ALIGN_RIGHT); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::LEFT_TO_RIGHT); |
| EXPECT_EQ(textfield_->GetTextDirection(), |
| base::i18n::TextDirection::LEFT_TO_RIGHT); |
| EXPECT_EQ(textfield_->GetHorizontalAlignment(), |
| gfx::HorizontalAlignment::ALIGN_LEFT); |
| |
| // If the text is center-aligned, only the text direction should change. |
| textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| EXPECT_EQ(textfield_->GetTextDirection(), |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| EXPECT_EQ(textfield_->GetHorizontalAlignment(), |
| gfx::HorizontalAlignment::ALIGN_CENTER); |
| |
| // If the text is aligned to the text direction, its alignment should change |
| // iff the text direction changes. We test both scenarios. |
| auto dir = base::i18n::TextDirection::RIGHT_TO_LEFT; |
| auto opposite_dir = base::i18n::TextDirection::LEFT_TO_RIGHT; |
| EXPECT_EQ(textfield_->GetTextDirection(), dir); |
| textfield_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); |
| textfield_->ChangeTextDirectionAndLayoutAlignment(opposite_dir); |
| EXPECT_EQ(textfield_->GetTextDirection(), opposite_dir); |
| EXPECT_NE(textfield_->GetHorizontalAlignment(), gfx::ALIGN_TO_HEAD); |
| |
| dir = base::i18n::TextDirection::LEFT_TO_RIGHT; |
| EXPECT_EQ(textfield_->GetTextDirection(), dir); |
| textfield_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); |
| textfield_->ChangeTextDirectionAndLayoutAlignment(dir); |
| EXPECT_EQ(textfield_->GetTextDirection(), dir); |
| EXPECT_EQ(textfield_->GetHorizontalAlignment(), gfx::ALIGN_TO_HEAD); |
| } |
| |
| TEST_F(TextfieldTest, AccessibilityTextDirection) { |
| InitTextfield(); |
| ui::AXNodeData node_data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextDirection), |
| static_cast<int32_t>(ax::mojom::WritingDirection::kLtr)); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::RIGHT_TO_LEFT); |
| node_data = ui::AXNodeData(); // Reset the node data. |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextDirection), |
| static_cast<int32_t>(ax::mojom::WritingDirection::kRtl)); |
| |
| textfield_->ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection::LEFT_TO_RIGHT); |
| node_data = ui::AXNodeData(); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextDirection), |
| static_cast<int32_t>(ax::mojom::WritingDirection::kLtr)); |
| } |
| |
| TEST_F(TextfieldTest, TextChangedCallbackTest) { |
| InitTextfield(); |
| |
| bool text_changed = false; |
| auto subscription = textfield_->AddTextChangedCallback(base::BindRepeating( |
| [](bool* text_changed) { *text_changed = true; }, &text_changed)); |
| |
| textfield_->SetText(u"abc"); |
| EXPECT_TRUE(text_changed); |
| |
| text_changed = false; |
| textfield_->AppendText(u"def"); |
| EXPECT_TRUE(text_changed); |
| |
| // Undo should still cause callback. |
| text_changed = false; |
| SendKeyEvent(ui::VKEY_Z, false, true); |
| EXPECT_TRUE(text_changed); |
| |
| text_changed = false; |
| SendKeyEvent(ui::VKEY_BACK); |
| EXPECT_TRUE(text_changed); |
| } |
| |
| // Tests that invalid characters like non-displayable characters are filtered |
| // out when inserted into the text field. |
| TEST_F(TextfieldTest, InsertInvalidCharsTest) { |
| InitTextfield(); |
| |
| textfield_->InsertText( |
| u"\babc\ndef\t", |
| ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); |
| |
| EXPECT_EQ(textfield_->GetText(), u"abcdef"); |
| } |
| |
| TEST_F(TextfieldTest, ScrollCommands) { |
| InitTextfield(); |
| |
| // Scroll commands are only available on Mac. |
| #if BUILDFLAG(IS_MAC) |
| textfield_->SetText(u"12 34567 89"); |
| textfield_->SetEditableSelectionRange(gfx::Range(6)); |
| |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_PAGE_UP)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_PAGE_DOWN)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT)); |
| EXPECT_TRUE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT)); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::SCROLL_PAGE_UP); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 0u); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::SCROLL_PAGE_DOWN); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 11u); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 0u); |
| |
| GetTextfieldTestApi().ExecuteTextEditCommand( |
| ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT); |
| EXPECT_EQ(textfield_->GetCursorPosition(), 11u); |
| #else |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_PAGE_UP)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_PAGE_DOWN)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT)); |
| EXPECT_FALSE(textfield_->IsTextEditCommandEnabled( |
| ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT)); |
| #endif |
| } |
| |
| TEST_F(TextfieldTest, AccessibleTextDirectionRTL) { |
| InitTextfield(); |
| textfield_->SetText(u"abc"); |
| |
| ui::AXNodeData node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextDirection), |
| static_cast<int32_t>(ax::mojom::WritingDirection::kLtr)); |
| |
| textfield_->SetText(u"اللغة العربيي"); |
| |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kTextDirection), |
| static_cast<int32_t>(ax::mojom::WritingDirection::kRtl)); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleDefaultActionVerb) { |
| InitTextfield(); |
| ui::AXNodeData data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetDefaultActionVerb(), |
| ax::mojom::DefaultActionVerb::kActivate); |
| |
| data = ui::AXNodeData(); |
| textfield_->SetEnabled(false); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_FALSE( |
| data.HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb)); |
| |
| data = ui::AXNodeData(); |
| textfield_->SetEnabled(true); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&data); |
| EXPECT_EQ(data.GetDefaultActionVerb(), |
| ax::mojom::DefaultActionVerb::kActivate); |
| } |
| |
| #if BUILDFLAG(SUPPORTS_AX_TEXT_OFFSETS) |
| TEST_F(TextfieldTest, WordOffsets) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| MockAXModeAdded(); |
| ASSERT_TRUE(textfield_->GetViewAccessibility().is_initialized()); |
| |
| ui::AXNodeData node_data; |
| textfield_->SetText(u"abc 12 34 def hij :' $*() "); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| std::vector<int32_t> expected_starts = {0, 4, 7, 10, 14}; |
| std::vector<int32_t> expected_ends = {3, 6, 9, 13, 17}; |
| EXPECT_EQ( |
| node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts), |
| expected_starts); |
| EXPECT_EQ( |
| node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds), |
| expected_ends); |
| } |
| |
| TEST_F(TextfieldTest, WordOffsetsAXNotOn) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| ASSERT_FALSE(textfield_->GetViewAccessibility().is_initialized()); |
| |
| ui::AXNodeData node_data; |
| textfield_->SetText(u"abc 12 34 def hij :' $*() "); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| std::vector<int32_t> expected_starts = {}; |
| std::vector<int32_t> expected_ends = {}; |
| EXPECT_EQ( |
| node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts), |
| expected_starts); |
| EXPECT_EQ( |
| node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds), |
| expected_ends); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleGraphemeOffsets) { |
| struct TestCase { |
| std::u16string text; |
| std::vector<int32_t> expected_offsets; |
| }; |
| |
| const auto kTestCases = std::to_array<TestCase>({ |
| {std::u16string(), {}}, |
| // LTR. |
| {u"asdfghkl:/", {0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100}}, |
| // RTL: should render left-to-right as "<space>43210 \n cba9876". |
| // Note this used to say "Arabic language", in Arabic, but the last |
| // character in the string (\u0629) got fancy in an updated Mac font, so |
| // now the penultimate character repeats. |
| // |
| // TODO(accessibility): This is not the correct order of grapheme offsets. |
| // Blink returns the offsets from the right boundary when in RTL and so |
| // should we for Views. |
| {u"اللغة العربيي", |
| {120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0, 10}}, |
| // LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR abc, and LTR |
| // कि. |
| {u"\u0915\u093fabc\u0915\u093f", {0, 20, 30, 40, 50, 70}}, |
| // LTR ab, LTR कि (DEVANAGARI KA with VOWEL I) (2-char grapheme), LTR cd. |
| {u"ab\u0915\u093fcd", {0, 10, 20, 40, 50, 60}}, |
| // LTR ab, 𝄞 'MUSICAL SYMBOL G CLEF' U+1D11E (surrogate pair), LTR cd. |
| // Windows requires wide strings for \Unnnnnnnn universal character names. |
| {u"ab\U0001D11Ecd", {0, 10, 20, 30, 40, 50}}, |
| }); |
| |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| MockAXModeAdded(); |
| ASSERT_TRUE(textfield_->GetViewAccessibility().is_initialized()); |
| |
| // Set the glyph width to a fixed value to avoid flakiness and dependency on |
| // each platform's default font size. |
| constexpr int kGlyphWidth = 10; |
| gfx::test::RenderTextTestApi(GetTextfieldTestApi().GetRenderText()) |
| .SetGlyphWidth(kGlyphWidth); |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(0, 0, 20 * kGlyphWidth, 100)); |
| |
| for (size_t i = 0; i < kTestCases.size(); i++) { |
| SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i)); |
| textfield_->SetText(kTestCases[i].text); |
| |
| ui::AXNodeData node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_EQ(node_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets), |
| kTestCases[i].expected_offsets); |
| } |
| } |
| |
| TEST_F(TextfieldTest, AccessibleGraphemeOffsetsObscured) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| MockAXModeAdded(); |
| ASSERT_TRUE(textfield_->GetViewAccessibility().is_initialized()); |
| textfield_->SetText(u"abcdef"); |
| |
| ASSERT_FALSE(GetTextfieldTestApi().GetRenderText()->obscured()); |
| |
| ui::AXNodeData node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| std::vector<int32_t> non_obscured_offsets = node_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets); |
| |
| textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| EXPECT_NE(node_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets), |
| non_obscured_offsets); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleGraphemeOffsetsElidedTail) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| MockAXModeAdded(); |
| ASSERT_TRUE(textfield_->GetViewAccessibility().is_initialized()); |
| |
| constexpr int kGlyphWidth = 10; |
| |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(0, 0, 5 * kGlyphWidth, 100)); |
| GetTextfieldTestApi().GetRenderText()->SetElideBehavior(gfx::ELIDE_TAIL); |
| gfx::test::RenderTextTestApi(GetTextfieldTestApi().GetRenderText()) |
| .SetGlyphWidth(kGlyphWidth); |
| |
| textfield_->SetText(u"abcdef"); |
| |
| ui::AXNodeData node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| std::vector<int32_t> expected_offsets = {0, 10, 20, 30, 40, 40, 40}; |
| EXPECT_EQ(node_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets), |
| expected_offsets); |
| } |
| |
| TEST_F(TextfieldTest, AccessibleGraphemeOffsetsIndependentOfDisplayOffset) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(::features::kUiaProvider); |
| InitTextfield(); |
| const ::ui::ScopedAXModeSetter ax_mode_setter(ui::AXMode::kNativeAPIs); |
| MockAXModeAdded(); |
| ASSERT_TRUE(textfield_->GetViewAccessibility().is_initialized()); |
| |
| // Size the textfield wide enough to hold 10 characters. |
| gfx::test::RenderTextTestApi render_text_test_api( |
| GetTextfieldTestApi().GetRenderText()); |
| constexpr int kGlyphWidth = 10; |
| render_text_test_api.SetGlyphWidth(kGlyphWidth); |
| GetTextfieldTestApi().GetRenderText()->SetDisplayRect( |
| gfx::Rect(kGlyphWidth * 10, 20)); |
| textfield_->SetTextWithoutCaretBoundsChangeNotification( |
| u"3.141592653589793238462", 0); |
| GetTextfieldTestApi().SetDisplayOffsetX(0); |
| |
| ui::AXNodeData node_data; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data); |
| std::vector<int32_t> expected_offsets = { |
| 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, |
| 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230}; |
| EXPECT_EQ(node_data.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets), |
| expected_offsets); |
| GetTextfieldTestApi().SetDisplayOffsetX(-100); |
| EXPECT_EQ(GetTextfieldTestApi().GetDisplayOffsetX(), -100); |
| |
| ui::AXNodeData node_data_2; |
| textfield_->GetViewAccessibility().GetAccessibleNodeData(&node_data_2); |
| // The offsets should be the same. |
| EXPECT_EQ(node_data_2.GetIntListAttribute( |
| ax::mojom::IntListAttribute::kCharacterOffsets), |
| expected_offsets); |
| } |
| #endif // BUILDFLAG(SUPPORTS_AX_TEXT_OFFSETS) |
| |
| TEST_F(TextfieldTest, DragOutsideSelectionModifiesSelection) { |
| allow_drag_event_ = false; |
| |
| InitTextfield(); |
| textfield_->SetText(u"Hello World"); |
| textfield_->SetSelectedRange(gfx::Range(0, 5)); // Selects "Hello" |
| EXPECT_EQ(u"Hello", textfield_->GetSelectedText()); |
| |
| // Simulate a mouse click and drag starting outside the current selection. |
| gfx::Point start_drag = |
| GetTextfieldTestApi() |
| .GetRenderText() |
| ->GetCursorBounds(gfx::SelectionModel(6, gfx::CURSOR_FORWARD), true) |
| .origin(); |
| gfx::Point end_drag = |
| GetTextfieldTestApi() |
| .GetRenderText() |
| ->GetCursorBounds(gfx::SelectionModel(11, gfx::CURSOR_FORWARD), true) |
| .origin(); |
| |
| ui::MouseEvent press_event(ui::EventType::kMousePressed, start_drag, |
| start_drag, ui::EventTimeForNow(), |
| ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| textfield_->OnMousePressed(press_event); |
| |
| ui::MouseEvent drag_event(ui::EventType::kMouseDragged, end_drag, end_drag, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0); |
| textfield_->OnMouseDragged(drag_event); |
| |
| EXPECT_EQ(u"World", textfield_->GetSelectedText()); |
| } |
| |
| } // namespace views::test |