blob: f46e0acd76c05c56bad9c3a82dc62cb3bd1c4fff [file] [log] [blame]
// Copyright 2018 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 "components/exo/text_input.h"
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "components/exo/buffer.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/input_method_observer.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/views/widget/widget.h"
using testing::_;
namespace exo {
namespace {
class MockTextInputDelegate : public TextInput::Delegate {
public:
MockTextInputDelegate() = default;
// TextInput::Delegate:
MOCK_METHOD0(Activated, void());
MOCK_METHOD0(Deactivated, void());
MOCK_METHOD1(OnVirtualKeyboardVisibilityChanged, void(bool));
MOCK_METHOD1(SetCompositionText, void(const ui::CompositionText&));
MOCK_METHOD1(Commit, void(const base::string16&));
MOCK_METHOD1(SetCursor, void(const gfx::Range&));
MOCK_METHOD1(DeleteSurroundingText, void(const gfx::Range&));
MOCK_METHOD1(SendKey, void(const ui::KeyEvent&));
MOCK_METHOD1(OnLanguageChanged, void(const std::string&));
MOCK_METHOD1(OnTextDirectionChanged,
void(base::i18n::TextDirection direction));
private:
DISALLOW_COPY_AND_ASSIGN(MockTextInputDelegate);
};
class TestingInputMethodObserver : public ui::InputMethodObserver {
public:
TestingInputMethodObserver(ui::InputMethod* input_method)
: input_method_(input_method) {
input_method_->AddObserver(this);
}
~TestingInputMethodObserver() override {
input_method_->RemoveObserver(this);
}
// ui::InputMethodObserver
MOCK_METHOD0(OnFocus, void());
MOCK_METHOD0(OnBlur, void());
MOCK_METHOD1(OnCaretBoundsChanged, void(const ui::TextInputClient*));
MOCK_METHOD1(OnTextInputStateChanged, void(const ui::TextInputClient*));
MOCK_METHOD1(OnInputMethodDestroyed, void(const ui::InputMethod*));
MOCK_METHOD0(OnShowVirtualKeyboardIfEnabled, void());
private:
ui::InputMethod* input_method_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(TestingInputMethodObserver);
};
class TextInputTest : public test::ExoTestBase {
public:
TextInputTest() = default;
void SetUp() override {
test::ExoTestBase::SetUp();
text_input_ =
std::make_unique<TextInput>(std::make_unique<MockTextInputDelegate>());
SetupSurface();
}
void TearDown() override {
TearDownSurface();
text_input_.reset();
test::ExoTestBase::TearDown();
}
void SetupSurface() {
gfx::Size buffer_size(32, 32);
buffer_ = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(buffer_size));
surface_ = std::make_unique<Surface>();
shell_surface_ = std::make_unique<ShellSurface>(surface_.get());
surface_->Attach(buffer_.get());
surface_->Commit();
gfx::Point origin(100, 100);
shell_surface_->SetGeometry(gfx::Rect(origin, buffer_size));
}
void TearDownSurface() {
shell_surface_.reset();
surface_.reset();
buffer_.reset();
}
protected:
TextInput* text_input() { return text_input_.get(); }
MockTextInputDelegate* delegate() {
return static_cast<MockTextInputDelegate*>(text_input_->delegate());
}
Surface* surface() { return surface_.get(); }
ui::InputMethod* GetInputMethod() {
return surface_->window()->GetHost()->GetInputMethod();
}
void SetCompositionText(const std::string& utf8) {
ui::CompositionText t;
t.text = base::UTF8ToUTF16(utf8);
t.selection = gfx::Range(1u);
t.ime_text_spans.push_back(
ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, t.text.size(),
ui::ImeTextSpan::Thickness::kThick));
EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
text_input()->SetCompositionText(t);
}
private:
std::unique_ptr<TextInput> text_input_;
std::unique_ptr<Buffer> buffer_;
std::unique_ptr<Surface> surface_;
std::unique_ptr<ShellSurface> shell_surface_;
DISALLOW_COPY_AND_ASSIGN(TextInputTest);
};
TEST_F(TextInputTest, Activate) {
EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, text_input()->GetTextInputType());
EXPECT_EQ(ui::TEXT_INPUT_MODE_DEFAULT, text_input()->GetTextInputMode());
EXPECT_CALL(*delegate(), Activated).Times(1);
text_input()->Activate(surface());
EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, text_input()->GetTextInputType());
EXPECT_EQ(ui::TEXT_INPUT_MODE_TEXT, text_input()->GetTextInputMode());
EXPECT_EQ(0, text_input()->GetTextInputFlags());
EXPECT_CALL(*delegate(), Deactivated).Times(1);
text_input()->Deactivate();
EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, text_input()->GetTextInputType());
EXPECT_EQ(ui::TEXT_INPUT_MODE_DEFAULT, text_input()->GetTextInputMode());
}
TEST_F(TextInputTest, ShowVirtualKeyboardIfEnabled) {
TestingInputMethodObserver observer(GetInputMethod());
EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
EXPECT_CALL(*delegate(), Activated).Times(1);
text_input()->Activate(surface());
EXPECT_CALL(observer, OnShowVirtualKeyboardIfEnabled)
.WillOnce(testing::Invoke(
[this]() { text_input()->OnKeyboardVisibilityChanged(true); }));
EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
text_input()->ShowVirtualKeyboardIfEnabled();
EXPECT_CALL(*delegate(), Deactivated).Times(1);
text_input()->Deactivate();
}
TEST_F(TextInputTest, ShowVirtualKeyboardIfEnabledBeforeActivated) {
TestingInputMethodObserver observer(GetInputMethod());
// ShowVirtualKeyboardIfEnabled before activation.
text_input()->ShowVirtualKeyboardIfEnabled();
EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
EXPECT_CALL(observer, OnShowVirtualKeyboardIfEnabled)
.WillOnce(testing::Invoke(
[this]() { text_input()->OnKeyboardVisibilityChanged(true); }));
EXPECT_CALL(*delegate(), Activated).Times(1);
EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
text_input()->Activate(surface());
EXPECT_CALL(*delegate(), Deactivated).Times(1);
}
TEST_F(TextInputTest, SetTypeModeFlag) {
TestingInputMethodObserver observer(GetInputMethod());
EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
EXPECT_CALL(*delegate(), Activated).Times(1);
text_input()->Activate(surface());
EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, text_input()->GetTextInputType());
EXPECT_EQ(ui::TEXT_INPUT_MODE_TEXT, text_input()->GetTextInputMode());
EXPECT_EQ(0, text_input()->GetTextInputFlags());
EXPECT_TRUE(text_input()->ShouldDoLearning());
int flags = ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF |
ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_NONE;
EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
text_input()->SetTypeModeFlags(ui::TEXT_INPUT_TYPE_URL,
ui::TEXT_INPUT_MODE_URL, flags, false);
EXPECT_EQ(ui::TEXT_INPUT_TYPE_URL, text_input()->GetTextInputType());
EXPECT_EQ(ui::TEXT_INPUT_MODE_URL, text_input()->GetTextInputMode());
EXPECT_EQ(flags, text_input()->GetTextInputFlags());
EXPECT_FALSE(text_input()->ShouldDoLearning());
EXPECT_CALL(*delegate(), Deactivated).Times(1);
}
TEST_F(TextInputTest, CaretBounds) {
TestingInputMethodObserver observer(GetInputMethod());
EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
EXPECT_CALL(*delegate(), Activated).Times(1);
text_input()->Activate(surface());
gfx::Rect bounds(10, 10, 0, 16);
EXPECT_CALL(observer, OnCaretBoundsChanged(text_input())).Times(1);
text_input()->SetCaretBounds(bounds);
EXPECT_EQ(bounds.size().ToString(),
text_input()->GetCaretBounds().size().ToString());
gfx::Point origin = surface()->window()->GetBoundsInScreen().origin();
origin += bounds.OffsetFromOrigin();
EXPECT_EQ(origin.ToString(),
text_input()->GetCaretBounds().origin().ToString());
EXPECT_CALL(*delegate(), Deactivated).Times(1);
}
TEST_F(TextInputTest, CompositionText) {
SetCompositionText("composition");
ui::CompositionText empty;
EXPECT_CALL(*delegate(), SetCompositionText(empty)).Times(1);
text_input()->ClearCompositionText();
}
TEST_F(TextInputTest, CommitCompositionText) {
SetCompositionText("composition");
EXPECT_CALL(*delegate(), Commit(base::UTF8ToUTF16("composition"))).Times(1);
text_input()->ConfirmCompositionText();
}
TEST_F(TextInputTest, Commit) {
base::string16 s = base::ASCIIToUTF16("commit text");
EXPECT_CALL(*delegate(), Commit(s)).Times(1);
text_input()->InsertText(s);
}
TEST_F(TextInputTest, InsertChar) {
ui::KeyEvent ev(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, 0);
EXPECT_CALL(*delegate(), SendKey(testing::Ref(ev))).Times(1);
text_input()->InsertChar(ev);
}
TEST_F(TextInputTest, InsertCharNormalKey) {
base::char16 ch = 'x';
ui::KeyEvent ev(ch, ui::VKEY_X, ui::DomCode::NONE, 0);
EXPECT_CALL(*delegate(), Commit(base::string16(1, ch))).Times(1);
EXPECT_CALL(*delegate(), SendKey(_)).Times(0);
text_input()->InsertChar(ev);
}
TEST_F(TextInputTest, SurroundingText) {
gfx::Range range;
EXPECT_FALSE(text_input()->GetTextRange(&range));
EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
EXPECT_FALSE(text_input()->GetEditableSelectionRange(&range));
base::string16 got_text;
EXPECT_FALSE(text_input()->GetTextFromRange(gfx::Range(0, 1), &got_text));
base::string16 text = base::UTF8ToUTF16("surrounding\xE3\x80\x80text");
text_input()->SetSurroundingText(text, 11, 12);
EXPECT_TRUE(text_input()->GetTextRange(&range));
EXPECT_EQ(gfx::Range(0, text.size()).ToString(), range.ToString());
EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
EXPECT_TRUE(text_input()->GetTextFromRange(gfx::Range(11, 12), &got_text));
EXPECT_EQ(text.substr(11, 1), got_text);
// DeleteSurroundingText receives the range in UTF8 -- so (11, 14) range is
// expected.
EXPECT_CALL(*delegate(), DeleteSurroundingText(gfx::Range(11, 14))).Times(1);
text_input()->ExtendSelectionAndDelete(0, 0);
size_t composition_size = std::string("composition").size();
SetCompositionText("composition");
EXPECT_TRUE(text_input()->GetCompositionTextRange(&range));
EXPECT_EQ(gfx::Range(11, 11 + composition_size).ToString(), range.ToString());
EXPECT_TRUE(text_input()->GetTextRange(&range));
EXPECT_EQ(gfx::Range(0, text.size() - 1 + composition_size).ToString(),
range.ToString());
EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
}
TEST_F(TextInputTest, GetTextRange) {
base::string16 text = base::UTF8ToUTF16("surrounding text");
text_input()->SetSurroundingText(text, 11, 12);
SetCompositionText("composition");
const struct {
gfx::Range range;
std::string expected;
} kTestCases[] = {
{gfx::Range(0, 3), "sur"},
{gfx::Range(10, 13), "gco"},
{gfx::Range(10, 23), "gcompositiont"},
{gfx::Range(12, 15), "omp"},
{gfx::Range(12, 23), "ompositiont"},
{gfx::Range(22, 25), "tex"},
};
for (auto& c : kTestCases) {
base::string16 result;
EXPECT_TRUE(text_input()->GetTextFromRange(c.range, &result))
<< c.range.ToString();
EXPECT_EQ(base::UTF8ToUTF16(c.expected), result) << c.range.ToString();
}
}
} // anonymous namespace
} // namespace exo