Flutter Linux Embedder
fl_accessible_text_field_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 // Included first as it collides with the X11 headers.
6 #include "gtest/gtest.h"
7 
8 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
12 #include "flutter/shell/platform/linux/testing/mock_signal_handler.h"
13 
14 // MOCK_ENGINE_PROC is leaky by design
15 // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
16 
17 static FlValue* decode_semantic_data(const uint8_t* data, size_t data_length) {
18  g_autoptr(GBytes) bytes = g_bytes_new(data, data_length);
19  g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
20  return fl_message_codec_decode_message(FL_MESSAGE_CODEC(codec), bytes,
21  nullptr);
22 }
23 
24 // Tests that semantic node value updates from Flutter emit AtkText::text-insert
25 // and AtkText::text-remove signals as expected.
26 TEST(FlAccessibleTextFieldTest, SetValue) {
27  g_autoptr(FlDartProject) project = fl_dart_project_new();
28  g_autoptr(FlEngine) engine = fl_engine_new(project);
29  g_autoptr(FlAccessibleNode) node =
30  fl_accessible_text_field_new(engine, 123, 1);
31 
32  // "" -> "Flutter"
33  {
34  flutter::testing::MockSignalHandler2<int, int> text_inserted(node,
35  "text-insert");
36  flutter::testing::MockSignalHandler text_removed(node, "text-remove");
37 
38  EXPECT_SIGNAL2(text_inserted, ::testing::Eq(0), ::testing::Eq(7));
39  EXPECT_SIGNAL(text_removed).Times(0);
40 
41  fl_accessible_node_set_value(node, "Flutter");
42  }
43 
44  // "Flutter" -> "Flutter"
45  {
46  flutter::testing::MockSignalHandler text_inserted(node, "text-insert");
47  flutter::testing::MockSignalHandler text_removed(node, "text-remove");
48 
49  EXPECT_SIGNAL(text_inserted).Times(0);
50  EXPECT_SIGNAL(text_removed).Times(0);
51 
52  fl_accessible_node_set_value(node, "Flutter");
53  }
54 
55  // "Flutter" -> "engine"
56  {
57  flutter::testing::MockSignalHandler2<int, int> text_inserted(node,
58  "text-insert");
59  flutter::testing::MockSignalHandler2<int, int> text_removed(node,
60  "text-remove");
61 
62  EXPECT_SIGNAL2(text_inserted, ::testing::Eq(0), ::testing::Eq(6));
63  EXPECT_SIGNAL2(text_removed, ::testing::Eq(0), ::testing::Eq(7));
64 
65  fl_accessible_node_set_value(node, "engine");
66  }
67 
68  // "engine" -> ""
69  {
70  flutter::testing::MockSignalHandler text_inserted(node, "text-insert");
71  flutter::testing::MockSignalHandler2<int, int> text_removed(node,
72  "text-remove");
73 
74  EXPECT_SIGNAL(text_inserted).Times(0);
75  EXPECT_SIGNAL2(text_removed, ::testing::Eq(0), ::testing::Eq(6));
76 
78  }
79 }
80 
81 // Tests that semantic node selection updates from Flutter emit
82 // AtkText::text-selection-changed and AtkText::text-caret-moved signals as
83 // expected.
84 TEST(FlAccessibleTextFieldTest, SetTextSelection) {
85  g_autoptr(FlDartProject) project = fl_dart_project_new();
86  g_autoptr(FlEngine) engine = fl_engine_new(project);
87  g_autoptr(FlAccessibleNode) node =
88  fl_accessible_text_field_new(engine, 123, 1);
89 
90  // [-1,-1] -> [2,3]
91  {
92  flutter::testing::MockSignalHandler text_selection_changed(
93  node, "text-selection-changed");
94  flutter::testing::MockSignalHandler1<int> text_caret_moved(
95  node, "text-caret-moved");
96 
97  EXPECT_SIGNAL(text_selection_changed);
98  EXPECT_SIGNAL1(text_caret_moved, ::testing::Eq(3));
99 
101  }
102 
103  // [2,3] -> [3,3]
104  {
105  flutter::testing::MockSignalHandler text_selection_changed(
106  node, "text-selection-changed");
107  flutter::testing::MockSignalHandler text_caret_moved(node,
108  "text-caret-moved");
109 
110  EXPECT_SIGNAL(text_selection_changed);
111  EXPECT_SIGNAL(text_caret_moved).Times(0);
112 
114  }
115 
116  // [3,3] -> [3,3]
117  {
118  flutter::testing::MockSignalHandler text_selection_changed(
119  node, "text-selection-changed");
120  flutter::testing::MockSignalHandler text_caret_moved(node,
121  "text-caret-moved");
122 
123  EXPECT_SIGNAL(text_selection_changed).Times(0);
124  EXPECT_SIGNAL(text_caret_moved).Times(0);
125 
127  }
128 
129  // [3,3] -> [4,4]
130  {
131  flutter::testing::MockSignalHandler text_selection_changed(
132  node, "text-selection-changed");
133  flutter::testing::MockSignalHandler1<int> text_caret_moved(
134  node, "text-caret-moved");
135 
136  EXPECT_SIGNAL(text_selection_changed).Times(0);
137  EXPECT_SIGNAL1(text_caret_moved, ::testing::Eq(4));
138 
140  }
141 }
142 
143 // Tests that fl_accessible_text_field_perform_action() passes the required
144 // "expandSelection" argument for semantic cursor move actions.
145 TEST(FlAccessibleTextFieldTest, PerformAction) {
146  g_autoptr(GPtrArray) action_datas = g_ptr_array_new_with_free_func(
147  reinterpret_cast<GDestroyNotify>(fl_value_unref));
148 
149  g_autoptr(FlDartProject) project = fl_dart_project_new();
150  g_autoptr(FlEngine) engine = fl_engine_new(project);
151 
152  g_autoptr(GError) error = nullptr;
153  EXPECT_TRUE(fl_engine_start(engine, &error));
154  EXPECT_EQ(error, nullptr);
155 
156  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
157  SendSemanticsAction,
158  ([&action_datas](auto engine,
159  const FlutterSendSemanticsActionInfo* info) {
160  g_ptr_array_add(action_datas,
161  decode_semantic_data(info->data, info->data_length));
162  return kSuccess;
163  }));
164 
165  g_autoptr(FlAccessibleNode) node =
166  fl_accessible_text_field_new(engine, 123, 1);
168  node, static_cast<FlutterSemanticsAction>(
169  kFlutterSemanticsActionMoveCursorForwardByCharacter |
170  kFlutterSemanticsActionMoveCursorBackwardByCharacter |
171  kFlutterSemanticsActionMoveCursorForwardByWord |
172  kFlutterSemanticsActionMoveCursorBackwardByWord));
173 
174  g_autoptr(FlValue) expand_selection = fl_value_new_bool(false);
175 
176  for (int i = 0; i < 4; ++i) {
177  atk_action_do_action(ATK_ACTION(node), i);
178 
179  FlValue* data = static_cast<FlValue*>(g_ptr_array_index(action_datas, i));
180  EXPECT_NE(data, nullptr);
181  EXPECT_TRUE(fl_value_equal(data, expand_selection));
182  }
183 }
184 
185 // Tests AtkText::get_character_count.
186 TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
187  g_autoptr(FlDartProject) project = fl_dart_project_new();
188  g_autoptr(FlEngine) engine = fl_engine_new(project);
189  g_autoptr(FlAccessibleNode) node =
190  fl_accessible_text_field_new(engine, 123, 1);
191 
192  EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 0);
193 
194  fl_accessible_node_set_value(node, "Flutter!");
195 
196  EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 8);
197 }
198 
199 // Tests AtkText::get_text.
200 TEST(FlAccessibleTextFieldTest, GetText) {
201  g_autoptr(FlDartProject) project = fl_dart_project_new();
202  g_autoptr(FlEngine) engine = fl_engine_new(project);
203  g_autoptr(FlAccessibleNode) node =
204  fl_accessible_text_field_new(engine, 123, 1);
205 
206  g_autofree gchar* empty = atk_text_get_text(ATK_TEXT(node), 0, -1);
207  EXPECT_STREQ(empty, "");
208 
209  flutter::testing::MockSignalHandler text_inserted(node, "text-insert");
210  EXPECT_SIGNAL(text_inserted).Times(1);
211 
212  fl_accessible_node_set_value(node, "Flutter!");
213 
214  g_autofree gchar* flutter = atk_text_get_text(ATK_TEXT(node), 0, -1);
215  EXPECT_STREQ(flutter, "Flutter!");
216 
217  g_autofree gchar* tt = atk_text_get_text(ATK_TEXT(node), 3, 5);
218  EXPECT_STREQ(tt, "tt");
219 }
220 
221 // Tests AtkText::get_text with out-of-bounds offsets.
222 TEST(FlAccessibleTextFieldTest, GetTextBoundsChecking) {
223  g_autoptr(FlDartProject) project = fl_dart_project_new();
224  g_autoptr(FlEngine) engine = fl_engine_new(project);
225  g_autoptr(FlAccessibleNode) node =
226  fl_accessible_text_field_new(engine, 123, 1);
227 
228  fl_accessible_node_set_value(node, "Hello");
229 
230  // start beyond end of text
231  g_autofree gchar* beyond = atk_text_get_text(ATK_TEXT(node), 100, -1);
232  EXPECT_STREQ(beyond, "");
233 
234  // end beyond text length
235  g_autofree gchar* end_beyond = atk_text_get_text(ATK_TEXT(node), 2, 100);
236  EXPECT_STREQ(end_beyond, "llo");
237 
238  // both beyond text length
239  g_autofree gchar* both_beyond = atk_text_get_text(ATK_TEXT(node), 50, 100);
240  EXPECT_STREQ(both_beyond, "");
241 
242  // empty buffer
244  g_autofree gchar* empty_beyond = atk_text_get_text(ATK_TEXT(node), 5, 10);
245  EXPECT_STREQ(empty_beyond, "");
246 }
247 
248 // Tests AtkText::get_caret_offset.
249 TEST(FlAccessibleTextFieldTest, GetCaretOffset) {
250  g_autoptr(FlDartProject) project = fl_dart_project_new();
251  g_autoptr(FlEngine) engine = fl_engine_new(project);
252  g_autoptr(FlAccessibleNode) node =
253  fl_accessible_text_field_new(engine, 123, 1);
254 
255  EXPECT_EQ(atk_text_get_caret_offset(ATK_TEXT(node)), -1);
256 
258 
259  EXPECT_EQ(atk_text_get_caret_offset(ATK_TEXT(node)), 2);
260 }
261 
262 // Tests AtkText::set_caret_offset.
263 TEST(FlAccessibleTextFieldTest, SetCaretOffset) {
264  int base = -1;
265  int extent = -1;
266 
267  g_autoptr(FlDartProject) project = fl_dart_project_new();
268  g_autoptr(FlEngine) engine = fl_engine_new(project);
269 
270  g_autoptr(GError) error = nullptr;
271  EXPECT_TRUE(fl_engine_start(engine, &error));
272  EXPECT_EQ(error, nullptr);
273 
274  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
275  SendSemanticsAction,
276  ([&base, &extent](auto engine,
277  const FlutterSendSemanticsActionInfo* info) {
278  EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
280  decode_semantic_data(info->data, info->data_length);
283  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
284  return kSuccess;
285  }));
286 
287  g_autoptr(FlAccessibleNode) node =
288  fl_accessible_text_field_new(engine, 123, 1);
289 
290  EXPECT_TRUE(atk_text_set_caret_offset(ATK_TEXT(node), 3));
291  EXPECT_EQ(base, 3);
292  EXPECT_EQ(extent, 3);
293 }
294 
295 // Tests AtkText::get_n_selections.
296 TEST(FlAccessibleTextFieldTest, GetNSelections) {
297  g_autoptr(FlDartProject) project = fl_dart_project_new();
298  g_autoptr(FlEngine) engine = fl_engine_new(project);
299  g_autoptr(FlAccessibleNode) node =
300  fl_accessible_text_field_new(engine, 123, 1);
301 
302  EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 0);
303 
305 
306  EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 1);
307 }
308 
309 // Tests AtkText::get_selection.
310 TEST(FlAccessibleTextFieldTest, GetSelection) {
311  g_autoptr(FlDartProject) project = fl_dart_project_new();
312  g_autoptr(FlEngine) engine = fl_engine_new(project);
313  g_autoptr(FlAccessibleNode) node =
314  fl_accessible_text_field_new(engine, 123, 1);
315 
316  EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, nullptr, nullptr),
317  nullptr);
318 
319  fl_accessible_node_set_value(node, "Flutter");
321 
322  gint start, end;
323  g_autofree gchar* selection =
324  atk_text_get_selection(ATK_TEXT(node), 0, &start, &end);
325  EXPECT_STREQ(selection, "utt");
326  EXPECT_EQ(start, 2);
327  EXPECT_EQ(end, 5);
328 
329  // reverse
331  g_autofree gchar* reverse =
332  atk_text_get_selection(ATK_TEXT(node), 0, &start, &end);
333  EXPECT_STREQ(reverse, "utt");
334  EXPECT_EQ(start, 2);
335  EXPECT_EQ(end, 5);
336 
337  // empty
339  EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, &start, &end), nullptr);
340 
341  // selection num != 0
342  EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 1, &start, &end), nullptr);
343 }
344 
345 // Tests AtkText::add_selection.
346 TEST(FlAccessibleTextFieldTest, AddSelection) {
347  int base = -1;
348  int extent = -1;
349 
350  g_autoptr(FlDartProject) project = fl_dart_project_new();
351  g_autoptr(FlEngine) engine = fl_engine_new(project);
352 
353  g_autoptr(GError) error = nullptr;
354  EXPECT_TRUE(fl_engine_start(engine, &error));
355  EXPECT_EQ(error, nullptr);
356 
357  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
358  SendSemanticsAction,
359  ([&base, &extent](auto engine,
360  const FlutterSendSemanticsActionInfo* info) {
361  EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
363  decode_semantic_data(info->data, info->data_length);
366  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
367  return kSuccess;
368  }));
369 
370  g_autoptr(FlAccessibleNode) node =
371  fl_accessible_text_field_new(engine, 123, 1);
372 
373  EXPECT_TRUE(atk_text_add_selection(ATK_TEXT(node), 2, 4));
374  EXPECT_EQ(base, 2);
375  EXPECT_EQ(extent, 4);
376 
378 
379  // already has selection
380  EXPECT_FALSE(atk_text_add_selection(ATK_TEXT(node), 6, 7));
381  EXPECT_EQ(base, 2);
382  EXPECT_EQ(extent, 4);
383 }
384 
385 // Tests AtkText::remove_selection.
386 TEST(FlAccessibleTextFieldTest, RemoveSelection) {
387  int base = -1;
388  int extent = -1;
389 
390  g_autoptr(FlDartProject) project = fl_dart_project_new();
391  g_autoptr(FlEngine) engine = fl_engine_new(project);
392 
393  g_autoptr(GError) error = nullptr;
394  EXPECT_TRUE(fl_engine_start(engine, &error));
395  EXPECT_EQ(error, nullptr);
396 
397  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
398  SendSemanticsAction,
399  ([&base, &extent](auto engine,
400  const FlutterSendSemanticsActionInfo* info) {
401  EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
403  decode_semantic_data(info->data, info->data_length);
406  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
407  return kSuccess;
408  }));
409 
410  g_autoptr(FlAccessibleNode) node =
411  fl_accessible_text_field_new(engine, 123, 1);
412 
413  // no selection
414  EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 0));
415  EXPECT_EQ(base, -1);
416  EXPECT_EQ(extent, -1);
417 
419 
420  // selection num != 0
421  EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 1));
422  EXPECT_EQ(base, -1);
423  EXPECT_EQ(extent, -1);
424 
425  // ok, collapses selection
426  EXPECT_TRUE(atk_text_remove_selection(ATK_TEXT(node), 0));
427  EXPECT_EQ(base, 4);
428  EXPECT_EQ(extent, 4);
429 }
430 
431 // Tests AtkText::set_selection.
432 TEST(FlAccessibleTextFieldTest, SetSelection) {
433  int base = -1;
434  int extent = -1;
435 
436  g_autoptr(FlDartProject) project = fl_dart_project_new();
437  g_autoptr(FlEngine) engine = fl_engine_new(project);
438 
439  g_autoptr(GError) error = nullptr;
440  EXPECT_TRUE(fl_engine_start(engine, &error));
441  EXPECT_EQ(error, nullptr);
442 
443  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
444  SendSemanticsAction,
445  ([&base, &extent](auto engine,
446  const FlutterSendSemanticsActionInfo* info) {
447  EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
449  decode_semantic_data(info->data, info->data_length);
452  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
453  return kSuccess;
454  }));
455 
456  g_autoptr(FlAccessibleNode) node =
457  fl_accessible_text_field_new(engine, 123, 1);
458 
459  // selection num != 0
460  EXPECT_FALSE(atk_text_set_selection(ATK_TEXT(node), 1, 2, 4));
461  EXPECT_EQ(base, -1);
462  EXPECT_EQ(extent, -1);
463 
464  EXPECT_TRUE(atk_text_set_selection(ATK_TEXT(node), 0, 2, 4));
465  EXPECT_EQ(base, 2);
466  EXPECT_EQ(extent, 4);
467 
468  EXPECT_TRUE(atk_text_set_selection(ATK_TEXT(node), 0, 5, 1));
469  EXPECT_EQ(base, 5);
470  EXPECT_EQ(extent, 1);
471 }
472 
473 // Tests AtkEditableText::set_text_contents.
474 TEST(FlAccessibleTextFieldTest, SetTextContents) {
475  g_autofree gchar* text = nullptr;
476 
477  g_autoptr(FlDartProject) project = fl_dart_project_new();
478  g_autoptr(FlEngine) engine = fl_engine_new(project);
479 
480  g_autoptr(GError) error = nullptr;
481  EXPECT_TRUE(fl_engine_start(engine, &error));
482  EXPECT_EQ(error, nullptr);
483 
484  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
485  SendSemanticsAction,
486  ([&text](auto engine, const FlutterSendSemanticsActionInfo* info) {
487  EXPECT_EQ(info->action, kFlutterSemanticsActionSetText);
489  decode_semantic_data(info->data, info->data_length);
491  text = g_strdup(fl_value_get_string(value));
492  return kSuccess;
493  }));
494 
495  g_autoptr(FlAccessibleNode) node =
496  fl_accessible_text_field_new(engine, 123, 1);
497 
498  atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(node), "Flutter");
499  EXPECT_STREQ(text, "Flutter");
500 }
501 
502 // Tests AtkEditableText::insert/delete_text.
503 TEST(FlAccessibleTextFieldTest, InsertDeleteText) {
504  g_autofree gchar* text = nullptr;
505  int base = -1;
506  int extent = -1;
507 
508  g_autoptr(FlDartProject) project = fl_dart_project_new();
509  g_autoptr(FlEngine) engine = fl_engine_new(project);
510 
511  g_autoptr(GError) error = nullptr;
512  EXPECT_TRUE(fl_engine_start(engine, &error));
513  EXPECT_EQ(error, nullptr);
514 
515  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
516  SendSemanticsAction,
517  ([&text, &base, &extent](auto engine,
518  const FlutterSendSemanticsActionInfo* info) {
519  EXPECT_THAT(info->action,
520  ::testing::AnyOf(kFlutterSemanticsActionSetText,
521  kFlutterSemanticsActionSetSelection));
522  if (info->action == kFlutterSemanticsActionSetText) {
524  decode_semantic_data(info->data, info->data_length);
526  g_free(text);
527  text = g_strdup(fl_value_get_string(value));
528  } else {
530  decode_semantic_data(info->data, info->data_length);
533  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
534  }
535  return kSuccess;
536  }));
537 
538  g_autoptr(FlAccessibleNode) node =
539  fl_accessible_text_field_new(engine, 123, 1);
540  fl_accessible_node_set_value(node, "Fler");
541 
542  gint pos = 2;
543  atk_editable_text_insert_text(ATK_EDITABLE_TEXT(node), "utt", 3, &pos);
544  EXPECT_EQ(pos, 5);
545  EXPECT_STREQ(text, "Flutter");
546  EXPECT_EQ(base, pos);
547  EXPECT_EQ(extent, pos);
548 
549  atk_editable_text_delete_text(ATK_EDITABLE_TEXT(node), 2, 5);
550  EXPECT_STREQ(text, "Fler");
551  EXPECT_EQ(base, 2);
552  EXPECT_EQ(extent, 2);
553 }
554 
555 // Tests AtkEditableText::copy/cut/paste_text.
556 TEST(FlAccessibleTextFieldTest, CopyCutPasteText) {
557  int base = -1;
558  int extent = -1;
559  FlutterSemanticsAction act = kFlutterSemanticsActionCustomAction;
560 
561  g_autoptr(FlDartProject) project = fl_dart_project_new();
562  g_autoptr(FlEngine) engine = fl_engine_new(project);
563 
564  g_autoptr(GError) error = nullptr;
565  EXPECT_TRUE(fl_engine_start(engine, &error));
566  EXPECT_EQ(error, nullptr);
567 
568  fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
569  SendSemanticsAction,
570  ([&act, &base, &extent](auto engine,
571  const FlutterSendSemanticsActionInfo* info) {
572  EXPECT_THAT(info->action,
573  ::testing::AnyOf(kFlutterSemanticsActionCut,
574  kFlutterSemanticsActionCopy,
575  kFlutterSemanticsActionPaste,
576  kFlutterSemanticsActionSetSelection));
577  act = info->action;
578  if (info->action == kFlutterSemanticsActionSetSelection) {
580  decode_semantic_data(info->data, info->data_length);
583  extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
584  }
585  return kSuccess;
586  }));
587 
588  g_autoptr(FlAccessibleNode) node =
589  fl_accessible_text_field_new(engine, 123, 1);
590 
591  atk_editable_text_copy_text(ATK_EDITABLE_TEXT(node), 2, 5);
592  EXPECT_EQ(base, 2);
593  EXPECT_EQ(extent, 5);
594  EXPECT_EQ(act, kFlutterSemanticsActionCopy);
595 
596  atk_editable_text_cut_text(ATK_EDITABLE_TEXT(node), 1, 4);
597  EXPECT_EQ(base, 1);
598  EXPECT_EQ(extent, 4);
599  EXPECT_EQ(act, kFlutterSemanticsActionCut);
600 
601  atk_editable_text_paste_text(ATK_EDITABLE_TEXT(node), 3);
602  EXPECT_EQ(base, 3);
603  EXPECT_EQ(extent, 3);
604  EXPECT_EQ(act, kFlutterSemanticsActionPaste);
605 }
606 
607 TEST(FlAccessibleTextFieldTest, TextBoundary) {
608  g_autoptr(FlDartProject) project = fl_dart_project_new();
609  g_autoptr(FlEngine) engine = fl_engine_new(project);
610  g_autoptr(FlAccessibleNode) node =
611  fl_accessible_text_field_new(engine, 123, 1);
612 
614  "Lorem ipsum.\nDolor sit amet. Praesent commodo?"
615  "\n\nPraesent et felis dui.");
616 
617  // |Lorem
618  gint start_offset = -1, end_offset = -1;
619  g_autofree gchar* lorem_char = atk_text_get_string_at_offset(
620  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_CHAR, &start_offset, &end_offset);
621  EXPECT_STREQ(lorem_char, "L");
622  EXPECT_EQ(start_offset, 0);
623  EXPECT_EQ(end_offset, 1);
624 
625  g_autofree gchar* lorem_word = atk_text_get_string_at_offset(
626  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_WORD, &start_offset, &end_offset);
627  EXPECT_STREQ(lorem_word, "Lorem");
628  EXPECT_EQ(start_offset, 0);
629  EXPECT_EQ(end_offset, 5);
630 
631  g_autofree gchar* lorem_sentence = atk_text_get_string_at_offset(
632  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_SENTENCE, &start_offset,
633  &end_offset);
634  EXPECT_STREQ(lorem_sentence, "Lorem ipsum.");
635  EXPECT_EQ(start_offset, 0);
636  EXPECT_EQ(end_offset, 12);
637 
638  g_autofree gchar* lorem_line = atk_text_get_string_at_offset(
639  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_LINE, &start_offset, &end_offset);
640  EXPECT_STREQ(lorem_line, "Lorem ipsum.");
641  EXPECT_EQ(start_offset, 0);
642  EXPECT_EQ(end_offset, 12);
643 
644  g_autofree gchar* lorem_paragraph = atk_text_get_string_at_offset(
645  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_PARAGRAPH, &start_offset,
646  &end_offset);
647  EXPECT_STREQ(lorem_paragraph,
648  "Lorem ipsum.\nDolor sit amet. Praesent commodo?");
649  EXPECT_EQ(start_offset, 0);
650  EXPECT_EQ(end_offset, 46);
651 
652  // Pra|esent
653  g_autofree gchar* praesent_char = atk_text_get_string_at_offset(
654  ATK_TEXT(node), 32, ATK_TEXT_GRANULARITY_CHAR, &start_offset,
655  &end_offset);
656  EXPECT_STREQ(praesent_char, "e");
657  EXPECT_EQ(start_offset, 32);
658  EXPECT_EQ(end_offset, 33);
659 
660  g_autofree gchar* praesent_word = atk_text_get_string_at_offset(
661  ATK_TEXT(node), 32, ATK_TEXT_GRANULARITY_WORD, &start_offset,
662  &end_offset);
663  EXPECT_STREQ(praesent_word, "Praesent");
664  EXPECT_EQ(start_offset, 29);
665  EXPECT_EQ(end_offset, 37);
666 
667  g_autofree gchar* praesent_sentence = atk_text_get_string_at_offset(
668  ATK_TEXT(node), 32, ATK_TEXT_GRANULARITY_SENTENCE, &start_offset,
669  &end_offset);
670  EXPECT_STREQ(praesent_sentence, "Praesent commodo?");
671  EXPECT_EQ(start_offset, 29);
672  EXPECT_EQ(end_offset, 46);
673 
674  g_autofree gchar* praesent_line = atk_text_get_string_at_offset(
675  ATK_TEXT(node), 32, ATK_TEXT_GRANULARITY_LINE, &start_offset,
676  &end_offset);
677  EXPECT_STREQ(praesent_line, "Dolor sit amet. Praesent commodo?");
678  EXPECT_EQ(start_offset, 13);
679  EXPECT_EQ(end_offset, 46);
680 
681  g_autofree gchar* praesent_paragraph = atk_text_get_string_at_offset(
682  ATK_TEXT(node), 32, ATK_TEXT_GRANULARITY_PARAGRAPH, &start_offset,
683  &end_offset);
684  EXPECT_STREQ(praesent_paragraph,
685  "Lorem ipsum.\nDolor sit amet. Praesent commodo?");
686  EXPECT_EQ(start_offset, 0);
687  EXPECT_EQ(end_offset, 46);
688 
689  // feli|s
690  g_autofree gchar* felis_char = atk_text_get_string_at_offset(
691  ATK_TEXT(node), 64, ATK_TEXT_GRANULARITY_CHAR, &start_offset,
692  &end_offset);
693  EXPECT_STREQ(felis_char, "s");
694  EXPECT_EQ(start_offset, 64);
695  EXPECT_EQ(end_offset, 65);
696 
697  g_autofree gchar* felis_word = atk_text_get_string_at_offset(
698  ATK_TEXT(node), 64, ATK_TEXT_GRANULARITY_WORD, &start_offset,
699  &end_offset);
700  EXPECT_STREQ(felis_word, "felis");
701  EXPECT_EQ(start_offset, 60);
702  EXPECT_EQ(end_offset, 65);
703 
704  g_autofree gchar* felis_sentence = atk_text_get_string_at_offset(
705  ATK_TEXT(node), 64, ATK_TEXT_GRANULARITY_SENTENCE, &start_offset,
706  &end_offset);
707  EXPECT_STREQ(felis_sentence, "Praesent et felis dui.");
708  EXPECT_EQ(start_offset, 48);
709  EXPECT_EQ(end_offset, 70);
710 
711  g_autofree gchar* felis_line = atk_text_get_string_at_offset(
712  ATK_TEXT(node), 64, ATK_TEXT_GRANULARITY_LINE, &start_offset,
713  &end_offset);
714  EXPECT_STREQ(felis_line, "Praesent et felis dui.");
715  EXPECT_EQ(start_offset, 48);
716  EXPECT_EQ(end_offset, 70);
717 
718  g_autofree gchar* felis_paragraph = atk_text_get_string_at_offset(
719  ATK_TEXT(node), 64, ATK_TEXT_GRANULARITY_PARAGRAPH, &start_offset,
720  &end_offset);
721  EXPECT_STREQ(felis_paragraph, "\nPraesent et felis dui.");
722  EXPECT_EQ(start_offset, 47);
723  EXPECT_EQ(end_offset, 70);
724 }
725 
726 // Tests that get_string_at_offset handles offset beyond text length.
727 TEST(FlAccessibleTextFieldTest, TextBoundaryOffsetBeyondEnd) {
728  g_autoptr(FlDartProject) project = fl_dart_project_new();
729  g_autoptr(FlEngine) engine = fl_engine_new(project);
730  g_autoptr(FlAccessibleNode) node =
731  fl_accessible_text_field_new(engine, 123, 1);
732 
733  fl_accessible_node_set_value(node, "Hello");
734 
735  // Offset well beyond the text length should not crash and should return
736  // a valid result clamped to the text boundaries.
737  gint start_offset = -1, end_offset = -1;
738  g_autofree gchar* char_result = atk_text_get_string_at_offset(
739  ATK_TEXT(node), 100, ATK_TEXT_GRANULARITY_CHAR, &start_offset,
740  &end_offset);
741  EXPECT_NE(char_result, nullptr);
742  EXPECT_GE(start_offset, 0);
743  EXPECT_LE(end_offset, 5);
744 
745  g_autofree gchar* word_result = atk_text_get_string_at_offset(
746  ATK_TEXT(node), 100, ATK_TEXT_GRANULARITY_WORD, &start_offset,
747  &end_offset);
748  EXPECT_NE(word_result, nullptr);
749  EXPECT_GE(start_offset, 0);
750  EXPECT_LE(end_offset, 5);
751 
752  g_autofree gchar* sentence_result = atk_text_get_string_at_offset(
753  ATK_TEXT(node), 100, ATK_TEXT_GRANULARITY_SENTENCE, &start_offset,
754  &end_offset);
755  EXPECT_NE(sentence_result, nullptr);
756  EXPECT_GE(start_offset, 0);
757  EXPECT_LE(end_offset, 5);
758 }
759 
760 // Tests that get_string_at_offset handles offset at position zero.
761 TEST(FlAccessibleTextFieldTest, TextBoundaryOffsetAtStart) {
762  g_autoptr(FlDartProject) project = fl_dart_project_new();
763  g_autoptr(FlEngine) engine = fl_engine_new(project);
764  g_autoptr(FlAccessibleNode) node =
765  fl_accessible_text_field_new(engine, 123, 1);
766 
767  fl_accessible_node_set_value(node, "Hello");
768 
769  // Offset at zero should return the first character/word.
770  gint start_offset = -1, end_offset = -1;
771  g_autofree gchar* char_result = atk_text_get_string_at_offset(
772  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_CHAR, &start_offset, &end_offset);
773  EXPECT_NE(char_result, nullptr);
774  EXPECT_EQ(start_offset, 0);
775  EXPECT_EQ(end_offset, 1);
776  EXPECT_STREQ(char_result, "H");
777 
778  g_autofree gchar* word_result = atk_text_get_string_at_offset(
779  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_WORD, &start_offset, &end_offset);
780  EXPECT_NE(word_result, nullptr);
781  EXPECT_EQ(start_offset, 0);
782  EXPECT_EQ(end_offset, 5);
783  EXPECT_STREQ(word_result, "Hello");
784 }
785 
786 // Tests that get_string_at_offset handles empty text.
787 TEST(FlAccessibleTextFieldTest, TextBoundaryEmptyText) {
788  g_autoptr(FlDartProject) project = fl_dart_project_new();
789  g_autoptr(FlEngine) engine = fl_engine_new(project);
790  g_autoptr(FlAccessibleNode) node =
791  fl_accessible_text_field_new(engine, 123, 1);
792 
793  // Empty text - should not crash.
794  gint start_offset = -1, end_offset = -1;
795  g_autofree gchar* char_result = atk_text_get_string_at_offset(
796  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_CHAR, &start_offset, &end_offset);
797  EXPECT_NE(char_result, nullptr);
798  EXPECT_EQ(start_offset, 0);
799  EXPECT_STREQ(char_result, "");
800 
801  g_autofree gchar* word_result = atk_text_get_string_at_offset(
802  ATK_TEXT(node), 0, ATK_TEXT_GRANULARITY_WORD, &start_offset, &end_offset);
803  EXPECT_NE(word_result, nullptr);
804  EXPECT_EQ(start_offset, 0);
805  EXPECT_STREQ(word_result, "");
806 }
807 
808 // Tests that get_string_at_offset handles offset at exact text length.
809 TEST(FlAccessibleTextFieldTest, TextBoundaryOffsetAtEnd) {
810  g_autoptr(FlDartProject) project = fl_dart_project_new();
811  g_autoptr(FlEngine) engine = fl_engine_new(project);
812  g_autoptr(FlAccessibleNode) node =
813  fl_accessible_text_field_new(engine, 123, 1);
814 
815  fl_accessible_node_set_value(node, "Hello world");
816 
817  // Offset at exactly the character count (one past last char).
818  gint char_count = atk_text_get_character_count(ATK_TEXT(node));
819  EXPECT_EQ(char_count, 11);
820 
821  gint start_offset = -1, end_offset = -1;
822  g_autofree gchar* char_result = atk_text_get_string_at_offset(
823  ATK_TEXT(node), char_count, ATK_TEXT_GRANULARITY_CHAR, &start_offset,
824  &end_offset);
825  EXPECT_NE(char_result, nullptr);
826  EXPECT_GE(start_offset, 0);
827  EXPECT_LE(end_offset, char_count);
828 
829  g_autofree gchar* word_result = atk_text_get_string_at_offset(
830  ATK_TEXT(node), char_count, ATK_TEXT_GRANULARITY_WORD, &start_offset,
831  &end_offset);
832  EXPECT_NE(word_result, nullptr);
833  EXPECT_GE(start_offset, 0);
834  EXPECT_LE(end_offset, char_count);
835 }
836 
837 // NOLINTEND(clang-analyzer-core.StackAddressEscape)
g_autoptr(FlEngine) engine
void fl_accessible_node_set_text_selection(FlAccessibleNode *self, gint base, gint extent)
void fl_accessible_node_set_actions(FlAccessibleNode *self, FlutterSemanticsAction actions)
void fl_accessible_node_set_value(FlAccessibleNode *self, const gchar *value)
FlAccessibleNode * fl_accessible_text_field_new(FlEngine *engine, FlutterViewId view_id, int32_t id)
glong glong end
static FlValue * decode_semantic_data(const uint8_t *data, size_t data_length)
TEST(FlAccessibleTextFieldTest, SetValue)
G_MODULE_EXPORT FlDartProject * fl_dart_project_new()
FlutterEngineProcTable * fl_engine_get_embedder_api(FlEngine *self)
Definition: fl_engine.cc:902
gboolean fl_engine_start(FlEngine *self, GError **error)
Definition: fl_engine.cc:760
G_MODULE_EXPORT FlEngine * fl_engine_new(FlDartProject *project)
Definition: fl_engine.cc:731
G_MODULE_EXPORT FlValue * fl_message_codec_decode_message(FlMessageCodec *self, GBytes *message, GError **error)
const uint8_t uint32_t uint32_t GError ** error
uint8_t value
G_MODULE_EXPORT FlStandardMessageCodec * fl_standard_message_codec_new()
G_MODULE_EXPORT FlValue * fl_value_lookup_string(FlValue *self, const gchar *key)
Definition: fl_value.cc:811
G_MODULE_EXPORT int64_t fl_value_get_int(FlValue *self)
Definition: fl_value.cc:668
G_MODULE_EXPORT void fl_value_unref(FlValue *self)
Definition: fl_value.cc:400
G_MODULE_EXPORT FlValueType fl_value_get_type(FlValue *self)
Definition: fl_value.cc:466
G_MODULE_EXPORT FlValue * fl_value_new_bool(bool value)
Definition: fl_value.cc:255
G_MODULE_EXPORT bool fl_value_equal(FlValue *a, FlValue *b)
Definition: fl_value.cc:471
G_MODULE_EXPORT const gchar * fl_value_get_string(FlValue *self)
Definition: fl_value.cc:682
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition: fl_value.h:42
@ FL_VALUE_TYPE_STRING
Definition: fl_value.h:68
@ FL_VALUE_TYPE_MAP
Definition: fl_value.h:74