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