| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/ime/input_method_chromeos.h" |
| |
| #include <X11/Xlib.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #undef Bool |
| #undef FocusIn |
| #undef FocusOut |
| #undef None |
| |
| #include <cstring> |
| |
| #include "base/i18n/char_iterator.h" |
| #include "base/macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/ime/chromeos/mock_ime_candidate_window_handler.h" |
| #include "ui/base/ime/chromeos/mock_ime_engine_handler.h" |
| #include "ui/base/ime/composition_text.h" |
| #include "ui/base/ime/dummy_text_input_client.h" |
| #include "ui/base/ime/ime_bridge.h" |
| #include "ui/base/ime/ime_engine_handler_interface.h" |
| #include "ui/base/ime/input_method_delegate.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/test/events_test_utils_x11.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| using base::UTF8ToUTF16; |
| using base::UTF16ToUTF8; |
| |
| namespace ui { |
| namespace { |
| |
| const base::string16 kSampleText = base::UTF8ToUTF16( |
| "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"); |
| |
| typedef IMEEngineHandlerInterface::KeyEventDoneCallback KeyEventCallback; |
| |
| uint32_t GetOffsetInUTF16(const base::string16& utf16_string, |
| uint32_t utf8_offset) { |
| DCHECK_LT(utf8_offset, utf16_string.size()); |
| base::i18n::UTF16CharIterator char_iterator(&utf16_string); |
| for (size_t i = 0; i < utf8_offset; ++i) |
| char_iterator.Advance(); |
| return char_iterator.array_pos(); |
| } |
| |
| enum KeyEventHandlerBehavior { |
| KEYEVENT_CONSUME, |
| KEYEVENT_NOT_CONSUME, |
| }; |
| |
| } // namespace |
| |
| |
| class TestableInputMethodChromeOS : public InputMethodChromeOS { |
| public: |
| explicit TestableInputMethodChromeOS(internal::InputMethodDelegate* delegate) |
| : InputMethodChromeOS(delegate), |
| process_key_event_post_ime_call_count_(0) { |
| } |
| |
| struct ProcessKeyEventPostIMEArgs { |
| ProcessKeyEventPostIMEArgs() : event(NULL), handled(false) {} |
| const ui::KeyEvent* event; |
| bool handled; |
| }; |
| |
| // Overridden from InputMethodChromeOS: |
| ui::EventDispatchDetails ProcessKeyEventPostIME(ui::KeyEvent* key_event, |
| bool handled) override { |
| ui::EventDispatchDetails details = |
| InputMethodChromeOS::ProcessKeyEventPostIME(key_event, handled); |
| process_key_event_post_ime_args_.event = key_event; |
| process_key_event_post_ime_args_.handled = handled; |
| ++process_key_event_post_ime_call_count_; |
| return details; |
| } |
| |
| void ResetCallCount() { |
| process_key_event_post_ime_call_count_ = 0; |
| } |
| |
| const ProcessKeyEventPostIMEArgs& process_key_event_post_ime_args() const { |
| return process_key_event_post_ime_args_; |
| } |
| |
| int process_key_event_post_ime_call_count() const { |
| return process_key_event_post_ime_call_count_; |
| } |
| |
| // Change access rights for testing. |
| using InputMethodChromeOS::ExtractCompositionText; |
| using InputMethodChromeOS::ResetContext; |
| |
| private: |
| ProcessKeyEventPostIMEArgs process_key_event_post_ime_args_; |
| int process_key_event_post_ime_call_count_; |
| }; |
| |
| class SynchronousKeyEventHandler { |
| public: |
| SynchronousKeyEventHandler(uint32_t expected_keyval, |
| uint32_t expected_keycode, |
| uint32_t expected_state, |
| KeyEventHandlerBehavior behavior) |
| : expected_keyval_(expected_keyval), |
| expected_keycode_(expected_keycode), |
| expected_state_(expected_state), |
| behavior_(behavior) {} |
| |
| virtual ~SynchronousKeyEventHandler() {} |
| |
| void Run(uint32_t keyval, |
| uint32_t keycode, |
| uint32_t state, |
| const KeyEventCallback& callback) { |
| EXPECT_EQ(expected_keyval_, keyval); |
| EXPECT_EQ(expected_keycode_, keycode); |
| EXPECT_EQ(expected_state_, state); |
| callback.Run(behavior_ == KEYEVENT_CONSUME); |
| } |
| |
| private: |
| const uint32_t expected_keyval_; |
| const uint32_t expected_keycode_; |
| const uint32_t expected_state_; |
| const KeyEventHandlerBehavior behavior_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SynchronousKeyEventHandler); |
| }; |
| |
| class AsynchronousKeyEventHandler { |
| public: |
| AsynchronousKeyEventHandler(uint32_t expected_keyval, |
| uint32_t expected_keycode, |
| uint32_t expected_state) |
| : expected_keyval_(expected_keyval), |
| expected_keycode_(expected_keycode), |
| expected_state_(expected_state) {} |
| |
| virtual ~AsynchronousKeyEventHandler() {} |
| |
| void Run(uint32_t keyval, |
| uint32_t keycode, |
| uint32_t state, |
| const KeyEventCallback& callback) { |
| EXPECT_EQ(expected_keyval_, keyval); |
| EXPECT_EQ(expected_keycode_, keycode); |
| EXPECT_EQ(expected_state_, state); |
| callback_ = callback; |
| } |
| |
| void RunCallback(KeyEventHandlerBehavior behavior) { |
| callback_.Run(behavior == KEYEVENT_CONSUME); |
| } |
| |
| private: |
| const uint32_t expected_keyval_; |
| const uint32_t expected_keycode_; |
| const uint32_t expected_state_; |
| KeyEventCallback callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AsynchronousKeyEventHandler); |
| }; |
| |
| class SetSurroundingTextVerifier { |
| public: |
| SetSurroundingTextVerifier(const std::string& expected_surrounding_text, |
| uint32_t expected_cursor_position, |
| uint32_t expected_anchor_position) |
| : expected_surrounding_text_(expected_surrounding_text), |
| expected_cursor_position_(expected_cursor_position), |
| expected_anchor_position_(expected_anchor_position) {} |
| |
| void Verify(const std::string& text, |
| uint32_t cursor_pos, |
| uint32_t anchor_pos) { |
| EXPECT_EQ(expected_surrounding_text_, text); |
| EXPECT_EQ(expected_cursor_position_, cursor_pos); |
| EXPECT_EQ(expected_anchor_position_, anchor_pos); |
| } |
| |
| private: |
| const std::string expected_surrounding_text_; |
| const uint32_t expected_cursor_position_; |
| const uint32_t expected_anchor_position_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SetSurroundingTextVerifier); |
| }; |
| |
| class InputMethodChromeOSTest : public internal::InputMethodDelegate, |
| public testing::Test, |
| public DummyTextInputClient { |
| public: |
| InputMethodChromeOSTest() |
| : dispatched_key_event_(ui::ET_UNKNOWN, ui::VKEY_UNKNOWN, ui::EF_NONE), |
| stop_propagation_post_ime_(false) { |
| ResetFlags(); |
| } |
| |
| ~InputMethodChromeOSTest() override {} |
| |
| void SetUp() override { |
| IMEBridge::Initialize(); |
| |
| mock_ime_engine_handler_.reset( |
| new chromeos::MockIMEEngineHandler()); |
| IMEBridge::Get()->SetCurrentEngineHandler( |
| mock_ime_engine_handler_.get()); |
| |
| mock_ime_candidate_window_handler_.reset( |
| new chromeos::MockIMECandidateWindowHandler()); |
| IMEBridge::Get()->SetCandidateWindowHandler( |
| mock_ime_candidate_window_handler_.get()); |
| |
| ime_.reset(new TestableInputMethodChromeOS(this)); |
| ime_->SetFocusedTextInputClient(this); |
| } |
| |
| void TearDown() override { |
| if (ime_.get()) |
| ime_->SetFocusedTextInputClient(NULL); |
| ime_.reset(); |
| IMEBridge::Get()->SetCurrentEngineHandler(NULL); |
| IMEBridge::Get()->SetCandidateWindowHandler(NULL); |
| mock_ime_engine_handler_.reset(); |
| mock_ime_candidate_window_handler_.reset(); |
| IMEBridge::Shutdown(); |
| |
| ResetFlags(); |
| } |
| |
| // Overridden from ui::internal::InputMethodDelegate: |
| ui::EventDispatchDetails DispatchKeyEventPostIME( |
| ui::KeyEvent* event) override { |
| dispatched_key_event_ = *event; |
| if (stop_propagation_post_ime_) |
| event->StopPropagation(); |
| return ui::EventDispatchDetails(); |
| } |
| |
| // Overridden from ui::TextInputClient: |
| void SetCompositionText(const CompositionText& composition) override { |
| composition_text_ = composition; |
| } |
| void ConfirmCompositionText() override { |
| confirmed_text_ = composition_text_; |
| composition_text_.Clear(); |
| } |
| void ClearCompositionText() override { composition_text_.Clear(); } |
| void InsertText(const base::string16& text) override { |
| inserted_text_ = text; |
| } |
| void InsertChar(const KeyEvent& event) override { |
| inserted_char_ = event.GetCharacter(); |
| inserted_char_flags_ = event.flags(); |
| } |
| TextInputType GetTextInputType() const override { return input_type_; } |
| TextInputMode GetTextInputMode() const override { return input_mode_; } |
| bool CanComposeInline() const override { return can_compose_inline_; } |
| gfx::Rect GetCaretBounds() const override { return caret_bounds_; } |
| bool HasCompositionText() const override { |
| CompositionText empty; |
| return composition_text_ != empty; |
| } |
| bool GetTextRange(gfx::Range* range) const override { |
| *range = text_range_; |
| return true; |
| } |
| bool GetSelectionRange(gfx::Range* range) const override { |
| *range = selection_range_; |
| return true; |
| } |
| bool GetTextFromRange(const gfx::Range& range, |
| base::string16* text) const override { |
| *text = surrounding_text_.substr(range.GetMin(), range.length()); |
| return true; |
| } |
| void OnInputMethodChanged() override { |
| ++on_input_method_changed_call_count_; |
| } |
| |
| bool HasNativeEvent() const { |
| return dispatched_key_event_.HasNativeEvent(); |
| } |
| |
| void ResetFlags() { |
| dispatched_key_event_ = ui::KeyEvent(ui::ET_UNKNOWN, ui::VKEY_UNKNOWN, |
| ui::EF_NONE); |
| |
| composition_text_.Clear(); |
| confirmed_text_.Clear(); |
| inserted_text_.clear(); |
| inserted_char_ = 0; |
| inserted_char_flags_ = 0; |
| on_input_method_changed_call_count_ = 0; |
| |
| input_type_ = TEXT_INPUT_TYPE_NONE; |
| input_mode_ = TEXT_INPUT_MODE_DEFAULT; |
| can_compose_inline_ = true; |
| caret_bounds_ = gfx::Rect(); |
| } |
| |
| std::unique_ptr<TestableInputMethodChromeOS> ime_; |
| |
| // Copy of the dispatched key event. |
| ui::KeyEvent dispatched_key_event_; |
| |
| // Variables for remembering the parameters that are passed to |
| // ui::TextInputClient functions. |
| CompositionText composition_text_; |
| CompositionText confirmed_text_; |
| base::string16 inserted_text_; |
| base::char16 inserted_char_; |
| unsigned int on_input_method_changed_call_count_; |
| int inserted_char_flags_; |
| |
| // Variables that will be returned from the ui::TextInputClient functions. |
| TextInputType input_type_; |
| TextInputMode input_mode_; |
| bool can_compose_inline_; |
| gfx::Rect caret_bounds_; |
| gfx::Range text_range_; |
| gfx::Range selection_range_; |
| base::string16 surrounding_text_; |
| |
| std::unique_ptr<chromeos::MockIMEEngineHandler> mock_ime_engine_handler_; |
| std::unique_ptr<chromeos::MockIMECandidateWindowHandler> |
| mock_ime_candidate_window_handler_; |
| |
| bool stop_propagation_post_ime_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InputMethodChromeOSTest); |
| }; |
| |
| // Tests public APIs in ui::InputMethod first. |
| |
| TEST_F(InputMethodChromeOSTest, GetInputTextType) { |
| EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType()); |
| input_type_ = TEXT_INPUT_TYPE_PASSWORD; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType()); |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(TEXT_INPUT_TYPE_TEXT, ime_->GetTextInputType()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, CanComposeInline) { |
| EXPECT_TRUE(ime_->CanComposeInline()); |
| can_compose_inline_ = false; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_FALSE(ime_->CanComposeInline()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, GetTextInputClient) { |
| EXPECT_EQ(this, ime_->GetTextInputClient()); |
| ime_->SetFocusedTextInputClient(NULL); |
| EXPECT_EQ(NULL, ime_->GetTextInputClient()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, GetInputTextType_WithoutFocusedClient) { |
| EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType()); |
| ime_->SetFocusedTextInputClient(NULL); |
| input_type_ = TEXT_INPUT_TYPE_PASSWORD; |
| ime_->OnTextInputTypeChanged(this); |
| // The OnTextInputTypeChanged() call above should be ignored since |this| is |
| // not the current focused client. |
| EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType()); |
| |
| ime_->SetFocusedTextInputClient(this); |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType()); |
| } |
| |
| // Confirm that IBusClient::FocusIn is called on "connected" if input_type_ is |
| // TEXT. |
| TEST_F(InputMethodChromeOSTest, FocusIn_Text) { |
| // A context shouldn't be created since the daemon is not running. |
| EXPECT_EQ(0U, on_input_method_changed_call_count_); |
| // Click a text input form. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| // Since a form has focus, IBusClient::FocusIn() should be called. |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ( |
| 1, |
| mock_ime_candidate_window_handler_->set_cursor_bounds_call_count()); |
| // ui::TextInputClient::OnInputMethodChanged() should be called when |
| // ui::InputMethodChromeOS connects/disconnects to/from ibus-daemon and the |
| // current text input type is not NONE. |
| EXPECT_EQ(1U, on_input_method_changed_call_count_); |
| } |
| |
| // Confirm that InputMethodEngine::FocusIn is called on "connected" even if |
| // input_type_ is PASSWORD. |
| TEST_F(InputMethodChromeOSTest, FocusIn_Password) { |
| EXPECT_EQ(0U, on_input_method_changed_call_count_); |
| input_type_ = TEXT_INPUT_TYPE_PASSWORD; |
| ime_->OnTextInputTypeChanged(this); |
| // InputMethodEngine::FocusIn() should be called even for password field. |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(1U, on_input_method_changed_call_count_); |
| } |
| |
| // Confirm that IBusClient::FocusOut is called as expected. |
| TEST_F(InputMethodChromeOSTest, FocusOut_None) { |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(0, mock_ime_engine_handler_->focus_out_call_count()); |
| input_type_ = TEXT_INPUT_TYPE_NONE; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_out_call_count()); |
| } |
| |
| // Confirm that IBusClient::FocusOut is called as expected. |
| TEST_F(InputMethodChromeOSTest, FocusOut_Password) { |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(0, mock_ime_engine_handler_->focus_out_call_count()); |
| input_type_ = TEXT_INPUT_TYPE_PASSWORD; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ(2, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_out_call_count()); |
| } |
| |
| // FocusIn/FocusOut scenario test |
| TEST_F(InputMethodChromeOSTest, Focus_Scenario) { |
| // Confirm that both FocusIn and FocusOut are NOT called. |
| EXPECT_EQ(0, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(0, mock_ime_engine_handler_->focus_out_call_count()); |
| EXPECT_EQ(TEXT_INPUT_TYPE_NONE, |
| mock_ime_engine_handler_->last_text_input_context().type); |
| EXPECT_EQ(TEXT_INPUT_MODE_DEFAULT, |
| mock_ime_engine_handler_->last_text_input_context().mode); |
| |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| input_mode_ = TEXT_INPUT_MODE_LATIN; |
| ime_->OnTextInputTypeChanged(this); |
| // Confirm that only FocusIn is called, the TextInputType is TEXT and the |
| // TextInputMode is LATIN.. |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(0, mock_ime_engine_handler_->focus_out_call_count()); |
| EXPECT_EQ(TEXT_INPUT_TYPE_TEXT, |
| mock_ime_engine_handler_->last_text_input_context().type); |
| EXPECT_EQ(TEXT_INPUT_MODE_LATIN, |
| mock_ime_engine_handler_->last_text_input_context().mode); |
| |
| input_mode_ = TEXT_INPUT_MODE_KANA; |
| ime_->OnTextInputTypeChanged(this); |
| // Confirm that both FocusIn and FocusOut are called for mode change. |
| EXPECT_EQ(2, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(1, mock_ime_engine_handler_->focus_out_call_count()); |
| EXPECT_EQ(TEXT_INPUT_TYPE_TEXT, |
| mock_ime_engine_handler_->last_text_input_context().type); |
| EXPECT_EQ(TEXT_INPUT_MODE_KANA, |
| mock_ime_engine_handler_->last_text_input_context().mode); |
| |
| input_type_ = TEXT_INPUT_TYPE_URL; |
| ime_->OnTextInputTypeChanged(this); |
| // Confirm that both FocusIn and FocusOut are called and the TextInputType is |
| // changed to URL. |
| EXPECT_EQ(3, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(2, mock_ime_engine_handler_->focus_out_call_count()); |
| EXPECT_EQ(TEXT_INPUT_TYPE_URL, |
| mock_ime_engine_handler_->last_text_input_context().type); |
| EXPECT_EQ(TEXT_INPUT_MODE_KANA, |
| mock_ime_engine_handler_->last_text_input_context().mode); |
| |
| // Confirm that FocusOut is called when set focus to NULL client. |
| ime_->SetFocusedTextInputClient(NULL); |
| EXPECT_EQ(3, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(3, mock_ime_engine_handler_->focus_out_call_count()); |
| // Confirm that FocusIn is called when set focus to this client. |
| ime_->SetFocusedTextInputClient(this); |
| EXPECT_EQ(4, mock_ime_engine_handler_->focus_in_call_count()); |
| EXPECT_EQ(3, mock_ime_engine_handler_->focus_out_call_count()); |
| } |
| |
| // Test if the new |caret_bounds_| is correctly sent to ibus-daemon. |
| TEST_F(InputMethodChromeOSTest, OnCaretBoundsChanged) { |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| EXPECT_EQ( |
| 1, |
| mock_ime_candidate_window_handler_->set_cursor_bounds_call_count()); |
| caret_bounds_ = gfx::Rect(1, 2, 3, 4); |
| ime_->OnCaretBoundsChanged(this); |
| EXPECT_EQ( |
| 2, |
| mock_ime_candidate_window_handler_->set_cursor_bounds_call_count()); |
| caret_bounds_ = gfx::Rect(0, 2, 3, 4); |
| ime_->OnCaretBoundsChanged(this); |
| EXPECT_EQ( |
| 3, |
| mock_ime_candidate_window_handler_->set_cursor_bounds_call_count()); |
| caret_bounds_ = gfx::Rect(0, 2, 3, 4); // unchanged |
| ime_->OnCaretBoundsChanged(this); |
| // Current InputMethodChromeOS implementation performs the IPC |
| // regardless of the bounds are changed or not. |
| EXPECT_EQ( |
| 4, |
| mock_ime_candidate_window_handler_->set_cursor_bounds_call_count()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_NoAttribute) { |
| const base::string16 kSampleAsciiText = UTF8ToUTF16("Sample Text"); |
| const uint32_t kCursorPos = 2UL; |
| |
| CompositionText chromeos_composition_text; |
| chromeos_composition_text.text = kSampleAsciiText; |
| |
| CompositionText composition_text; |
| ime_->ExtractCompositionText( |
| chromeos_composition_text, kCursorPos, &composition_text); |
| EXPECT_EQ(kSampleAsciiText, composition_text.text); |
| // If there is no selection, |selection| represents cursor position. |
| EXPECT_EQ(kCursorPos, composition_text.selection.start()); |
| EXPECT_EQ(kCursorPos, composition_text.selection.end()); |
| // If there is no underline, |underlines| contains one underline and it is |
| // whole text underline. |
| ASSERT_EQ(1UL, composition_text.underlines.size()); |
| EXPECT_EQ(0UL, composition_text.underlines[0].start_offset); |
| EXPECT_EQ(kSampleAsciiText.size(), composition_text.underlines[0].end_offset); |
| EXPECT_FALSE(composition_text.underlines[0].thick); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_SingleUnderline) { |
| const uint32_t kCursorPos = 2UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| CompositionUnderline underline(1UL, 4UL, SK_ColorBLACK, false, |
| SK_ColorTRANSPARENT); |
| composition_text.underlines.push_back(underline); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| // If there is no selection, |selection| represents cursor position. |
| EXPECT_EQ(kCursorPos, composition_text2.selection.start()); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_offset), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_offset), |
| composition_text2.underlines[0].end_offset); |
| // Single underline represents as black thin line. |
| EXPECT_EQ(SK_ColorBLACK, composition_text2.underlines[0].color); |
| EXPECT_FALSE(composition_text2.underlines[0].thick); |
| EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), |
| composition_text2.underlines[0].background_color); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_DoubleUnderline) { |
| const uint32_t kCursorPos = 2UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| CompositionUnderline underline(1UL, 4UL, SK_ColorBLACK, true, |
| SK_ColorTRANSPARENT); |
| composition_text.underlines.push_back(underline); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| // If there is no selection, |selection| represents cursor position. |
| EXPECT_EQ(kCursorPos, composition_text2.selection.start()); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_offset), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_offset), |
| composition_text2.underlines[0].end_offset); |
| // Double underline represents as black thick line. |
| EXPECT_EQ(SK_ColorBLACK, composition_text2.underlines[0].color); |
| EXPECT_TRUE(composition_text2.underlines[0].thick); |
| EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), |
| composition_text2.underlines[0].background_color); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_ErrorUnderline) { |
| const uint32_t kCursorPos = 2UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| CompositionUnderline underline(1UL, 4UL, SK_ColorRED, false, |
| SK_ColorTRANSPARENT); |
| composition_text.underlines.push_back(underline); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.start()); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_offset), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_offset), |
| composition_text2.underlines[0].end_offset); |
| // Error underline represents as red thin line. |
| EXPECT_EQ(SK_ColorRED, composition_text2.underlines[0].color); |
| EXPECT_FALSE(composition_text2.underlines[0].thick); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_Selection) { |
| const uint32_t kCursorPos = 2UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| composition_text.selection.set_start(1UL); |
| composition_text.selection.set_end(4UL); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.start()); |
| EXPECT_EQ(kCursorPos, composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.start()), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), |
| composition_text2.underlines[0].end_offset); |
| EXPECT_EQ(SK_ColorBLACK, composition_text2.underlines[0].color); |
| EXPECT_TRUE(composition_text2.underlines[0].thick); |
| EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), |
| composition_text2.underlines[0].background_color); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, |
| ExtractCompositionTextTest_SelectionStartWithCursor) { |
| const uint32_t kCursorPos = 1UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| composition_text.selection.set_start(kCursorPos); |
| composition_text.selection.set_end(4UL); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| // If the cursor position is same as selection bounds, selection start |
| // position become opposit side of selection from cursor. |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), |
| composition_text2.selection.start()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, kCursorPos), |
| composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.start()), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), |
| composition_text2.underlines[0].end_offset); |
| EXPECT_EQ(SK_ColorBLACK, composition_text2.underlines[0].color); |
| EXPECT_TRUE(composition_text2.underlines[0].thick); |
| EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), |
| composition_text2.underlines[0].background_color); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, |
| ExtractCompositionTextTest_SelectionEndWithCursor) { |
| const uint32_t kCursorPos = 4UL; |
| |
| // Set up chromeos composition text with one underline attribute. |
| CompositionText composition_text; |
| composition_text.text = kSampleText; |
| composition_text.selection.set_start(1UL); |
| composition_text.selection.set_end(kCursorPos); |
| |
| CompositionText composition_text2; |
| ime_->ExtractCompositionText(composition_text, kCursorPos, |
| &composition_text2); |
| EXPECT_EQ(kSampleText, composition_text2.text); |
| // If the cursor position is same as selection bounds, selection start |
| // position become opposit side of selection from cursor. |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.start()), |
| composition_text2.selection.start()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, kCursorPos), |
| composition_text2.selection.end()); |
| ASSERT_EQ(1UL, composition_text2.underlines.size()); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.start()), |
| composition_text2.underlines[0].start_offset); |
| EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), |
| composition_text2.underlines[0].end_offset); |
| EXPECT_EQ(SK_ColorBLACK, composition_text2.underlines[0].color); |
| EXPECT_TRUE(composition_text2.underlines[0].thick); |
| EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), |
| composition_text2.underlines[0].background_color); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, SurroundingText_NoSelectionTest) { |
| // Click a text input form. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| // Set the TextInputClient behaviors. |
| surrounding_text_ = UTF8ToUTF16("abcdef"); |
| text_range_ = gfx::Range(0, 6); |
| selection_range_ = gfx::Range(3, 3); |
| |
| // Set the verifier for SetSurroundingText mock call. |
| SetSurroundingTextVerifier verifier(UTF16ToUTF8(surrounding_text_), 3, 3); |
| |
| |
| ime_->OnCaretBoundsChanged(this); |
| |
| // Check the call count. |
| EXPECT_EQ(1, |
| mock_ime_engine_handler_->set_surrounding_text_call_count()); |
| EXPECT_EQ(UTF16ToUTF8(surrounding_text_), |
| mock_ime_engine_handler_->last_set_surrounding_text()); |
| EXPECT_EQ(3U, |
| mock_ime_engine_handler_->last_set_surrounding_cursor_pos()); |
| EXPECT_EQ(3U, |
| mock_ime_engine_handler_->last_set_surrounding_anchor_pos()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, SurroundingText_SelectionTest) { |
| // Click a text input form. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| // Set the TextInputClient behaviors. |
| surrounding_text_ = UTF8ToUTF16("abcdef"); |
| text_range_ = gfx::Range(0, 6); |
| selection_range_ = gfx::Range(2, 5); |
| |
| // Set the verifier for SetSurroundingText mock call. |
| SetSurroundingTextVerifier verifier(UTF16ToUTF8(surrounding_text_), 2, 5); |
| |
| ime_->OnCaretBoundsChanged(this); |
| |
| // Check the call count. |
| EXPECT_EQ(1, |
| mock_ime_engine_handler_->set_surrounding_text_call_count()); |
| EXPECT_EQ(UTF16ToUTF8(surrounding_text_), |
| mock_ime_engine_handler_->last_set_surrounding_text()); |
| EXPECT_EQ(2U, |
| mock_ime_engine_handler_->last_set_surrounding_cursor_pos()); |
| EXPECT_EQ(5U, |
| mock_ime_engine_handler_->last_set_surrounding_anchor_pos()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, SurroundingText_PartialText) { |
| // Click a text input form. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| // Set the TextInputClient behaviors. |
| surrounding_text_ = UTF8ToUTF16("abcdefghij"); |
| text_range_ = gfx::Range(5, 10); |
| selection_range_ = gfx::Range(7, 9); |
| |
| ime_->OnCaretBoundsChanged(this); |
| |
| // Check the call count. |
| EXPECT_EQ(1, |
| mock_ime_engine_handler_->set_surrounding_text_call_count()); |
| // Set the verifier for SetSurroundingText mock call. |
| // Here (2, 4) is selection range in expected surrounding text coordinates. |
| EXPECT_EQ("fghij", |
| mock_ime_engine_handler_->last_set_surrounding_text()); |
| EXPECT_EQ(2U, |
| mock_ime_engine_handler_->last_set_surrounding_cursor_pos()); |
| EXPECT_EQ(4U, |
| mock_ime_engine_handler_->last_set_surrounding_anchor_pos()); |
| } |
| |
| TEST_F(InputMethodChromeOSTest, SurroundingText_BecomeEmptyText) { |
| // Click a text input form. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| // Set the TextInputClient behaviors. |
| // If the surrounding text becomes empty, text_range become (0, 0) and |
| // selection range become invalid. |
| surrounding_text_ = UTF8ToUTF16(""); |
| text_range_ = gfx::Range(0, 0); |
| selection_range_ = gfx::Range::InvalidRange(); |
| |
| ime_->OnCaretBoundsChanged(this); |
| |
| // Check the call count. |
| EXPECT_EQ(0, |
| mock_ime_engine_handler_->set_surrounding_text_call_count()); |
| |
| // Should not be called twice with same condition. |
| ime_->OnCaretBoundsChanged(this); |
| EXPECT_EQ(0, |
| mock_ime_engine_handler_->set_surrounding_text_call_count()); |
| } |
| |
| class InputMethodChromeOSKeyEventTest : public InputMethodChromeOSTest { |
| public: |
| InputMethodChromeOSKeyEventTest() {} |
| ~InputMethodChromeOSKeyEventTest() override {} |
| |
| DISALLOW_COPY_AND_ASSIGN(InputMethodChromeOSKeyEventTest); |
| }; |
| |
| TEST_F(InputMethodChromeOSKeyEventTest, KeyEventDelayResponseTest) { |
| const int kFlags = ui::EF_SHIFT_DOWN; |
| ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, kFlags); |
| |
| // Do key event. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| ime_->DispatchKeyEvent(&event); |
| |
| // Check before state. |
| const ui::KeyEvent* key_event = |
| mock_ime_engine_handler_->last_processed_key_event(); |
| EXPECT_EQ(1, mock_ime_engine_handler_->process_key_event_call_count()); |
| EXPECT_EQ(ui::VKEY_A, key_event->key_code()); |
| EXPECT_EQ(kFlags, key_event->flags()); |
| EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count()); |
| |
| (static_cast<IMEInputContextHandlerInterface*>(ime_.get())) |
| ->CommitText("A"); |
| |
| EXPECT_EQ(0, inserted_char_); |
| |
| // Do callback. |
| mock_ime_engine_handler_->last_passed_callback().Run(true); |
| |
| // Check the results |
| EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count()); |
| const ui::KeyEvent* stored_event = |
| ime_->process_key_event_post_ime_args().event; |
| EXPECT_EQ(ui::VKEY_A, stored_event->key_code()); |
| EXPECT_EQ(kFlags, stored_event->flags()); |
| EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled); |
| |
| EXPECT_EQ(L'A', inserted_char_); |
| } |
| |
| TEST_F(InputMethodChromeOSKeyEventTest, MultiKeyEventDelayResponseTest) { |
| // Preparation |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| const int kFlags = ui::EF_SHIFT_DOWN; |
| ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_B, kFlags); |
| |
| // Do key event. |
| ime_->DispatchKeyEvent(&event); |
| const ui::KeyEvent* key_event = |
| mock_ime_engine_handler_->last_processed_key_event(); |
| EXPECT_EQ(ui::VKEY_B, key_event->key_code()); |
| EXPECT_EQ(kFlags, key_event->flags()); |
| |
| KeyEventCallback first_callback = |
| mock_ime_engine_handler_->last_passed_callback(); |
| |
| // Do key event again. |
| ui::KeyEvent event2(ui::ET_KEY_PRESSED, ui::VKEY_C, kFlags); |
| |
| ime_->DispatchKeyEvent(&event2); |
| const ui::KeyEvent* key_event2 = |
| mock_ime_engine_handler_->last_processed_key_event(); |
| EXPECT_EQ(ui::VKEY_C, key_event2->key_code()); |
| EXPECT_EQ(kFlags, key_event2->flags()); |
| |
| // Check before state. |
| EXPECT_EQ(2, |
| mock_ime_engine_handler_->process_key_event_call_count()); |
| EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count()); |
| |
| CompositionText comp; |
| comp.text = base::ASCIIToUTF16("B"); |
| (static_cast<IMEInputContextHandlerInterface*>(ime_.get())) |
| ->UpdateCompositionText(comp, comp.text.length(), true); |
| |
| EXPECT_EQ(0, composition_text_.text[0]); |
| |
| // Do callback for first key event. |
| first_callback.Run(true); |
| |
| EXPECT_EQ(comp.text, composition_text_.text); |
| |
| // Check the results for first key event. |
| EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count()); |
| const ui::KeyEvent* stored_event = |
| ime_->process_key_event_post_ime_args().event; |
| EXPECT_EQ(ui::VKEY_B, stored_event->key_code()); |
| EXPECT_EQ(kFlags, stored_event->flags()); |
| EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled); |
| EXPECT_EQ(0, inserted_char_); |
| |
| // Do callback for second key event. |
| mock_ime_engine_handler_->last_passed_callback().Run(false); |
| |
| // Check the results for second key event. |
| EXPECT_EQ(2, ime_->process_key_event_post_ime_call_count()); |
| stored_event = ime_->process_key_event_post_ime_args().event; |
| EXPECT_EQ(ui::VKEY_C, stored_event->key_code()); |
| EXPECT_EQ(kFlags, stored_event->flags()); |
| EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled); |
| |
| EXPECT_EQ(L'C', inserted_char_); |
| } |
| |
| TEST_F(InputMethodChromeOSKeyEventTest, StopPropagationTest) { |
| // Preparation |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| // Do key event with event being stopped propagation. |
| stop_propagation_post_ime_ = true; |
| ui::KeyEvent eventA(ui::ET_KEY_PRESSED, ui::VKEY_A, EF_NONE); |
| eventA.set_character(L'A'); |
| ime_->DispatchKeyEvent(&eventA); |
| mock_ime_engine_handler_->last_passed_callback().Run(false); |
| |
| const ui::KeyEvent* key_event = |
| mock_ime_engine_handler_->last_processed_key_event(); |
| EXPECT_EQ(ui::VKEY_A, key_event->key_code()); |
| EXPECT_EQ(0, inserted_char_); |
| |
| // Do key event with event not being stopped propagation. |
| stop_propagation_post_ime_ = false; |
| ime_->DispatchKeyEvent(&eventA); |
| mock_ime_engine_handler_->last_passed_callback().Run(false); |
| |
| key_event = mock_ime_engine_handler_->last_processed_key_event(); |
| EXPECT_EQ(ui::VKEY_A, key_event->key_code()); |
| EXPECT_EQ(L'A', inserted_char_); |
| } |
| |
| TEST_F(InputMethodChromeOSKeyEventTest, DeadKeyPressTest) { |
| // Preparation. |
| input_type_ = TEXT_INPUT_TYPE_TEXT; |
| ime_->OnTextInputTypeChanged(this); |
| |
| ui::KeyEvent eventA(ET_KEY_PRESSED, |
| VKEY_OEM_4, // '[' |
| DomCode::BRACKET_LEFT, |
| 0, |
| DomKey::DeadKeyFromCombiningCharacter('^'), |
| EventTimeForNow()); |
| ime_->ProcessKeyEventPostIME(&eventA, true); |
| |
| const ui::KeyEvent& key_event = dispatched_key_event_; |
| |
| EXPECT_EQ(ET_KEY_PRESSED, key_event.type()); |
| EXPECT_EQ(VKEY_PROCESSKEY, key_event.key_code()); |
| EXPECT_EQ(eventA.code(), key_event.code()); |
| EXPECT_EQ(eventA.flags(), key_event.flags()); |
| EXPECT_EQ(eventA.GetDomKey(), key_event.GetDomKey()); |
| EXPECT_EQ(eventA.time_stamp(), key_event.time_stamp()); |
| } |
| |
| } // namespace ui |