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