blob: 73d747969c5242fe761e6a0061c885865f3809e7 [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 "src/ui/bin/root_presenter/virtual_keyboard_controller.h"
#include <fuchsia/input/virtualkeyboard/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <optional>
#include <gtest/gtest.h>
#include <src/lib/testing/loop_fixture/test_loop_fixture.h>
#include "src/ui/bin/root_presenter/virtual_keyboard_coordinator.h"
namespace root_presenter {
namespace virtual_keyboard_controller {
namespace {
class FakeVirtualKeyboardCoordinator : public VirtualKeyboardCoordinator {
public:
FakeVirtualKeyboardCoordinator() : weak_ptr_factory_(this) {}
virtual ~FakeVirtualKeyboardCoordinator() = default;
// |VirtualKeyboardCoordinator|
void NotifyVisibilityChange(
bool is_visible, fuchsia::input::virtualkeyboard::VisibilityChangeReason reason) override {
FX_NOTIMPLEMENTED();
}
void NotifyManagerError(zx_status_t error) override { FX_NOTIMPLEMENTED(); }
void RequestTypeAndVisibility(fuchsia::input::virtualkeyboard::TextType text_type,
bool is_visible) override {
requested_text_type_ = text_type;
want_visible_ = is_visible;
}
// Test support.
void Reset() {
want_visible_.reset();
requested_text_type_.reset();
}
const auto& want_visible() { return want_visible_; }
const auto& requested_text_type() { return requested_text_type_; }
fxl::WeakPtr<FakeVirtualKeyboardCoordinator> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
std::optional<bool> want_visible_;
std::optional<fuchsia::input::virtualkeyboard::TextType> requested_text_type_;
fxl::WeakPtrFactory<FakeVirtualKeyboardCoordinator> weak_ptr_factory_;
};
class VirtualKeyboardControllerTest : public gtest::TestLoopFixture {
protected:
VirtualKeyboardControllerTest() { view_ref_pair_ = scenic::ViewRefPair::New(); }
fuchsia::ui::views::ViewRef view_ref() const {
fuchsia::ui::views::ViewRef clone;
EXPECT_EQ(ZX_OK, view_ref_pair_.view_ref.Clone(&clone));
return clone;
}
fxl::WeakPtr<FakeVirtualKeyboardCoordinator> coordinator() { return coordinator_.GetWeakPtr(); }
private:
scenic::ViewRefPair view_ref_pair_;
FakeVirtualKeyboardCoordinator coordinator_;
};
TEST_F(VirtualKeyboardControllerTest, FirstWatchReturnsImmediately) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
bool was_called = false;
controller.WatchVisibility([&was_called](bool visibility) { was_called = true; });
ASSERT_TRUE(was_called);
}
TEST_F(VirtualKeyboardControllerTest, InitialVisibilityIsFalse) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
std::optional<bool> is_visible;
controller.WatchVisibility([&is_visible](bool visibility) { is_visible = visibility; });
ASSERT_EQ(false, is_visible);
}
TEST_F(VirtualKeyboardControllerTest, SecondWatchHangsUntilChange) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
// Make the initial call to WatchVisibility(), which invokes its callback immediately, so that
// the next call will block until a visibility change.
controller.WatchVisibility([](bool visibility) {});
// Invoke WatchVisibility() again without any change to visibility. WatchVisibility() should _not_
// invoke its callback.
bool was_called = false;
controller.WatchVisibility([&was_called](bool visibility) { was_called = true; });
ASSERT_FALSE(was_called);
// Make a no-op request. WatchVisibility() should _not_ invoke its callback.
controller.RequestHide();
ASSERT_FALSE(was_called);
// Make a request that changes visibility. WatchVisibility() _should_ invoke its callback.
controller.RequestShow();
ASSERT_TRUE(was_called);
}
TEST_F(VirtualKeyboardControllerTest, SecondWatchReturnsImmediatelyIfAlreadyChanged) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
// Make the initial call to WatchVisibility(), which invokes its callback immediately, so that
// we know we're exercise the second-and-later logic.
controller.WatchVisibility([](bool visibility) {});
// Make a change before invoking WatchVisibility().
controller.RequestShow();
// Invoke WatchVisibility() again. The callback should be invoked immediately.
bool was_called = false;
controller.WatchVisibility([&was_called](bool visibility) { was_called = true; });
ASSERT_TRUE(was_called);
}
TEST_F(VirtualKeyboardControllerTest, FirstWatchCallbackIsOnlyInvokedOnce) {
// Make the initial call to WatchVisibility(), which invokes its callback immediately.
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
size_t n_callbacks = 0;
controller.WatchVisibility([&n_callbacks](bool visibility) { ++n_callbacks; });
ASSERT_EQ(1u, n_callbacks);
// Watches are one-shot, so a change to visibility should not trigger another callback.
controller.RequestShow();
ASSERT_EQ(1u, n_callbacks);
}
TEST_F(VirtualKeyboardControllerTest, SecondWatchCallbackIsOnlyInvokedOnce) {
// Make the initial call to WatchVisibility(), which invokes its callback immediately, so that
// we know we're exercise the second-and-later logic.
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
controller.WatchVisibility([](bool visibility) {});
// Set a watch, and make a change, causing the watch to fire.
size_t n_callbacks = 0;
controller.WatchVisibility([&n_callbacks](bool visibility) { ++n_callbacks; });
controller.RequestShow();
ASSERT_EQ(1u, n_callbacks);
// Watches are one-shot, so a change to visibility should not trigger another callback.
controller.RequestHide();
ASSERT_EQ(1u, n_callbacks);
}
TEST_F(VirtualKeyboardControllerTest, ConcurrentCallsLastWatcherGetsNewValue) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
// Make the initial call to WatchVisibility(), which invokes its callback immediately, so that
// subsequent calls will block until a visibility change.
controller.WatchVisibility([](bool visibility) {});
// Invoke WatchVisibility() twice, concurrently. Then change the visibility.
// The later call should get the new value.
std::optional<bool> last_watcher_visibility;
controller.WatchVisibility([](bool visibility) {});
controller.WatchVisibility(
[&last_watcher_visibility](bool visibility) { last_watcher_visibility = visibility; });
controller.RequestShow();
ASSERT_EQ(true, last_watcher_visibility);
}
TEST_F(VirtualKeyboardControllerTest, ConcurrentCallsFirstWatchersGetsOldValue) {
auto controller = FidlBoundVirtualKeyboardController(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
// Make the initial call to WatchVisibility(), which invokes its callback immediately, so that
// subsequent calls will block until a visibility change.
controller.WatchVisibility([](bool visibility) {});
// Invoke WatchVisibility() twice, concurrently. Then change the visibility.
// The earlier call should get the old value.
std::optional<bool> first_watcher_visibility;
controller.WatchVisibility(
[&first_watcher_visibility](bool visibility) { first_watcher_visibility = visibility; });
controller.WatchVisibility([](bool visibility) {});
controller.RequestShow();
ASSERT_EQ(false, first_watcher_visibility);
}
TEST_F(VirtualKeyboardControllerTest, RequestShowInformsCoordinatorOfVisibility) {
FidlBoundVirtualKeyboardController(coordinator(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC)
.RequestShow();
ASSERT_EQ(true, coordinator()->want_visible());
}
TEST_F(VirtualKeyboardControllerTest, RequestHideInformsCoordinatorOfVisibility) {
FidlBoundVirtualKeyboardController(coordinator(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC)
.RequestHide();
ASSERT_EQ(false, coordinator()->want_visible());
}
TEST_F(VirtualKeyboardControllerTest, RequestShowDoesNotCrashWhenCoordinatorIsNull) {
std::optional<FakeVirtualKeyboardCoordinator> coordinator(std::in_place);
FidlBoundVirtualKeyboardController controller(
coordinator->GetWeakPtr(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
coordinator.reset();
controller.RequestShow();
}
TEST_F(VirtualKeyboardControllerTest, RequestHideDoesNotCrashWhenCoordinatorIsNull) {
std::optional<FakeVirtualKeyboardCoordinator> coordinator(std::in_place);
FidlBoundVirtualKeyboardController controller(
coordinator->GetWeakPtr(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
coordinator.reset();
controller.RequestHide();
}
TEST_F(VirtualKeyboardControllerTest, SetTextTypeKeepsKeyboardShown) {
FidlBoundVirtualKeyboardController controller(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
controller.RequestShow();
coordinator()->Reset();
controller.SetTextType(::fuchsia::input::virtualkeyboard::TextType::PHONE);
ASSERT_EQ(true, coordinator()->want_visible());
}
TEST_F(VirtualKeyboardControllerTest, SetTextTypeKeepsKeyboardHidden) {
FidlBoundVirtualKeyboardController controller(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
coordinator()->Reset();
controller.SetTextType(::fuchsia::input::virtualkeyboard::TextType::PHONE);
ASSERT_EQ(false, coordinator()->want_visible());
}
TEST_F(VirtualKeyboardControllerTest, SetTextTypeDoesNotReopenKeyboardClosedByUser) {
// Create controller, and request that the keyboard be shown.
FidlBoundVirtualKeyboardController controller(
coordinator(), view_ref(), fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
controller.RequestShow();
ASSERT_EQ(true, coordinator()->want_visible());
// Report that the user hid the keyboard, and reset previous state of the fake coordinator.
controller.OnUserAction(VirtualKeyboardController::UserAction::HIDE_KEYBOARD);
coordinator()->Reset();
ASSERT_EQ(std::nullopt, coordinator()->want_visible());
// Modify the text type. This should not override the user's choice to hide the keyboard.
controller.SetTextType(::fuchsia::input::virtualkeyboard::TextType::PHONE);
ASSERT_EQ(false, coordinator()->want_visible());
}
class VirtualKeyboardControllerTextTypeParamFixture
: public VirtualKeyboardControllerTest,
public testing::WithParamInterface<fuchsia::input::virtualkeyboard::TextType> {};
TEST_P(VirtualKeyboardControllerTextTypeParamFixture,
RequestShowInformsCoordinatorOfInitialTextType) {
auto expected_text_type = GetParam();
FidlBoundVirtualKeyboardController(coordinator(), view_ref(), expected_text_type).RequestShow();
ASSERT_EQ(expected_text_type, coordinator()->requested_text_type());
}
TEST_P(VirtualKeyboardControllerTextTypeParamFixture, SetTextTypeInformsCoordinator) {
auto expected_text_type = GetParam();
FidlBoundVirtualKeyboardController(coordinator(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC)
.SetTextType(expected_text_type);
ASSERT_EQ(expected_text_type, coordinator()->requested_text_type());
}
INSTANTIATE_TEST_SUITE_P(VirtualKeyboardControllerTextTypeParameterizedTests,
VirtualKeyboardControllerTextTypeParamFixture,
::testing::Values(fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC,
fuchsia::input::virtualkeyboard::TextType::NUMERIC,
fuchsia::input::virtualkeyboard::TextType::PHONE));
TEST_F(VirtualKeyboardControllerTest, SetTextTypeDoesNotCrashWhenCoordinatorIsNull) {
std::optional<FakeVirtualKeyboardCoordinator> coordinator(std::in_place);
FidlBoundVirtualKeyboardController controller(
coordinator->GetWeakPtr(), view_ref(),
fuchsia::input::virtualkeyboard::TextType::ALPHANUMERIC);
coordinator.reset();
controller.SetTextType(fuchsia::input::virtualkeyboard::TextType::NUMERIC);
}
} // namespace
} // namespace virtual_keyboard_controller
} // namespace root_presenter