| Flutter Linux Embedder |

fl_text_input_handler_test.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <utility>
6
7 #include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
8 #include "flutter/shell/platform/linux/fl_method_codec_private.h"
9 #include "flutter/shell/platform/linux/fl_text_input_handler.h"
10 #include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h"
11 #include "flutter/shell/platform/linux/testing/fl_test.h"
12 #include "flutter/shell/platform/linux/testing/mock_gtk.h"
13 #include "flutter/testing/testing.h"
14
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
17
18 static FlValue* build_map(std::map<const gchar*, FlValue*> args) {
19 FlValue* value = fl_value_new_map();
20 for (auto it = args.begin(); it != args.end(); ++it) {
21 fl_value_set_string_take(value, it->first, it->second);
22 }
23 return value;
24 }
25
26 static FlValue* build_list(std::vector<FlValue*> args) {
27 FlValue* value = fl_value_new_list();
28 for (auto it = args.begin(); it != args.end(); ++it) {
29 fl_value_append_take(value, *it);
30 }
31 return value;
32 }
33
34 struct InputConfig {
35 int64_t client_id = -1;
36 const gchar* input_type = "TextInputType.text";
37 const gchar* input_action = "TextInputAction.none";
38 gboolean enable_delta_model = false;
39 };
40
41 static FlValue* build_input_config(InputConfig config) {
42 return build_list({
43 fl_value_new_int(config.client_id),
44 build_map({
45 {"inputAction", fl_value_new_string(config.input_action)},
46 {"inputType", build_map({
47 {"name", fl_value_new_string(config.input_type)},
48 })},
49 {"enableDeltaModel", fl_value_new_bool(config.enable_delta_model)},
50 }),
51 });
52 }
53
54 struct EditingState {
55 const gchar* text = "";
56 int selection_base = -1;
57 int selection_extent = -1;
58 int composing_base = -1;
59 int composing_extent = -1;
60 };
61
62 static FlValue* build_editing_state(EditingState state) {
63 return build_map({
64 {"text", fl_value_new_string(state.text)},
65 {"selectionBase", fl_value_new_int(state.selection_base)},
66 {"selectionExtent", fl_value_new_int(state.selection_extent)},
67 {"selectionAffinity", fl_value_new_string("TextAffinity.downstream")},
68 {"selectionIsDirectional", fl_value_new_bool(false)},
69 {"composingBase", fl_value_new_int(state.composing_base)},
70 {"composingExtent", fl_value_new_int(state.composing_extent)},
71 });
72 }
73
74 struct EditingDelta {
75 const gchar* old_text = "";
76 const gchar* delta_text = "";
77 int delta_start = -1;
78 int delta_end = -1;
79 int selection_base = -1;
80 int selection_extent = -1;
81 int composing_base = -1;
82 int composing_extent = -1;
83 };
84
85 static FlValue* build_editing_delta(EditingDelta delta) {
86 return build_map({
87 {"oldText", fl_value_new_string(delta.old_text)},
88 {"deltaText", fl_value_new_string(delta.delta_text)},
89 {"deltaStart", fl_value_new_int(delta.delta_start)},
90 {"deltaEnd", fl_value_new_int(delta.delta_end)},
91 {"selectionBase", fl_value_new_int(delta.selection_base)},
92 {"selectionExtent", fl_value_new_int(delta.selection_extent)},
93 {"selectionAffinity", fl_value_new_string("TextAffinity.downstream")},
94 {"selectionIsDirectional", fl_value_new_bool(false)},
95 {"composingBase", fl_value_new_int(delta.composing_base)},
96 {"composingExtent", fl_value_new_int(delta.composing_extent)},
97 });
98 }
99
100 static void set_client(FlMockBinaryMessenger* messenger, InputConfig config) {
101 gboolean called = FALSE;
102 g_autoptr(FlValue) args = build_input_config(config);
103 fl_mock_binary_messenger_invoke_json_method(
104 messenger, "flutter/textinput", "TextInput.setClient", args,
105 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
106 gpointer user_data) {
107 gboolean* called = static_cast<gboolean*>(user_data);
108 *called = TRUE;
109
110 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
111
112 g_autoptr(FlValue) expected_result = fl_value_new_null();
113 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
114 FL_METHOD_SUCCESS_RESPONSE(response)),
115 expected_result));
116 },
117 &called);
118 EXPECT_TRUE(called);
119 }
120
121 static void set_editing_state(FlMockBinaryMessenger* messenger,
122 EditingState state) {
123 gboolean called = FALSE;
124 g_autoptr(FlValue) args = build_editing_state(state);
125 fl_mock_binary_messenger_invoke_json_method(
126 messenger, "flutter/textinput", "TextInput.setEditingState", args,
127 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
128 gpointer user_data) {
129 gboolean* called = static_cast<gboolean*>(user_data);
130 *called = TRUE;
131
132 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
133
134 g_autoptr(FlValue) expected_result = fl_value_new_null();
135 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
136 FL_METHOD_SUCCESS_RESPONSE(response)),
137 expected_result));
138 },
139 &called);
140 EXPECT_TRUE(called);
141 }
142
143 static void send_key_event(FlTextInputHandler* handler,
144 gint keyval,
145 gint state = 0) {
146 GdkEvent* gdk_event = gdk_event_new(GDK_KEY_PRESS);
147 gdk_event->key.keyval = keyval;
148 gdk_event->key.state = state;
149 g_autoptr(FlKeyEvent) key_event = fl_key_event_new_from_gdk_event(gdk_event);
150 fl_text_input_handler_filter_keypress(handler, key_event);
151 }
152
153 TEST(FlTextInputHandlerTest, MessageHandler) {
154 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
155 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
156
157 g_autoptr(FlTextInputHandler) handler =
158 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
159 EXPECT_NE(handler, nullptr);
160
161 EXPECT_TRUE(
162 fl_mock_binary_messenger_has_handler(messenger, "flutter/textinput"));
163
164 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
165 }
166
167 TEST(FlTextInputHandlerTest, SetClient) {
168 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
169 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
170
171 g_autoptr(FlTextInputHandler) handler =
172 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
173 EXPECT_NE(handler, nullptr);
174
175 set_client(messenger, {.client_id = 1});
176
177 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
178 }
179
180 TEST(FlTextInputHandlerTest, Show) {
181 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
182 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
183
184 g_autoptr(FlTextInputHandler) handler =
185 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
186 EXPECT_NE(handler, nullptr);
187
188 EXPECT_CALL(mock_gtk, gtk_im_context_focus_in);
189
190 gboolean called = FALSE;
191 fl_mock_binary_messenger_invoke_json_method(
192 messenger, "flutter/textinput", "TextInput.show", nullptr,
193 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
194 gpointer user_data) {
195 gboolean* called = static_cast<gboolean*>(user_data);
196 *called = TRUE;
197
198 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
199
200 g_autoptr(FlValue) expected_result = fl_value_new_null();
201 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
202 FL_METHOD_SUCCESS_RESPONSE(response)),
203 expected_result));
204 },
205 &called);
206 EXPECT_TRUE(called);
207
208 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
209 }
210
211 TEST(FlTextInputHandlerTest, Hide) {
212 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
213 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
214
215 g_autoptr(FlTextInputHandler) handler =
216 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
217 EXPECT_NE(handler, nullptr);
218
219 EXPECT_CALL(mock_gtk, gtk_im_context_focus_out);
220
221 gboolean called = FALSE;
222 fl_mock_binary_messenger_invoke_json_method(
223 messenger, "flutter/textinput", "TextInput.hide", nullptr,
224 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
225 gpointer user_data) {
226 gboolean* called = static_cast<gboolean*>(user_data);
227 *called = TRUE;
228
229 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
230
231 g_autoptr(FlValue) expected_result = fl_value_new_null();
232 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
233 FL_METHOD_SUCCESS_RESPONSE(response)),
234 expected_result));
235 },
236 &called);
237 EXPECT_TRUE(called);
238
239 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
240 }
241
242 TEST(FlTextInputHandlerTest, ClearClient) {
243 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
244 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
245
246 g_autoptr(FlTextInputHandler) handler =
247 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
248 EXPECT_NE(handler, nullptr);
249
250 gboolean called = FALSE;
251 fl_mock_binary_messenger_invoke_json_method(
252 messenger, "flutter/textinput", "TextInput.clearClient", nullptr,
253 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
254 gpointer user_data) {
255 gboolean* called = static_cast<gboolean*>(user_data);
256 *called = TRUE;
257
258 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
259
260 g_autoptr(FlValue) expected_result = fl_value_new_null();
261 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
262 FL_METHOD_SUCCESS_RESPONSE(response)),
263 expected_result));
264 },
265 &called);
266 EXPECT_TRUE(called);
267
268 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
269 }
270
271 TEST(FlTextInputHandlerTest, PerformAction) {
272 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
273 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
274
275 g_autoptr(FlTextInputHandler) handler =
276 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
277 EXPECT_NE(handler, nullptr);
278
279 set_client(messenger, {
280 .client_id = 1,
281 .input_type = "TextInputType.multiline",
282 .input_action = "TextInputAction.newline",
283 });
284 set_editing_state(messenger, {
285 .text = "Flutter",
286 .selection_base = 7,
287 .selection_extent = 7,
288 });
289
290 // Client will update editing state and perform action
291 int call_count = 0;
292 fl_mock_binary_messenger_set_json_method_channel(
293 messenger, "flutter/textinput",
294 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
295 FlValue* args, gpointer user_data) {
296 int* call_count = static_cast<int*>(user_data);
297
298 if (strcmp(name, "TextInputClient.updateEditingState") == 0) {
299 g_autoptr(FlValue) expected_args = build_list({
300 fl_value_new_int(1), // client_id
301 build_editing_state({
302 .text = "Flutter\n",
303 .selection_base = 8,
304 .selection_extent = 8,
305 }),
306 });
307 EXPECT_TRUE(fl_value_equal(args, expected_args));
308 EXPECT_EQ(*call_count, 0);
309 (*call_count)++;
310 } else if (strcmp(name, "TextInputClient.performAction") == 0) {
311 g_autoptr(FlValue) expected_args = build_list({
312 fl_value_new_int(1), // client_id
313 fl_value_new_string("TextInputAction.newline"),
314 });
315 EXPECT_TRUE(fl_value_equal(args, expected_args));
316 EXPECT_EQ(*call_count, 1);
317 (*call_count)++;
318 }
319
320 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
321 },
322 &call_count);
323
324 send_key_event(handler, GDK_KEY_Return);
325 EXPECT_EQ(call_count, 2);
326
327 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
328 }
329
330 // Regression test for https://github.com/flutter/flutter/issues/125879.
331 TEST(FlTextInputHandlerTest, MultilineWithSendAction) {
332 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
333 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
334
335 g_autoptr(FlTextInputHandler) handler =
336 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
337 EXPECT_NE(handler, nullptr);
338
339 set_client(messenger, {
340 .client_id = 1,
341 .input_type = "TextInputType.multiline",
342 .input_action = "TextInputAction.send",
343 });
344 set_editing_state(messenger, {
345 .text = "Flutter",
346 .selection_base = 7,
347 .selection_extent = 7,
348 });
349
350 // Because the input action is not set to TextInputAction.newline, the next
351 // expected call is "TextInputClient.performAction". If the input action was
352 // set to TextInputAction.newline the next call would be
353 // "TextInputClient.updateEditingState" (this case is tested in the test named
354 // 'PerformAction').
355 int call_count = 0;
356 fl_mock_binary_messenger_set_json_method_channel(
357 messenger, "flutter/textinput",
358 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
359 FlValue* args, gpointer user_data) {
360 int* call_count = static_cast<int*>(user_data);
361
362 EXPECT_STREQ(name, "TextInputClient.performAction");
363 g_autoptr(FlValue) expected_args = nullptr;
364 switch (*call_count) {
365 case 0:
366 // Perform action.
367 expected_args = build_list({
368 fl_value_new_int(1), // client_id
369 fl_value_new_string("TextInputAction.send"),
370 });
371 break;
372 default:
373 g_assert_not_reached();
374 break;
375 }
376 EXPECT_TRUE(fl_value_equal(args, expected_args));
377 (*call_count)++;
378
379 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
380 },
381 &call_count);
382
383 send_key_event(handler, GDK_KEY_Return);
384 EXPECT_EQ(call_count, 1);
385
386 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
387 }
388
389 TEST(FlTextInputHandlerTest, MoveCursor) {
390 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
391 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
392
393 g_autoptr(FlTextInputHandler) handler =
394 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
395 EXPECT_NE(handler, nullptr);
396
397 set_client(messenger, {.client_id = 1});
398 set_editing_state(messenger, {
399 .text = "Flutter",
400 .selection_base = 4,
401 .selection_extent = 4,
402 });
403
404 int call_count = 0;
405 fl_mock_binary_messenger_set_json_method_channel(
406 messenger, "flutter/textinput",
407 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
408 FlValue* args, gpointer user_data) {
409 int* call_count = static_cast<int*>(user_data);
410
411 EXPECT_STREQ(name, "TextInputClient.updateEditingState");
412 g_autoptr(FlValue) expected_args = nullptr;
413 switch (*call_count) {
414 case 0:
415 // move cursor to beginning
416 expected_args = build_list({
417 fl_value_new_int(1), // client_id
418 build_editing_state({
419 .text = "Flutter",
420 .selection_base = 0,
421 .selection_extent = 0,
422 }),
423 });
424 break;
425 case 1:
426 // move cursor to end
427 expected_args = build_list({
428 fl_value_new_int(1), // client_id
429 build_editing_state({
430 .text = "Flutter",
431 .selection_base = 7,
432 .selection_extent = 7,
433 }),
434 });
435 break;
436 default:
437 g_assert_not_reached();
438 break;
439 }
440 EXPECT_TRUE(fl_value_equal(args, expected_args));
441 (*call_count)++;
442
443 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
444 },
445 &call_count);
446
447 send_key_event(handler, GDK_KEY_Home);
448 send_key_event(handler, GDK_KEY_End);
449 EXPECT_EQ(call_count, 2);
450
451 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
452 }
453
454 TEST(FlTextInputHandlerTest, Select) {
455 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
456 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
457
458 g_autoptr(FlTextInputHandler) handler =
459 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
460 EXPECT_NE(handler, nullptr);
461
462 set_client(messenger, {.client_id = 1});
463 set_editing_state(messenger, {
464 .text = "Flutter",
465 .selection_base = 4,
466 .selection_extent = 4,
467 });
468
469 int call_count = 0;
470 fl_mock_binary_messenger_set_json_method_channel(
471 messenger, "flutter/textinput",
472 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
473 FlValue* args, gpointer user_data) {
474 int* call_count = static_cast<int*>(user_data);
475
476 EXPECT_STREQ(name, "TextInputClient.updateEditingState");
477 g_autoptr(FlValue) expected_args = nullptr;
478 switch (*call_count) {
479 case 0:
480 // select to end
481 expected_args = build_list({
482 fl_value_new_int(1), // client_id
483 build_editing_state({
484 .text = "Flutter",
485 .selection_base = 4,
486 .selection_extent = 7,
487 }),
488 });
489 break;
490 case 1:
491 // select to beginning
492 expected_args = build_list({
493 fl_value_new_int(1), // client_id
494 build_editing_state({
495 .text = "Flutter",
496 .selection_base = 4,
497 .selection_extent = 0,
498 }),
499 });
500 break;
501 default:
502 g_assert_not_reached();
503 break;
504 }
505 EXPECT_TRUE(fl_value_equal(args, expected_args));
506 (*call_count)++;
507
508 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
509 },
510 &call_count);
511
512 send_key_event(handler, GDK_KEY_End, GDK_SHIFT_MASK);
513 send_key_event(handler, GDK_KEY_Home, GDK_SHIFT_MASK);
514 EXPECT_EQ(call_count, 2);
515
516 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
517 }
518
519 TEST(FlTextInputHandlerTest, Composing) {
520 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
521 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
522
523 g_autoptr(FlTextInputHandler) handler =
524 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
525 EXPECT_NE(handler, nullptr);
526
527 // update
528 EXPECT_CALL(mock_gtk, gtk_im_context_get_preedit_string(
529 ::testing::_, ::testing::A<gchar**>(), ::testing::_,
530 ::testing::A<gint*>()))
531 .WillOnce(
532 ::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter")),
533 ::testing::SetArgPointee<3>(0)));
534
535 int call_count = 0;
536 fl_mock_binary_messenger_set_json_method_channel(
537 messenger, "flutter/textinput",
538 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
539 FlValue* args, gpointer user_data) {
540 int* call_count = static_cast<int*>(user_data);
541
542 EXPECT_STREQ(name, "TextInputClient.updateEditingState");
543 g_autoptr(FlValue) expected_args = nullptr;
544 switch (*call_count) {
545 case 0:
546 expected_args = build_list({
547 fl_value_new_int(-1), // client_id
548 build_editing_state({
549 .text = "Flutter",
550 .selection_base = 0,
551 .selection_extent = 0,
552 .composing_base = 0,
553 .composing_extent = 7,
554 }),
555 });
556 break;
557 case 1:
558 // commit
559 expected_args = build_list({
560 fl_value_new_int(-1), // client_id
561 build_editing_state({
562 .text = "engine",
563 .selection_base = 6,
564 .selection_extent = 6,
565 }),
566 });
567 break;
568 case 2:
569 // end
570 expected_args = build_list({
571 fl_value_new_int(-1), // client_id
572 build_editing_state({
573 .text = "engine",
574 .selection_base = 6,
575 .selection_extent = 6,
576 }),
577 });
578 break;
579 default:
580 g_assert_not_reached();
581 break;
582 }
583 EXPECT_TRUE(fl_value_equal(args, expected_args));
584 (*call_count)++;
585
586 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
587 },
588 &call_count);
589
590 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
591 "preedit-start", nullptr);
592 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
593 "preedit-changed", nullptr);
594 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
595 "engine", nullptr);
596 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
597 "preedit-end", nullptr);
598 EXPECT_EQ(call_count, 3);
599
600 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
601 }
602
603 TEST(FlTextInputHandlerTest, SurroundingText) {
604 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
605 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
606
607 g_autoptr(FlTextInputHandler) handler =
608 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
609 EXPECT_NE(handler, nullptr);
610
611 set_client(messenger, {.client_id = 1});
612 set_editing_state(messenger, {
613 .text = "Flutter",
614 .selection_base = 3,
615 .selection_extent = 3,
616 });
617
618 // retrieve
619 EXPECT_CALL(mock_gtk, gtk_im_context_set_surrounding(
620 ::testing::_, ::testing::StrEq("Flutter"), -1, 3));
621
622 gboolean retrieved = false;
623 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
624 "retrieve-surrounding", &retrieved, nullptr);
625 EXPECT_TRUE(retrieved);
626
627 int call_count = 0;
628 fl_mock_binary_messenger_set_json_method_channel(
629 messenger, "flutter/textinput",
630 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
631 FlValue* args, gpointer user_data) {
632 int* call_count = static_cast<int*>(user_data);
633
634 EXPECT_STREQ(name, "TextInputClient.updateEditingState");
635 g_autoptr(FlValue) expected_args = nullptr;
636 switch (*call_count) {
637 case 0:
638 // delete
639 expected_args = build_list({
640 fl_value_new_int(1), // client_id
641 build_editing_state({
642 .text = "Flutr",
643 .selection_base = 3,
644 .selection_extent = 3,
645 }),
646 });
647 break;
648 default:
649 g_assert_not_reached();
650 break;
651 }
652 EXPECT_TRUE(fl_value_equal(args, expected_args));
653 (*call_count)++;
654
655 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
656 },
657 &call_count);
658
659 gboolean deleted = false;
660 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
661 "delete-surrounding", 1, 2, &deleted, nullptr);
662 EXPECT_TRUE(deleted);
663 EXPECT_EQ(call_count, 1);
664
665 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
666 }
667
668 TEST(FlTextInputHandlerTest, SetMarkedTextRect) {
669 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
670 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
671
672 g_autoptr(FlTextInputHandler) handler =
673 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
674 EXPECT_NE(handler, nullptr);
675
676 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
677 "preedit-start", nullptr);
678
679 // set editable size and transform
680 g_autoptr(FlValue) size_and_transform = build_map({
681 {
682 "transform",
683 build_list({
684 fl_value_new_float(1),
685 fl_value_new_float(2),
686 fl_value_new_float(3),
687 fl_value_new_float(4),
688 fl_value_new_float(5),
689 fl_value_new_float(6),
690 fl_value_new_float(7),
691 fl_value_new_float(8),
692 fl_value_new_float(9),
693 fl_value_new_float(10),
694 fl_value_new_float(11),
695 fl_value_new_float(12),
696 fl_value_new_float(13),
697 fl_value_new_float(14),
698 fl_value_new_float(15),
699 fl_value_new_float(16),
700 }),
701 },
702 });
703 gboolean called = FALSE;
704 fl_mock_binary_messenger_invoke_json_method(
705 messenger, "flutter/textinput", "TextInput.setEditableSizeAndTransform",
706 size_and_transform,
707 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
708 gpointer user_data) {
709 gboolean* called = static_cast<gboolean*>(user_data);
710 *called = TRUE;
711
712 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
713
714 g_autoptr(FlValue) expected_result = fl_value_new_null();
715 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
716 FL_METHOD_SUCCESS_RESPONSE(response)),
717 expected_result));
718 },
719 &called);
720 EXPECT_TRUE(called);
721
722 EXPECT_CALL(mock_gtk, gtk_widget_translate_coordinates(
723 ::testing::_, ::testing::_, ::testing::Eq(27),
724 ::testing::Eq(32), ::testing::_, ::testing::_))
725 .WillOnce(::testing::DoAll(::testing::SetArgPointee<4>(123),
726 ::testing::SetArgPointee<5>(456),
727 ::testing::Return(true)));
728
729 EXPECT_CALL(mock_gtk, gtk_im_context_set_cursor_location(
730 ::testing::_,
731 ::testing::Pointee(::testing::AllOf(
732 ::testing::Field(&GdkRectangle::x, 123),
733 ::testing::Field(&GdkRectangle::y, 456),
734 ::testing::Field(&GdkRectangle::width, 0),
735 ::testing::Field(&GdkRectangle::height, 0)))));
736
737 // set marked text rect
738 g_autoptr(FlValue) rect = build_map({
739 {"x", fl_value_new_float(1)},
740 {"y", fl_value_new_float(2)},
741 {"width", fl_value_new_float(3)},
742 {"height", fl_value_new_float(4)},
743 });
744 called = FALSE;
745 fl_mock_binary_messenger_invoke_json_method(
746 messenger, "flutter/textinput", "TextInput.setMarkedTextRect", rect,
747 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
748 gpointer user_data) {
749 gboolean* called = static_cast<gboolean*>(user_data);
750 *called = TRUE;
751
752 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
753
754 g_autoptr(FlValue) expected_result = fl_value_new_null();
755 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
756 FL_METHOD_SUCCESS_RESPONSE(response)),
757 expected_result));
758 },
759 &called);
760 EXPECT_TRUE(called);
761
762 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
763 }
764
765 TEST(FlTextInputHandlerTest, TextInputTypeNone) {
766 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
767 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
768
769 g_autoptr(FlTextInputHandler) handler =
770 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
771 EXPECT_NE(handler, nullptr);
772
773 set_client(messenger, {
774 .client_id = 1,
775 .input_type = "TextInputType.none",
776 });
777
778 EXPECT_CALL(mock_gtk, gtk_im_context_focus_in).Times(0);
779 EXPECT_CALL(mock_gtk, gtk_im_context_focus_out);
780
781 gboolean called = FALSE;
782 fl_mock_binary_messenger_invoke_json_method(
783 messenger, "flutter/textinput", "TextInput.show", nullptr,
784 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
785 gpointer user_data) {
786 gboolean* called = static_cast<gboolean*>(user_data);
787 *called = TRUE;
788
789 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
790
791 g_autoptr(FlValue) expected_result = fl_value_new_null();
792 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
793 FL_METHOD_SUCCESS_RESPONSE(response)),
794 expected_result));
795 },
796 &called);
797 EXPECT_TRUE(called);
798
799 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
800 }
801
802 TEST(FlTextInputHandlerTest, TextEditingDelta) {
803 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
804 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
805
806 g_autoptr(FlTextInputHandler) handler =
807 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
808 EXPECT_NE(handler, nullptr);
809
810 set_client(messenger, {
811 .client_id = 1,
812 .enable_delta_model = true,
813 });
814 set_editing_state(messenger, {
815 .text = "Flutter",
816 .selection_base = 7,
817 .selection_extent = 7,
818 });
819
820 // update editing state with deltas
821 int call_count = 0;
822 fl_mock_binary_messenger_set_json_method_channel(
823 messenger, "flutter/textinput",
824 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
825 FlValue* args, gpointer user_data) {
826 int* call_count = static_cast<int*>(user_data);
827
828 EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas");
829 g_autoptr(FlValue) expected_args = nullptr;
830 switch (*call_count) {
831 case 0:
832 expected_args = build_list({
833 fl_value_new_int(1), // client_id
834 build_map({{
835 "deltas",
836 build_list({
837 build_editing_delta({
838 .old_text = "Flutter",
839 .delta_text = "Flutter",
840 .delta_start = 7,
841 .delta_end = 7,
842 .selection_base = 0,
843 .selection_extent = 0,
844 }),
845 }),
846 }}),
847 });
848 break;
849 default:
850 g_assert_not_reached();
851 break;
852 }
853 EXPECT_TRUE(fl_value_equal(args, expected_args));
854 (*call_count)++;
855
856 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
857 },
858 &call_count);
859
860 send_key_event(handler, GDK_KEY_Home);
861 EXPECT_EQ(call_count, 1);
862
863 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
864 }
865
866 TEST(FlTextInputHandlerTest, ComposingDelta) {
867 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
868 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
869
870 g_autoptr(FlTextInputHandler) handler =
871 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
872 EXPECT_NE(handler, nullptr);
873
874 // set config
875 set_client(messenger, {
876 .client_id = 1,
877 .enable_delta_model = true,
878 });
879
880 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
881 "preedit-start", nullptr);
882
883 // update
884 EXPECT_CALL(mock_gtk, gtk_im_context_get_preedit_string(
885 ::testing::_, ::testing::A<gchar**>(), ::testing::_,
886 ::testing::A<gint*>()))
887 .WillOnce(
888 ::testing::DoAll(::testing::SetArgPointee<1>(g_strdup("Flutter ")),
889 ::testing::SetArgPointee<3>(8)));
890
891 int call_count = 0;
892 fl_mock_binary_messenger_set_json_method_channel(
893 messenger, "flutter/textinput",
894 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
895 FlValue* args, gpointer user_data) {
896 int* call_count = static_cast<int*>(user_data);
897
898 EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas");
899 g_autoptr(FlValue) expected_args = nullptr;
900 switch (*call_count) {
901 case 0:
902 expected_args = build_list({
903 fl_value_new_int(1), // client_id
904 build_map({{
905 "deltas",
906 build_list({
907 build_editing_delta({
908 .old_text = "",
909 .delta_text = "Flutter ",
910 .delta_start = 0,
911 .delta_end = 0,
912 .selection_base = 8,
913 .selection_extent = 8,
914 .composing_base = 0,
915 .composing_extent = 8,
916 }),
917 }),
918 }}),
919 });
920 break;
921 case 1:
922 // commit
923 expected_args = build_list({
924 fl_value_new_int(1), // client_id
925 build_map({{
926 "deltas",
927 build_list({
928 build_editing_delta({
929 .old_text = "Flutter ",
930 .delta_text = "Flutter engine",
931 .delta_start = 0,
932 .delta_end = 8,
933 .selection_base = 14,
934 .selection_extent = 14,
935 .composing_base = -1,
936 .composing_extent = -1,
937 }),
938 }),
939 }}),
940 });
941 break;
942 case 2:
943 // end
944 expected_args = build_list({
945 fl_value_new_int(1), // client_id
946 build_map({{
947 "deltas",
948 build_list({
949 build_editing_delta({
950 .old_text = "Flutter engine",
951 .selection_base = 14,
952 .selection_extent = 14,
953 }),
954 }),
955 }}),
956 });
957 break;
958 default:
959 g_assert_not_reached();
960 break;
961 }
962 EXPECT_TRUE(fl_value_equal(args, expected_args));
963 (*call_count)++;
964
965 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
966 },
967 &call_count);
968
969 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
970 "preedit-changed", nullptr);
971 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
972 "Flutter engine", nullptr);
973 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler),
974 "preedit-end", nullptr);
975 EXPECT_EQ(call_count, 3);
976
977 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
978 }
979
980 TEST(FlTextInputHandlerTest, NonComposingDelta) {
981 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
982 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
983
984 g_autoptr(FlTextInputHandler) handler =
985 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
986 EXPECT_NE(handler, nullptr);
987
988 // set config
989 set_client(messenger, {
990 .client_id = 1,
991 .enable_delta_model = true,
992 });
993
994 int call_count = 0;
995 fl_mock_binary_messenger_set_json_method_channel(
996 messenger, "flutter/textinput",
997 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
998 FlValue* args, gpointer user_data) {
999 int* call_count = static_cast<int*>(user_data);
1000
1001 EXPECT_STREQ(name, "TextInputClient.updateEditingStateWithDeltas");
1002 g_autoptr(FlValue) expected_args = nullptr;
1003 switch (*call_count) {
1004 case 0:
1005 // commit F
1006 expected_args = build_list({
1007 fl_value_new_int(1), // client_id
1008 build_map({{
1009 "deltas",
1010 build_list({
1011 build_editing_delta({
1012 .old_text = "",
1013 .delta_text = "F",
1014 .delta_start = 0,
1015 .delta_end = 0,
1016 .selection_base = 1,
1017 .selection_extent = 1,
1018 .composing_base = -1,
1019 .composing_extent = -1,
1020 }),
1021 }),
1022 }}),
1023 });
1024 break;
1025 case 1:
1026 // commit l
1027 expected_args = build_list({
1028 fl_value_new_int(1), // client_id
1029 build_map({{
1030 "deltas",
1031 build_list({
1032 build_editing_delta({
1033 .old_text = "F",
1034 .delta_text = "l",
1035 .delta_start = 1,
1036 .delta_end = 1,
1037 .selection_base = 2,
1038 .selection_extent = 2,
1039 .composing_base = -1,
1040 .composing_extent = -1,
1041 }),
1042 }),
1043 }}),
1044 });
1045 break;
1046 case 2:
1047 // commit u
1048 expected_args = build_list({
1049 fl_value_new_int(1), // client_id
1050 build_map({{
1051 "deltas",
1052 build_list({
1053 build_editing_delta({
1054 .old_text = "Fl",
1055 .delta_text = "u",
1056 .delta_start = 2,
1057 .delta_end = 2,
1058 .selection_base = 3,
1059 .selection_extent = 3,
1060 .composing_base = -1,
1061 .composing_extent = -1,
1062 }),
1063 }),
1064 }}),
1065 });
1066 break;
1067 case 3:
1068 // commit t
1069 expected_args = build_list({
1070 fl_value_new_int(1), // client_id
1071 build_map({{
1072 "deltas",
1073 build_list({
1074 build_editing_delta({
1075 .old_text = "Flu",
1076 .delta_text = "t",
1077 .delta_start = 3,
1078 .delta_end = 3,
1079 .selection_base = 4,
1080 .selection_extent = 4,
1081 .composing_base = -1,
1082 .composing_extent = -1,
1083 }),
1084 }),
1085 }}),
1086 });
1087 break;
1088 case 4:
1089 // commit t again
1090 expected_args = build_list({
1091 fl_value_new_int(1), // client_id
1092 build_map({{
1093 "deltas",
1094 build_list({
1095 build_editing_delta({
1096 .old_text = "Flut",
1097 .delta_text = "t",
1098 .delta_start = 4,
1099 .delta_end = 4,
1100 .selection_base = 5,
1101 .selection_extent = 5,
1102 .composing_base = -1,
1103 .composing_extent = -1,
1104 }),
1105 }),
1106 }}),
1107 });
1108 break;
1109 case 5:
1110 // commit e
1111 expected_args = build_list({
1112 fl_value_new_int(1), // client_id
1113 build_map({{
1114 "deltas",
1115 build_list({
1116 build_editing_delta({
1117 .old_text = "Flutt",
1118 .delta_text = "e",
1119 .delta_start = 5,
1120 .delta_end = 5,
1121 .selection_base = 6,
1122 .selection_extent = 6,
1123 .composing_base = -1,
1124 .composing_extent = -1,
1125 }),
1126 }),
1127 }}),
1128 });
1129 break;
1130 case 6:
1131 // commit r
1132 expected_args = build_list({
1133 fl_value_new_int(1), // client_id
1134 build_map({{
1135 "deltas",
1136 build_list({
1137 build_editing_delta({
1138 .old_text = "Flutte",
1139 .delta_text = "r",
1140 .delta_start = 6,
1141 .delta_end = 6,
1142 .selection_base = 7,
1143 .selection_extent = 7,
1144 .composing_base = -1,
1145 .composing_extent = -1,
1146 }),
1147 }),
1148 }}),
1149 });
1150 break;
1151 default:
1152 g_assert_not_reached();
1153 break;
1154 }
1155 EXPECT_TRUE(fl_value_equal(args, expected_args));
1156 (*call_count)++;
1157
1158 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
1159 },
1160 &call_count);
1161
1162 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1163 "F", nullptr);
1164 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1165 "l", nullptr);
1166 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1167 "u", nullptr);
1168 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1169 "t", nullptr);
1170 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1171 "t", nullptr);
1172 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1173 "e", nullptr);
1174 g_signal_emit_by_name(fl_text_input_handler_get_im_context(handler), "commit",
1175 "r", nullptr);
1176 EXPECT_EQ(call_count, 7);
1177
1178 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1179 }
1180
1181 TEST(FlTextInputHandlerTest, InputPurposeNumber) {
1182 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1183 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1184
1185 g_autoptr(FlTextInputHandler) handler =
1186 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1187 EXPECT_NE(handler, nullptr);
1188
1189 EXPECT_CALL(mock_gtk,
1190 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1191 GTK_INPUT_PURPOSE_NUMBER));
1192 EXPECT_CALL(mock_gtk,
1193 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1194 GTK_INPUT_HINT_NONE));
1195
1196 set_client(messenger, {
1197 .client_id = 1,
1198 .input_type = "TextInputType.number",
1199 });
1200
1201 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1202 }
1203
1204 TEST(FlTextInputHandlerTest, InputPurposePhone) {
1205 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1206 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1207
1208 g_autoptr(FlTextInputHandler) handler =
1209 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1210 EXPECT_NE(handler, nullptr);
1211
1212 EXPECT_CALL(mock_gtk,
1213 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1214 GTK_INPUT_PURPOSE_PHONE));
1215 EXPECT_CALL(mock_gtk,
1216 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1217 GTK_INPUT_HINT_NONE));
1218
1219 set_client(messenger, {
1220 .client_id = 1,
1221 .input_type = "TextInputType.phone",
1222 });
1223
1224 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1225 }
1226
1227 TEST(FlTextInputHandlerTest, InputPurposeEmail) {
1228 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1229 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1230
1231 g_autoptr(FlTextInputHandler) handler =
1232 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1233 EXPECT_NE(handler, nullptr);
1234
1235 EXPECT_CALL(mock_gtk,
1236 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1237 GTK_INPUT_PURPOSE_EMAIL));
1238 EXPECT_CALL(mock_gtk,
1239 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1240 GTK_INPUT_HINT_NONE));
1241
1242 set_client(messenger, {
1243 .client_id = 1,
1244 .input_type = "TextInputType.emailAddress",
1245 });
1246
1247 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1248 }
1249
1250 TEST(FlTextInputHandlerTest, InputPurposeUrl) {
1251 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1252 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1253
1254 g_autoptr(FlTextInputHandler) handler =
1255 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1256 EXPECT_NE(handler, nullptr);
1257
1258 EXPECT_CALL(mock_gtk,
1259 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1260 GTK_INPUT_PURPOSE_URL));
1261 EXPECT_CALL(mock_gtk,
1262 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1263 GTK_INPUT_HINT_NONE));
1264
1265 set_client(messenger, {
1266 .client_id = 1,
1267 .input_type = "TextInputType.url",
1268 });
1269
1270 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1271 }
1272
1273 TEST(FlTextInputHandlerTest, InputPurposePassword) {
1274 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1275 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1276
1277 g_autoptr(FlTextInputHandler) handler =
1278 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1279 EXPECT_NE(handler, nullptr);
1280
1281 EXPECT_CALL(mock_gtk,
1282 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1283 GTK_INPUT_PURPOSE_PASSWORD));
1284 EXPECT_CALL(mock_gtk,
1285 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1286 GTK_INPUT_HINT_NONE));
1287
1288 set_client(messenger, {
1289 .client_id = 1,
1290 .input_type = "TextInputType.visiblePassword",
1291 });
1292
1293 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1294 }
1295
1296 TEST(FlTextInputHandlerTest, InputPurposeName) {
1297 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1298 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1299
1300 g_autoptr(FlTextInputHandler) handler =
1301 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1302 EXPECT_NE(handler, nullptr);
1303
1304 EXPECT_CALL(mock_gtk,
1305 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1306 GTK_INPUT_PURPOSE_NAME));
1307 EXPECT_CALL(mock_gtk,
1308 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1309 GTK_INPUT_HINT_UPPERCASE_WORDS));
1310
1311 set_client(messenger, {
1312 .client_id = 1,
1313 .input_type = "TextInputType.name",
1314 });
1315
1316 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1317 }
1318
1319 TEST(FlTextInputHandlerTest, InputHintsAddress) {
1320 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1321 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1322
1323 g_autoptr(FlTextInputHandler) handler =
1324 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1325 EXPECT_NE(handler, nullptr);
1326
1327 EXPECT_CALL(mock_gtk,
1328 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1329 GTK_INPUT_PURPOSE_FREE_FORM));
1330 EXPECT_CALL(mock_gtk,
1331 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1332 GTK_INPUT_HINT_UPPERCASE_WORDS));
1333
1334 set_client(messenger, {
1335 .client_id = 1,
1336 .input_type = "TextInputType.address",
1337 });
1338
1339 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1340 }
1341
1342 TEST(FlTextInputHandlerTest, InputHintsMultiline) {
1343 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1344 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1345
1346 g_autoptr(FlTextInputHandler) handler =
1347 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1348 EXPECT_NE(handler, nullptr);
1349
1350 EXPECT_CALL(mock_gtk,
1351 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1352 GTK_INPUT_PURPOSE_FREE_FORM));
1353 EXPECT_CALL(
1354 mock_gtk,
1355 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1356 static_cast<gint>(GTK_INPUT_HINT_SPELLCHECK |
1357 GTK_INPUT_HINT_UPPERCASE_SENTENCES)));
1358
1359 set_client(messenger, {
1360 .client_id = 1,
1361 .input_type = "TextInputType.multiline",
1362 });
1363
1364 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1365 }
1366
1367 TEST(FlTextInputHandlerTest, InputHintsWebSearch) {
1368 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1369 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1370
1371 g_autoptr(FlTextInputHandler) handler =
1372 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1373 EXPECT_NE(handler, nullptr);
1374
1375 EXPECT_CALL(mock_gtk,
1376 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1377 GTK_INPUT_PURPOSE_FREE_FORM));
1378 EXPECT_CALL(mock_gtk,
1379 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1380 GTK_INPUT_HINT_LOWERCASE));
1381
1382 set_client(messenger, {
1383 .client_id = 1,
1384 .input_type = "TextInputType.webSearch",
1385 });
1386
1387 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1388 }
1389
1390 TEST(FlTextInputHandlerTest, InputHintsTwitter) {
1391 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1392 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1393
1394 g_autoptr(FlTextInputHandler) handler =
1395 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1396 EXPECT_NE(handler, nullptr);
1397
1398 EXPECT_CALL(mock_gtk,
1399 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1400 GTK_INPUT_PURPOSE_FREE_FORM));
1401 EXPECT_CALL(
1402 mock_gtk,
1403 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1404 static_cast<gint>(GTK_INPUT_HINT_SPELLCHECK |
1405 GTK_INPUT_HINT_UPPERCASE_SENTENCES)));
1406
1407 set_client(messenger, {
1408 .client_id = 1,
1409 .input_type = "TextInputType.twitter",
1410 });
1411
1412 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1413 }
1414
1415 TEST(FlTextInputHandlerTest, DefaultInputPurposeAndHints) {
1416 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1417 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1418
1419 g_autoptr(FlTextInputHandler) handler =
1420 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1421 EXPECT_NE(handler, nullptr);
1422
1423 EXPECT_CALL(mock_gtk,
1424 g_object_set(::testing::_, ::testing::StrEq("input-purpose"),
1425 GTK_INPUT_PURPOSE_FREE_FORM));
1426 EXPECT_CALL(mock_gtk,
1427 g_object_set(::testing::_, ::testing::StrEq("input-hints"),
1428 GTK_INPUT_HINT_NONE));
1429
1430 set_client(messenger, {
1431 .client_id = 1,
1432 .input_type = "TextInputType.text",
1433 });
1434
1435 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1436 }
1437
1438 TEST(FlTextInputHandlerTest, UpdateConfig) {
1439 g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
1440 ::testing::NiceMock<flutter::testing::MockGtk> mock_gtk;
1441
1442 g_autoptr(FlTextInputHandler) handler =
1443 fl_text_input_handler_new(FL_BINARY_MESSENGER(messenger));
1444 EXPECT_NE(handler, nullptr);
1445
1446 set_client(messenger, {
1447 .client_id = 1,
1448 .input_action = "TextInputAction.none",
1449 });
1450
1451 // Update config with a different input action
1452 gboolean called = FALSE;
1453 g_autoptr(FlValue) config = build_map({
1454 {"inputAction", fl_value_new_string("TextInputAction.send")},
1455 {"inputType", build_map({
1456 {"name", fl_value_new_string("TextInputType.text")},
1457 })},
1458 {"enableDeltaModel", fl_value_new_bool(false)},
1459 });
1460 fl_mock_binary_messenger_invoke_json_method(
1461 messenger, "flutter/textinput", "TextInput.updateConfig", config,
1462 [](FlMockBinaryMessenger* messenger, FlMethodResponse* response,
1463 gpointer user_data) {
1464 gboolean* called = static_cast<gboolean*>(user_data);
1465 *called = TRUE;
1466
1467 EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
1468
1469 g_autoptr(FlValue) expected_result = fl_value_new_null();
1470 EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
1471 FL_METHOD_SUCCESS_RESPONSE(response)),
1472 expected_result));
1473 },
1474 &called);
1475 EXPECT_TRUE(called);
1476
1477 set_editing_state(messenger, {
1478 .text = "Flutter",
1479 .selection_base = 7,
1480 .selection_extent = 7,
1481 });
1482
1483 // Verify the action was updated by checking perform action call
1484 int call_count = 0;
1485 fl_mock_binary_messenger_set_json_method_channel(
1486 messenger, "flutter/textinput",
1487 [](FlMockBinaryMessenger* messenger, GTask* task, const gchar* name,
1488 FlValue* args, gpointer user_data) {
1489 int* call_count = static_cast<int*>(user_data);
1490
1491 EXPECT_STREQ(name, "TextInputClient.performAction");
1492 g_autoptr(FlValue) expected_args = build_list({
1493 fl_value_new_int(1), // client_id
1494 fl_value_new_string("TextInputAction.send"),
1495 });
1496 EXPECT_TRUE(fl_value_equal(args, expected_args));
1497 (*call_count)++;
1498
1499 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
1500 },
1501 &call_count);
1502
1503 send_key_event(handler, GDK_KEY_Return);
1504 EXPECT_EQ(call_count, 1);
1505
1506 fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger));
1507 }
g_autoptr
g_autoptr(FlEngine) engine
user_data
const char FlTextDirection FlAssertiveness gpointer user_data
Definition: fl_accessibility_handler.cc:24
fl_binary_messenger_shutdown
void fl_binary_messenger_shutdown(FlBinaryMessenger *self)
Definition: fl_binary_messenger.cc:500
fl_binary_messenger_private.h
height
self height
Definition: fl_compositor_software.cc:43
width
self width
Definition: fl_compositor_software.cc:42
TRUE
return TRUE
Definition: fl_compositor_software.cc:68
args
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
Definition: fl_event_channel.h:89
fl_key_event_new_from_gdk_event
FlKeyEvent * fl_key_event_new_from_gdk_event(GdkEvent *event)
Definition: fl_key_event.cc:53
fl_method_codec_private.h
fl_method_success_response_new
G_MODULE_EXPORT FlMethodSuccessResponse * fl_method_success_response_new(FlValue *result)
Definition: fl_method_response.cc:126
fl_method_success_response_get_result
G_MODULE_EXPORT FlValue * fl_method_success_response_get_result(FlMethodSuccessResponse *self)
Definition: fl_method_response.cc:138
value
uint8_t value
Definition: fl_standard_message_codec.cc:36
fl_text_input_handler_new
FlTextInputHandler * fl_text_input_handler_new(FlBinaryMessenger *messenger)
Definition: fl_text_input_handler.cc:437
fl_text_input_handler_filter_keypress
gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler *self, FlKeyEvent *event)
Definition: fl_text_input_handler.cc:492
fl_text_input_handler_get_im_context
GtkIMContext * fl_text_input_handler_get_im_context(FlTextInputHandler *self)
Definition: fl_text_input_handler.cc:474
fl_text_input_handler.h
send_key_event
static void send_key_event(FlTextInputHandler *handler, gint keyval, gint state=0)
Definition: fl_text_input_handler_test.cc:143
build_list
static FlValue * build_list(std::vector< FlValue * > args)
Definition: fl_text_input_handler_test.cc:26
set_client
static void set_client(FlMockBinaryMessenger *messenger, InputConfig config)
Definition: fl_text_input_handler_test.cc:100
build_editing_delta
static FlValue * build_editing_delta(EditingDelta delta)
Definition: fl_text_input_handler_test.cc:85
build_editing_state
static FlValue * build_editing_state(EditingState state)
Definition: fl_text_input_handler_test.cc:62
build_map
static FlValue * build_map(std::map< const gchar *, FlValue * > args)
Definition: fl_text_input_handler_test.cc:18
TEST
TEST(FlTextInputHandlerTest, MessageHandler)
Definition: fl_text_input_handler_test.cc:153
build_input_config
static FlValue * build_input_config(InputConfig config)
Definition: fl_text_input_handler_test.cc:41
set_editing_state
static void set_editing_state(FlMockBinaryMessenger *messenger, EditingState state)
Definition: fl_text_input_handler_test.cc:121
fl_value_new_float
G_MODULE_EXPORT FlValue * fl_value_new_float(double value)
Definition: fl_value.cc:269
fl_value_set_string_take
G_MODULE_EXPORT void fl_value_set_string_take(FlValue *self, const gchar *key, FlValue *value)
Definition: fl_value.cc:650
fl_value_new_string
G_MODULE_EXPORT FlValue * fl_value_new_string(const gchar *value)
Definition: fl_value.cc:276
fl_value_new_bool
G_MODULE_EXPORT FlValue * fl_value_new_bool(bool value)
Definition: fl_value.cc:255
fl_value_new_list
G_MODULE_EXPORT FlValue * fl_value_new_list()
Definition: fl_value.cc:349
fl_value_append_take
G_MODULE_EXPORT void fl_value_append_take(FlValue *self, FlValue *value)
Definition: fl_value.cc:600
fl_value_new_null
G_MODULE_EXPORT FlValue * fl_value_new_null()
Definition: fl_value.cc:251
fl_value_equal
G_MODULE_EXPORT bool fl_value_equal(FlValue *a, FlValue *b)
Definition: fl_value.cc:471
fl_value_new_int
G_MODULE_EXPORT FlValue * fl_value_new_int(int64_t value)
Definition: fl_value.cc:262
fl_value_new_map
G_MODULE_EXPORT FlValue * fl_value_new_map()
Definition: fl_value.cc:366
FlValue
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition: fl_value.h:42
flutter::MessageHandler
std::function< void(const T &message, const MessageReply< T > &reply)> MessageHandler
Definition: basic_message_channel.h:51
EditingDelta
Definition: fl_text_input_handler_test.cc:74
EditingDelta::selection_extent
int selection_extent
Definition: fl_text_input_handler_test.cc:80
EditingDelta::selection_base
int selection_base
Definition: fl_text_input_handler_test.cc:79
EditingDelta::delta_text
const gchar * delta_text
Definition: fl_text_input_handler_test.cc:76
EditingDelta::composing_base
int composing_base
Definition: fl_text_input_handler_test.cc:81
EditingDelta::composing_extent
int composing_extent
Definition: fl_text_input_handler_test.cc:82
EditingDelta::old_text
const gchar * old_text
Definition: fl_text_input_handler_test.cc:75
EditingDelta::delta_end
int delta_end
Definition: fl_text_input_handler_test.cc:78
EditingDelta::delta_start
int delta_start
Definition: fl_text_input_handler_test.cc:77
EditingState
Definition: fl_text_input_handler_test.cc:54
EditingState::selection_extent
int selection_extent
Definition: fl_text_input_handler_test.cc:57
EditingState::composing_base
int composing_base
Definition: fl_text_input_handler_test.cc:58
EditingState::text
const gchar * text
Definition: fl_text_input_handler_test.cc:55
EditingState::selection_base
int selection_base
Definition: fl_text_input_handler_test.cc:56
EditingState::composing_extent
int composing_extent
Definition: fl_text_input_handler_test.cc:59
InputConfig
Definition: fl_text_input_handler_test.cc:34
InputConfig::input_type
const gchar * input_type
Definition: fl_text_input_handler_test.cc:36
InputConfig::enable_delta_model
gboolean enable_delta_model
Definition: fl_text_input_handler_test.cc:38
InputConfig::input_action
const gchar * input_action
Definition: fl_text_input_handler_test.cc:37
InputConfig::client_id
int64_t client_id
Definition: fl_text_input_handler_test.cc:35