| Flutter Windows Embedder |

text_input_plugin_unittest.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 #include "flutter/shell/platform/windows/text_input_plugin.h"
5
6 #include <rapidjson/document.h>
7 #include <windows.h>
8 #include <memory>
9
10 #include "flutter/fml/macros.h"
11 #include "flutter/shell/platform/common/json_message_codec.h"
12 #include "flutter/shell/platform/common/json_method_codec.h"
13 #include "flutter/shell/platform/windows/flutter_windows_view.h"
14 #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h"
15 #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
16 #include "flutter/shell/platform/windows/testing/test_binary_messenger.h"
17 #include "flutter/shell/platform/windows/testing/windows_test.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
20
21 namespace flutter {
22 namespace testing {
23
24 namespace {
25 using ::testing::Return;
26
27 static constexpr char kScanCodeKey[] = "scanCode";
28 static constexpr int kHandledScanCode = 20;
29 static constexpr int kUnhandledScanCode = 21;
30 static constexpr char kTextPlainFormat[] = "text/plain";
31 static constexpr int kDefaultClientId = 42;
32 // Should be identical to constants in text_input_plugin.cc.
33 static constexpr char kChannelName[] = "flutter/textinput";
34 static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
35 static constexpr char kSetClientMethod[] = "TextInput.setClient";
36 static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
37 static constexpr char kTextKey[] = "text";
38 static constexpr char kSelectionBaseKey[] = "selectionBase";
39 static constexpr char kSelectionExtentKey[] = "selectionExtent";
40 static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
41 static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
42 static constexpr char kComposingBaseKey[] = "composingBase";
43 static constexpr char kComposingExtentKey[] = "composingExtent";
44 static constexpr char kUpdateEditingStateMethod[] =
45 "TextInputClient.updateEditingState";
46
47 static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
48 auto response_doc =
49 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
50 auto& allocator = response_doc->GetAllocator();
51 response_doc->AddMember("handled", handled, allocator);
52 return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc);
53 }
54
55 static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
56 std::string type_name,
57 std::string input_action) {
58 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
59 auto& allocator = arguments->GetAllocator();
60 arguments->PushBack(kDefaultClientId, allocator);
61
62 rapidjson::Value config(rapidjson::kObjectType);
63 config.AddMember("inputAction", input_action, allocator);
64 config.AddMember(kEnableDeltaModel, false, allocator);
65 rapidjson::Value type_info(rapidjson::kObjectType);
66 type_info.AddMember("name", type_name, allocator);
67 config.AddMember("inputType", type_info, allocator);
68 arguments->PushBack(config, allocator);
69
70 return arguments;
71 }
72
73 static std::unique_ptr<rapidjson::Document> EncodedEditingState(
74 std::string text,
75 TextRange selection) {
76 auto model = std::make_unique<TextInputModel>();
77 model->SetText(text);
78 model->SetSelection(selection);
79
80 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
81 auto& allocator = arguments->GetAllocator();
82 arguments->PushBack(kDefaultClientId, allocator);
83
84 rapidjson::Value editing_state(rapidjson::kObjectType);
85 editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
86 allocator);
87 editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator);
88 editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator);
89 editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
90
91 int composing_base =
92 model->composing() ? model->composing_range().base() : -1;
93 int composing_extent =
94 model->composing() ? model->composing_range().extent() : -1;
95 editing_state.AddMember(kComposingBaseKey, composing_base, allocator);
96 editing_state.AddMember(kComposingExtentKey, composing_extent, allocator);
97 editing_state.AddMember(kTextKey,
98 rapidjson::Value(model->GetText(), allocator).Move(),
99 allocator);
100 arguments->PushBack(editing_state, allocator);
101
102 return arguments;
103 }
104
105 class MockFlutterWindowsView : public FlutterWindowsView {
106 public:
107 MockFlutterWindowsView(std::unique_ptr<WindowBindingHandler> window)
108 : FlutterWindowsView(std::move(window)) {}
109 virtual ~MockFlutterWindowsView() = default;
110
111 MOCK_METHOD(void, OnCursorRectUpdated, (const Rect&), (override));
112 MOCK_METHOD(void, OnResetImeComposing, (), (override));
113
114 private:
115 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
116 };
117
118 } // namespace
119
120 class TextInputPluginTest : public WindowsTest {
121 public:
122 TextInputPluginTest() = default;
123 virtual ~TextInputPluginTest() = default;
124
125 protected:
126 FlutterWindowsEngine* engine() { return engine_.get(); }
127 MockFlutterWindowsView* view() { return view_.get(); }
128 MockWindowBindingHandler* window() { return window_; }
129
130 void UseHeadlessEngine() {
131 FlutterWindowsEngineBuilder builder{GetContext()};
132
133 engine_ = builder.Build();
134 }
135
136 void UseEngineWithView() {
137 FlutterWindowsEngineBuilder builder{GetContext()};
138
139 auto window = std::make_unique<MockWindowBindingHandler>();
140
141 window_ = window.get();
142 EXPECT_CALL(*window_, SetView).Times(1);
143 EXPECT_CALL(*window, GetWindowHandle).WillRepeatedly(Return(nullptr));
144
145 engine_ = builder.Build();
146 view_ = std::make_unique<MockFlutterWindowsView>(std::move(window));
147
148 engine_->SetView(view_.get());
149 }
150
151 private:
152 std::unique_ptr<FlutterWindowsEngine> engine_;
153 std::unique_ptr<MockFlutterWindowsView> view_;
154 MockWindowBindingHandler* window_;
155
156 FML_DISALLOW_COPY_AND_ASSIGN(TextInputPluginTest);
157 };
158
159 TEST_F(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
160 UseEngineWithView();
161
162 auto handled_message = CreateResponse(true);
163 auto unhandled_message = CreateResponse(false);
164 int received_scancode = 0;
165
166 TestBinaryMessenger messenger(
167 [&received_scancode, &handled_message, &unhandled_message](
168 const std::string& channel, const uint8_t* message,
169 size_t message_size, BinaryReply reply) {});
170
171 int redispatch_scancode = 0;
172 TextInputPlugin handler(&messenger, engine());
173
174 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
175 handler.ComposeBeginHook();
176 std::u16string text;
177 text.push_back('\n');
178 handler.ComposeChangeHook(text, 1);
179 handler.ComposeEndHook();
180
181 // Passes if it did not crash
182 }
183
184 TEST_F(TextInputPluginTest, ClearClientResetsComposing) {
185 UseEngineWithView();
186
187 TestBinaryMessenger messenger([](const std::string& channel,
188 const uint8_t* message, size_t message_size,
189 BinaryReply reply) {});
190 BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
191
192 TextInputPlugin handler(&messenger, engine());
193
194 EXPECT_CALL(*view(), OnResetImeComposing());
195
196 auto& codec = JsonMethodCodec::GetInstance();
197 auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr});
198 messenger.SimulateEngineMessage(kChannelName, message->data(),
199 message->size(), reply_handler);
200 }
201
202 // Verify that clear client fails if in headless mode.
203 TEST_F(TextInputPluginTest, ClearClientRequiresView) {
204 UseHeadlessEngine();
205
206 TestBinaryMessenger messenger([](const std::string& channel,
207 const uint8_t* message, size_t message_size,
208 BinaryReply reply) {});
209
210 std::string reply;
211 BinaryReply reply_handler = [&reply](const uint8_t* reply_bytes,
212 size_t reply_size) {
213 reply = std::string(reinterpret_cast<const char*>(reply_bytes), reply_size);
214 };
215
216 TextInputPlugin handler(&messenger, engine());
217
218 auto& codec = JsonMethodCodec::GetInstance();
219 auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr});
220 messenger.SimulateEngineMessage(kChannelName, message->data(),
221 message->size(), reply_handler);
222
223 EXPECT_EQ(reply,
224 "[\"Internal Consistency Error\",\"Text input is not available in "
225 "Windows headless mode\",null]");
226 }
227
228 // Verify that the embedder sends state update messages to the framework during
229 // IME composing.
230 TEST_F(TextInputPluginTest, VerifyComposingSendStateUpdate) {
231 UseEngineWithView();
232
233 bool sent_message = false;
234 TestBinaryMessenger messenger(
235 [&sent_message](const std::string& channel, const uint8_t* message,
236 size_t message_size,
237 BinaryReply reply) { sent_message = true; });
238 BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
239
240 TextInputPlugin handler(&messenger, engine());
241
242 auto& codec = JsonMethodCodec::GetInstance();
243
244 // Call TextInput.setClient to initialize the TextInputModel.
245 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
246 auto& allocator = arguments->GetAllocator();
247 arguments->PushBack(kDefaultClientId, allocator);
248 rapidjson::Value config(rapidjson::kObjectType);
249 config.AddMember("inputAction", "done", allocator);
250 config.AddMember("inputType", "text", allocator);
251 config.AddMember(kEnableDeltaModel, false, allocator);
252 arguments->PushBack(config, allocator);
253 auto message =
254 codec.EncodeMethodCall({"TextInput.setClient", std::move(arguments)});
255 messenger.SimulateEngineMessage("flutter/textinput", message->data(),
256 message->size(), reply_handler);
257
258 // ComposeBeginHook should send state update.
259 sent_message = false;
260 handler.ComposeBeginHook();
261 EXPECT_TRUE(sent_message);
262
263 // ComposeChangeHook should send state update.
264 sent_message = false;
265 handler.ComposeChangeHook(u"4", 1);
266 EXPECT_TRUE(sent_message);
267
268 // ComposeCommitHook should NOT send state update.
269 //
270 // Commit messages are always immediately followed by a change message or an
271 // end message, both of which will send an update. Sending intermediate state
272 // with a collapsed composing region will trigger the framework to assume
273 // composing has ended, which is not the case until a WM_IME_ENDCOMPOSING
274 // event is received in the main event loop, which will trigger a call to
275 // ComposeEndHook.
276 sent_message = false;
277 handler.ComposeCommitHook();
278 EXPECT_FALSE(sent_message);
279
280 // ComposeEndHook should send state update.
281 sent_message = false;
282 handler.ComposeEndHook();
283 EXPECT_TRUE(sent_message);
284 }
285
286 TEST_F(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
287 UseEngineWithView();
288
289 // Store messages as std::string for convenience.
290 std::vector<std::string> messages;
291
292 TestBinaryMessenger messenger(
293 [&messages](const std::string& channel, const uint8_t* message,
294 size_t message_size, BinaryReply reply) {
295 std::string last_message(reinterpret_cast<const char*>(message),
296 message_size);
297 messages.push_back(last_message);
298 });
299 BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
300
301 TextInputPlugin handler(&messenger, engine());
302
303 auto& codec = JsonMethodCodec::GetInstance();
304
305 // Call TextInput.setClient to initialize the TextInputModel.
306 auto set_client_arguments =
307 EncodedClientConfig("TextInputType.multiline", "TextInputAction.newline");
308 auto message = codec.EncodeMethodCall(
309 {"TextInput.setClient", std::move(set_client_arguments)});
310 messenger.SimulateEngineMessage("flutter/textinput", message->data(),
311 message->size(), reply_handler);
312
313 // Simulate a key down event for '\n'.
314 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
315
316 // Two messages are expected, the first is TextInput.updateEditingState and
317 // the second is TextInputClient.performAction.
318 EXPECT_EQ(messages.size(), 2);
319
320 // Editing state should have been updated.
321 auto encoded_arguments = EncodedEditingState("\n", TextRange(1));
322 auto update_state_message = codec.EncodeMethodCall(
323 {kUpdateEditingStateMethod, std::move(encoded_arguments)});
324
325 EXPECT_TRUE(std::equal(update_state_message->begin(),
326 update_state_message->end(),
327 messages.front().begin()));
328
329 // TextInputClient.performAction should have been called.
330 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
331 auto& allocator = arguments->GetAllocator();
332 arguments->PushBack(kDefaultClientId, allocator);
333 arguments->PushBack(
334 rapidjson::Value("TextInputAction.newline", allocator).Move(), allocator);
335 auto invoke_action_message = codec.EncodeMethodCall(
336 {"TextInputClient.performAction", std::move(arguments)});
337
338 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
339 invoke_action_message->end(),
340 messages.back().begin()));
341 }
342
343 // Regression test for https://github.com/flutter/flutter/issues/125879.
344 TEST_F(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
345 UseEngineWithView();
346
347 std::vector<std::vector<uint8_t>> messages;
348
349 TestBinaryMessenger messenger(
350 [&messages](const std::string& channel, const uint8_t* message,
351 size_t message_size, BinaryReply reply) {
352 int length = static_cast<int>(message_size);
353 std::vector<uint8_t> last_message(length);
354 memcpy(&last_message[0], &message[0], length * sizeof(uint8_t));
355 messages.push_back(last_message);
356 });
357 BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
358
359 TextInputPlugin handler(&messenger, engine());
360
361 auto& codec = JsonMethodCodec::GetInstance();
362
363 // Call TextInput.setClient to initialize the TextInputModel.
364 auto set_client_arguments =
365 EncodedClientConfig("TextInputType.multiline", "TextInputAction.send");
366 auto message = codec.EncodeMethodCall(
367 {"TextInput.setClient", std::move(set_client_arguments)});
368 messenger.SimulateEngineMessage("flutter/textinput", message->data(),
369 message->size(), reply_handler);
370
371 // Simulate a key down event for '\n'.
372 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
373
374 // Only a call to TextInputClient.performAction is expected.
375 EXPECT_EQ(messages.size(), 1);
376
377 // TextInputClient.performAction should have been called.
378 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
379 auto& allocator = arguments->GetAllocator();
380 arguments->PushBack(kDefaultClientId, allocator);
381 arguments->PushBack(
382 rapidjson::Value("TextInputAction.send", allocator).Move(), allocator);
383 auto invoke_action_message = codec.EncodeMethodCall(
384 {"TextInputClient.performAction", std::move(arguments)});
385
386 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
387 invoke_action_message->end(),
388 messages.front().begin()));
389 }
390
391 TEST_F(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
392 UseEngineWithView();
393
394 auto handled_message = CreateResponse(true);
395 auto unhandled_message = CreateResponse(false);
396 int received_scancode = 0;
397
398 TestBinaryMessenger messenger(
399 [&received_scancode, &handled_message, &unhandled_message](
400 const std::string& channel, const uint8_t* message,
401 size_t message_size, BinaryReply reply) {});
402
403 int redispatch_scancode = 0;
404 TextInputPlugin handler(&messenger, engine());
405
406 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
407 auto& allocator = args->GetAllocator();
408 args->PushBack(123, allocator); // client_id
409
410 rapidjson::Value client_config(rapidjson::kObjectType);
411 client_config.AddMember(kEnableDeltaModel, true, allocator);
412
413 args->PushBack(client_config, allocator);
414 auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
415 MethodCall<rapidjson::Document>(kSetClientMethod, std::move(args)));
416
417 EXPECT_TRUE(messenger.SimulateEngineMessage(
418 kChannelName, encoded->data(), encoded->size(),
419 [](const uint8_t* reply, size_t reply_size) {}));
420
421 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
422 handler.ComposeBeginHook();
423 std::u16string text;
424 text.push_back('\n');
425 handler.ComposeChangeHook(text, 1);
426 handler.ComposeEndHook();
427
428 handler.KeyboardHook(0x4E, 100, WM_KEYDOWN, 'n', false, false);
429 handler.ComposeBeginHook();
430 std::u16string textN;
431 text.push_back('n');
432 handler.ComposeChangeHook(textN, 1);
433 handler.KeyboardHook(0x49, 100, WM_KEYDOWN, 'i', false, false);
434 std::u16string textNi;
435 text.push_back('n');
436 text.push_back('i');
437 handler.ComposeChangeHook(textNi, 2);
438 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN, '\n', false, false);
439 std::u16string textChineseCharacter;
440 text.push_back(u'\u4F60');
441 handler.ComposeChangeHook(textChineseCharacter, 1);
442 handler.ComposeCommitHook();
443 handler.ComposeEndHook();
444
445 // Passes if it did not crash
446 }
447
448 // Regression test for https://github.com/flutter/flutter/issues/123749
449 TEST_F(TextInputPluginTest, CompositionCursorPos) {
450 UseEngineWithView();
451
452 int selection_base = -1;
453 TestBinaryMessenger messenger([&](const std::string& channel,
454 const uint8_t* message, size_t size,
455 BinaryReply reply) {
456 auto method = JsonMethodCodec::GetInstance().DecodeMethodCall(
457 std::vector<uint8_t>(message, message + size));
458 if (method->method_name() == kUpdateEditingStateMethod) {
459 const auto& args = *method->arguments();
460 const auto& editing_state = args[1];
461 auto base = editing_state.FindMember(kSelectionBaseKey);
462 auto extent = editing_state.FindMember(kSelectionExtentKey);
463 ASSERT_NE(base, editing_state.MemberEnd());
464 ASSERT_TRUE(base->value.IsInt());
465 ASSERT_NE(extent, editing_state.MemberEnd());
466 ASSERT_TRUE(extent->value.IsInt());
467 selection_base = base->value.GetInt();
468 EXPECT_EQ(extent->value.GetInt(), selection_base);
469 }
470 });
471
472 TextInputPlugin plugin(&messenger, engine());
473
474 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
475 auto& allocator = args->GetAllocator();
476 args->PushBack(123, allocator); // client_id
477 rapidjson::Value client_config(rapidjson::kObjectType);
478 args->PushBack(client_config, allocator);
479 auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall(
480 MethodCall<rapidjson::Document>(kSetClientMethod, std::move(args)));
481 EXPECT_TRUE(messenger.SimulateEngineMessage(
482 kChannelName, encoded->data(), encoded->size(),
483 [](const uint8_t* reply, size_t reply_size) {}));
484
485 plugin.ComposeBeginHook();
486 EXPECT_EQ(selection_base, 0);
487 plugin.ComposeChangeHook(u"abc", 3);
488 EXPECT_EQ(selection_base, 3);
489
490 plugin.ComposeCommitHook();
491 plugin.ComposeEndHook();
492 EXPECT_EQ(selection_base, 3);
493
494 plugin.ComposeBeginHook();
495 plugin.ComposeChangeHook(u"1", 1);
496 EXPECT_EQ(selection_base, 4);
497
498 plugin.ComposeChangeHook(u"12", 2);
499 EXPECT_EQ(selection_base, 5);
500
501 plugin.ComposeChangeHook(u"12", 1);
502 EXPECT_EQ(selection_base, 4);
503
504 plugin.ComposeChangeHook(u"12", 2);
505 EXPECT_EQ(selection_base, 5);
506 }
507
508 TEST_F(TextInputPluginTest, TransformCursorRect) {
509 UseEngineWithView();
510
511 // A position of `EditableText`.
512 double view_x = 100;
513 double view_y = 200;
514
515 // A position and size of marked text, in `EditableText` local coordinates.
516 double ime_x = 3;
517 double ime_y = 4;
518 double ime_width = 50;
519 double ime_height = 60;
520
521 // Transformation matrix.
522 std::array<std::array<double, 4>, 4> editabletext_transform = {
523 1.0, 0.0, 0.0, view_x, //
524 0.0, 1.0, 0.0, view_y, //
525 0.0, 0.0, 0.0, 0.0, //
526 0.0, 0.0, 0.0, 1.0};
527
528 TestBinaryMessenger messenger([](const std::string& channel,
529 const uint8_t* message, size_t message_size,
530 BinaryReply reply) {});
531 BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};
532
533 TextInputPlugin handler(&messenger, engine());
534
535 auto& codec = JsonMethodCodec::GetInstance();
536
537 EXPECT_CALL(*view(), OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
538
539 {
540 auto arguments =
541 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
542 auto& allocator = arguments->GetAllocator();
543
544 rapidjson::Value transoform(rapidjson::kArrayType);
545 for (int i = 0; i < 4 * 4; i++) {
546 // Pack 2-dimensional array by column-major order.
547 transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
548 }
549
550 arguments->AddMember("transform", transoform, allocator);
551
552 auto message = codec.EncodeMethodCall(
553 {"TextInput.setEditableSizeAndTransform", std::move(arguments)});
554 messenger.SimulateEngineMessage(kChannelName, message->data(),
555 message->size(), reply_handler);
556 }
557
558 EXPECT_CALL(*view(),
559 OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
560 {ime_width, ime_height}}));
561
562 {
563 auto arguments =
564 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
565 auto& allocator = arguments->GetAllocator();
566
567 arguments->AddMember("x", ime_x, allocator);
568 arguments->AddMember("y", ime_y, allocator);
569 arguments->AddMember("width", ime_width, allocator);
570 arguments->AddMember("height", ime_height, allocator);
571
572 auto message = codec.EncodeMethodCall(
573 {"TextInput.setMarkedTextRect", std::move(arguments)});
574 messenger.SimulateEngineMessage(kChannelName, message->data(),
575 message->size(), reply_handler);
576 }
577 }
578
579 TEST_F(TextInputPluginTest, SetMarkedTextRectRequiresView) {
580 UseHeadlessEngine();
581
582 TestBinaryMessenger messenger([](const std::string& channel,
583 const uint8_t* message, size_t message_size,
584 BinaryReply reply) {});
585
586 std::string reply;
587 BinaryReply reply_handler = [&reply](const uint8_t* reply_bytes,
588 size_t reply_size) {
589 reply = std::string(reinterpret_cast<const char*>(reply_bytes), reply_size);
590 };
591
592 TextInputPlugin handler(&messenger, engine());
593
594 auto& codec = JsonMethodCodec::GetInstance();
595
596 auto arguments =
597 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
598 auto& allocator = arguments->GetAllocator();
599
600 arguments->AddMember("x", 0, allocator);
601 arguments->AddMember("y", 0, allocator);
602 arguments->AddMember("width", 0, allocator);
603 arguments->AddMember("height", 0, allocator);
604
605 auto message = codec.EncodeMethodCall(
606 {"TextInput.setMarkedTextRect", std::move(arguments)});
607 messenger.SimulateEngineMessage(kChannelName, message->data(),
608 message->size(), reply_handler);
609
610 EXPECT_EQ(reply,
611 "[\"Internal Consistency Error\",\"Text input is not available in "
612 "Windows headless mode\",null]");
613 }
614
615 } // namespace testing
616 } // namespace flutter
flutter::TextInputPlugin::ComposeBeginHook
virtual void ComposeBeginHook()
Definition: text_input_plugin.cc:125
flutter::TextInputPlugin::ComposeChangeHook
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
Definition: text_input_plugin.cc:192
flutter::testing::TextInputPluginTest::~TextInputPluginTest
virtual ~TextInputPluginTest()=default
kAffinityDownstream
static constexpr char kAffinityDownstream[]
Definition: text_input_plugin.cc:46
flutter::JsonMessageCodec::GetInstance
static const JsonMessageCodec & GetInstance()
Definition: json_message_codec.cc:17
flutter::testing::TextInputPluginTest::window
MockWindowBindingHandler * window()
Definition: text_input_plugin_unittest.cc:128
text_input_plugin.h
flutter::FlutterWindowsEngine
Definition: flutter_windows_engine.h:78
kUpdateEditingStateMethod
static constexpr char kUpdateEditingStateMethod[]
Definition: text_input_plugin.cc:28
flutter::TextInputPlugin::ComposeEndHook
virtual void ComposeEndHook()
Definition: text_input_plugin.cc:175
flutter::testing::TextInputPluginTest::view
MockFlutterWindowsView * view()
Definition: text_input_plugin_unittest.cc:127
json_method_codec.h
kComposingExtentKey
static constexpr char kComposingExtentKey[]
Definition: text_input_plugin.cc:44
flutter::TextInputPlugin::ComposeCommitHook
virtual void ComposeCommitHook()
Definition: text_input_plugin.cc:140
flutter::testing::MockFlutterWindowsView::MockFlutterWindowsView
MockFlutterWindowsView(std::unique_ptr< WindowBindingHandler > wbh)
Definition: flutter_windows_engine_unittests.cc:621
flutter::Rect
Definition: geometry.h:56
json_message_codec.h
flutter::JsonMethodCodec::GetInstance
static const JsonMethodCodec & GetInstance()
Definition: json_method_codec.cc:36
flutter::testing::TextInputPluginTest
Definition: text_input_plugin_unittest.cc:120
kEnableDeltaModel
static constexpr char kEnableDeltaModel[]
Definition: text_input_plugin.cc:39
kSelectionIsDirectionalKey
static constexpr char kSelectionIsDirectionalKey[]
Definition: text_input_plugin.cc:49
kSelectionExtentKey
static constexpr char kSelectionExtentKey[]
Definition: text_input_plugin.cc:48
flutter::testing::TextInputPluginTest::engine
FlutterWindowsEngine * engine()
Definition: text_input_plugin_unittest.cc:126
flutter::TextRange
Definition: text_range.h:19
flutter_windows_view.h
text
std::u16string text
Definition: keyboard_unittests.cc:332
flutter::testing::TEST_F
TEST_F(CursorHandlerTest, ActivateSystemCursor)
Definition: cursor_handler_unittests.cc:94
flutter::MethodCall
Definition: method_call.h:18
flutter::testing::TextInputPluginTest::UseHeadlessEngine
void UseHeadlessEngine()
Definition: text_input_plugin_unittest.cc:130
flutter::BinaryReply
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
Definition: binary_messenger.h:17
flutter::MethodCodec::EncodeMethodCall
std::unique_ptr< std::vector< uint8_t > > EncodeMethodCall(const MethodCall< T > &method_call) const
Definition: method_codec.h:48
kSelectionBaseKey
static constexpr char kSelectionBaseKey[]
Definition: text_input_plugin.cc:47
flutter
Definition: accessibility_bridge_windows.cc:11
flutter::testing::TextInputPluginTest::TextInputPluginTest
TextInputPluginTest()=default
kTextPlainFormat
static constexpr char kTextPlainFormat[]
Definition: platform_handler.cc:38
flutter::testing::MockFlutterWindowsView::MOCK_METHOD
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
kSetClientMethod
static constexpr char kSetClientMethod[]
Definition: text_input_plugin.cc:19
kChannelName
static constexpr char kChannelName[]
Definition: cursor_handler.cc:13
kTextKey
static constexpr char kTextKey[]
Definition: platform_handler.cc:39
flutter::TextInputPlugin
Definition: text_input_plugin.h:28
kSelectionAffinityKey
static constexpr char kSelectionAffinityKey[]
Definition: text_input_plugin.cc:45
flutter::TextInputPlugin::KeyboardHook
virtual void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down)
Definition: text_input_plugin.cc:85
flutter::MessageCodec::EncodeMessage
std::unique_ptr< std::vector< uint8_t > > EncodeMessage(const T &message) const
Definition: message_codec.h:45
message
Win32Message message
Definition: keyboard_unittests.cc:137
kComposingBaseKey
static constexpr char kComposingBaseKey[]
Definition: text_input_plugin.cc:43
flutter::testing::TextInputPluginTest::UseEngineWithView
void UseEngineWithView()
Definition: text_input_plugin_unittest.cc:136
flutter::MethodCodec::DecodeMethodCall
std::unique_ptr< MethodCall< T > > DecodeMethodCall(const uint8_t *message, size_t message_size) const
Definition: method_codec.h:32
flutter::FlutterWindowsView::FlutterWindowsView
FlutterWindowsView(std::unique_ptr< WindowBindingHandler > window_binding, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
Definition: flutter_windows_view.cc:72