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