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"
27 : text_input_plugin(text_input_plugin) {}
30 text_input_plugin->view_id_ = view_id;
42 using ::testing::Return;
44 static constexpr
char kScanCodeKey[] =
"scanCode";
45 static constexpr
int kHandledScanCode = 20;
46 static constexpr
int kUnhandledScanCode = 21;
48 static constexpr
int kDefaultClientId = 42;
50 static constexpr
char kChannelName[] =
"flutter/textinput";
52 static constexpr
char kViewId[] =
"viewId";
55 static constexpr
char kTextKey[] =
"text";
63 "TextInputClient.updateEditingState";
65 static std::unique_ptr<std::vector<uint8_t>> CreateResponse(
bool handled) {
67 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
68 auto& allocator = response_doc->GetAllocator();
69 response_doc->AddMember(
"handled", handled, allocator);
73 static std::unique_ptr<rapidjson::Document> EncodedClientConfig(
74 std::string type_name,
75 std::string input_action) {
76 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
77 auto& allocator = arguments->GetAllocator();
78 arguments->PushBack(kDefaultClientId, allocator);
80 rapidjson::Value config(rapidjson::kObjectType);
81 config.AddMember(
"inputAction", input_action, allocator);
83 config.AddMember(
kViewId, 456, allocator);
84 rapidjson::Value type_info(rapidjson::kObjectType);
85 type_info.AddMember(
"name", type_name, allocator);
86 config.AddMember(
"inputType", type_info, allocator);
87 arguments->PushBack(config, allocator);
92 static std::unique_ptr<rapidjson::Document> EncodedEditingState(
94 TextRange selection) {
95 auto model = std::make_unique<TextInputModel>();
97 model->SetSelection(selection);
99 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
100 auto& allocator = arguments->GetAllocator();
101 arguments->PushBack(kDefaultClientId, allocator);
103 rapidjson::Value editing_state(rapidjson::kObjectType);
111 model->composing() ? model->composing_range().base() : -1;
112 int composing_extent =
113 model->composing() ? model->composing_range().extent() : -1;
117 rapidjson::Value(model->GetText(), allocator).Move(),
119 arguments->PushBack(editing_state, allocator);
124 class MockFlutterWindowsView :
public FlutterWindowsView {
127 std::unique_ptr<WindowBindingHandler> window)
135 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
136 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
139 FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView);
151 MockFlutterWindowsView*
view() {
return view_.get(); }
152 MockWindowBindingHandler*
window() {
return window_; }
155 FlutterWindowsEngineBuilder builder{GetContext()};
157 engine_ = builder.Build();
161 FlutterWindowsEngineBuilder builder{GetContext()};
163 auto window = std::make_unique<MockWindowBindingHandler>();
166 EXPECT_CALL(*window_, SetView).Times(1);
167 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
169 engine_ = builder.Build();
170 view_ = std::make_unique<MockFlutterWindowsView>(engine_.get(),
173 EngineModifier modifier{engine_.get()};
174 modifier.SetViewById(view_.get(), 456);
178 EXPECT_NE(engine_,
nullptr);
179 auto window = std::make_unique<MockWindowBindingHandler>();
180 EXPECT_CALL(*
window, SetView).Times(1);
181 EXPECT_CALL(*
window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
182 auto view = std::make_unique<MockFlutterWindowsView>(engine_.get(),
185 EngineModifier modifier{engine_.get()};
186 modifier.SetViewById(view_.get(), view_id);
191 std::unique_ptr<FlutterWindowsEngine> engine_;
192 std::unique_ptr<MockFlutterWindowsView> view_;
193 MockWindowBindingHandler* window_;
201 auto handled_message = CreateResponse(
true);
202 auto unhandled_message = CreateResponse(
false);
203 int received_scancode = 0;
205 TestBinaryMessenger messenger(
206 [&received_scancode, &handled_message, &unhandled_message](
207 const std::string& channel,
const uint8_t*
message,
210 int redispatch_scancode = 0;
213 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
216 text.push_back(
'\n');
226 TestBinaryMessenger messenger([](
const std::string& channel,
227 const uint8_t*
message,
size_t message_size,
229 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
235 EXPECT_CALL(*view(), OnResetImeComposing());
238 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
240 message->size(), reply_handler);
247 TestBinaryMessenger messenger([](
const std::string& channel,
248 const uint8_t*
message,
size_t message_size,
252 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
254 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
260 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
262 message->size(), reply_handler);
266 "[\"Internal Consistency Error\",\"Text input is not available because "
267 "view with view_id=0 cannot be found\",null]");
275 bool sent_message =
false;
276 TestBinaryMessenger messenger(
277 [&sent_message](
const std::string& channel,
const uint8_t*
message,
280 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
287 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
288 auto& allocator = arguments->GetAllocator();
289 arguments->PushBack(kDefaultClientId, allocator);
290 rapidjson::Value config(rapidjson::kObjectType);
291 config.AddMember(
"inputAction",
"done", allocator);
292 config.AddMember(
"inputType",
"text", allocator);
294 config.AddMember(
kViewId, 456, allocator);
295 arguments->PushBack(config, allocator);
297 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
298 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
299 message->size(), reply_handler);
302 sent_message =
false;
304 EXPECT_TRUE(sent_message);
307 sent_message =
false;
309 EXPECT_TRUE(sent_message);
319 sent_message =
false;
321 EXPECT_FALSE(sent_message);
324 sent_message =
false;
326 EXPECT_TRUE(sent_message);
333 std::vector<std::string> messages;
335 TestBinaryMessenger messenger(
336 [&messages](
const std::string& channel,
const uint8_t*
message,
338 std::string last_message(
reinterpret_cast<const char*
>(
message),
340 messages.push_back(last_message);
342 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
349 auto set_client_arguments =
350 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.newline");
351 auto message = codec.EncodeMethodCall(
352 {
"TextInput.setClient", std::move(set_client_arguments)});
353 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
354 message->size(), reply_handler);
357 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
361 EXPECT_EQ(messages.size(), 2);
364 auto encoded_arguments = EncodedEditingState(
"\n",
TextRange(1));
365 auto update_state_message = codec.EncodeMethodCall(
368 EXPECT_TRUE(std::equal(update_state_message->begin(),
369 update_state_message->end(),
370 messages.front().begin()));
373 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
374 auto& allocator = arguments->GetAllocator();
375 arguments->PushBack(kDefaultClientId, allocator);
377 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
378 auto invoke_action_message = codec.EncodeMethodCall(
379 {
"TextInputClient.performAction", std::move(arguments)});
381 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
382 invoke_action_message->end(),
383 messages.back().begin()));
390 std::vector<std::vector<uint8_t>> messages;
392 TestBinaryMessenger messenger(
393 [&messages](
const std::string& channel,
const uint8_t*
message,
395 int length =
static_cast<int>(message_size);
396 std::vector<uint8_t> last_message(length);
397 memcpy(&last_message[0], &
message[0], length *
sizeof(uint8_t));
398 messages.push_back(last_message);
400 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
407 auto set_client_arguments =
408 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.send");
409 auto message = codec.EncodeMethodCall(
410 {
"TextInput.setClient", std::move(set_client_arguments)});
411 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
412 message->size(), reply_handler);
415 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
418 EXPECT_EQ(messages.size(), 1);
421 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
422 auto& allocator = arguments->GetAllocator();
423 arguments->PushBack(kDefaultClientId, allocator);
425 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
426 auto invoke_action_message = codec.EncodeMethodCall(
427 {
"TextInputClient.performAction", std::move(arguments)});
429 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
430 invoke_action_message->end(),
431 messages.front().begin()));
437 TestBinaryMessenger messenger([](
const std::string& channel,
438 const uint8_t*
message,
size_t message_size,
443 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
444 auto& allocator = args->GetAllocator();
445 args->PushBack(123, allocator);
447 rapidjson::Value client_config(rapidjson::kObjectType);
449 args->PushBack(client_config, allocator);
454 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
456 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
459 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
460 encoded->size(), reply_handler));
463 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
469 TestBinaryMessenger messenger([](
const std::string& channel,
470 const uint8_t*
message,
size_t message_size,
475 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
476 auto& allocator = args->GetAllocator();
477 args->PushBack(123, allocator);
479 rapidjson::Value client_config(rapidjson::kObjectType);
480 client_config.AddMember(
kViewId,
"Not an integer", allocator);
482 args->PushBack(client_config, allocator);
487 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
489 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
492 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
493 encoded->size(), reply_handler));
496 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
502 auto handled_message = CreateResponse(
true);
503 auto unhandled_message = CreateResponse(
false);
504 int received_scancode = 0;
506 TestBinaryMessenger messenger(
507 [&received_scancode, &handled_message, &unhandled_message](
508 const std::string& channel,
const uint8_t*
message,
511 int redispatch_scancode = 0;
514 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
515 auto& allocator = args->GetAllocator();
516 args->PushBack(123, allocator);
518 rapidjson::Value client_config(rapidjson::kObjectType);
520 client_config.AddMember(
kViewId, 456, allocator);
522 args->PushBack(client_config, allocator);
526 EXPECT_TRUE(messenger.SimulateEngineMessage(
528 [](
const uint8_t* reply,
size_t reply_size) {}));
530 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
533 text.push_back(
'\n');
537 handler.
KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
539 std::u16string textN;
542 handler.
KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
543 std::u16string textNi;
547 handler.
KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
548 std::u16string textChineseCharacter;
549 text.push_back(u
'\u4F60');
561 int selection_base = -1;
562 TestBinaryMessenger messenger([&](
const std::string& channel,
563 const uint8_t*
message,
size_t size,
568 const auto& args = *method->arguments();
569 const auto& editing_state = args[1];
572 ASSERT_NE(base, editing_state.MemberEnd());
573 ASSERT_TRUE(base->value.IsInt());
574 ASSERT_NE(extent, editing_state.MemberEnd());
575 ASSERT_TRUE(extent->value.IsInt());
576 selection_base = base->value.GetInt();
577 EXPECT_EQ(extent->value.GetInt(), selection_base);
583 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
584 auto& allocator = args->GetAllocator();
585 args->PushBack(123, allocator);
586 rapidjson::Value client_config(rapidjson::kObjectType);
587 client_config.AddMember(
kViewId, 456, allocator);
588 args->PushBack(client_config, allocator);
591 EXPECT_TRUE(messenger.SimulateEngineMessage(
593 [](
const uint8_t* reply,
size_t reply_size) {}));
595 plugin.ComposeBeginHook();
596 EXPECT_EQ(selection_base, 0);
597 plugin.ComposeChangeHook(u
"abc", 3);
598 EXPECT_EQ(selection_base, 3);
600 plugin.ComposeCommitHook();
601 plugin.ComposeEndHook();
602 EXPECT_EQ(selection_base, 3);
604 plugin.ComposeBeginHook();
605 plugin.ComposeChangeHook(u
"1", 1);
606 EXPECT_EQ(selection_base, 4);
608 plugin.ComposeChangeHook(u
"12", 2);
609 EXPECT_EQ(selection_base, 5);
611 plugin.ComposeChangeHook(u
"12", 1);
612 EXPECT_EQ(selection_base, 4);
614 plugin.ComposeChangeHook(u
"12", 2);
615 EXPECT_EQ(selection_base, 5);
628 double ime_width = 50;
629 double ime_height = 60;
632 std::array<std::array<double, 4>, 4> editabletext_transform = {
633 1.0, 0.0, 0.0, view_x,
634 0.0, 1.0, 0.0, view_y,
638 TestBinaryMessenger messenger([](
const std::string& channel,
639 const uint8_t*
message,
size_t message_size,
641 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
649 EXPECT_CALL(*view(), OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
653 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
654 auto& allocator = arguments->GetAllocator();
656 rapidjson::Value transoform(rapidjson::kArrayType);
657 for (
int i = 0; i < 4 * 4; i++) {
659 transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
662 arguments->AddMember(
"transform", transoform, allocator);
664 auto message = codec.EncodeMethodCall(
665 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
667 message->size(), reply_handler);
671 OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
672 {ime_width, ime_height}}));
676 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
677 auto& allocator = arguments->GetAllocator();
679 arguments->AddMember(
"x", ime_x, allocator);
680 arguments->AddMember(
"y", ime_y, allocator);
681 arguments->AddMember(
"width", ime_width, allocator);
682 arguments->AddMember(
"height", ime_height, allocator);
684 auto message = codec.EncodeMethodCall(
685 {
"TextInput.setMarkedTextRect", std::move(arguments)});
687 message->size(), reply_handler);
694 TestBinaryMessenger messenger([](
const std::string& channel,
695 const uint8_t*
message,
size_t message_size,
699 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
701 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
709 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
710 auto& allocator = arguments->GetAllocator();
712 arguments->AddMember(
"x", 0, allocator);
713 arguments->AddMember(
"y", 0, allocator);
714 arguments->AddMember(
"width", 0, allocator);
715 arguments->AddMember(
"height", 0, allocator);
717 auto message = codec.EncodeMethodCall(
718 {
"TextInput.setMarkedTextRect", std::move(arguments)});
720 message->size(), reply_handler);
724 "[\"Internal Consistency Error\",\"Text input is not available because "
725 "view with view_id=0 cannot be found\",null]");
732 bool sent_message =
false;
733 TestBinaryMessenger messenger(
734 [&sent_message](
const std::string& channel,
const uint8_t*
message,
740 auto const set_client_and_send_message = [&](
int client_id,
int view_id) {
741 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
742 auto& allocator = args->GetAllocator();
743 args->PushBack(client_id, allocator);
745 rapidjson::Value client_config(rapidjson::kObjectType);
746 client_config.AddMember(
kViewId, view_id, allocator);
748 args->PushBack(client_config, allocator);
753 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
756 std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
759 EXPECT_TRUE(messenger.SimulateEngineMessage(
760 kChannelName, encoded->data(), encoded->size(), reply_handler));
762 sent_message =
false;
763 handler.ComposeBeginHook();
764 EXPECT_TRUE(sent_message);
765 sent_message =
false;
766 handler.ComposeChangeHook(u
"4", 1);
767 EXPECT_TRUE(sent_message);
768 sent_message =
false;
769 handler.ComposeCommitHook();
770 EXPECT_FALSE(sent_message);
771 sent_message =
false;
772 handler.ComposeEndHook();
773 EXPECT_TRUE(sent_message);
776 set_client_and_send_message(123, 456);
777 set_client_and_send_message(123, 789);
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, bool is_sized_to_content, const BoxConstraints &box_constraints, FlutterWindowsViewSizingDelegate *sizing_delegate=nullptr, std::shared_ptr< WindowsProcTable > windows_proc_table=nullptr)
static const JsonMessageCodec & GetInstance()
static const JsonMethodCodec & GetInstance()
std::unique_ptr< std::vector< uint8_t > > EncodeMessage(const T &message) const
std::unique_ptr< MethodCall< T > > DecodeMethodCall(const uint8_t *message, size_t message_size) const
std::unique_ptr< std::vector< uint8_t > > EncodeMethodCall(const MethodCall< T > &method_call) const
virtual void ComposeCommitHook()
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
virtual void ComposeEndHook()
virtual void KeyboardHook(int key, int scancode, int action, char32_t character, bool extended, bool was_down)
virtual void ComposeBeginHook()
void SetViewId(FlutterViewId view_id)
TextInputPluginModifier(TextInputPlugin *text_input_plugin)
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
TextInputPluginTest()=default
MockWindowBindingHandler * window()
virtual ~TextInputPluginTest()=default
FlutterWindowsEngine * engine()
MockFlutterWindowsView * view()
std::unique_ptr< MockFlutterWindowsView > AddViewWithId(int view_id)
static constexpr char kChannelName[]
TEST_F(AccessibilityPluginTest, DirectAnnounceCall)
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply
constexpr FlutterViewId kImplicitViewId
static constexpr char kEnableDeltaModel[]
static constexpr char kSelectionIsDirectionalKey[]
static constexpr char kSelectionExtentKey[]
static constexpr char kSetClientMethod[]
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kComposingExtentKey[]
static constexpr char kComposingBaseKey[]
static constexpr char kAffinityDownstream[]
static constexpr char kSelectionAffinityKey[]
static constexpr char kViewId[]
static constexpr char kSelectionBaseKey[]