6#include <rapidjson/document.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;
42using ::testing::Return;
45static constexpr int kHandledScanCode = 20;
46static constexpr int kUnhandledScanCode = 21;
48static constexpr int kDefaultClientId = 42;
50static constexpr char kChannelName[] =
"flutter/textinput";
52static constexpr char kViewId[] =
"viewId";
55static constexpr char kTextKey[] =
"text";
63 "TextInputClient.updateEditingState";
65static 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);
73static 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);
92static 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);
124class MockFlutterWindowsView :
public FlutterWindowsView {
127 std::unique_ptr<WindowBindingHandler>
window)
131 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
132 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
140class TextInputPluginTest :
public WindowsTest {
142 TextInputPluginTest() =
default;
143 virtual ~TextInputPluginTest() =
default;
146 FlutterWindowsEngine*
engine() {
return engine_.get(); }
147 MockFlutterWindowsView*
view() {
return view_.get(); }
148 MockWindowBindingHandler*
window() {
return window_; }
150 void UseHeadlessEngine() {
151 FlutterWindowsEngineBuilder builder{
GetContext()};
153 engine_ = builder.Build();
156 void UseEngineWithView() {
157 FlutterWindowsEngineBuilder builder{
GetContext()};
159 auto window = std::make_unique<MockWindowBindingHandler>();
162 EXPECT_CALL(*window_, SetView).Times(1);
163 EXPECT_CALL(*window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
165 engine_ = builder.Build();
166 view_ = std::make_unique<MockFlutterWindowsView>(engine_.get(),
169 EngineModifier modifier{engine_.get()};
170 modifier.SetViewById(view_.get(), 456);
173 std::unique_ptr<MockFlutterWindowsView> AddViewWithId(
int view_id) {
174 EXPECT_NE(engine_,
nullptr);
175 auto window = std::make_unique<MockWindowBindingHandler>();
176 EXPECT_CALL(*window, SetView).Times(1);
177 EXPECT_CALL(*window, GetWindowHandle).WillRepeatedly(Return(
nullptr));
178 auto view = std::make_unique<MockFlutterWindowsView>(engine_.get(),
181 EngineModifier modifier{engine_.get()};
182 modifier.SetViewById(view_.get(),
view_id);
187 std::unique_ptr<FlutterWindowsEngine> engine_;
188 std::unique_ptr<MockFlutterWindowsView> view_;
189 MockWindowBindingHandler* window_;
194TEST_F(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
197 auto handled_message = CreateResponse(
true);
198 auto unhandled_message = CreateResponse(
false);
199 int received_scancode = 0;
201 TestBinaryMessenger messenger(
202 [&received_scancode, &handled_message, &unhandled_message](
206 int redispatch_scancode = 0;
209 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
212 text.push_back(
'\n');
219TEST_F(TextInputPluginTest, ClearClientResetsComposing) {
222 TestBinaryMessenger messenger([](
const std::string&
channel,
223 const uint8_t*
message,
size_t message_size,
225 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
228 TextInputPluginModifier modifier(&
handler);
229 modifier.SetViewId(456);
231 EXPECT_CALL(*
view(), OnResetImeComposing());
234 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
236 message->size(), reply_handler);
240TEST_F(TextInputPluginTest, ClearClientRequiresView) {
243 TestBinaryMessenger messenger([](
const std::string&
channel,
244 const uint8_t*
message,
size_t message_size,
248 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
250 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
256 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
258 message->size(), reply_handler);
262 "[\"Internal Consistency Error\",\"Text input is not available because "
263 "view with view_id=0 cannot be found\",null]");
268TEST_F(TextInputPluginTest, VerifyComposingSendStateUpdate) {
271 bool sent_message =
false;
272 TestBinaryMessenger messenger(
273 [&sent_message](
const std::string&
channel,
const uint8_t*
message,
276 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
283 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
284 auto& allocator = arguments->GetAllocator();
285 arguments->PushBack(kDefaultClientId, allocator);
286 rapidjson::Value config(rapidjson::kObjectType);
287 config.AddMember(
"inputAction",
"done", allocator);
288 config.AddMember(
"inputType",
"text", allocator);
290 config.AddMember(
kViewId, 456, allocator);
291 arguments->PushBack(config, allocator);
293 codec.EncodeMethodCall({
"TextInput.setClient", std::move(arguments)});
294 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
295 message->size(), reply_handler);
298 sent_message =
false;
300 EXPECT_TRUE(sent_message);
303 sent_message =
false;
304 handler.ComposeChangeHook(u
"4", 1);
305 EXPECT_TRUE(sent_message);
315 sent_message =
false;
317 EXPECT_FALSE(sent_message);
320 sent_message =
false;
322 EXPECT_TRUE(sent_message);
325TEST_F(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
329 std::vector<std::string> messages;
331 TestBinaryMessenger messenger(
334 std::string last_message(
reinterpret_cast<const char*
>(
message),
336 messages.push_back(last_message);
338 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
345 auto set_client_arguments =
346 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.newline");
347 auto message = codec.EncodeMethodCall(
348 {
"TextInput.setClient", std::move(set_client_arguments)});
349 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
350 message->size(), reply_handler);
353 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
357 EXPECT_EQ(messages.size(), 2);
360 auto encoded_arguments = EncodedEditingState(
"\n", TextRange(1));
361 auto update_state_message = codec.EncodeMethodCall(
364 EXPECT_TRUE(std::equal(update_state_message->begin(),
365 update_state_message->end(),
366 messages.front().begin()));
369 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
370 auto& allocator = arguments->GetAllocator();
371 arguments->PushBack(kDefaultClientId, allocator);
373 rapidjson::Value(
"TextInputAction.newline", allocator).Move(), allocator);
374 auto invoke_action_message = codec.EncodeMethodCall(
375 {
"TextInputClient.performAction", std::move(arguments)});
377 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
378 invoke_action_message->end(),
379 messages.back().begin()));
383TEST_F(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
386 std::vector<std::vector<uint8_t>> messages;
388 TestBinaryMessenger messenger(
391 int length =
static_cast<int>(message_size);
392 std::vector<uint8_t> last_message(
length);
393 memcpy(&last_message[0], &
message[0],
length *
sizeof(uint8_t));
394 messages.push_back(last_message);
396 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
403 auto set_client_arguments =
404 EncodedClientConfig(
"TextInputType.multiline",
"TextInputAction.send");
405 auto message = codec.EncodeMethodCall(
406 {
"TextInput.setClient", std::move(set_client_arguments)});
407 messenger.SimulateEngineMessage(
"flutter/textinput",
message->data(),
408 message->size(), reply_handler);
411 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
414 EXPECT_EQ(messages.size(), 1);
417 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
418 auto& allocator = arguments->GetAllocator();
419 arguments->PushBack(kDefaultClientId, allocator);
421 rapidjson::Value(
"TextInputAction.send", allocator).Move(), allocator);
422 auto invoke_action_message = codec.EncodeMethodCall(
423 {
"TextInputClient.performAction", std::move(arguments)});
425 EXPECT_TRUE(std::equal(invoke_action_message->begin(),
426 invoke_action_message->end(),
427 messages.front().begin()));
430TEST_F(TextInputPluginTest, SetClientRequiresViewId) {
433 TestBinaryMessenger messenger([](
const std::string&
channel,
434 const uint8_t*
message,
size_t message_size,
439 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
440 auto& allocator =
args->GetAllocator();
441 args->PushBack(123, allocator);
443 rapidjson::Value client_config(rapidjson::kObjectType);
445 args->PushBack(client_config, allocator);
450 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
452 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
455 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
456 encoded->size(), reply_handler));
459 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
462TEST_F(TextInputPluginTest, SetClientRequiresViewIdToBeInteger) {
465 TestBinaryMessenger messenger([](
const std::string&
channel,
466 const uint8_t*
message,
size_t message_size,
471 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
472 auto& allocator =
args->GetAllocator();
473 args->PushBack(123, allocator);
475 rapidjson::Value client_config(rapidjson::kObjectType);
476 client_config.AddMember(
kViewId,
"Not an integer", allocator);
478 args->PushBack(client_config, allocator);
483 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
485 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
488 EXPECT_TRUE(messenger.SimulateEngineMessage(
kChannelName, encoded->data(),
489 encoded->size(), reply_handler));
492 "[\"Bad Arguments\",\"Could not set client, view ID is null.\",null]");
495TEST_F(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
498 auto handled_message = CreateResponse(
true);
499 auto unhandled_message = CreateResponse(
false);
500 int received_scancode = 0;
502 TestBinaryMessenger messenger(
503 [&received_scancode, &handled_message, &unhandled_message](
507 int redispatch_scancode = 0;
510 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
511 auto& allocator =
args->GetAllocator();
512 args->PushBack(123, allocator);
514 rapidjson::Value client_config(rapidjson::kObjectType);
516 client_config.AddMember(
kViewId, 456, allocator);
518 args->PushBack(client_config, allocator);
522 EXPECT_TRUE(messenger.SimulateEngineMessage(
524 [](
const uint8_t* reply,
size_t reply_size) {}));
526 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
529 text.push_back(
'\n');
533 handler.KeyboardHook(0x4E, 100, WM_KEYDOWN,
'n',
false,
false);
535 std::u16string textN;
537 handler.ComposeChangeHook(textN, 1);
538 handler.KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
539 std::u16string textNi;
542 handler.ComposeChangeHook(textNi, 2);
543 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
544 std::u16string textChineseCharacter;
545 text.push_back(u
'\u4F60');
546 handler.ComposeChangeHook(textChineseCharacter, 1);
554TEST_F(TextInputPluginTest, CompositionCursorPos) {
557 int selection_base = -1;
558 TestBinaryMessenger messenger([&](
const std::string&
channel,
564 const auto&
args = *method->arguments();
565 const auto& editing_state =
args[1];
568 ASSERT_NE(
base, editing_state.MemberEnd());
569 ASSERT_TRUE(
base->value.IsInt());
570 ASSERT_NE(extent, editing_state.MemberEnd());
571 ASSERT_TRUE(extent->value.IsInt());
572 selection_base =
base->value.GetInt();
573 EXPECT_EQ(extent->value.GetInt(), selection_base);
577 TextInputPlugin plugin(&messenger,
engine());
579 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
580 auto& allocator =
args->GetAllocator();
581 args->PushBack(123, allocator);
582 rapidjson::Value client_config(rapidjson::kObjectType);
583 client_config.AddMember(
kViewId, 456, allocator);
584 args->PushBack(client_config, allocator);
587 EXPECT_TRUE(messenger.SimulateEngineMessage(
589 [](
const uint8_t* reply,
size_t reply_size) {}));
591 plugin.ComposeBeginHook();
592 EXPECT_EQ(selection_base, 0);
593 plugin.ComposeChangeHook(u
"abc", 3);
594 EXPECT_EQ(selection_base, 3);
596 plugin.ComposeCommitHook();
597 plugin.ComposeEndHook();
598 EXPECT_EQ(selection_base, 3);
600 plugin.ComposeBeginHook();
601 plugin.ComposeChangeHook(u
"1", 1);
602 EXPECT_EQ(selection_base, 4);
604 plugin.ComposeChangeHook(u
"12", 2);
605 EXPECT_EQ(selection_base, 5);
607 plugin.ComposeChangeHook(u
"12", 1);
608 EXPECT_EQ(selection_base, 4);
610 plugin.ComposeChangeHook(u
"12", 2);
611 EXPECT_EQ(selection_base, 5);
614TEST_F(TextInputPluginTest, TransformCursorRect) {
624 double ime_width = 50;
625 double ime_height = 60;
628 std::array<std::array<double, 4>, 4> editabletext_transform = {
629 1.0, 0.0, 0.0, view_x,
630 0.0, 1.0, 0.0, view_y,
634 TestBinaryMessenger messenger([](
const std::string&
channel,
635 const uint8_t*
message,
size_t message_size,
637 BinaryReply reply_handler = [](
const uint8_t* reply,
size_t reply_size) {};
640 TextInputPluginModifier modifier(&
handler);
641 modifier.SetViewId(456);
645 EXPECT_CALL(*
view(), OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));
649 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
650 auto& allocator = arguments->GetAllocator();
652 rapidjson::Value transoform(rapidjson::kArrayType);
653 for (
int i = 0;
i < 4 * 4;
i++) {
655 transoform.PushBack(editabletext_transform[
i % 4][
i / 4], allocator);
658 arguments->AddMember(
"transform", transoform, allocator);
660 auto message = codec.EncodeMethodCall(
661 {
"TextInput.setEditableSizeAndTransform", std::move(arguments)});
663 message->size(), reply_handler);
667 OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
668 {ime_width, ime_height}}));
672 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
673 auto& allocator = arguments->GetAllocator();
675 arguments->AddMember(
"x", ime_x, allocator);
676 arguments->AddMember(
"y", ime_y, allocator);
677 arguments->AddMember(
"width", ime_width, allocator);
678 arguments->AddMember(
"height", ime_height, allocator);
680 auto message = codec.EncodeMethodCall(
681 {
"TextInput.setMarkedTextRect", std::move(arguments)});
683 message->size(), reply_handler);
687TEST_F(TextInputPluginTest, SetMarkedTextRectRequiresView) {
690 TestBinaryMessenger messenger([](
const std::string&
channel,
691 const uint8_t*
message,
size_t message_size,
695 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
697 reply = std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
705 std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
706 auto& allocator = arguments->GetAllocator();
708 arguments->AddMember(
"x", 0, allocator);
709 arguments->AddMember(
"y", 0, allocator);
710 arguments->AddMember(
"width", 0, allocator);
711 arguments->AddMember(
"height", 0, allocator);
713 auto message = codec.EncodeMethodCall(
714 {
"TextInput.setMarkedTextRect", std::move(arguments)});
716 message->size(), reply_handler);
720 "[\"Internal Consistency Error\",\"Text input is not available because "
721 "view with view_id=0 cannot be found\",null]");
724TEST_F(TextInputPluginTest, SetAndUseMultipleClients) {
728 bool sent_message =
false;
729 TestBinaryMessenger messenger(
730 [&sent_message](
const std::string&
channel,
const uint8_t*
message,
736 auto const set_client_and_send_message = [&](
int client_id,
int view_id) {
737 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
738 auto& allocator =
args->GetAllocator();
739 args->PushBack(client_id, allocator);
741 rapidjson::Value client_config(rapidjson::kObjectType);
744 args->PushBack(client_config, allocator);
749 BinaryReply reply_handler = [&reply](
const uint8_t* reply_bytes,
752 std::string(
reinterpret_cast<const char*
>(reply_bytes), reply_size);
755 EXPECT_TRUE(messenger.SimulateEngineMessage(
756 kChannelName, encoded->data(), encoded->size(), reply_handler));
758 sent_message =
false;
760 EXPECT_TRUE(sent_message);
761 sent_message =
false;
762 handler.ComposeChangeHook(u
"4", 1);
763 EXPECT_TRUE(sent_message);
764 sent_message =
false;
766 EXPECT_FALSE(sent_message);
767 sent_message =
false;
769 EXPECT_TRUE(sent_message);
772 set_client_and_send_message(123, 456);
773 set_client_and_send_message(123, 789);
FlutterWindowsView(FlutterViewId view_id, FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > window_binding, 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
void SetViewId(FlutterViewId view_id)
TextInputPluginModifier(TextInputPlugin *text_input_plugin)
~MockFlutterWindowsView()
MOCK_METHOD(void, NotifyWinEventWrapper,(ui::AXPlatformNodeWin *, ax::mojom::Event),(override))
MockFlutterWindowsView(FlutterWindowsEngine *engine, std::unique_ptr< WindowBindingHandler > wbh)
WindowsTestContext & GetContext()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
const gchar FlBinaryMessengerMessageHandler handler
G_BEGIN_DECLS GBytes * message
G_BEGIN_DECLS FlutterViewId view_id
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kAffinityDownstream[]
static NSString *const kEnableDeltaModel
static NSString *const kSetClientMethod
static constexpr char kScanCodeKey[]
constexpr char kTextPlainFormat[]
Clipboard plain text format.
static NSString *const kViewId
static NSString *const kTextKey
static NSString *const kSelectionBaseKey
static NSString *const kSelectionAffinityKey
static NSString *const kComposingExtentKey
static NSString *const kSelectionExtentKey
static NSString *const kComposingBaseKey
static NSString *const kSelectionIsDirectionalKey
TEST_F(DisplayListTest, Defaults)
constexpr int64_t kImplicitViewId
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
std::function< void(const uint8_t *reply, size_t reply_size)> BinaryReply