blob: e1b4d863642ac96c5493dd16abc8a793035ca368 [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/model/model.h"
#include "chrome/browser/vr/test/animation_utils.h"
#include "chrome/browser/vr/test/constants.h"
#include "chrome/browser/vr/test/mock_keyboard_delegate.h"
#include "chrome/browser/vr/test/mock_text_input_delegate.h"
#include "chrome/browser/vr/test/ui_test.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 "ui/gfx/render_text.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::StrictMock;
namespace vr {
namespace {
constexpr float kFontHeightMeters = 0.050f;
}
class TextInputSceneTest : public UiTest {
public:
void SetUp() override {
UiTest::SetUp();
CreateScene(kNotInWebVr);
// Make test text input.
text_input_delegate_ =
std::make_unique<StrictMock<MockTextInputDelegate>>();
edited_text_ = std::make_unique<EditedText>(base::ASCIIToUTF16("asdfg"));
auto text_input = CreateTextInput(1, model_, edited_text_.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<EditedText> edited_text_;
testing::Sequence in_sequence_;
private:
std::unique_ptr<TextInput> CreateTextInput(
float font_height_meters,
Model* model,
EditedText* text_input_model,
TextInputDelegate* text_input_delegate) {
auto text_input = std::make_unique<TextInput>(
font_height_meters,
base::BindRepeating(
[](EditedText* model, const EditedText& text_input_info) {
*model = text_input_info;
},
base::Unretained(text_input_model)));
EventHandlers event_handlers;
event_handlers.focus_change = base::BindRepeating(
[](Model* model, TextInput* text_input, EditedText* text_input_info,
bool focused) {
if (focused) {
model->editing_input = true;
text_input->UpdateInput(*text_input_info);
} else {
model->editing_input = false;
}
},
base::Unretained(model), base::Unretained(text_input.get()),
base::Unretained(text_input_model));
text_input->SetSize(1.0f, 0.1f);
text_input->set_event_handlers(event_handlers);
text_input->SetDrawPhase(kPhaseNone);
text_input->SetTextInputDelegate(text_input_delegate);
text_input->AddBinding(std::make_unique<Binding<EditedText>>(
VR_BIND_LAMBDA([](EditedText* info) { return *info; },
base::Unretained(text_input_model)),
VR_BIND_LAMBDA([](TextInput* e,
const EditedText& value) { e->UpdateInput(value); },
base::Unretained(text_input.get()))));
return text_input;
}
};
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(), base::TimeTicks());
// Focusing on an input field should show the keyboard and tell the delegate
// the field's content.
EXPECT_CALL(*text_input_delegate_, UpdateInput(edited_text_->current))
.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());
EXPECT_EQ(*edited_text_, text_input_->edited_text());
// 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());
EXPECT_EQ(*edited_text_, text_input_->edited_text());
}
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_);
EditedText info(base::ASCIIToUTF16("asdfgh"));
text_input_->OnInputEdited(info);
EXPECT_TRUE(OnBeginFrame());
EXPECT_EQ(info, *edited_text_);
}
TEST_F(TextInputSceneTest, ClickOnTextGrabsFocus) {
EXPECT_CALL(*text_input_delegate_, RequestFocus(_));
text_input_->get_text_element()->OnButtonUp({0, 0}, base::TimeTicks());
}
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, OnHoverMove(_)).InSequence(s);
EXPECT_CALL(*kb_delegate, OnButtonDown(_)).InSequence(s);
EXPECT_CALL(*kb_delegate, OnButtonUp(_)).InSequence(s);
gfx::PointF p;
keyboard->OnHoverEnter(p, base::TimeTicks());
keyboard->OnHoverLeave(base::TimeTicks());
keyboard->OnHoverMove(p, base::TimeTicks());
keyboard->OnButtonDown(p, base::TimeTicks());
keyboard->OnButtonUp(p, base::TimeTicks());
}
TEST(TextInputTest, HintText) {
UiScene scene;
auto instance = std::make_unique<TextInput>(
kFontHeightMeters, TextInput::OnInputEditedCallback());
EventHandlers event_handlers;
event_handlers.focus_change = TextInput::OnFocusChangedCallback();
instance->set_event_handlers(event_handlers);
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.
EditedText info(base::UTF8ToUTF16("text"));
element->UpdateInput(info);
scene.OnBeginFrame(base::TimeTicks(), kStartHeadPose);
EXPECT_EQ(element->get_hint_element()->GetTargetOpacity(), 0);
}
TEST(TextInputTest, CursorBlinking) {
UiScene scene;
auto instance = std::make_unique<TextInput>(
kFontHeightMeters, TextInput::OnInputEditedCallback());
EventHandlers event_handlers;
event_handlers.focus_change = TextInput::OnFocusChangedCallback();
instance->set_event_handlers(event_handlers);
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);
// With a selection, the cursor should not be blinking or visible.
EditedText info(base::UTF8ToUTF16("text"));
info.current.selection_start = 0;
info.current.selection_end = info.current.text.size();
element->UpdateInput(info);
EXPECT_EQ(0.f, element->get_cursor_element()->GetTargetOpacity());
for (int ms = 0; ms <= 2000; ms += 100) {
scene.OnBeginFrame(MsToTicks(ms), kStartHeadPose);
EXPECT_EQ(0.f, element->get_cursor_element()->GetTargetOpacity());
}
}
// TODO(cjgrant): Have this test, and others similar, check the actual position
// of the cursor element. To make this work, the OnBeginFrame logic needs to be
// updated to perform more of the measurement and layout steps in a test
// environment. As of now, much of this is skipped due to lack of a GL context.
TEST(TextInputTest, CursorPositionUpdatesOnKeyboardInput) {
auto element = std::make_unique<TextInput>(
kFontHeightMeters, TextInput::OnInputEditedCallback());
EventHandlers event_handlers;
event_handlers.focus_change = TextInput::OnFocusChangedCallback();
element->set_event_handlers(event_handlers);
element->SetSize(1, 0);
EditedText info(base::UTF8ToUTF16("text"));
info.current.selection_start = 0;
info.current.selection_end = 0;
element->UpdateInput(info);
element->get_text_element()->PrepareToDrawForTest();
int x1 = element->get_text_element()->GetRawCursorBounds().x();
info.current.selection_start = 1;
info.current.selection_end = 1;
element->UpdateInput(info);
element->get_text_element()->PrepareToDrawForTest();
int x2 = element->get_text_element()->GetRawCursorBounds().x();
EXPECT_LT(x1, x2);
}
TEST(TextInputTest, CursorPositionUpdatesOnClicks) {
auto element = std::make_unique<TextInput>(
kFontHeightMeters, TextInput::OnInputEditedCallback());
EventHandlers event_handlers;
event_handlers.focus_change = TextInput::OnFocusChangedCallback();
element->set_event_handlers(event_handlers);
element->SetSize(1, 0);
EditedText info(base::UTF8ToUTF16("text"));
element->UpdateInput(info);
element->get_text_element()->PrepareToDrawForTest();
// Click on the left edge of the field.
element->OnButtonDown(gfx::PointF(0.0, 0.5), base::TimeTicks());
element->OnButtonUp(gfx::PointF(0.0, 0.5), base::TimeTicks());
element->get_text_element()->PrepareToDrawForTest();
auto x1 = element->get_text_element()->GetRawCursorBounds().x();
// Click on the right edge of the field.
element->OnButtonDown(gfx::PointF(1.0, 0.5), base::TimeTicks());
element->OnButtonUp(gfx::PointF(1.0, 0.5), base::TimeTicks());
element->get_text_element()->PrepareToDrawForTest();
auto x2 = element->get_text_element()->GetRawCursorBounds().x();
EXPECT_EQ(x1, 0);
EXPECT_GT(x2, 0);
// Set a selection and ensure that a click clears it.
info.current.selection_start = 0;
info.current.selection_end = info.current.text.size();
element->UpdateInput(info);
EXPECT_GT(element->edited_text().current.SelectionSize(), 0u);
element->OnButtonDown(gfx::PointF(0.5, 0.5), base::TimeTicks());
element->OnButtonUp(gfx::PointF(0.5, 0.5), base::TimeTicks());
EXPECT_EQ(element->edited_text().current.SelectionSize(), 0u);
}
} // namespace vr