blob: a5f450cb1bf0f0dbc7e41ace80d3281e9f437d31 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/vr/ui_input_manager.h"
#include <memory>
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/vr/databinding/binding.h"
#include "chrome/browser/vr/elements/keyboard.h"
#include "chrome/browser/vr/elements/text.h"
#include "chrome/browser/vr/elements/ui_element.h"
#include "chrome/browser/vr/keyboard_delegate.h"
#include "chrome/browser/vr/model/camera_model.h"
#include "chrome/browser/vr/model/model.h"
#include "chrome/browser/vr/test/animation_utils.h"
#include "chrome/browser/vr/test/constants.h"
#include "chrome/browser/vr/test/ui_test.h"
#include "chrome/browser/vr/text_input_delegate.h"
#include "chrome/browser/vr/ui_scene.h"
#include "chrome/browser/vr/ui_scene_constants.h"
#include "chrome/browser/vr/ui_scene_creator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/WebKit/public/platform/WebGestureEvent.h"
#include "ui/gfx/render_text.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::StrictMock;
namespace vr {
class MockTextInputDelegate : public TextInputDelegate {
public:
MockTextInputDelegate() = default;
~MockTextInputDelegate() override = default;
MOCK_METHOD1(UpdateInput, void(const TextInputInfo& info));
MOCK_METHOD1(RequestFocus, void(int));
private:
DISALLOW_COPY_AND_ASSIGN(MockTextInputDelegate);
};
class MockKeyboardDelegate : public KeyboardDelegate {
public:
MockKeyboardDelegate() = default;
~MockKeyboardDelegate() override = default;
MOCK_METHOD0(ShowKeyboard, void());
MOCK_METHOD0(HideKeyboard, void());
MOCK_METHOD1(SetTransform, void(const gfx::Transform&));
MOCK_METHOD3(HitTest,
bool(const gfx::Point3F&, const gfx::Point3F&, gfx::Point3F*));
MOCK_METHOD0(OnBeginFrame, void());
MOCK_METHOD1(Draw, void(const CameraModel&));
MOCK_METHOD1(OnHoverEnter, void(const gfx::PointF&));
MOCK_METHOD0(OnHoverLeave, void());
MOCK_METHOD1(OnMove, void(const gfx::PointF&));
MOCK_METHOD1(OnButtonDown, void(const gfx::PointF&));
MOCK_METHOD1(OnButtonUp, void(const gfx::PointF&));
private:
DISALLOW_COPY_AND_ASSIGN(MockKeyboardDelegate);
};
class TextInputSceneTest : public UiTest {
public:
void SetUp() override {
UiTest::SetUp();
CreateScene(kNotInCct, kNotInWebVr);
// Make test text input.
text_input_delegate_ =
std::make_unique<StrictMock<MockTextInputDelegate>>();
text_input_info_ = std::make_unique<TextInputInfo>();
auto text_input = UiSceneCreator::CreateTextInput(
1, model_, text_input_info_.get(), text_input_delegate_.get());
text_input_ = text_input.get();
scene_->AddUiElement(k2dBrowsingForeground, std::move(text_input));
EXPECT_TRUE(OnBeginFrame());
}
protected:
TextInput* text_input_;
std::unique_ptr<StrictMock<MockTextInputDelegate>> text_input_delegate_;
std::unique_ptr<TextInputInfo> text_input_info_;
testing::Sequence in_sequence_;
};
TEST_F(TextInputSceneTest, InputFieldFocus) {
// Set mock delegates.
auto* kb = static_cast<Keyboard*>(scene_->GetUiElementByName(kKeyboard));
auto kb_delegate = std::make_unique<StrictMock<MockKeyboardDelegate>>();
EXPECT_CALL(*kb_delegate, HideKeyboard()).InSequence(in_sequence_);
kb->SetKeyboardDelegate(kb_delegate.get());
// Clicking on the text field should request focus.
EXPECT_CALL(*text_input_delegate_, RequestFocus(_)).InSequence(in_sequence_);
text_input_->OnButtonUp(gfx::PointF());
// Focusing on an input field should show the keyboard and tell the delegate
// the field's content.
EXPECT_CALL(*text_input_delegate_, UpdateInput(_)).InSequence(in_sequence_);
text_input_->OnFocusChanged(true);
EXPECT_CALL(*kb_delegate, ShowKeyboard()).InSequence(in_sequence_);
EXPECT_CALL(*kb_delegate, OnBeginFrame()).InSequence(in_sequence_);
EXPECT_CALL(*kb_delegate, SetTransform(_)).InSequence(in_sequence_);
EXPECT_TRUE(OnBeginFrame());
// Focusing out of an input field should hide the keyboard.
text_input_->OnFocusChanged(false);
EXPECT_CALL(*kb_delegate, HideKeyboard()).InSequence(in_sequence_);
EXPECT_CALL(*kb_delegate, OnBeginFrame()).InSequence(in_sequence_);
EXPECT_CALL(*kb_delegate, SetTransform(_)).InSequence(in_sequence_);
EXPECT_TRUE(OnBeginFrame());
}
TEST_F(TextInputSceneTest, InputFieldEdit) {
// UpdateInput should not be called if the text input doesn't have focus.
EXPECT_CALL(*text_input_delegate_, UpdateInput(_))
.Times(0)
.InSequence(in_sequence_);
text_input_->OnFocusChanged(false);
// Focus on input field.
EXPECT_CALL(*text_input_delegate_, UpdateInput(_)).InSequence(in_sequence_);
text_input_->OnFocusChanged(true);
// Edits from the keyboard update the underlying text input model.
EXPECT_CALL(*text_input_delegate_, UpdateInput(_)).InSequence(in_sequence_);
TextInputInfo info(base::ASCIIToUTF16("asdfg"));
text_input_->OnInputEdited(info);
EXPECT_TRUE(OnBeginFrame());
EXPECT_EQ(info, *text_input_info_);
}
TEST_F(TextInputSceneTest, ClickOnTextGrabsFocus) {
EXPECT_CALL(*text_input_delegate_, RequestFocus(_));
text_input_->get_text_element()->OnButtonUp({0, 0});
}
TEST(TextInputTest, ControllerInteractionsSentToDelegate) {
auto keyboard = std::make_unique<Keyboard>();
auto kb_delegate = std::make_unique<StrictMock<MockKeyboardDelegate>>();
testing::Sequence s;
EXPECT_CALL(*kb_delegate, HideKeyboard()).InSequence(s);
keyboard->SetKeyboardDelegate(kb_delegate.get());
EXPECT_CALL(*kb_delegate, OnHoverEnter(_)).InSequence(s);
EXPECT_CALL(*kb_delegate, OnHoverLeave()).InSequence(s);
EXPECT_CALL(*kb_delegate, OnMove(_)).InSequence(s);
EXPECT_CALL(*kb_delegate, OnButtonDown(_)).InSequence(s);
EXPECT_CALL(*kb_delegate, OnButtonUp(_)).InSequence(s);
gfx::PointF p;
keyboard->OnHoverEnter(p);
keyboard->OnHoverLeave();
keyboard->OnMove(p);
keyboard->OnButtonDown(p);
keyboard->OnButtonUp(p);
}
TEST(TextInputTest, HintText) {
UiScene scene;
auto instance =
std::make_unique<TextInput>(10, TextInput::OnFocusChangedCallback(),
TextInput::OnInputEditedCallback());
instance->SetName(kOmniboxTextField);
instance->SetSize(1, 0);
TextInput* element = instance.get();
scene.root_element().AddChild(std::move(instance));
// Text field is empty, so we should be showing hint text.
scene.OnBeginFrame(base::TimeTicks(), kStartHeadPose);
EXPECT_GT(element->get_hint_element()->GetTargetOpacity(), 0);
// When text enters the field, the hint should disappear.
TextInputInfo info;
info.text = base::UTF8ToUTF16("text");
element->UpdateInput(info);
scene.OnBeginFrame(base::TimeTicks(), kStartHeadPose);
EXPECT_EQ(element->get_hint_element()->GetTargetOpacity(), 0);
}
TEST(TextInputTest, Cursor) {
UiScene scene;
auto instance =
std::make_unique<TextInput>(10, TextInput::OnFocusChangedCallback(),
TextInput::OnInputEditedCallback());
instance->SetName(kOmniboxTextField);
instance->SetSize(1, 0);
TextInput* element = instance.get();
scene.root_element().AddChild(std::move(instance));
// The cursor should not be blinking or visible.
float initial = element->get_cursor_element()->GetTargetOpacity();
EXPECT_EQ(initial, 0.f);
for (int ms = 0; ms <= 2000; ms += 100) {
scene.OnBeginFrame(MsToTicks(ms), kStartHeadPose);
EXPECT_EQ(initial, element->get_cursor_element()->GetTargetOpacity());
}
// When focused, the cursor should start blinking.
element->OnFocusChanged(true);
initial = element->get_cursor_element()->GetTargetOpacity();
bool toggled = false;
for (int ms = 0; ms <= 2000; ms += 100) {
scene.OnBeginFrame(MsToTicks(ms), kStartHeadPose);
if (initial != element->get_cursor_element()->GetTargetOpacity())
toggled = true;
}
EXPECT_TRUE(toggled);
// TODO(cjgrant): Continue with the test cases below, when they're debugged.
#if 0
TextInputInfo info;
info.text = base::UTF8ToUTF16("text");
// When the cursor position moves, the cursor element should move.
element->UpdateInput(info);
auto result = element->get_text_element()->LayOutTextForTest({512, 512});
auto position1 = element->get_text_element()->GetRawCursorBounds();
info.selection_end = 1;
element->UpdateInput(info);
element->get_text_element()->LayOutTextForTest({512, 512});
auto position2 = element->get_text_element()->GetRawCursorBounds();
EXPECT_NE(position1, position2);
#endif
}
} // namespace vr