Flutter iOS Embedder
SemanticsObjectTest.mm
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 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7 
14 
16 
17 const float kFloatCompareEpsilon = 0.001;
18 
20 - (UIView<UITextInput>*)textInputSurrogate;
21 @end
22 
23 @interface SemanticsObjectTest : XCTestCase
24 @end
25 
26 @implementation SemanticsObjectTest
27 
28 - (void)testCreate {
29  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
31  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
32  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
33  XCTAssertNotNil(object);
34 }
35 
36 - (void)testSetChildren {
37  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
39  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
40  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
41  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
42  parent.children = @[ child ];
43  XCTAssertEqual(parent, child.parent);
44  parent.children = @[];
45  XCTAssertNil(child.parent);
46 }
47 
48 - (void)testAccessibilityHitTestFocusAtLeaf {
49  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
51  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
52  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
53  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
54  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
55  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
56  object0.children = @[ object1 ];
57  object0.childrenInHitTestOrder = @[ object1 ];
58  object1.children = @[ object2, object3 ];
59  object1.childrenInHitTestOrder = @[ object2, object3 ];
60 
61  flutter::SemanticsNode node0;
62  node0.id = 0;
63  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
64  node0.label = "0";
65  [object0 setSemanticsNode:&node0];
66 
67  flutter::SemanticsNode node1;
68  node1.id = 1;
69  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
70  node1.label = "1";
71  [object1 setSemanticsNode:&node1];
72 
73  flutter::SemanticsNode node2;
74  node2.id = 2;
75  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
76  node2.label = "2";
77  [object2 setSemanticsNode:&node2];
78 
79  flutter::SemanticsNode node3;
80  node3.id = 3;
81  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
82  node3.label = "3";
83  [object3 setSemanticsNode:&node3];
84 
85  CGPoint point = CGPointMake(10, 10);
86  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
87 
88  // Focus to object2 because it's the first object in hit test order
89  XCTAssertEqual(hitTestResult, object2);
90 }
91 
92 - (void)testAccessibilityHitTestNoFocusableItem {
93  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
95  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
96  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
97  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
98  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
99  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
100  object0.children = @[ object1 ];
101  object0.childrenInHitTestOrder = @[ object1 ];
102  object1.children = @[ object2, object3 ];
103  object1.childrenInHitTestOrder = @[ object2, object3 ];
104 
105  flutter::SemanticsNode node0;
106  node0.id = 0;
107  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
108  [object0 setSemanticsNode:&node0];
109 
110  flutter::SemanticsNode node1;
111  node1.id = 1;
112  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
113  [object1 setSemanticsNode:&node1];
114 
115  flutter::SemanticsNode node2;
116  node2.id = 2;
117  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
118  [object2 setSemanticsNode:&node2];
119 
120  flutter::SemanticsNode node3;
121  node3.id = 3;
122  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
123  [object3 setSemanticsNode:&node3];
124 
125  CGPoint point = CGPointMake(10, 10);
126  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
127 
128  XCTAssertNil(hitTestResult);
129 }
130 
131 - (void)testAccessibilityScrollToVisible {
132  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
134  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
135  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
136 
137  flutter::SemanticsNode node3;
138  node3.id = 3;
139  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
140  [object3 setSemanticsNode:&node3];
141 
143 
144  XCTAssertTrue(bridge->observations.size() == 1);
145  XCTAssertTrue(bridge->observations[0].id == 3);
146  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
147 }
148 
149 - (void)testAccessibilityScrollToVisibleWithChild {
150  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
152  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
153  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
154 
155  flutter::SemanticsNode node3;
156  node3.id = 3;
157  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
158  [object3 setSemanticsNode:&node3];
159 
160  [object3 accessibilityScrollToVisibleWithChild:object3];
161 
162  XCTAssertTrue(bridge->observations.size() == 1);
163  XCTAssertTrue(bridge->observations[0].id == 3);
164  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
165 }
166 
167 - (void)testAccessibilityHitTestOutOfRect {
168  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
170  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
171  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
172  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
173  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
174  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
175  object0.children = @[ object1 ];
176  object0.childrenInHitTestOrder = @[ object1 ];
177  object1.children = @[ object2, object3 ];
178  object1.childrenInHitTestOrder = @[ object2, object3 ];
179 
180  flutter::SemanticsNode node0;
181  node0.id = 0;
182  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
183  node0.label = "0";
184  [object0 setSemanticsNode:&node0];
185 
186  flutter::SemanticsNode node1;
187  node1.id = 1;
188  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
189  node1.label = "1";
190  [object1 setSemanticsNode:&node1];
191 
192  flutter::SemanticsNode node2;
193  node2.id = 2;
194  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
195  node2.label = "2";
196  [object2 setSemanticsNode:&node2];
197 
198  flutter::SemanticsNode node3;
199  node3.id = 3;
200  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
201  node3.label = "3";
202  [object3 setSemanticsNode:&node3];
203 
204  CGPoint point = CGPointMake(300, 300);
205  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
206 
207  XCTAssertNil(hitTestResult);
208 }
209 
210 - (void)testReplaceChildAtIndex {
211  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
213  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
214  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
215  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
216  SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
217  parent.children = @[ child1 ];
218  [parent replaceChildAtIndex:0 withChild:child2];
219  XCTAssertNil(child1.parent);
220  XCTAssertEqual(parent, child2.parent);
221  XCTAssertEqualObjects(parent.children, @[ child2 ]);
222 }
223 
224 - (void)testPlainSemanticsObjectWithLabelHasStaticTextTrait {
225  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
227  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
228  flutter::SemanticsNode node;
229  node.label = "foo";
230  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
231  [object setSemanticsNode:&node];
232  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitStaticText);
233 }
234 
235 - (void)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden {
236  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
238  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
239  flutter::SemanticsNode node;
240  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling) |
241  static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
242  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
243  [object setSemanticsNode:&node];
244  XCTAssertEqual(object.isAccessibilityElement, YES);
245 }
246 
247 - (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
248  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
250  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
251  flutter::SemanticsNode node;
252  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
253  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
254  [object setSemanticsNode:&node];
255  XCTAssertEqual(object.isAccessibilityElement, NO);
256 }
257 
258 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
259  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
261  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
262  flutter::SemanticsNode node;
263  node.label = "foo";
264  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
265  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
266  object.children = @[ child1 ];
267  [object setSemanticsNode:&node];
268  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
269 }
270 
271 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
272  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
274  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
275  flutter::SemanticsNode node;
276  node.label = "foo";
277  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField);
278  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
279  [object setSemanticsNode:&node];
280  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
281 }
282 
283 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
284  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
286  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
287  flutter::SemanticsNode node;
288  node.label = "foo";
289  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsButton);
290  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
291  [object setSemanticsNode:&node];
292  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
293 }
294 
295 - (void)testVerticalFlutterScrollableSemanticsObject {
296  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
298  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
299 
300  float transformScale = 0.5f;
301  float screenScale = [[bridge->view() window] screen].scale;
302  float effectivelyScale = transformScale / screenScale;
303  float x = 10;
304  float y = 10;
305  float w = 100;
306  float h = 200;
307  float scrollExtentMax = 500.0;
308  float scrollPosition = 150.0;
309 
310  flutter::SemanticsNode node;
311  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
312  node.actions = flutter::kVerticalScrollSemanticsActions;
313  node.rect = SkRect::MakeXYWH(x, y, w, h);
314  node.scrollExtentMax = scrollExtentMax;
315  node.scrollPosition = scrollPosition;
316  node.transform = {
317  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
319  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
320  [scrollable setSemanticsNode:&node];
322  UIScrollView* scrollView = [scrollable nativeAccessibility];
323 
324  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
325  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
326  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
328  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
330 
331  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
333  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
334  (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
335 
336  XCTAssertEqual(scrollView.contentOffset.x, 0);
337  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
339 }
340 
341 - (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
342  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
344  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
345 
346  float transformScale = 0.5f;
347  float x = 10;
348  float y = 10;
349  float w = 100;
350  float h = 200;
351  float scrollExtentMax = 500.0;
352  float scrollPosition = 150.0;
353 
354  flutter::SemanticsNode node;
355  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
356  node.actions = flutter::kVerticalScrollSemanticsActions;
357  node.rect = SkRect::MakeXYWH(x, y, w, h);
358  node.scrollExtentMax = scrollExtentMax;
359  node.scrollPosition = scrollPosition;
360  node.transform = {
361  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
363  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
364  [scrollable setSemanticsNode:&node];
366  XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
367 }
368 
369 - (void)testHorizontalFlutterScrollableSemanticsObject {
370  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
372  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
373 
374  float transformScale = 0.5f;
375  float screenScale = [[bridge->view() window] screen].scale;
376  float effectivelyScale = transformScale / screenScale;
377  float x = 10;
378  float y = 10;
379  float w = 100;
380  float h = 200;
381  float scrollExtentMax = 500.0;
382  float scrollPosition = 150.0;
383 
384  flutter::SemanticsNode node;
385  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
386  node.actions = flutter::kHorizontalScrollSemanticsActions;
387  node.rect = SkRect::MakeXYWH(x, y, w, h);
388  node.scrollExtentMax = scrollExtentMax;
389  node.scrollPosition = scrollPosition;
390  node.transform = {
391  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
393  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
394  [scrollable setSemanticsNode:&node];
396  UIScrollView* scrollView = [scrollable nativeAccessibility];
397 
398  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
399  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
400  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
402  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
404 
405  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
407  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
409 
410  XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
412  XCTAssertEqual(scrollView.contentOffset.y, 0);
413 }
414 
415 - (void)testCanHandleInfiniteScrollExtent {
416  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
418  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
419 
420  float transformScale = 0.5f;
421  float screenScale = [[bridge->view() window] screen].scale;
422  float effectivelyScale = transformScale / screenScale;
423  float x = 10;
424  float y = 10;
425  float w = 100;
426  float h = 200;
427  float scrollExtentMax = INFINITY;
428  float scrollPosition = 150.0;
429 
430  flutter::SemanticsNode node;
431  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
432  node.actions = flutter::kVerticalScrollSemanticsActions;
433  node.rect = SkRect::MakeXYWH(x, y, w, h);
434  node.scrollExtentMax = scrollExtentMax;
435  node.scrollPosition = scrollPosition;
436  node.transform = {
437  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
439  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
440  [scrollable setSemanticsNode:&node];
442  UIScrollView* scrollView = [scrollable nativeAccessibility];
443  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
444  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
445  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
447  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
449 
450  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
452  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
453  (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
455 
456  XCTAssertEqual(scrollView.contentOffset.x, 0);
457  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
459 }
460 
461 - (void)testCanHandleNaNScrollExtentAndScrollPoisition {
462  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
464  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
465 
466  float transformScale = 0.5f;
467  float screenScale = [[bridge->view() window] screen].scale;
468  float effectivelyScale = transformScale / screenScale;
469  float x = 10;
470  float y = 10;
471  float w = 100;
472  float h = 200;
473  float scrollExtentMax = std::nan("");
474  float scrollPosition = std::nan("");
475 
476  flutter::SemanticsNode node;
477  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
478  node.actions = flutter::kVerticalScrollSemanticsActions;
479  node.rect = SkRect::MakeXYWH(x, y, w, h);
480  node.scrollExtentMax = scrollExtentMax;
481  node.scrollPosition = scrollPosition;
482  node.transform = {
483  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
485  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
486  [scrollable setSemanticsNode:&node];
488  UIScrollView* scrollView = [scrollable nativeAccessibility];
489 
490  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
491  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
492  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
494  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
496 
497  // Content size equal to the scrollable size.
498  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
500  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
502 
503  XCTAssertEqual(scrollView.contentOffset.x, 0);
504  XCTAssertEqual(scrollView.contentOffset.y, 0);
505 }
506 
507 - (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
508  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
510  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
511 
512  flutter::SemanticsNode node;
513  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
514  node.actions = flutter::kHorizontalScrollSemanticsActions;
515  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
516  node.scrollExtentMax = 100.0;
517  node.scrollPosition = 0.0;
518 
520  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
521  [scrollable setSemanticsNode:&node];
523  UIScrollView* scrollView = [scrollable nativeAccessibility];
524  XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
525 }
526 
527 - (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
529  mock->isVoiceOverRunningValue = false;
530  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
531  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
532 
533  flutter::SemanticsNode node;
534  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
535  node.actions = flutter::kHorizontalScrollSemanticsActions;
536  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
537  node.scrollExtentMax = 100.0;
538  node.scrollPosition = 0.0;
539 
541  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
542  [scrollable setSemanticsNode:&node];
544  UIScrollView* scrollView = [scrollable nativeAccessibility];
545  XCTAssertTrue(scrollView.isAccessibilityElement);
546  mock->isVoiceOverRunningValue = true;
547  XCTAssertFalse(scrollView.isAccessibilityElement);
548 }
549 
550 - (void)testFlutterSemanticsObjectHasIdentifier {
552  mock->isVoiceOverRunningValue = true;
553  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
554  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
555 
556  flutter::SemanticsNode node;
557  node.identifier = "identifier";
558 
559  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
560  [object setSemanticsNode:&node];
561  XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
562 }
563 
564 - (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
566  mock->isVoiceOverRunningValue = true;
567  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
568  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
569 
570  flutter::SemanticsNode node;
571  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
572  node.actions = flutter::kHorizontalScrollSemanticsActions;
573  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
574  node.label = "label";
575  node.value = "value";
576  node.hint = "hint";
577  node.scrollExtentMax = 100.0;
578  node.scrollPosition = 0.0;
579 
581  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
582  [scrollable setSemanticsNode:&node];
584  UIScrollView* scrollView = [scrollable nativeAccessibility];
585  XCTAssertTrue(scrollView.isAccessibilityElement);
586  XCTAssertTrue(
587  [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
588  XCTAssertTrue(
589  [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
590  XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
591 }
592 
593 - (void)testFlutterSemanticsObjectMergeTooltipToLabel {
595  mock->isVoiceOverRunningValue = true;
596  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
597  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
598 
599  flutter::SemanticsNode node;
600  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
601  node.label = "label";
602  node.tooltip = "tooltip";
603  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
604  [object setSemanticsNode:&node];
605  XCTAssertTrue(object.isAccessibilityElement);
606  XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
607 }
608 
609 - (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
611  mock->isVoiceOverRunningValue = true;
612  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
613  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
614 
615  flutter::SemanticsNode node;
616  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
617  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
618  [object setSemanticsNode:&node];
619  XCTAssertTrue(object.accessibilityAttributedLabel == nil);
620 }
621 
622 - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
624  mock->isVoiceOverRunningValue = true;
625  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
626  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
627 
628  flutter::SemanticsNode parent;
629  parent.id = 0;
630  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
631  parent.label = "label";
632  parent.value = "value";
633  parent.hint = "hint";
634 
635  flutter::SemanticsNode node;
636  node.id = 1;
637  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
638  node.actions = flutter::kHorizontalScrollSemanticsActions;
639  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
640  node.label = "label";
641  node.value = "value";
642  node.hint = "hint";
643  node.scrollExtentMax = 100.0;
644  node.scrollPosition = 0.0;
645  parent.childrenInTraversalOrder.push_back(1);
646 
647  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
648  uid:0];
649  [parentObject setSemanticsNode:&parent];
650 
652  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
653  [scrollable setSemanticsNode:&node];
654  UIScrollView* scrollView = [scrollable nativeAccessibility];
655 
656  parentObject.children = @[ scrollable ];
657  [parentObject accessibilityBridgeDidFinishUpdate];
659  XCTAssertTrue(scrollView.isAccessibilityElement);
660  SemanticsObjectContainer* container =
661  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
662  XCTAssertEqual(container.semanticsObject, parentObject);
663 }
664 
665 - (void)testFlutterScrollableSemanticsObjectHidesScrollBar {
666  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
668  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
669 
670  flutter::SemanticsNode node;
671  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
672  node.actions = flutter::kHorizontalScrollSemanticsActions;
673  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
674  node.scrollExtentMax = 100.0;
675  node.scrollPosition = 0.0;
676 
678  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
679  [scrollable setSemanticsNode:&node];
681  UIScrollView* scrollView = [scrollable nativeAccessibility];
682 
683  XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
684  XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
685 }
686 
687 - (void)testSemanticsObjectBuildsAttributedString {
688  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
690  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
691  flutter::SemanticsNode node;
692  node.label = "label";
693  std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
694  std::make_shared<flutter::SpellOutStringAttribute>();
695  attribute->start = 1;
696  attribute->end = 2;
697  attribute->type = flutter::StringAttributeType::kSpellOut;
698  node.labelAttributes.push_back(attribute);
699  node.value = "value";
700  attribute = std::make_shared<flutter::SpellOutStringAttribute>();
701  attribute->start = 2;
702  attribute->end = 3;
703  attribute->type = flutter::StringAttributeType::kSpellOut;
704  node.valueAttributes.push_back(attribute);
705  node.hint = "hint";
706  std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
707  std::make_shared<flutter::LocaleStringAttribute>();
708  local_attribute->start = 3;
709  local_attribute->end = 4;
710  local_attribute->type = flutter::StringAttributeType::kLocale;
711  local_attribute->locale = "en-MX";
712  node.hintAttributes.push_back(local_attribute);
713  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
714  [object setSemanticsNode:&node];
715  NSMutableAttributedString* expectedAttributedLabel =
716  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
717  NSDictionary* attributeDict = @{
718  UIAccessibilitySpeechAttributeSpellOut : @YES,
719  };
720  [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
721  XCTAssertTrue(
722  [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
723 
724  NSMutableAttributedString* expectedAttributedValue =
725  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
726  attributeDict = @{
727  UIAccessibilitySpeechAttributeSpellOut : @YES,
728  };
729  [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
730  XCTAssertTrue(
731  [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
732 
733  NSMutableAttributedString* expectedAttributedHint =
734  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
735  attributeDict = @{
736  UIAccessibilitySpeechAttributeLanguage : @"en-MX",
737  };
738  [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
739  XCTAssertTrue(
740  [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
741 }
742 
743 - (void)testShouldTriggerAnnouncement {
744  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
746  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
747  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
748 
749  // Handle nil with no node set.
750  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
751 
752  // Handle initial setting of node with liveRegion
753  flutter::SemanticsNode node;
754  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
755  node.label = "foo";
756  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
757 
758  // Handle nil with node set.
759  [object setSemanticsNode:&node];
760  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
761 
762  // Handle new node, still has live region, same label.
763  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
764 
765  // Handle update node with new label, still has live region.
766  flutter::SemanticsNode updatedNode;
767  updatedNode.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
768  updatedNode.label = "bar";
769  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
770 
771  // Handle dropping the live region flag.
772  updatedNode.flags = 0;
773  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
774 
775  // Handle adding the flag when the label has not changed.
776  updatedNode.label = "foo";
777  [object setSemanticsNode:&updatedNode];
778  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
779 }
780 
781 - (void)testShouldDispatchShowOnScreenActionForHeader {
782  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
784  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
785  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
786 
787  // Handle initial setting of node with header.
788  flutter::SemanticsNode node;
789  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHeader);
790  node.label = "foo";
791 
792  [object setSemanticsNode:&node];
793 
794  // Simulate accessibility focus.
795  [object accessibilityElementDidBecomeFocused];
796 
797  XCTAssertTrue(bridge->observations.size() == 1);
798  XCTAssertTrue(bridge->observations[0].id == 1);
799  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
800 }
801 
802 - (void)testShouldDispatchShowOnScreenActionForHidden {
803  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
805  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
806  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
807 
808  // Handle initial setting of node with hidden.
809  flutter::SemanticsNode node;
810  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
811  node.label = "foo";
812 
813  [object setSemanticsNode:&node];
814 
815  // Simulate accessibility focus.
816  [object accessibilityElementDidBecomeFocused];
817 
818  XCTAssertTrue(bridge->observations.size() == 1);
819  XCTAssertTrue(bridge->observations[0].id == 1);
820  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
821 }
822 
823 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
824  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
826  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
827  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
828  uid:1];
829 
830  // Handle initial setting of node with header.
831  flutter::SemanticsNode node;
832  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
833  static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled) |
834  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
835  node.label = "foo";
836  [object setSemanticsNode:&node];
837  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
838  UISwitch* nativeSwitch = [[UISwitch alloc] init];
839  nativeSwitch.on = YES;
840 
841  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
842  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
843 
844  // Set the toggled to false;
845  flutter::SemanticsNode update;
846  update.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
847  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
848  update.label = "foo";
849  [object setSemanticsNode:&update];
850 
851  nativeSwitch.on = NO;
852 
853  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
854  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
855 }
856 
857 - (void)testFlutterSemanticsObjectOfRadioButton {
858  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
860  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
861  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
862 
863  // Handle initial setting of node with header.
864  flutter::SemanticsNode node;
865  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
866  static_cast<int32_t>(flutter::SemanticsFlags::kHasCheckedState) |
867  static_cast<int32_t>(flutter::SemanticsFlags::kHasEnabledState) |
868  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
869  node.label = "foo";
870  [object setSemanticsNode:&node];
871  XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
872  XCTAssertNil(object.accessibilityValue);
873 }
874 
875 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
876  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
878  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
879  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
880  uid:1];
881 
882  // Handle initial setting of node with header.
883  flutter::SemanticsNode node;
884  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
885  static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled);
886  node.label = "foo";
887  [object setSemanticsNode:&node];
888  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
889  UISwitch* nativeSwitch = [[UISwitch alloc] init];
890  nativeSwitch.on = YES;
891  nativeSwitch.enabled = NO;
892 
893  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
894  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
895 }
896 
897 - (void)testSemanticsObjectDeallocated {
898  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
900  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
901  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
902  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
903  parent.children = @[ child ];
904  // Validate SemanticsObject deallocation does not crash.
905  // https://github.com/flutter/flutter/issues/66032
906  __weak SemanticsObject* weakObject = parent;
907  parent = nil;
908  XCTAssertNil(weakObject);
909 }
910 
911 - (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
912  FlutterSemanticsObject* parentObject;
914  FlutterSemanticsObject* object2;
915 
916  flutter::SemanticsNode parent;
917  parent.id = 0;
918  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
919  parent.label = "label";
920  parent.value = "value";
921  parent.hint = "hint";
922 
923  flutter::SemanticsNode node;
924  node.id = 1;
925  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
926  node.actions = flutter::kHorizontalScrollSemanticsActions;
927  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
928  node.label = "label";
929  node.value = "value";
930  node.hint = "hint";
931  node.scrollExtentMax = 100.0;
932  node.scrollPosition = 0.0;
933  parent.childrenInTraversalOrder.push_back(1);
934 
935  flutter::SemanticsNode node2;
936  node2.id = 2;
937  node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
938  node2.label = "label";
939  node2.value = "value";
940  node2.hint = "hint";
941  node2.scrollExtentMax = 100.0;
942  node2.scrollPosition = 0.0;
943  parent.childrenInTraversalOrder.push_back(2);
944 
945  {
948  mock->isVoiceOverRunningValue = true;
949  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
950  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
951 
952  parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
953  [parentObject setSemanticsNode:&parent];
954 
955  scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
956  [scrollable setSemanticsNode:&node];
958 
959  object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
960  [object2 setSemanticsNode:&node2];
961 
962  parentObject.children = @[ scrollable, object2 ];
963  [parentObject accessibilityBridgeDidFinishUpdate];
966 
967  // Returns the correct container if the bridge is alive.
968  SemanticsObjectContainer* container =
969  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
970  XCTAssertEqual(container.semanticsObject, parentObject);
971  SemanticsObjectContainer* container2 =
972  static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
973  XCTAssertEqual(container2.semanticsObject, parentObject);
974  }
975  // The bridge pointer went out of scope and was deallocated.
976 
977  XCTAssertNil(scrollable.accessibilityContainer);
978  XCTAssertNil(object2.accessibilityContainer);
979 }
980 
981 - (void)testAccessibilityHitTestSearchCanReturnPlatformView {
982  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
984  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
985  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
986  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
987  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
988  FlutterTouchInterceptingView* platformView =
989  [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
990  FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
991  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
992  uid:1
993  platformView:platformView];
994 
995  object0.children = @[ object1 ];
996  object0.childrenInHitTestOrder = @[ object1 ];
997  object1.children = @[ platformViewSemanticsContainer, object3 ];
998  object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
999 
1000  flutter::SemanticsNode node0;
1001  node0.id = 0;
1002  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1003  node0.label = "0";
1004  [object0 setSemanticsNode:&node0];
1005 
1006  flutter::SemanticsNode node1;
1007  node1.id = 1;
1008  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1009  node1.label = "1";
1010  [object1 setSemanticsNode:&node1];
1011 
1012  flutter::SemanticsNode node2;
1013  node2.id = 2;
1014  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1015  node2.label = "2";
1016  [platformViewSemanticsContainer setSemanticsNode:&node2];
1017 
1018  flutter::SemanticsNode node3;
1019  node3.id = 3;
1020  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1021  node3.label = "3";
1022  [object3 setSemanticsNode:&node3];
1023 
1024  CGPoint point = CGPointMake(10, 10);
1025  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1026 
1027  XCTAssertEqual(hitTestResult, platformView);
1028 }
1029 
1030 - (void)testFlutterPlatformViewSemanticsContainer {
1031  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
1033  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1034  __weak FlutterTouchInterceptingView* weakPlatformView;
1035  __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1036  @autoreleasepool {
1037  FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1038  weakPlatformView = platformView;
1039 
1040  @autoreleasepool {
1042  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1043  uid:1
1044  platformView:platformView];
1045  weakContainer = container;
1046  XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1047  XCTAssertNotNil(weakPlatformView);
1048  XCTAssertNotNil(weakContainer);
1049  }
1050  // Check the variables are still lived.
1051  // `container` is `retain` in `platformView`, so it will not be nil here.
1052  XCTAssertNotNil(weakPlatformView);
1053  XCTAssertNotNil(weakContainer);
1054  }
1055  // Check if there's no more strong references to `platformView` after container and platformView
1056  // are released.
1057  XCTAssertNil(weakPlatformView);
1058  XCTAssertNil(weakContainer);
1059 }
1060 
1061 - (void)testTextInputSemanticsObject {
1062  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1064  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1065 
1066  flutter::SemanticsNode node;
1067  node.label = "foo";
1068  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1069  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1070  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1071  [object setSemanticsNode:&node];
1073  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1074 }
1075 
1076 - (void)testTextInputSemanticsObject_canPerformAction {
1077  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1079  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1080 
1081  flutter::SemanticsNode node;
1082  node.label = "foo";
1083  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1084  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1085  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1086  [object setSemanticsNode:&node];
1088 
1089  id textInputSurrogate = OCMClassMock([UIResponder class]);
1090  id partialSemanticsObject = OCMPartialMock(object);
1091  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1092 
1093  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1094  .andReturn(YES);
1095  XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1096 
1097  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1098  .andReturn(NO);
1099  XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1100 }
1101 
1102 - (void)testTextInputSemanticsObject_editActions {
1103  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1105  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1106 
1107  flutter::SemanticsNode node;
1108  node.label = "foo";
1109  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1110  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1111  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1112  [object setSemanticsNode:&node];
1114 
1115  id textInputSurrogate = OCMClassMock([UIResponder class]);
1116  id partialSemanticsObject = OCMPartialMock(object);
1117  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1118 
1119  XCTestExpectation* copyExpectation =
1120  [self expectationWithDescription:@"Surrogate's copy method is called."];
1121  XCTestExpectation* cutExpectation =
1122  [self expectationWithDescription:@"Surrogate's cut method is called."];
1123  XCTestExpectation* pasteExpectation =
1124  [self expectationWithDescription:@"Surrogate's paste method is called."];
1125  XCTestExpectation* selectAllExpectation =
1126  [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1127  XCTestExpectation* deleteExpectation =
1128  [self expectationWithDescription:@"Surrogate's delete method is called."];
1129 
1130  OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1131  [copyExpectation fulfill];
1132  });
1133  OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1134  [cutExpectation fulfill];
1135  });
1136  OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1137  [pasteExpectation fulfill];
1138  });
1139  OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1140  [selectAllExpectation fulfill];
1141  });
1142  OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1143  [deleteExpectation fulfill];
1144  });
1145 
1146  [partialSemanticsObject copy:nil];
1147  [partialSemanticsObject cut:nil];
1148  [partialSemanticsObject paste:nil];
1149  [partialSemanticsObject selectAll:nil];
1150  [partialSemanticsObject delete:nil];
1151 
1152  [self waitForExpectationsWithTimeout:1 handler:nil];
1153 }
1154 
1155 @end
-[SemanticsObject accessibilityScrollToVisible]
BOOL accessibilityScrollToVisible()
-[SemanticsObject replaceChildAtIndex:withChild:]
void replaceChildAtIndex:withChild:(NSInteger index,[withChild] SemanticsObject *child)
Definition: SemanticsObject.mm:369
-[FlutterTouchInterceptingView accessibilityContainer]
id accessibilityContainer()
-[TextInputSemanticsObject(Test) textInputSurrogate]
UIView< UITextInput > * textInputSurrogate()
SemanticsObject::parent
SemanticsObject * parent
Definition: SemanticsObject.h:41
SemanticsObjectTestMocks.h
TextInputSemanticsObject(Test)
Definition: SemanticsObjectTest.mm:19
flutter::testing::MockAccessibilityBridge
Definition: SemanticsObjectTestMocks.h:24
kFloatCompareEpsilon
const FLUTTER_ASSERT_ARC float kFloatCompareEpsilon
Definition: SemanticsObjectTest.mm:17
SemanticsObjectContainer::semanticsObject
SemanticsObject * semanticsObject
Definition: SemanticsObject.h:234
-[SemanticsObject accessibilityScrollToVisibleWithChild:]
BOOL accessibilityScrollToVisibleWithChild:(id child)
TextInputSemanticsObject.h
-[SemanticsObject setSemanticsNode:]
void setSemanticsNode:(const flutter::SemanticsNode *NS_REQUIRES_SUPER)
Definition: SemanticsObject.mm:328
FlutterMacros.h
FlutterSemanticsObject
Definition: SemanticsObject.h:154
SemanticsObject::childrenInHitTestOrder
NSArray< SemanticsObject * > * childrenInHitTestOrder
Definition: SemanticsObject.h:73
kScrollExtentMaxForInf
constexpr float kScrollExtentMaxForInf
Definition: SemanticsObject.h:17
-[SemanticsObject _accessibilityHitTest:withEvent:]
id _accessibilityHitTest:withEvent:(CGPoint point,[withEvent] UIEvent *event)
FlutterPlatformViews_Internal.h
FlutterSwitchSemanticsObject
Definition: SemanticsObject.h:182
SemanticsObject::children
NSArray< SemanticsObject * > * children
Definition: SemanticsObject.h:67
SemanticsObject::nativeAccessibility
id nativeAccessibility
Definition: SemanticsObject.h:82
SemanticsObject.h
TextInputSemanticsObject
Definition: TextInputSemanticsObject.h:20
FlutterPlatformViewSemanticsContainer
Definition: SemanticsObject.h:168
flutter::testing::MockAccessibilityBridgeNoWindow
Definition: SemanticsObjectTestMocks.h:57
FlutterTouchInterceptingView_Test.h
-[SemanticsObject accessibilityBridgeDidFinishUpdate]
void accessibilityBridgeDidFinishUpdate()
Definition: SemanticsObject.mm:332
SemanticsObjectContainer
Definition: SemanticsObject.h:226
SemanticsObjectTest
Definition: SemanticsObjectTest.mm:23
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterTouchInterceptingView
Definition: FlutterPlatformViews_Internal.h:138
flutter::testing::MockAccessibilityBridge::isVoiceOverRunningValue
bool isVoiceOverRunningValue
Definition: SemanticsObjectTestMocks.h:50
FlutterScrollableSemanticsObject
Definition: SemanticsObject.h:188
SemanticsObject
Definition: SemanticsObject.h:30