6 #include <rapidjson/document.h>
10 #include "flutter/fml/macros.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"
26 using ::testing::Return;
28 static constexpr
char kScanCodeKey[] =
"scanCode";
29 static constexpr
int kHandledScanCode = 20;
30 static constexpr
int kUnhandledScanCode = 21;
32 static constexpr
int kDefaultClientId = 42;
34 static constexpr
char kChannelName[] =
"flutter/textinput";
38 static constexpr
char kTextKey[] =
"text";
46 "TextInputClient.updateEditingState";
48 static std::unique_ptr<std::vector<uint8_t>> CreateResponse(
bool handled) {
50 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
51 auto& allocator = response_doc->GetAllocator();
52 response_doc->AddMember(
"handled", handled, allocator);
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);
63 rapidjson::Value config(rapidjson::kObjectType);
64 config.AddMember(
"inputAction", input_action, 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);
74 static std::unique_ptr<rapidjson::Document> EncodedEditingState(
76 TextRange selection) {
77 auto model = std::make_unique<TextInputModel>();
79 model->SetSelection(selection);
81 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
82 auto& allocator = arguments->GetAllocator();
83 arguments->PushBack(kDefaultClientId, allocator);
85 rapidjson::Value editing_state(rapidjson::kObjectType);
93 model->composing() ? model->composing_range().base() : -1;
94 int composing_extent =
95 model->composing() ? model->composing_range().extent() : -1;
99 rapidjson::Value(model->GetText(), allocator).Move(),
101 arguments->PushBack(editing_state, allocator);
106 class MockFlutterWindowsView :
public FlutterWindowsView {
109 std::unique_ptr<WindowBindingHandler> window)
113 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
114 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
117 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
129 MockFlutterWindowsView*
view() {
return view_.get(); }
130 MockWindowBindingHandler*
window() {
return window_; }
133 FlutterWindowsEngineBuilder builder{GetContext()};
135 engine_ = builder.Build();
139 FlutterWindowsEngineBuilder builder{GetContext()};
141 auto window = std::make_unique<MockWindowBindingHandler>();
144 EXPECT_CALL(*window_, SetView).Times(1);
145 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
147 engine_ = builder.Build();
148 view_ = std::make_unique<MockFlutterWindowsView>(engine_.get(),
151 EngineModifier modifier{engine_.get()};
152 modifier.SetImplicitView(view_.get());
156 std::unique_ptr<FlutterWindowsEngine> engine_;
157 std::unique_ptr<MockFlutterWindowsView> view_;
158 MockWindowBindingHandler* window_;
166 auto handled_message = CreateResponse(
true);
167 auto unhandled_message = CreateResponse(
false);
168 int received_scancode = 0;
170 TestBinaryMessenger messenger(
171 [&received_scancode, &handled_message, &unhandled_message](
172 const std::string& channel,
const uint8_t*
message,
175 int redispatch_scancode = 0;
178 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
181 text.push_back(
'\n');
191 TestBinaryMessenger messenger([](
const std::string& channel,
192 const uint8_t*
message,
size_t message_size,
194 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
198 EXPECT_CALL(*view(), OnResetImeComposing());
201 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
203 message->size(), reply_handler);
210 TestBinaryMessenger messenger([](
const std::string& channel,
211 const uint8_t*
message,
size_t message_size,
215 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
217 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
223 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
225 message->size(), reply_handler);
228 "[\"Internal Consistency Error\",\"Text input is not available in "
229 "Windows headless mode\",null]");
237 bool sent_message =
false;
238 TestBinaryMessenger messenger(
239 [&sent_message](
const std::string& channel,
const uint8_t*
message,
242 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
256 arguments->PushBack(config, allocator);
258 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
259 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
260 message->size(), reply_handler);
263 sent_message =
false;
265 EXPECT_TRUE(sent_message);
268 sent_message =
false;
270 EXPECT_TRUE(sent_message);
280 sent_message =
false;
282 EXPECT_FALSE(sent_message);
285 sent_message =
false;
287 EXPECT_TRUE(sent_message);
294 std::vector<std::string> messages;
296 TestBinaryMessenger messenger(
297 [&messages](
const std::string& channel,
const uint8_t*
message,
299 std::string last_message(
reinterpret_cast<const char*
>(
message),
301 messages.push_back(last_message);
303 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
318 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
322 EXPECT_EQ(messages.size(), 2);
325 auto encoded_arguments = EncodedEditingState(
"\n",
TextRange(1));
326 auto update_state_message = codec.EncodeMethodCall(
329 EXPECT_TRUE(std::equal(update_state_message->begin(),
330 update_state_message->end(),
331 messages.front().begin()));
334 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
335 auto& allocator = arguments->GetAllocator();
336 arguments->PushBack(kDefaultClientId, allocator);
338 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
339 auto invoke_action_message = codec.EncodeMethodCall(
340 {
"TextInputClient.performAction", std::move(arguments)});
342 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
343 invoke_action_message->end(),
344 messages.back().begin()));
351 std::vector<std::vector<uint8_t>> messages;
353 TestBinaryMessenger messenger(
354 [&messages](
const std::string& channel,
const uint8_t*
message,
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);
361 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
376 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
379 EXPECT_EQ(messages.size(), 1);
382 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
383 auto& allocator = arguments->GetAllocator();
384 arguments->PushBack(kDefaultClientId, allocator);
386 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
387 auto invoke_action_message = codec.EncodeMethodCall(
388 {
"TextInputClient.performAction", std::move(arguments)});
390 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
391 invoke_action_message->end(),
392 messages.front().begin()));
398 auto handled_message = CreateResponse(
true);
399 auto unhandled_message = CreateResponse(
false);
400 int received_scancode = 0;
402 TestBinaryMessenger messenger(
403 [&received_scancode, &handled_message, &unhandled_message](
404 const std::string& channel,
const uint8_t*
message,
407 int redispatch_scancode = 0;
410 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
411 auto& allocator = args->GetAllocator();
412 args->PushBack(123, allocator);
414 rapidjson::Value client_config(rapidjson::kObjectType);
417 args->PushBack(client_config, allocator);
421 EXPECT_TRUE(messenger.SimulateEngineMessage(
423 [](
const uint8_t* reply,
size_t reply_size) {}));
425 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
428 text.push_back(
'\n');
432 handler.
KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
434 std::u16string textN;
437 handler.
KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
438 std::u16string textNi;
442 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
443 std::u16string textChineseCharacter;
444 text.push_back(u
'\u4F60');
456 int selection_base = -1;
457 TestBinaryMessenger messenger([&](
const std::string& channel,
458 const uint8_t*
message,
size_t size,
463 const auto& args = *method->arguments();
464 const auto& editing_state = args[1];
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);
478 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
479 auto& allocator = args->GetAllocator();
480 args->PushBack(123, allocator);
481 rapidjson::Value client_config(rapidjson::kObjectType);
482 args->PushBack(client_config, allocator);
485 EXPECT_TRUE(messenger.SimulateEngineMessage(
487 [](
const uint8_t* reply,
size_t reply_size) {}));
489 plugin.ComposeBeginHook();
490 EXPECT_EQ(selection_base, 0);
491 plugin.ComposeChangeHook(u
"abc", 3);
492 EXPECT_EQ(selection_base, 3);
494 plugin.ComposeCommitHook();
495 plugin.ComposeEndHook();
496 EXPECT_EQ(selection_base, 3);
498 plugin.ComposeBeginHook();
499 plugin.ComposeChangeHook(u
"1", 1);
500 EXPECT_EQ(selection_base, 4);
502 plugin.ComposeChangeHook(u
"12", 2);
503 EXPECT_EQ(selection_base, 5);
505 plugin.ComposeChangeHook(u
"12", 1);
506 EXPECT_EQ(selection_base, 4);
508 plugin.ComposeChangeHook(u
"12", 2);
509 EXPECT_EQ(selection_base, 5);
522 double ime_width = 50;
523 double ime_height = 60;
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,
532 TestBinaryMessenger messenger([](
const std::string& channel,
533 const uint8_t*
message,
size_t message_size,
535 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
541 EXPECT_CALL(*view(), OnCursorRectUpdated(
Rect{{view_x, view_y}, {0, 0}}));
545 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
546 auto& allocator = arguments->GetAllocator();
548 rapidjson::Value transoform(rapidjson::kArrayType);
549 for (
int i = 0; i < 4 * 4; i++) {
551 transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
554 arguments->AddMember(
"transform", transoform, allocator);
556 auto message = codec.EncodeMethodCall(
557 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
559 message->size(), reply_handler);
563 OnCursorRectUpdated(
Rect{{view_x + ime_x, view_y + ime_y},
564 {ime_width, ime_height}}));
568 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
569 auto& allocator = arguments->GetAllocator();
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);
576 auto message = codec.EncodeMethodCall(
577 {
"TextInput.setMarkedTextRect", std::move(arguments)});
579 message->size(), reply_handler);
586 TestBinaryMessenger messenger([](
const std::string& channel,
587 const uint8_t*
message,
size_t message_size,
591 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
593 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
601 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
602 auto& allocator = arguments->GetAllocator();
604 arguments->AddMember(
"x", 0, allocator);
605 arguments->AddMember(
"y", 0, allocator);
606 arguments->AddMember(
"width", 0, allocator);
607 arguments->AddMember(
"height", 0, allocator);
609 auto message = codec.EncodeMethodCall(
610 {
"TextInput.setMarkedTextRect", std::move(arguments)});
612 message->size(), reply_handler);
615 "[\"Internal Consistency Error\",\"Text input is not available in "
616 "Windows headless mode\",null]");