6 #include <rapidjson/document.h>
10 #include "flutter/fml/macros.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"
25 using ::testing::Return;
27 static constexpr
char kScanCodeKey[] =
"scanCode";
28 static constexpr
int kHandledScanCode = 20;
29 static constexpr
int kUnhandledScanCode = 21;
31 static constexpr
int kDefaultClientId = 42;
33 static constexpr
char kChannelName[] =
"flutter/textinput";
37 static constexpr
char kTextKey[] =
"text";
45 "TextInputClient.updateEditingState";
47 static std::unique_ptr<std::vector<uint8_t>> CreateResponse(
bool handled) {
49 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
50 auto& allocator = response_doc->GetAllocator();
51 response_doc->AddMember(
"handled", handled, allocator);
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);
62 rapidjson::Value config(rapidjson::kObjectType);
63 config.AddMember(
"inputAction", input_action, 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);
73 static std::unique_ptr<rapidjson::Document> EncodedEditingState(
75 TextRange selection) {
76 auto model = std::make_unique<TextInputModel>();
78 model->SetSelection(selection);
80 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
81 auto& allocator = arguments->GetAllocator();
82 arguments->PushBack(kDefaultClientId, allocator);
84 rapidjson::Value editing_state(rapidjson::kObjectType);
92 model->composing() ? model->composing_range().base() : -1;
93 int composing_extent =
94 model->composing() ? model->composing_range().extent() : -1;
98 rapidjson::Value(model->GetText(), allocator).Move(),
100 arguments->PushBack(editing_state, allocator);
105 class MockFlutterWindowsView :
public FlutterWindowsView {
111 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
112 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
115 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
127 MockFlutterWindowsView*
view() {
return view_.get(); }
128 MockWindowBindingHandler*
window() {
return window_; }
131 FlutterWindowsEngineBuilder builder{GetContext()};
133 engine_ = builder.Build();
137 FlutterWindowsEngineBuilder builder{GetContext()};
139 auto window = std::make_unique<MockWindowBindingHandler>();
142 EXPECT_CALL(*window_, SetView).Times(1);
143 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
145 engine_ = builder.Build();
146 view_ = std::make_unique<MockFlutterWindowsView>(std::move(
window));
148 engine_->SetView(view_.get());
152 std::unique_ptr<FlutterWindowsEngine> engine_;
153 std::unique_ptr<MockFlutterWindowsView> view_;
154 MockWindowBindingHandler* window_;
162 auto handled_message = CreateResponse(
true);
163 auto unhandled_message = CreateResponse(
false);
164 int received_scancode = 0;
166 TestBinaryMessenger messenger(
167 [&received_scancode, &handled_message, &unhandled_message](
168 const std::string& channel,
const uint8_t*
message,
171 int redispatch_scancode = 0;
174 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
177 text.push_back(
'\n');
187 TestBinaryMessenger messenger([](
const std::string& channel,
188 const uint8_t*
message,
size_t message_size,
190 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
194 EXPECT_CALL(*view(), OnResetImeComposing());
197 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
199 message->size(), reply_handler);
206 TestBinaryMessenger messenger([](
const std::string& channel,
207 const uint8_t*
message,
size_t message_size,
211 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
213 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
219 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
221 message->size(), reply_handler);
224 "[\"Internal Consistency Error\",\"Text input is not available in "
225 "Windows headless mode\",null]");
233 bool sent_message =
false;
234 TestBinaryMessenger messenger(
235 [&sent_message](
const std::string& channel,
const uint8_t*
message,
238 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
252 arguments->PushBack(config, allocator);
254 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
255 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
256 message->size(), reply_handler);
259 sent_message =
false;
261 EXPECT_TRUE(sent_message);
264 sent_message =
false;
266 EXPECT_TRUE(sent_message);
276 sent_message =
false;
278 EXPECT_FALSE(sent_message);
281 sent_message =
false;
283 EXPECT_TRUE(sent_message);
290 std::vector<std::string> messages;
292 TestBinaryMessenger messenger(
293 [&messages](
const std::string& channel,
const uint8_t*
message,
295 std::string last_message(
reinterpret_cast<const char*
>(
message),
297 messages.push_back(last_message);
299 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
314 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
318 EXPECT_EQ(messages.size(), 2);
321 auto encoded_arguments = EncodedEditingState(
"\n",
TextRange(1));
322 auto update_state_message = codec.EncodeMethodCall(
325 EXPECT_TRUE(std::equal(update_state_message->begin(),
326 update_state_message->end(),
327 messages.front().begin()));
330 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
331 auto& allocator = arguments->GetAllocator();
332 arguments->PushBack(kDefaultClientId, allocator);
334 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
335 auto invoke_action_message = codec.EncodeMethodCall(
336 {
"TextInputClient.performAction", std::move(arguments)});
338 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
339 invoke_action_message->end(),
340 messages.back().begin()));
347 std::vector<std::vector<uint8_t>> messages;
349 TestBinaryMessenger messenger(
350 [&messages](
const std::string& channel,
const uint8_t*
message,
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);
357 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
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);
372 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
375 EXPECT_EQ(messages.size(), 1);
378 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
379 auto& allocator = arguments->GetAllocator();
380 arguments->PushBack(kDefaultClientId, allocator);
382 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
383 auto invoke_action_message = codec.EncodeMethodCall(
384 {
"TextInputClient.performAction", std::move(arguments)});
386 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
387 invoke_action_message->end(),
388 messages.front().begin()));
394 auto handled_message = CreateResponse(
true);
395 auto unhandled_message = CreateResponse(
false);
396 int received_scancode = 0;
398 TestBinaryMessenger messenger(
399 [&received_scancode, &handled_message, &unhandled_message](
400 const std::string& channel,
const uint8_t*
message,
403 int redispatch_scancode = 0;
406 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
407 auto& allocator = args->GetAllocator();
408 args->PushBack(123, allocator);
410 rapidjson::Value client_config(rapidjson::kObjectType);
413 args->PushBack(client_config, allocator);
417 EXPECT_TRUE(messenger.SimulateEngineMessage(
419 [](
const uint8_t* reply,
size_t reply_size) {}));
421 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
424 text.push_back(
'\n');
428 handler.
KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
430 std::u16string textN;
433 handler.
KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
434 std::u16string textNi;
438 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
439 std::u16string textChineseCharacter;
440 text.push_back(u
'\u4F60');
452 int selection_base = -1;
453 TestBinaryMessenger messenger([&](
const std::string& channel,
454 const uint8_t*
message,
size_t size,
459 const auto& args = *method->arguments();
460 const auto& editing_state = args[1];
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);
474 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
475 auto& allocator = args->GetAllocator();
476 args->PushBack(123, allocator);
477 rapidjson::Value client_config(rapidjson::kObjectType);
478 args->PushBack(client_config, allocator);
481 EXPECT_TRUE(messenger.SimulateEngineMessage(
483 [](
const uint8_t* reply,
size_t reply_size) {}));
485 plugin.ComposeBeginHook();
486 EXPECT_EQ(selection_base, 0);
487 plugin.ComposeChangeHook(u
"abc", 3);
488 EXPECT_EQ(selection_base, 3);
490 plugin.ComposeCommitHook();
491 plugin.ComposeEndHook();
492 EXPECT_EQ(selection_base, 3);
494 plugin.ComposeBeginHook();
495 plugin.ComposeChangeHook(u
"1", 1);
496 EXPECT_EQ(selection_base, 4);
498 plugin.ComposeChangeHook(u
"12", 2);
499 EXPECT_EQ(selection_base, 5);
501 plugin.ComposeChangeHook(u
"12", 1);
502 EXPECT_EQ(selection_base, 4);
504 plugin.ComposeChangeHook(u
"12", 2);
505 EXPECT_EQ(selection_base, 5);
518 double ime_width = 50;
519 double ime_height = 60;
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,
528 TestBinaryMessenger messenger([](
const std::string& channel,
529 const uint8_t*
message,
size_t message_size,
531 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
537 EXPECT_CALL(*view(), OnCursorRectUpdated(
Rect{{view_x, view_y}, {0, 0}}));
541 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
542 auto& allocator = arguments->GetAllocator();
544 rapidjson::Value transoform(rapidjson::kArrayType);
545 for (
int i = 0; i < 4 * 4; i++) {
547 transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
550 arguments->AddMember(
"transform", transoform, allocator);
552 auto message = codec.EncodeMethodCall(
553 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
555 message->size(), reply_handler);
559 OnCursorRectUpdated(
Rect{{view_x + ime_x, view_y + ime_y},
560 {ime_width, ime_height}}));
564 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
565 auto& allocator = arguments->GetAllocator();
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);
572 auto message = codec.EncodeMethodCall(
573 {
"TextInput.setMarkedTextRect", std::move(arguments)});
575 message->size(), reply_handler);
582 TestBinaryMessenger messenger([](
const std::string& channel,
583 const uint8_t*
message,
size_t message_size,
587 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
589 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
597 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
598 auto& allocator = arguments->GetAllocator();
600 arguments->AddMember(
"x", 0, allocator);
601 arguments->AddMember(
"y", 0, allocator);
602 arguments->AddMember(
"width", 0, allocator);
603 arguments->AddMember(
"height", 0, allocator);
605 auto message = codec.EncodeMethodCall(
606 {
"TextInput.setMarkedTextRect", std::move(arguments)});
608 message->size(), reply_handler);
611 "[\"Internal Consistency Error\",\"Text input is not available in "
612 "Windows headless mode\",null]");