Flutter Engine
 
Loading...
Searching...
No Matches
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
14
15#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17
18static FlValue* build_map(std::map<const gchar*, FlValue*> args) {
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
26static FlValue* build_list(std::vector<FlValue*> args) {
28 for (auto it = args.begin(); it != args.end(); ++it) {
30 }
31 return value;
32}
33
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
42 return build_list({
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
55 const gchar* text = "";
60};
61
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
75 const gchar* old_text = "";
76 const gchar* delta_text = "";
77 int delta_start = -1;
78 int delta_end = -1;
83};
84
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
100static void set_client(FlMockBinaryMessenger* messenger, InputConfig config) {
101 gboolean called = FALSE;
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();
114 FL_METHOD_SUCCESS_RESPONSE(response)),
115 expected_result));
116 },
117 &called);
118 EXPECT_TRUE(called);
119}
120
121static void set_editing_state(FlMockBinaryMessenger* messenger,
122 EditingState state) {
123 gboolean called = FALSE;
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();
136 FL_METHOD_SUCCESS_RESPONSE(response)),
137 expected_result));
138 },
139 &called);
140 EXPECT_TRUE(called);
141}
142
143static 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);
151}
152
153TEST(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
167TEST(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
180TEST(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;
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();
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
211TEST(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;
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();
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
242TEST(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;
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();
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
271TEST(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;
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
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.
331TEST(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;
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
389TEST(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;
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
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
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
454TEST(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;
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
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
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
519TEST(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;
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
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
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
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
603TEST(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;
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
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
668TEST(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({
700 }),
701 },
702 });
703 gboolean called = FALSE;
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();
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;
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();
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
765TEST(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;
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();
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
802TEST(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;
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({
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
866TEST(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;
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({
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({
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({
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
980TEST(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;
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({
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({
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({
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({
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({
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({
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({
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}
int32_t value
void fl_binary_messenger_shutdown(FlBinaryMessenger *self)
g_autoptr(GMutexLocker) locker
return TRUE
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlKeyEvent * fl_key_event_new_from_gdk_event(GdkEvent *event)
G_MODULE_EXPORT FlValue * fl_method_success_response_get_result(FlMethodSuccessResponse *self)
G_MODULE_EXPORT FlMethodSuccessResponse * fl_method_success_response_new(FlValue *result)
gboolean fl_mock_binary_messenger_has_handler(FlMockBinaryMessenger *self, const gchar *channel)
void fl_mock_binary_messenger_invoke_json_method(FlMockBinaryMessenger *self, const gchar *channel, const char *name, FlValue *args, FlMockBinaryMessengerMethodCallback callback, gpointer user_data)
const gchar FlBinaryMessengerMessageHandler handler
FlMockBinaryMessenger * fl_mock_binary_messenger_new()
void fl_mock_binary_messenger_set_json_method_channel(FlMockBinaryMessenger *self, const gchar *channel, FlMockBinaryMessengerMethodChannelHandler handler, gpointer user_data)
GtkIMContext * fl_text_input_handler_get_im_context(FlTextInputHandler *self)
FlTextInputHandler * fl_text_input_handler_new(FlBinaryMessenger *messenger)
gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler *self, FlKeyEvent *event)
static void send_key_event(FlTextInputHandler *handler, gint keyval, gint state=0)
static FlValue * build_input_config(InputConfig config)
static void set_client(FlMockBinaryMessenger *messenger, InputConfig config)
static FlValue * build_editing_state(EditingState state)
static FlValue * build_map(std::map< const gchar *, FlValue * > args)
static FlValue * build_list(std::vector< FlValue * > args)
TEST(FlTextInputHandlerTest, MessageHandler)
static void set_editing_state(FlMockBinaryMessenger *messenger, EditingState state)
static FlValue * build_editing_delta(EditingDelta delta)
G_MODULE_EXPORT FlValue * fl_value_new_map()
Definition fl_value.cc:366
G_MODULE_EXPORT void fl_value_set_string_take(FlValue *self, const gchar *key, FlValue *value)
Definition fl_value.cc:650
G_MODULE_EXPORT FlValue * fl_value_new_null()
Definition fl_value.cc:251
G_MODULE_EXPORT FlValue * fl_value_new_string(const gchar *value)
Definition fl_value.cc:276
G_MODULE_EXPORT FlValue * fl_value_new_bool(bool value)
Definition fl_value.cc:255
G_MODULE_EXPORT FlValue * fl_value_new_int(int64_t value)
Definition fl_value.cc:262
G_MODULE_EXPORT FlValue * fl_value_new_float(double value)
Definition fl_value.cc:269
G_MODULE_EXPORT void fl_value_append_take(FlValue *self, FlValue *value)
Definition fl_value.cc:600
G_MODULE_EXPORT FlValue * fl_value_new_list()
Definition fl_value.cc:349
G_MODULE_EXPORT bool fl_value_equal(FlValue *a, FlValue *b)
Definition fl_value.cc:471
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition fl_value.h:42
const char * name
Definition fuchsia.cc:49
FlutterKeyEvent key_event
void gtk_im_context_focus_in(GtkIMContext *context)
Definition mock_gtk.cc:331
void gtk_im_context_set_cursor_location(GtkIMContext *context, const GdkRectangle *area)
Definition mock_gtk.cc:345
void gtk_im_context_focus_out(GtkIMContext *context)
Definition mock_gtk.cc:338
void gtk_im_context_get_preedit_string(GtkIMContext *context, gchar **str, PangoAttrList **attrs, gint *cursor_pos)
Definition mock_gtk.cc:311
void gtk_im_context_set_surrounding(GtkIMContext *context, const gchar *text, gint len, gint cursor_index)
Definition mock_gtk.cc:353
gboolean gtk_widget_translate_coordinates(GtkWidget *src_widget, GtkWidget *dest_widget, gint src_x, gint src_y, gint *dest_x, gint *dest_y)
Definition mock_gtk.cc:276