Flutter Engine
The Flutter Engine
text_delegate_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "text_delegate.h"
6
7#include <fuchsia/ui/input/cpp/fidl.h>
8#include <fuchsia/ui/input3/cpp/fidl.h>
9#include <fuchsia/ui/views/cpp/fidl.h>
10#include <gtest/gtest.h>
11#include <lib/async-loop/cpp/loop.h>
12#include <lib/async-loop/default.h>
13#include <lib/fidl/cpp/binding.h>
14#include <lib/fidl/cpp/binding_set.h>
15#include <lib/zx/eventpair.h>
16
18
19#include "flutter/lib/ui/window/platform_message.h"
20
21#include <memory>
22
24
25// Convert a |PlatformMessage| to string for ease of testing.
27 const char* data = reinterpret_cast<const char*>(message.data().GetMapping());
28 return std::string(data, message.data().GetSize());
29}
30
31// Fake |KeyboardService| implementation. Only responsibility is to remember
32// what it was called with.
33class FakeKeyboardService : public fuchsia::ui::input3::Keyboard {
34 public:
35 // |fuchsia.ui.input3/Keyboard.AddListener|
36 virtual void AddListener(
37 fuchsia::ui::views::ViewRef,
38 fidl::InterfaceHandle<fuchsia::ui::input3::KeyboardListener> listener,
39 fuchsia::ui::input3::Keyboard::AddListenerCallback callback) {
40 listener_ = listener.Bind();
41 callback();
42 }
43
44 fidl::InterfacePtr<fuchsia::ui::input3::KeyboardListener> listener_;
45};
46
47// Fake ImeService implementation. Only responsibility is to remember what
48// it was called with.
49class FakeImeService : public fuchsia::ui::input::ImeService {
50 public:
52 fuchsia::ui::input::KeyboardType keyboard_type,
53 fuchsia::ui::input::InputMethodAction action,
54 fuchsia::ui::input::TextInputState input_state,
55 fidl::InterfaceHandle<fuchsia::ui::input::InputMethodEditorClient> client,
56 fidl::InterfaceRequest<fuchsia::ui::input::InputMethodEditor> ime) {
57 keyboard_type_ = std::move(keyboard_type);
58 action_ = std::move(action);
59 input_state_ = std::move(input_state);
60 client_ = client.Bind();
61 ime_ = std::move(ime);
62 }
63
64 virtual void ShowKeyboard() { keyboard_shown_ = true; }
65
66 virtual void HideKeyboard() { keyboard_shown_ = false; }
67
69
70 bool keyboard_shown_ = false;
71
72 fuchsia::ui::input::KeyboardType keyboard_type_;
73 fuchsia::ui::input::InputMethodAction action_;
74 fuchsia::ui::input::TextInputState input_state_;
75 fidl::InterfacePtr<fuchsia::ui::input::InputMethodEditorClient> client_;
76 fidl::InterfaceRequest<fuchsia::ui::input::InputMethodEditor> ime_;
77};
78
79class TextDelegateTest : public ::testing::Test {
80 protected:
82 : loop_(&kAsyncLoopConfigAttachToCurrentThread),
85 fidl::InterfaceHandle<fuchsia::ui::input3::Keyboard> keyboard_handle;
86 auto keyboard_request = keyboard_handle.NewRequest();
87 keyboard_service_binding_.Bind(keyboard_request.TakeChannel());
88
89 fidl::InterfaceHandle<fuchsia::ui::input::ImeService> ime_service_handle;
90 ime_service_binding_.Bind(ime_service_handle.NewRequest().TakeChannel());
91
92 fuchsia::ui::views::ViewRefControl view_ref_control;
93 fuchsia::ui::views::ViewRef view_ref;
94 auto status = zx::eventpair::create(
95 /*options*/ 0u, &view_ref_control.reference, &view_ref.reference);
96 ZX_ASSERT(status == ZX_OK);
97 view_ref.reference.replace(ZX_RIGHTS_BASIC, &view_ref.reference);
98
99 text_delegate_ = std::make_unique<TextDelegate>(
100 std::move(view_ref), std::move(ime_service_handle),
101 std::move(keyboard_handle),
102 // Should this be accessed through a weak pointer?
103 [this](std::unique_ptr<flutter::PlatformMessage> message) {
104 last_message_ = std::move(message);
105 });
106
107 // TextDelegate has some async initialization that needs to happen.
109 }
110
111 // Runs the event loop until all scheduled events are spent.
112 void RunLoopUntilIdle() { loop_.RunUntilIdle(); }
113
114 void TearDown() override {
115 loop_.Quit();
116 ASSERT_EQ(loop_.ResetQuit(), 0);
117 }
118
119 async::Loop loop_;
120
122 fidl::Binding<fuchsia::ui::input3::Keyboard> keyboard_service_binding_;
123
125 fidl::Binding<fuchsia::ui::input::ImeService> ime_service_binding_;
126
127 // Unit under test.
128 std::unique_ptr<TextDelegate> text_delegate_;
129
130 std::unique_ptr<flutter::PlatformMessage> last_message_;
131};
132
133// Goes through several steps of a text edit protocol. These are hard to test
134// in isolation because the text edit protocol depends on the correct method
135// invocation sequence. The text editor is initialized with the editing
136// parameters, and we verify that the correct input action is parsed out. We
137// then exercise showing and hiding the keyboard, as well as a text state
138// update.
140 auto fake_platform_message_response = FakePlatformMessageResponse::Create();
141 {
142 // Initialize the editor. Without this initialization, the protocol code
143 // will crash.
144 const auto set_client_msg = R"(
145 {
146 "method": "TextInput.setClient",
147 "args": [
148 7,
149 {
150 "inputType": {
151 "name": "TextInputType.multiline",
152 "signed":null,
153 "decimal":null
154 },
155 "readOnly": false,
156 "obscureText": false,
157 "autocorrect":true,
158 "smartDashesType":"1",
159 "smartQuotesType":"1",
160 "enableSuggestions":true,
161 "enableInteractiveSelection":true,
162 "actionLabel":null,
163 "inputAction":"TextInputAction.newline",
164 "textCapitalization":"TextCapitalization.none",
165 "keyboardAppearance":"Brightness.dark",
166 "enableIMEPersonalizedLearning":true,
167 "enableDeltaModel":false
168 }
169 ]
170 }
171 )";
172 auto message = fake_platform_message_response->WithMessage(
173 kTextInputChannel, set_client_msg);
174 text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
175 std::move(message));
176 RunLoopUntilIdle();
177 EXPECT_EQ(ime_service_.action_,
178 fuchsia::ui::input::InputMethodAction::NEWLINE);
179 EXPECT_FALSE(ime_service_.IsKeyboardShown());
180 }
181
182 {
183 // Verify that showing keyboard results in the correct platform effect.
184 const auto set_client_msg = R"(
185 {
186 "method": "TextInput.show"
187 }
188 )";
189 auto message = fake_platform_message_response->WithMessage(
190 kTextInputChannel, set_client_msg);
191 text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
192 std::move(message));
193 RunLoopUntilIdle();
194 EXPECT_TRUE(ime_service_.IsKeyboardShown());
195 }
196
197 {
198 // Verify that hiding keyboard results in the correct platform effect.
199 const auto set_client_msg = R"(
200 {
201 "method": "TextInput.hide"
202 }
203 )";
204 auto message = fake_platform_message_response->WithMessage(
205 kTextInputChannel, set_client_msg);
206 text_delegate_->HandleFlutterTextInputChannelPlatformMessage(
207 std::move(message));
208 RunLoopUntilIdle();
209 EXPECT_FALSE(ime_service_.IsKeyboardShown());
210 }
211
212 {
213 // Update the editing state from the Fuchsia platform side.
214 fuchsia::ui::input::TextInputState state = {
215 .revision = 42,
216 .text = "Foo",
217 .selection = fuchsia::ui::input::TextSelection{},
218 .composing = fuchsia::ui::input::TextRange{},
219 };
220 auto input_event = std::make_unique<fuchsia::ui::input::InputEvent>();
221 ime_service_.client_->DidUpdateState(std::move(state),
222 std::move(input_event));
223 RunLoopUntilIdle();
224 EXPECT_EQ(
225 R"({"method":"TextInputClient.updateEditingState","args":[7,{"text":"Foo","selectionBase":0,"selectionExtent":0,"selectionAffinity":"TextAffinity.upstream","selectionIsDirectional":true,"composingBase":-1,"composingExtent":-1}]})",
226 MessageToString(*last_message_));
227 }
228
229 {
230 // Notify Flutter that the action key has been pressed.
231 ime_service_.client_->OnAction(fuchsia::ui::input::InputMethodAction::DONE);
232 RunLoopUntilIdle();
233 EXPECT_EQ(
234 R"({"method":"TextInputClient.performAction","args":[7,"TextInputAction.done"]})",
235 MessageToString(*last_message_));
236 }
237}
238
239// Hands a few typical |KeyEvent|s to the text delegate. Regular key events are
240// handled, "odd" key events are rejected (not handled). "Handling" a key event
241// means converting it to an appropriate |PlatformMessage| and forwarding it.
243 {
244 // A sensible key event is converted into a platform message.
245 fuchsia::ui::input3::KeyEvent key_event;
246 *key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::PRESSED;
247 *key_event.mutable_key() = fuchsia::input::Key::A;
248 key_event.mutable_key_meaning()->set_codepoint('a');
249
250 fuchsia::ui::input3::KeyEventStatus status;
251 keyboard_service_.listener_->OnKeyEvent(
252 std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
253 status = std::move(s);
254 });
255 RunLoopUntilIdle();
256 EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::HANDLED, status);
257 EXPECT_EQ(
258 R"({"type":"keydown","keymap":"fuchsia","hidUsage":458756,"codePoint":97,"modifiers":0})",
259 MessageToString(*last_message_));
260 }
261
262 {
263 // SYNC event is not handled.
264 // This is currently expected, though we may need to change that behavior.
265 fuchsia::ui::input3::KeyEvent key_event;
266 *key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::SYNC;
267
268 fuchsia::ui::input3::KeyEventStatus status;
269 keyboard_service_.listener_->OnKeyEvent(
270 std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
271 status = std::move(s);
272 });
273 RunLoopUntilIdle();
274 EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED, status);
275 }
276
277 {
278 // CANCEL event is not handled.
279 // This is currently expected, though we may need to change that behavior.
280 fuchsia::ui::input3::KeyEvent key_event;
281 *key_event.mutable_type() = fuchsia::ui::input3::KeyEventType::CANCEL;
282
283 fuchsia::ui::input3::KeyEventStatus status;
284 keyboard_service_.listener_->OnKeyEvent(
285 std::move(key_event), [&status](fuchsia::ui::input3::KeyEventStatus s) {
286 status = std::move(s);
287 });
288 RunLoopUntilIdle();
289 EXPECT_EQ(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED, status);
290 }
291}
292
293} // namespace flutter_runner::testing
fidl::InterfacePtr< fuchsia::ui::input::InputMethodEditorClient > client_
fuchsia::ui::input::InputMethodAction action_
fuchsia::ui::input::TextInputState input_state_
fuchsia::ui::input::KeyboardType keyboard_type_
fidl::InterfaceRequest< fuchsia::ui::input::InputMethodEditor > ime_
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)
virtual void AddListener(fuchsia::ui::views::ViewRef, fidl::InterfaceHandle< fuchsia::ui::input3::KeyboardListener > listener, fuchsia::ui::input3::Keyboard::AddListenerCallback callback)
fidl::InterfacePtr< fuchsia::ui::input3::KeyboardListener > listener_
static fml::RefPtr< FakePlatformMessageResponse > Create()
std::unique_ptr< TextDelegate > text_delegate_
fidl::Binding< fuchsia::ui::input3::Keyboard > keyboard_service_binding_
fidl::Binding< fuchsia::ui::input::ImeService > ime_service_binding_
std::unique_ptr< flutter::PlatformMessage > last_message_
struct MyStruct s
AtkStateType state
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlutterKeyEvent key_event
Win32Message message
static std::string MessageToString(PlatformMessage &message)
TEST_F(FocusDelegateTest, WatchCallbackSeries)
constexpr char kTextInputChannel[]
The channel name used for text editing platofrm messages.
Definition: text_delegate.h:27
SkRange< size_t > TextRange
Definition: TextStyle.h:337
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
int_closure create
#define EXPECT_TRUE(handle)
Definition: unit_test.h:678