blob: 99accfa42c63af411d66b24764a3c29c3a977b61 [file] [log] [blame]
// Copyright (c) 2012 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 <X11/Xlib.h>
#undef Bool
#undef FocusIn
#undef FocusOut
#undef None
#include <cstring>
#include "base/i18n/char_iterator.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/ibus/ibus_text.h"
#include "chromeos/dbus/ibus/mock_ibus_client.h"
#include "chromeos/dbus/ibus/mock_ibus_input_context_client.h"
#include "chromeos/dbus/mock_dbus_thread_manager_without_gmock.h"
#include "chromeos/ime/mock_ibus_daemon_controller.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/input_method_delegate.h"
#include "ui/base/ime/input_method_ibus.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/gfx/rect.h"
namespace ui {
namespace {
const int kCreateInputContextMaxTrialCount = 10;
const uint32 kTestIBusKeyVal1 = 97;
const uint32 kTestIBusKeyVal2 = 30;
const uint32 kTestIBusKeyVal3 = 0;
const uint32 kTestIBusKeyCode1 = 98;
const uint32 kTestIBusKeyCode2 = 48;
const uint32 kTestIBusKeyCode3 = 1;
const uint32 kTestIBusState1 = 99;
const uint32 kTestIBusState2 = 46;
const uint32 kTestIBusState3 = 8;
uint32 GetOffsetInUTF16(const std::string& utf8_string, uint32 utf8_offset) {
string16 utf16_string = UTF8ToUTF16(utf8_string);
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();
}
bool IsEqualXKeyEvent(const XEvent& e1, const XEvent& e2) {
if ((e1.type == KeyPress && e2.type == KeyPress) ||
(e1.type == KeyRelease && e2.type == KeyRelease)) {
return !std::memcmp(&e1.xkey, &e2.xkey, sizeof(XKeyEvent));
}
return false;
}
enum KeyEventHandlerBehavior {
KEYEVENT_CONSUME,
KEYEVENT_NOT_CONSUME,
KEYEVENT_ERROR,
};
} // namespace
using chromeos::IBusInputContextClient;
class TestableInputMethodIBus : public InputMethodIBus {
public:
explicit TestableInputMethodIBus(internal::InputMethodDelegate* delegate)
: InputMethodIBus(delegate),
process_key_event_post_ime_call_count_(0) {
}
struct ProcessKeyEventPostIMEArgs {
ProcessKeyEventPostIMEArgs() : handled(false) {
std::memset(&event, 0, sizeof(XEvent));
}
XEvent event;
bool handled;
};
struct IBusKeyEventFromNativeKeyEventResult {
IBusKeyEventFromNativeKeyEventResult() : keyval(0), keycode(0), state(0) {}
uint32 keyval;
uint32 keycode;
uint32 state;
};
// InputMethodIBus override.
virtual void ProcessKeyEventPostIME(const base::NativeEvent& native_key_event,
uint32 ibus_state,
bool handled) OVERRIDE {
process_key_event_post_ime_args_.event = *native_key_event;
process_key_event_post_ime_args_.handled = handled;
++process_key_event_post_ime_call_count_;
}
// We can't call X11 related function without display in unit test, so
// override with mock function.
virtual void IBusKeyEventFromNativeKeyEvent(
const base::NativeEvent& native_event,
uint32* ibus_keyval,
uint32* ibus_keycode,
uint32* ibus_state) OVERRIDE {
EXPECT_TRUE(native_event);
EXPECT_TRUE(ibus_keyval);
EXPECT_TRUE(ibus_keycode);
EXPECT_TRUE(ibus_state);
*ibus_keyval = ibus_key_event_from_native_key_event_result_.keyval;
*ibus_keycode = ibus_key_event_from_native_key_event_result_.keycode;
*ibus_state = ibus_key_event_from_native_key_event_result_.state;
}
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_;
}
IBusKeyEventFromNativeKeyEventResult*
mutable_ibus_key_event_from_native_key_event_result() {
return &ibus_key_event_from_native_key_event_result_;
}
// Change access rights for testing.
using InputMethodIBus::ExtractCompositionText;
using InputMethodIBus::ResetContext;
private:
ProcessKeyEventPostIMEArgs process_key_event_post_ime_args_;
int process_key_event_post_ime_call_count_;
IBusKeyEventFromNativeKeyEventResult
ibus_key_event_from_native_key_event_result_;
};
class CreateInputContextSuccessHandler {
public:
explicit CreateInputContextSuccessHandler(const dbus::ObjectPath& object_path)
: object_path_(object_path) {
}
void Run(const std::string& client_name,
const chromeos::IBusClient::CreateInputContextCallback& callback,
const chromeos::IBusClient::ErrorCallback& error_callback) {
EXPECT_EQ("chrome", client_name);
callback.Run(object_path_);
}
private:
dbus::ObjectPath object_path_;
DISALLOW_COPY_AND_ASSIGN(CreateInputContextSuccessHandler);
};
class CreateInputContextFailHandler {
public:
CreateInputContextFailHandler() {}
void Run(const std::string& client_name,
const chromeos::IBusClient::CreateInputContextCallback& callback,
const chromeos::IBusClient::ErrorCallback& error_callback) {
error_callback.Run();
}
private:
DISALLOW_COPY_AND_ASSIGN(CreateInputContextFailHandler);
};
class CreateInputContextNoResponseHandler {
public:
CreateInputContextNoResponseHandler() {}
void Run(const std::string& client_name,
const chromeos::IBusClient::CreateInputContextCallback& callback,
const chromeos::IBusClient::ErrorCallback& error_callback) {
}
private:
DISALLOW_COPY_AND_ASSIGN(CreateInputContextNoResponseHandler);
};
class CreateInputContextDelayHandler {
public:
explicit CreateInputContextDelayHandler(const dbus::ObjectPath& object_path)
: object_path_(object_path) {
}
void Run(const std::string& client_name,
const chromeos::IBusClient::CreateInputContextCallback& callback,
const chromeos::IBusClient::ErrorCallback& error_callback) {
callback_ = callback;
error_callback_ = error_callback;
}
void RunCallback(bool success) {
if (success)
callback_.Run(object_path_);
else
error_callback_.Run();
}
private:
dbus::ObjectPath object_path_;
chromeos::IBusClient::CreateInputContextCallback callback_;
chromeos::IBusClient::ErrorCallback error_callback_;
DISALLOW_COPY_AND_ASSIGN(CreateInputContextDelayHandler);
};
class SynchronousKeyEventHandler {
public:
SynchronousKeyEventHandler(uint32 expected_keyval,
uint32 expected_keycode,
uint32 expected_state,
KeyEventHandlerBehavior behavior)
: expected_keyval_(expected_keyval),
expected_keycode_(expected_keycode),
expected_state_(expected_state),
behavior_(behavior) {}
virtual ~SynchronousKeyEventHandler() {}
void Run(uint32 keyval,
uint32 keycode,
uint32 state,
const IBusInputContextClient::ProcessKeyEventCallback& callback,
const IBusInputContextClient::ErrorCallback& error_callback) {
EXPECT_EQ(expected_keyval_, keyval);
EXPECT_EQ(expected_keycode_, keycode);
EXPECT_EQ(expected_state_, state);
if (behavior_ == KEYEVENT_ERROR)
error_callback.Run();
else
callback.Run(behavior_ == KEYEVENT_CONSUME);
}
private:
const uint32 expected_keyval_;
const uint32 expected_keycode_;
const uint32 expected_state_;
const KeyEventHandlerBehavior behavior_;
DISALLOW_COPY_AND_ASSIGN(SynchronousKeyEventHandler);
};
class AsynchronousKeyEventHandler {
public:
AsynchronousKeyEventHandler(uint32 expected_keyval,
uint32 expected_keycode,
uint32 expected_state)
: expected_keyval_(expected_keyval),
expected_keycode_(expected_keycode),
expected_state_(expected_state) {}
virtual ~AsynchronousKeyEventHandler() {}
void Run(uint32 keyval,
uint32 keycode,
uint32 state,
const IBusInputContextClient::ProcessKeyEventCallback& callback,
const IBusInputContextClient::ErrorCallback& error_callback) {
EXPECT_EQ(expected_keyval_, keyval);
EXPECT_EQ(expected_keycode_, keycode);
EXPECT_EQ(expected_state_, state);
callback_ = callback;
error_callback_ = error_callback;
}
void RunCallback(KeyEventHandlerBehavior behavior) {
if (behavior == KEYEVENT_ERROR)
error_callback_.Run();
else
callback_.Run(behavior == KEYEVENT_CONSUME);
}
private:
const uint32 expected_keyval_;
const uint32 expected_keycode_;
const uint32 expected_state_;
IBusInputContextClient::ProcessKeyEventCallback callback_;
IBusInputContextClient::ErrorCallback error_callback_;
DISALLOW_COPY_AND_ASSIGN(AsynchronousKeyEventHandler);
};
class SetSurroundingTextVerifier {
public:
SetSurroundingTextVerifier(const std::string& expected_surrounding_text,
uint32 expected_cursor_position,
uint32 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 cursor_pos,
uint32 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 expected_cursor_position_;
const uint32 expected_anchor_position_;
DISALLOW_COPY_AND_ASSIGN(SetSurroundingTextVerifier);
};
class InputMethodIBusTest : public internal::InputMethodDelegate,
public testing::Test,
public TextInputClient {
public:
InputMethodIBusTest() {
ResetFlags();
}
virtual ~InputMethodIBusTest() {
}
// testing::Test overrides:
virtual void SetUp() OVERRIDE {
// |thread_manager_| will be released by DBusThreadManager::Shutdown
// function in TearDown function.
// Current MockIBusInputContext is strongly depend on gmock, but gmock is
// banned in ui/*. So just use stab implementation for testing.
mock_dbus_thread_manager_ =
new chromeos::MockDBusThreadManagerWithoutGMock();
chromeos::DBusThreadManager::InitializeForTesting(
mock_dbus_thread_manager_);
mock_ibus_daemon_controller_ = new chromeos::MockIBusDaemonController();
chromeos::IBusDaemonController::InitializeForTesting(
mock_ibus_daemon_controller_);
ime_.reset(new TestableInputMethodIBus(this));
ime_->SetFocusedTextInputClient(this);
}
virtual void TearDown() OVERRIDE {
if (ime_.get())
ime_->SetFocusedTextInputClient(NULL);
ime_.reset();
chromeos::DBusThreadManager::Shutdown();
chromeos::IBusDaemonController::Shutdown();
}
// Helper function to initialize IBus connection for testing.
void InitIBusBus() {
mock_dbus_thread_manager_->InitIBusBus("dummy address",
base::Bind(&base::DoNothing));
mock_ibus_client_ = mock_dbus_thread_manager_->mock_ibus_client();
mock_ibus_input_context_client_ =
mock_dbus_thread_manager_->mock_ibus_input_context_client();
}
// ui::internal::InputMethodDelegate overrides:
virtual bool DispatchKeyEventPostIME(
const base::NativeEvent& native_key_event) OVERRIDE {
dispatched_native_event_ = native_key_event;
return false;
}
virtual bool DispatchFabricatedKeyEventPostIME(ui::EventType type,
ui::KeyboardCode key_code,
int flags) OVERRIDE {
dispatched_fabricated_event_type_ = type;
dispatched_fabricated_event_key_code_ = key_code;
dispatched_fabricated_event_flags_ = flags;
return false;
}
// ui::TextInputClient overrides:
virtual void SetCompositionText(
const CompositionText& composition) OVERRIDE {
composition_text_ = composition;
}
virtual void ConfirmCompositionText() OVERRIDE {
confirmed_text_ = composition_text_;
composition_text_.Clear();
}
virtual void ClearCompositionText() OVERRIDE {
composition_text_.Clear();
}
virtual void InsertText(const string16& text) OVERRIDE {
inserted_text_ = text;
}
virtual void InsertChar(char16 ch, int flags) OVERRIDE {
inserted_char_ = ch;
inserted_char_flags_ = flags;
}
virtual gfx::NativeWindow GetAttachedWindow() const OVERRIDE {
return static_cast<gfx::NativeWindow>(NULL);
}
virtual TextInputType GetTextInputType() const OVERRIDE {
return input_type_;
}
virtual TextInputMode GetTextInputMode() const OVERRIDE {
return TEXT_INPUT_MODE_DEFAULT;
}
virtual bool CanComposeInline() const OVERRIDE {
return can_compose_inline_;
}
virtual gfx::Rect GetCaretBounds() OVERRIDE {
return caret_bounds_;
}
virtual bool GetCompositionCharacterBounds(uint32 index,
gfx::Rect* rect) OVERRIDE {
return false;
}
virtual bool HasCompositionText() OVERRIDE {
CompositionText empty;
return composition_text_ != empty;
}
virtual bool GetTextRange(Range* range) OVERRIDE {
*range = text_range_;
return true;
}
virtual bool GetCompositionTextRange(Range* range) OVERRIDE { return false; }
virtual bool GetSelectionRange(Range* range) OVERRIDE {
*range = selection_range_;
return true;
}
virtual bool SetSelectionRange(const Range& range) OVERRIDE { return false; }
virtual bool DeleteRange(const Range& range) OVERRIDE { return false; }
virtual bool GetTextFromRange(const Range& range, string16* text) OVERRIDE {
*text = surrounding_text_.substr(range.GetMin(), range.length());
return true;
}
virtual void OnInputMethodChanged() OVERRIDE {
++on_input_method_changed_call_count_;
}
virtual bool ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) OVERRIDE { return false; }
virtual void ExtendSelectionAndDelete(size_t before,
size_t after) OVERRIDE { }
virtual void EnsureCaretInRect(const gfx::Rect& rect) OVERRIDE { }
bool HasNativeEvent() const {
base::NativeEvent empty;
std::memset(&empty, 0, sizeof(empty));
return !!std::memcmp(&dispatched_native_event_,
&empty,
sizeof(dispatched_native_event_));
}
void ResetFlags() {
std::memset(&dispatched_native_event_, 0, sizeof(dispatched_native_event_));
DCHECK(!HasNativeEvent());
dispatched_fabricated_event_type_ = ET_UNKNOWN;
dispatched_fabricated_event_key_code_ = VKEY_UNKNOWN;
dispatched_fabricated_event_flags_ = 0;
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;
can_compose_inline_ = true;
caret_bounds_ = gfx::Rect();
}
void SetCreateContextSuccessHandler() {
CreateInputContextSuccessHandler create_input_context_handler(
dbus::ObjectPath("InputContext_1"));
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextSuccessHandler::Run,
base::Unretained(&create_input_context_handler)));
}
scoped_ptr<TestableInputMethodIBus> ime_;
// Variables for remembering the parameters that are passed to
// ui::internal::InputMethodDelegate functions.
base::NativeEvent dispatched_native_event_;
ui::EventType dispatched_fabricated_event_type_;
ui::KeyboardCode dispatched_fabricated_event_key_code_;
int dispatched_fabricated_event_flags_;
// Variables for remembering the parameters that are passed to
// ui::TextInputClient functions.
CompositionText composition_text_;
CompositionText confirmed_text_;
string16 inserted_text_;
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_;
bool can_compose_inline_;
gfx::Rect caret_bounds_;
ui::Range text_range_;
ui::Range selection_range_;
string16 surrounding_text_;
// Variables for mock dbus connections.
chromeos::MockDBusThreadManagerWithoutGMock* mock_dbus_thread_manager_;
chromeos::MockIBusClient* mock_ibus_client_;
chromeos::MockIBusInputContextClient* mock_ibus_input_context_client_;
chromeos::MockIBusDaemonController* mock_ibus_daemon_controller_;
DISALLOW_COPY_AND_ASSIGN(InputMethodIBusTest);
};
// Tests public APIs in ui::InputMethod first.
TEST_F(InputMethodIBusTest, GetInputLocale) {
// ui::InputMethodIBus does not support the API.
ime_->Init(true);
EXPECT_EQ("", ime_->GetInputLocale());
}
TEST_F(InputMethodIBusTest, GetInputTextDirection) {
// ui::InputMethodIBus does not support the API.
ime_->Init(true);
EXPECT_EQ(base::i18n::UNKNOWN_DIRECTION, ime_->GetInputTextDirection());
}
TEST_F(InputMethodIBusTest, IsActive) {
ime_->Init(true);
// ui::InputMethodIBus always returns true.
EXPECT_TRUE(ime_->IsActive());
}
TEST_F(InputMethodIBusTest, GetInputTextType) {
ime_->Init(true);
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(InputMethodIBusTest, CanComposeInline) {
ime_->Init(true);
EXPECT_TRUE(ime_->CanComposeInline());
can_compose_inline_ = false;
ime_->OnTextInputTypeChanged(this);
EXPECT_FALSE(ime_->CanComposeInline());
}
TEST_F(InputMethodIBusTest, GetTextInputClient) {
ime_->Init(true);
EXPECT_EQ(this, ime_->GetTextInputClient());
ime_->SetFocusedTextInputClient(NULL);
EXPECT_EQ(NULL, ime_->GetTextInputClient());
}
TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedClient) {
ime_->Init(true);
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());
}
TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedWindow) {
ime_->Init(true);
EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
ime_->OnBlur();
input_type_ = TEXT_INPUT_TYPE_PASSWORD;
ime_->OnTextInputTypeChanged(this);
// The OnTextInputTypeChanged() call above should be ignored since the top-
// level window which the ime_ is attached to is not currently focused.
EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
ime_->OnFocus();
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
}
TEST_F(InputMethodIBusTest, GetInputTextType_WithoutFocusedWindow2) {
ime_->Init(false); // the top-level is initially unfocused.
EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
input_type_ = TEXT_INPUT_TYPE_PASSWORD;
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(TEXT_INPUT_TYPE_NONE, ime_->GetTextInputType());
ime_->OnFocus();
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, ime_->GetTextInputType());
}
// Then, tests internal behavior of ui::InputMethodIBus, especially if the input
// method implementation calls ui::internal::IBusClient APIs as expected.
// Start ibus-daemon first, then create ui::InputMethodIBus. Check if a new
// input context is created.
TEST_F(InputMethodIBusTest, InitiallyConnected) {
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
// An input context should be created immediately since is_connected_ is true.
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->set_capabilities_call_count());
// However, since the current text input type is 'NONE' (the default), FocusIn
// shouldn't be called.
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_TRUE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Create ui::InputMethodIBus, then start ibus-daemon.
TEST_F(InputMethodIBusTest, InitiallyDisconnected) {
ime_->Init(true);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// A context should be created upon the signal delivery.
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_TRUE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash on "disconnected" signal
// delivery.
TEST_F(InputMethodIBusTest, Disconnect) {
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
// Currently we can't shutdown IBusBus connection except in
// DBusThreadManager's shutting down. So set ibus_bus_ as NULL to emulate
// dynamical shutting down.
mock_dbus_thread_manager_->set_ibus_bus(NULL);
mock_ibus_daemon_controller_->EmulateDisconnect();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus re-creates an input context when ibus-daemon
// restarts.
TEST_F(InputMethodIBusTest, DisconnectThenReconnect) {
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_EQ(0,
mock_ibus_input_context_client_->reset_object_proxy_call_caount());
mock_dbus_thread_manager_->set_ibus_bus(NULL);
mock_ibus_daemon_controller_->EmulateDisconnect();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
mock_ibus_daemon_controller_->EmulateDisconnect();
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// Check if the old context is deleted.
EXPECT_EQ(0,
mock_ibus_input_context_client_->reset_object_proxy_call_caount());
// Check if a new context is created.
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_TRUE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if NULL context is
// passed.
// TODO(yusukes): Currently, ui::InputMethodIBus does not try to create ic once
// it fails (unless ibus sends the "connected" signal to Chrome again). It might
// be better to add some retry logic. Will revisit later.
TEST_F(InputMethodIBusTest, CreateContextFail) {
InitIBusBus();
CreateInputContextFailHandler create_input_context_handler;
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextFailHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
// InputMethodIBus tries several times if the CreateInputContext method call
// is failed.
EXPECT_EQ(kCreateInputContextMaxTrialCount,
mock_ibus_client_->create_input_context_call_count());
// |set_capabilities_call_count()| should be zero since a context is not
// created yet.
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon does not
// respond.
TEST_F(InputMethodIBusTest, CreateContextNoResp) {
InitIBusBus();
CreateInputContextNoResponseHandler create_input_context_handler;
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextNoResponseHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon responds
// after ui::InputMethodIBus is deleted.
TEST_F(InputMethodIBusTest, CreateContextFailDelayed) {
InitIBusBus();
CreateInputContextDelayHandler create_input_context_handler(
dbus::ObjectPath("Sample object path"));
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextDelayHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
ime_->SetFocusedTextInputClient(NULL);
ime_.reset();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
create_input_context_handler.RunCallback(false);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon responds
// after ui::InputMethodIBus is deleted.
TEST_F(InputMethodIBusTest, CreateContextSuccessDelayed) {
InitIBusBus();
CreateInputContextDelayHandler create_input_context_handler(
dbus::ObjectPath("Sample object path"));
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextDelayHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
ime_->SetFocusedTextInputClient(NULL);
ime_.reset();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
create_input_context_handler.RunCallback(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon responds
// after disconnected from ibus-daemon.
TEST_F(InputMethodIBusTest, CreateContextSuccessDelayedAfterDisconnection) {
InitIBusBus();
CreateInputContextDelayHandler create_input_context_handler(
dbus::ObjectPath("Sample object path"));
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextDelayHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
mock_ibus_daemon_controller_->EmulateDisconnect();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
create_input_context_handler.RunCallback(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that ui::InputMethodIBus does not crash even if ibus-daemon responds
// after disconnected from ibus-daemon.
TEST_F(InputMethodIBusTest, CreateContextFailDelayedAfterDisconnection) {
InitIBusBus();
CreateInputContextDelayHandler create_input_context_handler(
dbus::ObjectPath("Sample object path"));
mock_ibus_client_->set_create_input_context_handler(base::Bind(
&CreateInputContextDelayHandler::Run,
base::Unretained(&create_input_context_handler)));
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
mock_ibus_daemon_controller_->EmulateDisconnect();
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
create_input_context_handler.RunCallback(false);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->set_capabilities_call_count());
EXPECT_FALSE(mock_ibus_input_context_client_->IsObjectProxyReady());
}
// Confirm that IBusClient::FocusIn is called on "connected" if input_type_ is
// TEXT.
TEST_F(InputMethodIBusTest, FocusIn_Text) {
ime_->Init(true);
// 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);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// A context should be created upon the signal delivery.
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
// Since a form has focus, IBusClient::FocusIn() should be called.
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(1,
mock_ibus_input_context_client_->set_cursor_location_call_count());
// ui::TextInputClient::OnInputMethodChanged() should be called when
// ui::InputMethodIBus 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 IBusClient::FocusIn is NOT called on "connected" if input_type_
// is PASSWORD.
TEST_F(InputMethodIBusTest, FocusIn_Password) {
ime_->Init(true);
EXPECT_EQ(0U, on_input_method_changed_call_count_);
input_type_ = TEXT_INPUT_TYPE_PASSWORD;
ime_->OnTextInputTypeChanged(this);
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
// Since a form has focus, IBusClient::FocusIn() should NOT be called.
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(1U, on_input_method_changed_call_count_);
}
// Confirm that IBusClient::FocusOut is called as expected.
TEST_F(InputMethodIBusTest, FocusOut_None) {
input_type_ = TEXT_INPUT_TYPE_TEXT;
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_out_call_count());
input_type_ = TEXT_INPUT_TYPE_NONE;
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_out_call_count());
}
// Confirm that IBusClient::FocusOut is called as expected.
TEST_F(InputMethodIBusTest, FocusOut_Password) {
input_type_ = TEXT_INPUT_TYPE_TEXT;
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_out_call_count());
input_type_ = TEXT_INPUT_TYPE_PASSWORD;
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_out_call_count());
}
// Confirm that IBusClient::FocusOut is NOT called.
TEST_F(InputMethodIBusTest, FocusOut_Url) {
input_type_ = TEXT_INPUT_TYPE_TEXT;
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
EXPECT_EQ(1, mock_ibus_client_->create_input_context_call_count());
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_out_call_count());
input_type_ = TEXT_INPUT_TYPE_URL;
ime_->OnTextInputTypeChanged(this);
EXPECT_EQ(1, mock_ibus_input_context_client_->focus_in_call_count());
EXPECT_EQ(0, mock_ibus_input_context_client_->focus_out_call_count());
}
// Test if the new |caret_bounds_| is correctly sent to ibus-daemon.
TEST_F(InputMethodIBusTest, OnCaretBoundsChanged) {
InitIBusBus();
SetCreateContextSuccessHandler();
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->Init(true);
EXPECT_EQ(0,
mock_ibus_input_context_client_->set_cursor_location_call_count());
caret_bounds_ = gfx::Rect(1, 2, 3, 4);
ime_->OnCaretBoundsChanged(this);
EXPECT_EQ(1,
mock_ibus_input_context_client_->set_cursor_location_call_count());
caret_bounds_ = gfx::Rect(0, 2, 3, 4);
ime_->OnCaretBoundsChanged(this);
EXPECT_EQ(2,
mock_ibus_input_context_client_->set_cursor_location_call_count());
caret_bounds_ = gfx::Rect(0, 2, 3, 4); // unchanged
ime_->OnCaretBoundsChanged(this);
// Current InputMethodIBus implementation performs the IPC regardless of the
// bounds are changed or not.
EXPECT_EQ(3,
mock_ibus_input_context_client_->set_cursor_location_call_count());
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_NoAttribute) {
const char kSampleText[] = "Sample Text";
const uint32 kCursorPos = 2UL;
const string16 utf16_string = UTF8ToUTF16(kSampleText);
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), 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(utf16_string.size(), composition_text.underlines[0].end_offset);
EXPECT_FALSE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_SingleUnderline) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 2UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::UnderlineAttribute underline;
underline.type = chromeos::IBusText::IBUS_TEXT_UNDERLINE_SINGLE;
underline.start_index = 1UL;
underline.end_index = 4UL;
ibus_text.mutable_underline_attributes()->push_back(underline);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), 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());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_index),
composition_text.underlines[0].end_offset);
// Single underline represents as black thin line.
EXPECT_EQ(SK_ColorBLACK, composition_text.underlines[0].color);
EXPECT_FALSE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_DoubleUnderline) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 2UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::UnderlineAttribute underline;
underline.type = chromeos::IBusText::IBUS_TEXT_UNDERLINE_DOUBLE;
underline.start_index = 1UL;
underline.end_index = 4UL;
ibus_text.mutable_underline_attributes()->push_back(underline);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), 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());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_index),
composition_text.underlines[0].end_offset);
// Double underline represents as black thick line.
EXPECT_EQ(SK_ColorBLACK, composition_text.underlines[0].color);
EXPECT_TRUE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_ErrorUnderline) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 2UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::UnderlineAttribute underline;
underline.type = chromeos::IBusText::IBUS_TEXT_UNDERLINE_ERROR;
underline.start_index = 1UL;
underline.end_index = 4UL;
ibus_text.mutable_underline_attributes()->push_back(underline);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), composition_text.text);
EXPECT_EQ(kCursorPos, composition_text.selection.start());
EXPECT_EQ(kCursorPos, composition_text.selection.end());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_index),
composition_text.underlines[0].end_offset);
// Error underline represents as red thin line.
EXPECT_EQ(SK_ColorRED, composition_text.underlines[0].color);
EXPECT_FALSE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_Selection) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 2UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::SelectionAttribute selection;
selection.start_index = 1UL;
selection.end_index = 4UL;
ibus_text.mutable_selection_attributes()->push_back(selection);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), composition_text.text);
EXPECT_EQ(kCursorPos, composition_text.selection.start());
EXPECT_EQ(kCursorPos, composition_text.selection.end());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.end_index),
composition_text.underlines[0].end_offset);
EXPECT_EQ(SK_ColorBLACK, composition_text.underlines[0].color);
EXPECT_TRUE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest,
ExtractCompositionTextTest_SelectionStartWithCursor) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 1UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::SelectionAttribute selection;
selection.start_index = kCursorPos;
selection.end_index = 4UL;
ibus_text.mutable_selection_attributes()->push_back(selection);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), composition_text.text);
// If the cursor position is same as selection bounds, selection start
// position become opposit side of selection from cursor.
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.end_index),
composition_text.selection.start());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, kCursorPos),
composition_text.selection.end());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.end_index),
composition_text.underlines[0].end_offset);
EXPECT_EQ(SK_ColorBLACK, composition_text.underlines[0].color);
EXPECT_TRUE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, ExtractCompositionTextTest_SelectionEndWithCursor) {
const char kSampleText[] = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86"
"\xE3\x81\x88\xE3\x81\x8A";
const uint32 kCursorPos = 4UL;
// Set up ibus text with one underline attribute.
chromeos::IBusText ibus_text;
ibus_text.set_text(kSampleText);
chromeos::IBusText::SelectionAttribute selection;
selection.start_index = 1UL;
selection.end_index = kCursorPos;
ibus_text.mutable_selection_attributes()->push_back(selection);
CompositionText composition_text;
ime_->ExtractCompositionText(ibus_text, kCursorPos, &composition_text);
EXPECT_EQ(UTF8ToUTF16(kSampleText), composition_text.text);
// If the cursor position is same as selection bounds, selection start
// position become opposit side of selection from cursor.
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.start_index),
composition_text.selection.start());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, kCursorPos),
composition_text.selection.end());
ASSERT_EQ(1UL, composition_text.underlines.size());
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.start_index),
composition_text.underlines[0].start_offset);
EXPECT_EQ(GetOffsetInUTF16(kSampleText, selection.end_index),
composition_text.underlines[0].end_offset);
EXPECT_EQ(SK_ColorBLACK, composition_text.underlines[0].color);
EXPECT_TRUE(composition_text.underlines[0].thick);
}
TEST_F(InputMethodIBusTest, SurroundingText_NoSelectionTest) {
ime_->Init(true);
// Click a text input form.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// Set the TextInputClient behaviors.
surrounding_text_ = UTF8ToUTF16("abcdef");
text_range_ = ui::Range(0, 6);
selection_range_ = ui::Range(3, 3);
// Set the verifier for SetSurroundingText mock call.
SetSurroundingTextVerifier verifier(UTF16ToUTF8(surrounding_text_), 3, 3);
mock_ibus_input_context_client_->set_set_surrounding_text_handler(
base::Bind(&SetSurroundingTextVerifier::Verify,
base::Unretained(&verifier)));
ime_->OnCaretBoundsChanged(this);
// Check the call count.
EXPECT_EQ(1,
mock_ibus_input_context_client_->set_surrounding_text_call_count());
}
TEST_F(InputMethodIBusTest, SurroundingText_SelectionTest) {
ime_->Init(true);
// Click a text input form.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// Set the TextInputClient behaviors.
surrounding_text_ = UTF8ToUTF16("abcdef");
text_range_ = ui::Range(0, 6);
selection_range_ = ui::Range(2, 5);
// Set the verifier for SetSurroundingText mock call.
SetSurroundingTextVerifier verifier(UTF16ToUTF8(surrounding_text_), 2, 5);
mock_ibus_input_context_client_->set_set_surrounding_text_handler(
base::Bind(&SetSurroundingTextVerifier::Verify,
base::Unretained(&verifier)));
ime_->OnCaretBoundsChanged(this);
// Check the call count.
EXPECT_EQ(1,
mock_ibus_input_context_client_->set_surrounding_text_call_count());
}
TEST_F(InputMethodIBusTest, SurroundingText_PartialText) {
ime_->Init(true);
// Click a text input form.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// Set the TextInputClient behaviors.
surrounding_text_ = UTF8ToUTF16("abcdefghij");
text_range_ = ui::Range(5, 10);
selection_range_ = ui::Range(7, 9);
// Set the verifier for SetSurroundingText mock call.
// Here (2, 4) is selection range in expected surrounding text coordinates.
SetSurroundingTextVerifier verifier("fghij", 2, 4);
mock_ibus_input_context_client_->set_set_surrounding_text_handler(
base::Bind(&SetSurroundingTextVerifier::Verify,
base::Unretained(&verifier)));
ime_->OnCaretBoundsChanged(this);
// Check the call count.
EXPECT_EQ(1,
mock_ibus_input_context_client_->set_surrounding_text_call_count());
}
TEST_F(InputMethodIBusTest, SurroundingText_BecomeEmptyText) {
ime_->Init(true);
// Click a text input form.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
// Start the daemon.
InitIBusBus();
SetCreateContextSuccessHandler();
mock_ibus_daemon_controller_->EmulateConnect();
// 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_ = ui::Range(0, 0);
selection_range_ = ui::Range::InvalidRange();
// Set the verifier for SetSurroundingText mock call.
SetSurroundingTextVerifier verifier("", 0, 0);
mock_ibus_input_context_client_->set_set_surrounding_text_handler(
base::Bind(&SetSurroundingTextVerifier::Verify,
base::Unretained(&verifier)));
ime_->OnCaretBoundsChanged(this);
// Check the call count.
EXPECT_EQ(0,
mock_ibus_input_context_client_->set_surrounding_text_call_count());
// Should not be called twice with same condition.
ime_->OnCaretBoundsChanged(this);
EXPECT_EQ(0,
mock_ibus_input_context_client_->set_surrounding_text_call_count());
}
class InputMethodIBusKeyEventTest : public InputMethodIBusTest {
public:
InputMethodIBusKeyEventTest() {}
virtual ~InputMethodIBusKeyEventTest() {}
virtual void SetUp() OVERRIDE {
InputMethodIBusTest::SetUp();
InitIBusBus();
SetCreateContextSuccessHandler();
ime_->Init(true);
mock_ibus_daemon_controller_->EmulateConnect();
mock_ibus_input_context_client_->SetIsXKBLayout(false);
}
DISALLOW_COPY_AND_ASSIGN(InputMethodIBusKeyEventTest);
};
TEST_F(InputMethodIBusKeyEventTest, KeyEventConsumeTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
SynchronousKeyEventHandler success_consume_handler(kTestIBusKeyVal1,
kTestIBusKeyCode1,
kTestIBusState1,
KEYEVENT_CONSUME);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&SynchronousKeyEventHandler::Run,
base::Unretained(&success_consume_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
// Check result
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, KeyEventNotConsumeTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
SynchronousKeyEventHandler success_nonconsume_handler(kTestIBusKeyVal1,
kTestIBusKeyCode1,
kTestIBusState1,
KEYEVENT_NOT_CONSUME);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&SynchronousKeyEventHandler::Run,
base::Unretained(&success_nonconsume_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, KeyEventFailTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
SynchronousKeyEventHandler fail_handler(kTestIBusKeyVal1,
kTestIBusKeyCode1,
kTestIBusState1,
KEYEVENT_ERROR);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&SynchronousKeyEventHandler::Run,
base::Unretained(&fail_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
// Check result
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
// If some error is happend, key should not be consumed.
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, KeyEventDelayResponseSuccessTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler success_consume_handler(kTestIBusKeyVal1,
kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&success_consume_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
// Do callback.
success_consume_handler.RunCallback(KEYEVENT_CONSUME);
// Check the results
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, KeyEventDelayResponseFailTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler fail_handler(kTestIBusKeyVal1, kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&fail_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
// Do callback.
fail_handler.RunCallback(KEYEVENT_ERROR);
// Check the results
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, MultiKeyEventDelayResponseSuccessTest) {
// Preparation
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result for first key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler1(kTestIBusKeyVal1, kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler1)));
// Do key event.
ime_->DispatchKeyEvent(&event);
// Set up IBusKeyEventFromNativeKeyEvent result for second key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState2;
// Set up yet another ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler2(kTestIBusKeyVal2, kTestIBusKeyCode2,
kTestIBusState2);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler2)));
// Do key event again.
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(2,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
// Do callback for first key event.
handler1.RunCallback(KEYEVENT_CONSUME);
// Check the results for first key event.
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled);
// Do callback for second key event.
handler2.RunCallback(KEYEVENT_NOT_CONSUME);
// Check the results for second key event.
EXPECT_EQ(2, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, MultiKeyEventDelayResponseFailTest) {
// Preparation
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result for first key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler1(kTestIBusKeyVal1, kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler1)));
// Do key event.
ime_->DispatchKeyEvent(&event);
// Set up IBusKeyEventFromNativeKeyEvent result for second key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState2;
// Set up yet another ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler2(kTestIBusKeyVal2, kTestIBusKeyCode2,
kTestIBusState2);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler2)));
// Do key event again.
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(2,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
// Do callback for first key event.
handler1.RunCallback(KEYEVENT_ERROR);
// Check the results for first key event.
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
// Do callback for second key event.
handler2.RunCallback(KEYEVENT_ERROR);
// Check the results for second key event.
EXPECT_EQ(2, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest,
MultiKeyEventDelayResponseSuccessFailMixTest) {
// Preparation
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result for first key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler1(kTestIBusKeyVal1, kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler1)));
// Do key event.
ime_->DispatchKeyEvent(&event);
// Set up IBusKeyEventFromNativeKeyEvent result for second key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode2;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState2;
// Set up yet another ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler2(kTestIBusKeyVal2, kTestIBusKeyCode2,
kTestIBusState2);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler2)));
// Do key event again.
ime_->DispatchKeyEvent(&event);
// Set up IBusKeyEventFromNativeKeyEvent result for second key event.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal3;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode3;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState3;
// Set up yet another ProcessKeyEvent handler.
AsynchronousKeyEventHandler handler3(kTestIBusKeyVal3, kTestIBusKeyCode3,
kTestIBusState3);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&handler3)));
// Do key event again.
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(3,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
// Do callback for first key event.
handler1.RunCallback(KEYEVENT_CONSUME);
// Check the results for first key event.
EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled);
// Do callback for second key event.
handler2.RunCallback(KEYEVENT_ERROR);
// Check the results for second key event.
EXPECT_EQ(2, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled);
// Do callback for first key event.
handler3.RunCallback(KEYEVENT_CONSUME);
// Check the results for first key event.
EXPECT_EQ(3, ime_->process_key_event_post_ime_call_count());
EXPECT_TRUE(IsEqualXKeyEvent(event,
ime_->process_key_event_post_ime_args().event));
EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled);
}
TEST_F(InputMethodIBusKeyEventTest, KeyEventDelayResponseResetTest) {
XEvent event = {};
event.xkey.type = KeyPress;
// Set up IBusKeyEventFromNativeKeyEvent result.
ime_->mutable_ibus_key_event_from_native_key_event_result()->keyval
= kTestIBusKeyVal1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->keycode
= kTestIBusKeyCode1;
ime_->mutable_ibus_key_event_from_native_key_event_result()->state
= kTestIBusState1;
// Set up ProcessKeyEvent handler.
AsynchronousKeyEventHandler success_consume_handler(kTestIBusKeyVal1,
kTestIBusKeyCode1,
kTestIBusState1);
mock_ibus_input_context_client_->set_process_key_event_handler(
base::Bind(&AsynchronousKeyEventHandler::Run,
base::Unretained(&success_consume_handler)));
// Do key event.
input_type_ = TEXT_INPUT_TYPE_TEXT;
ime_->OnTextInputTypeChanged(this);
ime_->DispatchKeyEvent(&event);
// Check before state.
EXPECT_EQ(1,
mock_ibus_input_context_client_->process_key_event_call_count());
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
ime_->ResetContext();
// Do callback.
success_consume_handler.RunCallback(KEYEVENT_CONSUME);
EXPECT_EQ(0, ime_->process_key_event_post_ime_call_count());
}
// TODO(nona): Introduce ProcessKeyEventPostIME tests(crbug.com/156593).
} // namespace ui