blob: 29835d0752d676b709750cea0224c33311fd83e0 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "text_delegate.h"
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/input3/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include "tests/fakes/platform_message.h"
#include "flutter/lib/ui/window/platform_message.h"
#include <memory>
namespace flutter_runner::testing {
// Convert a |PlatformMessage| to string for ease of testing.
static std::string MessageToString(PlatformMessage& message) {
const char* data = reinterpret_cast<const char*>(message.data().GetMapping());
return std::string(data, message.data().GetSize());
}
// Fake |KeyboardService| implementation. Only responsibility is to remember
// what it was called with.
class FakeKeyboardService : public fuchsia::ui::input3::Keyboard {
public:
// |fuchsia.ui.input3/Keyboard.AddListener|
virtual void AddListener(
fuchsia::ui::views::ViewRef,
fidl::InterfaceHandle<fuchsia::ui::input3::KeyboardListener> listener,
fuchsia::ui::input3::Keyboard::AddListenerCallback callback) {
listener_ = listener.Bind();
callback();
}
fidl::InterfacePtr<fuchsia::ui::input3::KeyboardListener> listener_;
};
// Fake ImeService implementation. Only responsibility is to remember what
// it was called with.
class FakeImeService : public fuchsia::ui::input::ImeService {
public:
virtual void GetInputMethodEditor(
fuchsia::ui::input::KeyboardType keyboard_type,
fuchsia::ui::input::InputMethodAction action,
fuchsia::ui::input::TextInputState input_state,
fidl::InterfaceHandle<fuchsia::ui::input::InputMethodEditorClient> client,
fidl::InterfaceRequest<fuchsia::ui::input::InputMethodEditor> ime) {
keyboard_type_ = std::move(keyboard_type);
action_ = std::move(action);
input_state_ = std::move(input_state);
client_ = client.Bind();
ime_ = std::move(ime);
}
virtual void ShowKeyboard() { keyboard_shown_ = true; }
virtual void HideKeyboard() { keyboard_shown_ = false; }
bool IsKeyboardShown() { return keyboard_shown_; }
bool keyboard_shown_ = false;
fuchsia::ui::input::KeyboardType keyboard_type_;
fuchsia::ui::input::InputMethodAction action_;
fuchsia::ui::input::TextInputState input_state_;
fidl::InterfacePtr<fuchsia::ui::input::InputMethodEditorClient> client_;
fidl::InterfaceRequest<fuchsia::ui::input::InputMethodEditor> ime_;
};
class TextDelegateTest : public ::testing::Test {
protected:
TextDelegateTest()
: loop_(&kAsyncLoopConfigAttachToCurrentThread),
keyboard_service_binding_(&keyboard_service_),
ime_service_binding_(&ime_service_) {
fidl::InterfaceHandle<fuchsia::ui::input3::Keyboard> keyboard_handle;
auto keyboard_request = keyboard_handle.NewRequest();
keyboard_service_binding_.Bind(keyboard_request.TakeChannel());
fidl::InterfaceHandle<fuchsia::ui::input::ImeService> ime_service_handle;
ime_service_binding_.Bind(ime_service_handle.NewRequest().TakeChannel());
auto fake_view_ref_pair = scenic::ViewRefPair::New();
text_delegate_ = std::make_unique<TextDelegate>(
std::move(fake_view_ref_pair.view_ref), std::move(ime_service_handle),
std::move(keyboard_handle),
// Should this be accessed through a weak pointer?
[this](std::unique_ptr<flutter::PlatformMessage> message) {
last_message_ = std::move(message);
});
// TextDelegate has some async initialization that needs to happen.
RunLoopUntilIdle();
}
// Runs the event loop until all scheduled events are spent.
void RunLoopUntilIdle() { loop_.RunUntilIdle(); }
void TearDown() override {
loop_.Quit();
ASSERT_EQ(loop_.ResetQuit(), 0);
}
async::Loop loop_;
FakeKeyboardService keyboard_service_;
fidl::Binding<fuchsia::ui::input3::Keyboard> keyboard_service_binding_;
FakeImeService ime_service_;
fidl::Binding<fuchsia::ui::input::ImeService> ime_service_binding_;
// Unit under test.
std::unique_ptr<TextDelegate> text_delegate_;
std::unique_ptr<flutter::PlatformMessage> last_message_;
};
// Goes through several steps of a text edit protocol. These are hard to test
// in isolation because the text edit protocol depends on the correct method
// invocation sequence. The text editor is initialized with the editing
// parameters, and we verify that the correct input action is parsed out. We
// then exercise showing and hiding the keyboard, as well as a text state
// update.
TEST_F(TextDelegateTest, ActivateIme) {
auto fake_platform_message_response = FakePlatformMessageResponse::Create();
{
// Initialize the editor. Without this initialization, the protocol code
// will crash.
const auto set_client_msg = R"(
{
"method": "TextInput.setClient",
"args": [
7,
{
"inputType": {
"name": "TextInputType.multiline",
"signed":null,
"decimal":null
},
"readOnly": false,
"obscureText": false,
"autocorrect":true,
"smartDashesType":"1",
"smartQuotesType":"1",
"enableSuggestions":true,
"enableInteractiveSelection":true,
"actionLabel":null,
"inputAction":"TextInputAction.newline",
"textCapitalization":"TextCapitalization.none",
"keyboardAppearance":"Brightness.dark",
"enableIMEPersonalizedLearning":true,
"enableDeltaModel":false
}
]
}
)";
auto message = fake_platform_message_response->WithMessage(
kTextInputChannel, set_client_msg);
text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
std::move(message));
RunLoopUntilIdle();
EXPECT_EQ(ime_service_.action_,
fuchsia::ui::input::InputMethodAction::NEWLINE);
EXPECT_FALSE(ime_service_.IsKeyboardShown());
}
{
// Verify that showing keyboard results in the correct platform effect.
const auto set_client_msg = R"(
{
"method": "TextInput.show"
}
)";
auto message = fake_platform_message_response->WithMessage(
kTextInputChannel, set_client_msg);
text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
std::move(message));
RunLoopUntilIdle();
EXPECT_TRUE(ime_service_.IsKeyboardShown());
}
{
// Verify that hiding keyboard results in the correct platform effect.
const auto set_client_msg = R"(
{
"method": "TextInput.hide"
}
)";
auto message = fake_platform_message_response->WithMessage(
kTextInputChannel, set_client_msg);
text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
std::move(message));
RunLoopUntilIdle();
EXPECT_FALSE(ime_service_.IsKeyboardShown());
}
{
// Update the editing state from the Fuchsia platform side.
fuchsia::ui::input::TextInputState state = {
.revision = 42,
.text = "Foo",
.selection = fuchsia::ui::input::TextSelection{},
.composing = fuchsia::ui::input::TextRange{},
};
auto input_event = std::make_unique<fuchsia::ui::input::InputEvent>();
ime_service_.client_->DidUpdateState(std::move(state),
std::move(input_event));
RunLoopUntilIdle();
EXPECT_EQ(
R"({"method":"TextInputClient.updateEditingState","args":[7,{"text":"Foo","selectionBase":0,"selectionExtent":0,"selectionAffinity":"TextAffinity.upstream","selectionIsDirectional":true,"composingBase":-1,"composingExtent":-1}]})",
MessageToString(*last_message_));
}
{
// Notify Flutter that the action key has been pressed.
ime_service_.client_->OnAction(fuchsia::ui::input::InputMethodAction::DONE);
RunLoopUntilIdle();
EXPECT_EQ(
R"({"method":"TextInputClient.performAction","args":[7,"TextInputAction.done"]})",
MessageToString(*last_message_));
}
}
// Hands a few typical |KeyEvent|s to the text delegate. Regular key events are
// handled, "odd" key events are rejected (not handled). "Handling" a key event
// means converting it to an appropriate |PlatformMessage| and forwarding it.
TEST_F(TextDelegateTest, OnAction) {
{
// A sensible key event is converted into a platform message.
fuchsia::ui::input3::KeyEvent key_event;
*key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::PRESSED;
*key_event.mutable_key() = fuchsia::input::Key::A;
key_event.mutable_key_meaning()->set_codepoint('a');
fuchsia::ui::input3::KeyEventStatus status;
keyboard_service_.listener_->OnKeyEvent(
std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
status = std::move(s);
});
RunLoopUntilIdle();
EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::HANDLED, status);
EXPECT_EQ(
R"({"type":"keydown","keymap":"fuchsia","hidUsage":458756,"codePoint":97,"modifiers":0})",
MessageToString(*last_message_));
}
{
// SYNC event is not handled.
// This is currently expected, though we may need to change that behavior.
fuchsia::ui::input3::KeyEvent key_event;
*key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::SYNC;
fuchsia::ui::input3::KeyEventStatus status;
keyboard_service_.listener_->OnKeyEvent(
std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
status = std::move(s);
});
RunLoopUntilIdle();
EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED, status);
}
{
// CANCEL event is not handled.
// This is currently expected, though we may need to change that behavior.
fuchsia::ui::input3::KeyEvent key_event;
*key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::CANCEL;
fuchsia::ui::input3::KeyEventStatus status;
keyboard_service_.listener_->OnKeyEvent(
std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
status = std::move(s);
});
RunLoopUntilIdle();
EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED, status);
}
}
} // namespace flutter_runner::testing