@@ -23,10 +23,19 @@ static constexpr char kScanCodeKey[] = "scanCode";
2323static constexpr int kHandledScanCode = 20 ;
2424static constexpr int kUnhandledScanCode = 21 ;
2525static constexpr char kTextPlainFormat [] = " text/plain" ;
26+ static constexpr int kDefaultClientId = 42 ;
2627// Should be identical to constants in text_input_plugin.cc.
2728static constexpr char kChannelName [] = " flutter/textinput" ;
2829static constexpr char kEnableDeltaModel [] = " enableDeltaModel" ;
2930static constexpr char kSetClientMethod [] = " TextInput.setClient" ;
31+ static constexpr char kAffinityDownstream [] = " TextAffinity.downstream" ;
32+ static constexpr char kTextKey [] = " text" ;
33+ static constexpr char kSelectionBaseKey [] = " selectionBase" ;
34+ static constexpr char kSelectionExtentKey [] = " selectionExtent" ;
35+ static constexpr char kSelectionAffinityKey [] = " selectionAffinity" ;
36+ static constexpr char kSelectionIsDirectionalKey [] = " selectionIsDirectional" ;
37+ static constexpr char kComposingBaseKey [] = " composingBase" ;
38+ static constexpr char kComposingExtentKey [] = " composingExtent" ;
3039
3140static std::unique_ptr<std::vector<uint8_t >> CreateResponse (bool handled) {
3241 auto response_doc =
@@ -36,6 +45,56 @@ static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
3645 return JsonMessageCodec::GetInstance ().EncodeMessage (*response_doc);
3746}
3847
48+ static std::unique_ptr<rapidjson::Document> EncodedClientConfig (
49+ std::string type_name,
50+ std::string input_action) {
51+ auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType );
52+ auto & allocator = arguments->GetAllocator ();
53+ arguments->PushBack (kDefaultClientId , allocator);
54+
55+ rapidjson::Value config (rapidjson::kObjectType );
56+ config.AddMember (" inputAction" , input_action, allocator);
57+ config.AddMember (kEnableDeltaModel , false , allocator);
58+ rapidjson::Value type_info (rapidjson::kObjectType );
59+ type_info.AddMember (" name" , type_name, allocator);
60+ config.AddMember (" inputType" , type_info, allocator);
61+ arguments->PushBack (config, allocator);
62+
63+ return arguments;
64+ }
65+
66+ static std::unique_ptr<rapidjson::Document> EncodedEditingState (
67+ std::string text,
68+ TextRange selection) {
69+ auto model = std::make_unique<TextInputModel>();
70+ model->SetText (text);
71+ model->SetSelection (selection);
72+
73+ auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType );
74+ auto & allocator = arguments->GetAllocator ();
75+ arguments->PushBack (kDefaultClientId , allocator);
76+
77+ rapidjson::Value editing_state (rapidjson::kObjectType );
78+ editing_state.AddMember (kSelectionAffinityKey , kAffinityDownstream ,
79+ allocator);
80+ editing_state.AddMember (kSelectionBaseKey , selection.base (), allocator);
81+ editing_state.AddMember (kSelectionExtentKey , selection.extent (), allocator);
82+ editing_state.AddMember (kSelectionIsDirectionalKey , false , allocator);
83+
84+ int composing_base =
85+ model->composing () ? model->composing_range ().base () : -1 ;
86+ int composing_extent =
87+ model->composing () ? model->composing_range ().extent () : -1 ;
88+ editing_state.AddMember (kComposingBaseKey , composing_base, allocator);
89+ editing_state.AddMember (kComposingExtentKey , composing_extent, allocator);
90+ editing_state.AddMember (kTextKey ,
91+ rapidjson::Value (model->GetText (), allocator).Move (),
92+ allocator);
93+ arguments->PushBack (editing_state, allocator);
94+
95+ return arguments;
96+ }
97+
3998class MockTextInputPluginDelegate : public TextInputPluginDelegate {
4099 public:
41100 MockTextInputPluginDelegate () {}
@@ -109,7 +168,7 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
109168 // Call TextInput.setClient to initialize the TextInputModel.
110169 auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType );
111170 auto & allocator = arguments->GetAllocator ();
112- arguments->PushBack (42 , allocator);
171+ arguments->PushBack (kDefaultClientId , allocator);
113172 rapidjson::Value config (rapidjson::kObjectType );
114173 config.AddMember (" inputAction" , " done" , allocator);
115174 config.AddMember (" inputType" , " text" , allocator);
@@ -148,6 +207,109 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
148207 EXPECT_TRUE (sent_message);
149208}
150209
210+ TEST (TextInputPluginTest, VerifyInputActionNewlineInsertNewLine) {
211+ // Store messages as std::string for convenience.
212+ std::vector<std::string> messages;
213+
214+ TestBinaryMessenger messenger (
215+ [&messages](const std::string& channel, const uint8_t * message,
216+ size_t message_size, BinaryReply reply) {
217+ std::string last_message (reinterpret_cast <const char *>(message),
218+ message_size);
219+ messages.push_back (last_message);
220+ });
221+ BinaryReply reply_handler = [](const uint8_t * reply, size_t reply_size) {};
222+
223+ MockTextInputPluginDelegate delegate;
224+ TextInputPlugin handler (&messenger, &delegate);
225+
226+ auto & codec = JsonMethodCodec::GetInstance ();
227+
228+ // Call TextInput.setClient to initialize the TextInputModel.
229+ auto set_client_arguments =
230+ EncodedClientConfig (" TextInputType.multiline" , " TextInputAction.newline" );
231+ auto message = codec.EncodeMethodCall (
232+ {" TextInput.setClient" , std::move (set_client_arguments)});
233+ messenger.SimulateEngineMessage (" flutter/textinput" , message->data (),
234+ message->size (), reply_handler);
235+
236+ // Simulate a key down event for '\n'.
237+ handler.KeyboardHook (VK_RETURN, 100 , WM_KEYDOWN, ' \n ' , false , false );
238+
239+ // Two messages are expected, the first is TextInput.updateEditingState and
240+ // the second is TextInputClient.performAction.
241+ EXPECT_EQ (messages.size (), 2 );
242+
243+ // Editing state should have been updated.
244+ auto encoded_arguments = EncodedEditingState (" \n " , TextRange (1 ));
245+ auto update_state_message = codec.EncodeMethodCall (
246+ {" TextInputClient.updateEditingState" , std::move (encoded_arguments)});
247+
248+ EXPECT_TRUE (std::equal (update_state_message->begin (),
249+ update_state_message->end (),
250+ messages.front ().begin ()));
251+
252+ // TextInputClient.performAction should have been called.
253+ auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType );
254+ auto & allocator = arguments->GetAllocator ();
255+ arguments->PushBack (kDefaultClientId , allocator);
256+ arguments->PushBack (
257+ rapidjson::Value (" TextInputAction.newline" , allocator).Move (), allocator);
258+ auto invoke_action_message = codec.EncodeMethodCall (
259+ {" TextInputClient.performAction" , std::move (arguments)});
260+
261+ EXPECT_TRUE (std::equal (invoke_action_message->begin (),
262+ invoke_action_message->end (),
263+ messages.back ().begin ()));
264+ }
265+
266+ // Regression test for https://github.com/flutter/flutter/issues/125879.
267+ TEST (TextInputPluginTest, VerifyInputActionSendDoesNotInsertNewLine) {
268+ std::vector<std::vector<uint8_t >> messages;
269+
270+ TestBinaryMessenger messenger (
271+ [&messages](const std::string& channel, const uint8_t * message,
272+ size_t message_size, BinaryReply reply) {
273+ int length = static_cast <int >(message_size);
274+ std::vector<uint8_t > last_message (length);
275+ memcpy (&last_message[0 ], &message[0 ], length * sizeof (uint8_t ));
276+ messages.push_back (last_message);
277+ });
278+ BinaryReply reply_handler = [](const uint8_t * reply, size_t reply_size) {};
279+
280+ MockTextInputPluginDelegate delegate;
281+ TextInputPlugin handler (&messenger, &delegate);
282+
283+ auto & codec = JsonMethodCodec::GetInstance ();
284+
285+ // Call TextInput.setClient to initialize the TextInputModel.
286+ auto set_client_arguments =
287+ EncodedClientConfig (" TextInputType.multiline" , " TextInputAction.send" );
288+ auto message = codec.EncodeMethodCall (
289+ {" TextInput.setClient" , std::move (set_client_arguments)});
290+ messenger.SimulateEngineMessage (" flutter/textinput" , message->data (),
291+ message->size (), reply_handler);
292+
293+ // Simulate a key down event for '\n'.
294+ handler.KeyboardHook (VK_RETURN, 100 , WM_KEYDOWN, ' \n ' , false , false );
295+
296+ // Only a call to TextInputClient.performAction is expected.
297+ EXPECT_EQ (messages.size (), 1 );
298+
299+ // TextInputClient.performAction should have been called.
300+ auto arguments = std::make_unique<rapidjson::Document>(rapidjson::kArrayType );
301+ auto & allocator = arguments->GetAllocator ();
302+ arguments->PushBack (kDefaultClientId , allocator);
303+ arguments->PushBack (
304+ rapidjson::Value (" TextInputAction.send" , allocator).Move (), allocator);
305+ auto invoke_action_message = codec.EncodeMethodCall (
306+ {" TextInputClient.performAction" , std::move (arguments)});
307+
308+ EXPECT_TRUE (std::equal (invoke_action_message->begin (),
309+ invoke_action_message->end (),
310+ messages.front ().begin ()));
311+ }
312+
151313TEST (TextInputPluginTest, TextEditingWorksWithDeltaModel) {
152314 auto handled_message = CreateResponse (true );
153315 auto unhandled_message = CreateResponse (false );
0 commit comments