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 
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_im_context.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) {
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) {
28  for (auto it = args.begin(); it != args.end(); ++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 
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 
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 
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 
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();
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,
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();
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::MockIMContext> context;
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::MockIMContext> context;
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::MockIMContext> context;
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(context, 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();
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::MockIMContext> context;
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(context, 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();
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::MockIMContext> context;
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();
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::MockIMContext> context;
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
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::MockIMContext> context;
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::MockIMContext> context;
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
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 
454 TEST(FlTextInputHandlerTest, Select) {
455  g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
456  ::testing::NiceMock<flutter::testing::MockIMContext> context;
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
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 
519 TEST(FlTextInputHandlerTest, Composing) {
520  g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
521  ::testing::NiceMock<flutter::testing::MockIMContext> context;
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(context, 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
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 
603 TEST(FlTextInputHandlerTest, SurroundingText) {
604  g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
605  ::testing::NiceMock<flutter::testing::MockIMContext> context;
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(context, 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
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::MockIMContext> context;
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({
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();
716  FL_METHOD_SUCCESS_RESPONSE(response)),
717  expected_result));
718  },
719  &called);
720  EXPECT_TRUE(called);
721 
722  EXPECT_CALL(context, 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(context, 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();
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::MockIMContext> context;
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(context, gtk_im_context_focus_in).Times(0);
779  EXPECT_CALL(context, 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();
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::MockIMContext> context;
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({
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::MockIMContext> context;
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(context, 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({
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 
980 TEST(FlTextInputHandlerTest, NonComposingDelta) {
981  g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new();
982  ::testing::NiceMock<flutter::testing::MockIMContext> context;
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({
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 }
EditingDelta::delta_end
int delta_end
Definition: fl_text_input_handler_test.cc:78
build_list
static FlValue * build_list(std::vector< FlValue * > args)
Definition: fl_text_input_handler_test.cc:26
set_editing_state
static void set_editing_state(FlMockBinaryMessenger *messenger, EditingState state)
Definition: fl_text_input_handler_test.cc:121
fl_binary_messenger_shutdown
void fl_binary_messenger_shutdown(FlBinaryMessenger *self)
Definition: fl_binary_messenger.cc:500
EditingDelta
Definition: fl_text_input_handler_test.cc:74
fl_key_event_new_from_gdk_event
FlKeyEvent * fl_key_event_new_from_gdk_event(GdkEvent *event)
Definition: fl_key_event.cc:53
InputConfig::enable_delta_model
gboolean enable_delta_model
Definition: fl_text_input_handler_test.cc:38
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_list
G_MODULE_EXPORT FlValue * fl_value_new_list()
Definition: fl_value.cc:349
fl_text_input_handler_new
FlTextInputHandler * fl_text_input_handler_new(FlBinaryMessenger *messenger)
Definition: fl_text_input_handler.cc:422
EditingDelta::composing_extent
int composing_extent
Definition: fl_text_input_handler_test.cc:82
TEST
TEST(FlTextInputHandlerTest, MessageHandler)
Definition: fl_text_input_handler_test.cc:153
set_client
static void set_client(FlMockBinaryMessenger *messenger, InputConfig config)
Definition: fl_text_input_handler_test.cc:100
EditingState::composing_base
int composing_base
Definition: fl_text_input_handler_test.cc:58
fl_value_new_bool
G_MODULE_EXPORT FlValue * fl_value_new_bool(bool value)
Definition: fl_value.cc:255
FlValue
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition: fl_value.h:42
fl_text_input_handler.h
fl_value_new_null
G_MODULE_EXPORT FlValue * fl_value_new_null()
Definition: fl_value.cc:251
InputConfig::client_id
int64_t client_id
Definition: fl_text_input_handler_test.cc:35
EditingDelta::delta_text
const gchar * delta_text
Definition: fl_text_input_handler_test.cc:76
EditingState::selection_base
int selection_base
Definition: fl_text_input_handler_test.cc:56
EditingDelta::delta_start
int delta_start
Definition: fl_text_input_handler_test.cc:77
fl_value_new_int
G_MODULE_EXPORT FlValue * fl_value_new_int(int64_t value)
Definition: fl_value.cc:262
EditingDelta::selection_extent
int selection_extent
Definition: fl_text_input_handler_test.cc:80
fl_method_success_response_new
G_MODULE_EXPORT FlMethodSuccessResponse * fl_method_success_response_new(FlValue *result)
Definition: fl_method_response.cc:126
state
AtkStateType state
Definition: fl_accessible_node.cc:10
user_data
G_BEGIN_DECLS G_MODULE_EXPORT FlValue gpointer user_data
Definition: fl_event_channel.h:90
build_map
static FlValue * build_map(std::map< const gchar *, FlValue * > args)
Definition: fl_text_input_handler_test.cc:18
fl_value_new_map
G_MODULE_EXPORT FlValue * fl_value_new_map()
Definition: fl_value.cc:366
EditingState::text
const gchar * text
Definition: fl_text_input_handler_test.cc:55
fl_text_input_handler_filter_keypress
gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler *self, FlKeyEvent *event)
Definition: fl_text_input_handler.cc:477
EditingDelta::old_text
const gchar * old_text
Definition: fl_text_input_handler_test.cc:75
EditingState
Definition: fl_text_input_handler_test.cc:54
fl_method_success_response_get_result
G_MODULE_EXPORT FlValue * fl_method_success_response_get_result(FlMethodSuccessResponse *self)
Definition: fl_method_response.cc:138
TRUE
return TRUE
Definition: fl_pixel_buffer_texture_test.cc:53
EditingState::composing_extent
int composing_extent
Definition: fl_text_input_handler_test.cc:59
fl_text_input_handler_get_im_context
GtkIMContext * fl_text_input_handler_get_im_context(FlTextInputHandler *self)
Definition: fl_text_input_handler.cc:459
fl_value_append_take
G_MODULE_EXPORT void fl_value_append_take(FlValue *self, FlValue *value)
Definition: fl_value.cc:600
fl_value_equal
G_MODULE_EXPORT bool fl_value_equal(FlValue *a, FlValue *b)
Definition: fl_value.cc:471
InputConfig
Definition: fl_text_input_handler_test.cc:34
EditingDelta::composing_base
int composing_base
Definition: fl_text_input_handler_test.cc:81
height
const uint8_t uint32_t uint32_t * height
Definition: fl_pixel_buffer_texture_test.cc:39
fl_binary_messenger_private.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_input_config
static FlValue * build_input_config(InputConfig config)
Definition: fl_text_input_handler_test.cc:41
args
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
Definition: fl_event_channel.h:89
build_editing_state
static FlValue * build_editing_state(EditingState state)
Definition: fl_text_input_handler_test.cc:62
InputConfig::input_type
const gchar * input_type
Definition: fl_text_input_handler_test.cc:36
EditingState::selection_extent
int selection_extent
Definition: fl_text_input_handler_test.cc:57
InputConfig::input_action
const gchar * input_action
Definition: fl_text_input_handler_test.cc:37
fl_value_new_float
G_MODULE_EXPORT FlValue * fl_value_new_float(double value)
Definition: fl_value.cc:269
width
const uint8_t uint32_t * width
Definition: fl_pixel_buffer_texture_test.cc:38
flutter::MessageHandler
std::function< void(const T &message, const MessageReply< T > &reply)> MessageHandler
Definition: basic_message_channel.h:51
fl_method_codec_private.h
build_editing_delta
static FlValue * build_editing_delta(EditingDelta delta)
Definition: fl_text_input_handler_test.cc:85
value
uint8_t value
Definition: fl_standard_message_codec.cc:36
fl_value_new_string
G_MODULE_EXPORT FlValue * fl_value_new_string(const gchar *value)
Definition: fl_value.cc:276
EditingDelta::selection_base
int selection_base
Definition: fl_text_input_handler_test.cc:79