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(
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);
84 rapidjson::Value type_info(rapidjson::kObjectType);
86 config.AddMember(
"inputType", type_info,
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)
135 MOCK_METHOD(
void, OnCursorRectUpdated, (
const Rect&), (
override));
136 MOCK_METHOD(
void, OnResetImeComposing, (), (
override));
144class TextInputPluginTest :
public WindowsTest {
146 TextInputPluginTest() =
default;
147 virtual ~TextInputPluginTest() =
default;
150 FlutterWindowsEngine*
engine() {
return engine_.get(); }
151 MockFlutterWindowsView*
view() {
return view_.get(); }
152 MockWindowBindingHandler*
window() {
return window_; }
154 void UseHeadlessEngine() {
155 FlutterWindowsEngineBuilder builder{
GetContext()};
157 engine_ = builder.Build();
160 void UseEngineWithView() {
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);
177 std::unique_ptr<MockFlutterWindowsView> AddViewWithId(
int view_id) {
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_;
198TEST_F(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
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](
210 int redispatch_scancode = 0;
213 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
216 text.push_back(
'\n');
223TEST_F(TextInputPluginTest, ClearClientResetsComposing) {
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) {};
232 TextInputPluginModifier modifier(&
handler);
233 modifier.SetViewId(456);
235 EXPECT_CALL(*
view(), OnResetImeComposing());
238 auto message = codec.EncodeMethodCall({
"TextInput.clearClient",
nullptr});
240 message->size(), reply_handler);
244TEST_F(TextInputPluginTest, ClearClientRequiresView) {
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]");
272TEST_F(TextInputPluginTest, VerifyComposingSendStateUpdate) {
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);
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;
308 handler.ComposeChangeHook(u
"4", 1);
309 EXPECT_TRUE(sent_message);
319 sent_message =
false;
321 EXPECT_FALSE(sent_message);
324 sent_message =
false;
326 EXPECT_TRUE(sent_message);
329TEST_F(TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
333 std::vector<std::string> messages;
335 TestBinaryMessenger messenger(
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);
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()));
387TEST_F(TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
390 std::vector<std::vector<uint8_t>> messages;
392 TestBinaryMessenger messenger(
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);
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()));
434TEST_F(TextInputPluginTest, SetClientRequiresViewId) {
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);
447 rapidjson::Value client_config(rapidjson::kObjectType);
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]");
466TEST_F(TextInputPluginTest, SetClientRequiresViewIdToBeInteger) {
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);
479 rapidjson::Value client_config(rapidjson::kObjectType);
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]");
499TEST_F(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
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](
511 int redispatch_scancode = 0;
514 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
518 rapidjson::Value client_config(rapidjson::kObjectType);
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;
541 handler.ComposeChangeHook(textN, 1);
542 handler.KeyboardHook(0x49, 100, WM_KEYDOWN,
'i',
false,
false);
543 std::u16string textNi;
546 handler.ComposeChangeHook(textNi, 2);
547 handler.KeyboardHook(VK_RETURN, 100, WM_KEYDOWN,
'\n',
false,
false);
548 std::u16string textChineseCharacter;
549 text.push_back(u
'\u4F60');
550 handler.ComposeChangeHook(textChineseCharacter, 1);
558TEST_F(TextInputPluginTest, CompositionCursorPos) {
561 int selection_base = -1;
562 TestBinaryMessenger messenger([&](
const std::string&
channel,
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);
581 TextInputPlugin plugin(&messenger,
engine());
583 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
586 rapidjson::Value client_config(rapidjson::kObjectType);
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);
618TEST_F(TextInputPluginTest, TransformCursorRect) {
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) {};
644 TextInputPluginModifier modifier(&
handler);
645 modifier.SetViewId(456);
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);
691TEST_F(TextInputPluginTest, SetMarkedTextRectRequiresView) {
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();
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]");
728TEST_F(TextInputPluginTest, SetAndUseMultipleClients) {
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);
745 rapidjson::Value client_config(rapidjson::kObjectType);
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;
764 EXPECT_TRUE(sent_message);
765 sent_message =
false;
766 handler.ComposeChangeHook(u
"4", 1);
767 EXPECT_TRUE(sent_message);
768 sent_message =
false;
770 EXPECT_FALSE(sent_message);
771 sent_message =
false;
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
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 FlutterViewId view_id
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kAffinityDownstream[]
std::shared_ptr< ImpellerAllocator > allocator
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