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 
15 
17 
18 const float kFloatCompareEpsilon = 0.001;
19 
20 @interface SemanticsObject (UIFocusSystem) <UIFocusItem, UIFocusItemContainer>
21 @end
22 
24  UIFocusItemScrollableContainer>
25 @end
26 
28 - (UIView<UITextInput>*)textInputSurrogate;
29 @end
30 
31 @interface SemanticsObjectTest : XCTestCase
32 @end
33 
34 @implementation SemanticsObjectTest
35 
36 - (void)testCreate {
37  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
39  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
40  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
41  XCTAssertNotNil(object);
42 }
43 
44 - (void)testSetChildren {
45  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
47  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
48  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
49  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
50  parent.children = @[ child ];
51  XCTAssertEqual(parent, child.parent);
52  parent.children = @[];
53  XCTAssertNil(child.parent);
54 }
55 
56 - (void)testAccessibilityHitTestFocusAtLeaf {
57  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
59  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
60  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
61  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
62  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
63  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
64  object0.children = @[ object1 ];
65  object0.childrenInHitTestOrder = @[ object1 ];
66  object1.children = @[ object2, object3 ];
67  object1.childrenInHitTestOrder = @[ object2, object3 ];
68 
69  flutter::SemanticsNode node0;
70  node0.id = 0;
71  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
72  node0.label = "0";
73  [object0 setSemanticsNode:&node0];
74 
75  flutter::SemanticsNode node1;
76  node1.id = 1;
77  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
78  node1.label = "1";
79  [object1 setSemanticsNode:&node1];
80 
81  flutter::SemanticsNode node2;
82  node2.id = 2;
83  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
84  node2.label = "2";
85  [object2 setSemanticsNode:&node2];
86 
87  flutter::SemanticsNode node3;
88  node3.id = 3;
89  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
90  node3.label = "3";
91  [object3 setSemanticsNode:&node3];
92 
93  CGPoint point = CGPointMake(10, 10);
94  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
95 
96  // Focus to object2 because it's the first object in hit test order
97  XCTAssertEqual(hitTestResult, object2);
98 }
99 
100 - (void)testAccessibilityHitTestNoFocusableItem {
101  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
103  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
104  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
105  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
106  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
107  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
108  object0.children = @[ object1 ];
109  object0.childrenInHitTestOrder = @[ object1 ];
110  object1.children = @[ object2, object3 ];
111  object1.childrenInHitTestOrder = @[ object2, object3 ];
112 
113  flutter::SemanticsNode node0;
114  node0.id = 0;
115  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
116  [object0 setSemanticsNode:&node0];
117 
118  flutter::SemanticsNode node1;
119  node1.id = 1;
120  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
121  [object1 setSemanticsNode:&node1];
122 
123  flutter::SemanticsNode node2;
124  node2.id = 2;
125  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
126  [object2 setSemanticsNode:&node2];
127 
128  flutter::SemanticsNode node3;
129  node3.id = 3;
130  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
131  [object3 setSemanticsNode:&node3];
132 
133  CGPoint point = CGPointMake(10, 10);
134  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
135 
136  XCTAssertNil(hitTestResult);
137 }
138 
139 - (void)testAccessibilityScrollToVisible {
140  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
142  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
143  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
144 
145  flutter::SemanticsNode node3;
146  node3.id = 3;
147  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
148  [object3 setSemanticsNode:&node3];
149 
151 
152  XCTAssertTrue(bridge->observations.size() == 1);
153  XCTAssertTrue(bridge->observations[0].id == 3);
154  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
155 }
156 
157 - (void)testAccessibilityScrollToVisibleWithChild {
158  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
160  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
161  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
162 
163  flutter::SemanticsNode node3;
164  node3.id = 3;
165  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
166  [object3 setSemanticsNode:&node3];
167 
168  [object3 accessibilityScrollToVisibleWithChild:object3];
169 
170  XCTAssertTrue(bridge->observations.size() == 1);
171  XCTAssertTrue(bridge->observations[0].id == 3);
172  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
173 }
174 
175 - (void)testAccessibilityHitTestOutOfRect {
176  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
178  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
179  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
180  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
181  SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
182  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
183  object0.children = @[ object1 ];
184  object0.childrenInHitTestOrder = @[ object1 ];
185  object1.children = @[ object2, object3 ];
186  object1.childrenInHitTestOrder = @[ object2, object3 ];
187 
188  flutter::SemanticsNode node0;
189  node0.id = 0;
190  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
191  node0.label = "0";
192  [object0 setSemanticsNode:&node0];
193 
194  flutter::SemanticsNode node1;
195  node1.id = 1;
196  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
197  node1.label = "1";
198  [object1 setSemanticsNode:&node1];
199 
200  flutter::SemanticsNode node2;
201  node2.id = 2;
202  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
203  node2.label = "2";
204  [object2 setSemanticsNode:&node2];
205 
206  flutter::SemanticsNode node3;
207  node3.id = 3;
208  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
209  node3.label = "3";
210  [object3 setSemanticsNode:&node3];
211 
212  CGPoint point = CGPointMake(300, 300);
213  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
214 
215  XCTAssertNil(hitTestResult);
216 }
217 
218 - (void)testReplaceChildAtIndex {
219  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
221  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
222  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
223  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
224  SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
225  parent.children = @[ child1 ];
226  [parent replaceChildAtIndex:0 withChild:child2];
227  XCTAssertNil(child1.parent);
228  XCTAssertEqual(parent, child2.parent);
229  XCTAssertEqualObjects(parent.children, @[ child2 ]);
230 }
231 
232 - (void)testPlainSemanticsObjectWithLabelHasStaticTextTrait {
233  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
235  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
236  flutter::SemanticsNode node;
237  node.label = "foo";
238  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
239  [object setSemanticsNode:&node];
240  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitStaticText);
241 }
242 
243 - (void)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden {
244  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
246  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
247  flutter::SemanticsNode node;
248 
249  node.flags.hasImplicitScrolling = true;
250  node.flags.isHidden = true;
251  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
252  [object setSemanticsNode:&node];
253  XCTAssertEqual(object.isAccessibilityElement, YES);
254 }
255 
256 - (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
257  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
259  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
260  flutter::SemanticsNode node;
261  node.flags.hasImplicitScrolling = true;
262  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
263  [object setSemanticsNode:&node];
264  XCTAssertEqual(object.isAccessibilityElement, NO);
265 }
266 
267 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
268  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
270  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
271  flutter::SemanticsNode node;
272  node.label = "foo";
273  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
274  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
275  object.children = @[ child1 ];
276  [object setSemanticsNode:&node];
277  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
278 }
279 
280 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
281  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
283  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
284  flutter::SemanticsNode node;
285  node.label = "foo";
286  node.flags.isTextField = true;
287  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
288  [object setSemanticsNode:&node];
289  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
290 }
291 
292 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
293  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
295  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
296  flutter::SemanticsNode node;
297  node.label = "foo";
298 
299  node.flags.isButton = true;
300  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
301  [object setSemanticsNode:&node];
302  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
303 }
304 
305 - (void)testVerticalFlutterScrollableSemanticsObject {
306  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
308  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
309 
310  float transformScale = 0.5f;
311  float screenScale = [[bridge->view() window] screen].scale;
312  float effectivelyScale = transformScale / screenScale;
313  float x = 10;
314  float y = 10;
315  float w = 100;
316  float h = 200;
317  float scrollExtentMax = 500.0;
318  float scrollPosition = 150.0;
319 
320  flutter::SemanticsNode node;
321  node.flags.hasImplicitScrolling = true;
322  node.actions = flutter::kVerticalScrollSemanticsActions;
323  node.rect = SkRect::MakeXYWH(x, y, w, h);
324  node.scrollExtentMax = scrollExtentMax;
325  node.scrollPosition = scrollPosition;
326  node.transform = {
327  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
329  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
330  [scrollable setSemanticsNode:&node];
332  UIScrollView* scrollView = [scrollable nativeAccessibility];
333 
334  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
335  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
336  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
338  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
340 
341  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
343  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
344  (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
345 
346  XCTAssertEqual(scrollView.contentOffset.x, 0);
347  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
349 }
350 
351 - (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
352  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
354  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
355 
356  float transformScale = 0.5f;
357  float x = 10;
358  float y = 10;
359  float w = 100;
360  float h = 200;
361  float scrollExtentMax = 500.0;
362  float scrollPosition = 150.0;
363 
364  flutter::SemanticsNode node;
365  node.flags.hasImplicitScrolling = true;
366  node.actions = flutter::kVerticalScrollSemanticsActions;
367  node.rect = SkRect::MakeXYWH(x, y, w, h);
368  node.scrollExtentMax = scrollExtentMax;
369  node.scrollPosition = scrollPosition;
370  node.transform = {
371  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
373  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
374  [scrollable setSemanticsNode:&node];
376  XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
377 }
378 
379 - (void)testHorizontalFlutterScrollableSemanticsObject {
380  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
382  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
383 
384  float transformScale = 0.5f;
385  float screenScale = [[bridge->view() window] screen].scale;
386  float effectivelyScale = transformScale / screenScale;
387  float x = 10;
388  float y = 10;
389  float w = 100;
390  float h = 200;
391  float scrollExtentMax = 500.0;
392  float scrollPosition = 150.0;
393 
394  flutter::SemanticsNode node;
395  node.flags.hasImplicitScrolling = true;
396  node.actions = flutter::kHorizontalScrollSemanticsActions;
397  node.rect = SkRect::MakeXYWH(x, y, w, h);
398  node.scrollExtentMax = scrollExtentMax;
399  node.scrollPosition = scrollPosition;
400  node.transform = {
401  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
403  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
404  [scrollable setSemanticsNode:&node];
406  UIScrollView* scrollView = [scrollable nativeAccessibility];
407 
408  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
409  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
410  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
412  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
414 
415  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
417  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
419 
420  XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
422  XCTAssertEqual(scrollView.contentOffset.y, 0);
423 }
424 
425 - (void)testCanHandleInfiniteScrollExtent {
426  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
428  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
429 
430  float transformScale = 0.5f;
431  float screenScale = [[bridge->view() window] screen].scale;
432  float effectivelyScale = transformScale / screenScale;
433  float x = 10;
434  float y = 10;
435  float w = 100;
436  float h = 200;
437  float scrollExtentMax = INFINITY;
438  float scrollPosition = 150.0;
439 
440  flutter::SemanticsNode node;
441  node.flags.hasImplicitScrolling = true;
442  node.actions = flutter::kVerticalScrollSemanticsActions;
443  node.rect = SkRect::MakeXYWH(x, y, w, h);
444  node.scrollExtentMax = scrollExtentMax;
445  node.scrollPosition = scrollPosition;
446  node.transform = {
447  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
449  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
450  [scrollable setSemanticsNode:&node];
452  UIScrollView* scrollView = [scrollable nativeAccessibility];
453  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
454  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
455  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
457  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
459 
460  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
462  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
463  (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
465 
466  XCTAssertEqual(scrollView.contentOffset.x, 0);
467  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
469 }
470 
471 - (void)testCanHandleNaNScrollExtentAndScrollPoisition {
472  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
474  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
475 
476  float transformScale = 0.5f;
477  float screenScale = [[bridge->view() window] screen].scale;
478  float effectivelyScale = transformScale / screenScale;
479  float x = 10;
480  float y = 10;
481  float w = 100;
482  float h = 200;
483  float scrollExtentMax = std::nan("");
484  float scrollPosition = std::nan("");
485 
486  flutter::SemanticsNode node;
487  node.flags.hasImplicitScrolling = true;
488  node.actions = flutter::kVerticalScrollSemanticsActions;
489  node.rect = SkRect::MakeXYWH(x, y, w, h);
490  node.scrollExtentMax = scrollExtentMax;
491  node.scrollPosition = scrollPosition;
492  node.transform = {
493  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
495  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
496  [scrollable setSemanticsNode:&node];
498  UIScrollView* scrollView = [scrollable nativeAccessibility];
499 
500  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
501  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
502  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
504  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
506 
507  // Content size equal to the scrollable size.
508  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
510  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
512 
513  XCTAssertEqual(scrollView.contentOffset.x, 0);
514  XCTAssertEqual(scrollView.contentOffset.y, 0);
515 }
516 
517 - (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
518  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
520  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
521 
522  flutter::SemanticsNode node;
523  node.flags.hasImplicitScrolling = true;
524  node.actions = flutter::kHorizontalScrollSemanticsActions;
525  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
526  node.scrollExtentMax = 100.0;
527  node.scrollPosition = 0.0;
528 
530  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
531  [scrollable setSemanticsNode:&node];
533  UIScrollView* scrollView = [scrollable nativeAccessibility];
534  XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
535 }
536 
537 - (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
539  mock->isVoiceOverRunningValue = false;
540  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
541  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
542 
543  flutter::SemanticsNode node;
544  node.flags.hasImplicitScrolling = true;
545  node.actions = flutter::kHorizontalScrollSemanticsActions;
546  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
547  node.scrollExtentMax = 100.0;
548  node.scrollPosition = 0.0;
549 
551  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
552  [scrollable setSemanticsNode:&node];
554  UIScrollView* scrollView = [scrollable nativeAccessibility];
555  XCTAssertTrue(scrollView.isAccessibilityElement);
556  mock->isVoiceOverRunningValue = true;
557  XCTAssertFalse(scrollView.isAccessibilityElement);
558 }
559 
560 - (void)testFlutterSemanticsObjectHasIdentifier {
562  mock->isVoiceOverRunningValue = true;
563  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
564  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
565 
566  flutter::SemanticsNode node;
567  node.identifier = "identifier";
568 
569  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
570  [object setSemanticsNode:&node];
571  XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
572 }
573 
574 - (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
576  mock->isVoiceOverRunningValue = true;
577  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
578  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
579 
580  flutter::SemanticsNode node;
581  node.flags.hasImplicitScrolling = true;
582  node.actions = flutter::kHorizontalScrollSemanticsActions;
583  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
584  node.label = "label";
585  node.value = "value";
586  node.hint = "hint";
587  node.scrollExtentMax = 100.0;
588  node.scrollPosition = 0.0;
589 
591  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
592  [scrollable setSemanticsNode:&node];
594  UIScrollView* scrollView = [scrollable nativeAccessibility];
595  XCTAssertTrue(scrollView.isAccessibilityElement);
596  XCTAssertTrue(
597  [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
598  XCTAssertTrue(
599  [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
600  XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
601 }
602 
603 - (void)testFlutterSemanticsObjectMergeTooltipToLabel {
605  mock->isVoiceOverRunningValue = true;
606  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
607  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
608 
609  flutter::SemanticsNode node;
610  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
611  node.label = "label";
612  node.tooltip = "tooltip";
613  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
614  [object setSemanticsNode:&node];
615  XCTAssertTrue(object.isAccessibilityElement);
616  XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
617 }
618 
619 - (void)testSemanticsObjectContainerAccessibilityFrameCoversEntireScreen {
621  mock->isVoiceOverRunningValue = true;
622  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
623  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
624 
625  flutter::SemanticsNode parent;
626  parent.id = 0;
627  parent.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
628 
629  flutter::SemanticsNode child;
630  child.id = 1;
631  child.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
632  child.rect = SkRect::MakeXYWH(0, 0, 100, 100);
633  parent.childrenInTraversalOrder.push_back(1);
634 
635  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
636  uid:0];
637  [parentObject setSemanticsNode:&parent];
638 
639  FlutterSemanticsObject* childObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
640  uid:1];
641  [childObject setSemanticsNode:&child];
642 
643  parentObject.children = @[ childObject ];
644  [parentObject accessibilityBridgeDidFinishUpdate];
646 
647  SemanticsObjectContainer* container =
648  static_cast<SemanticsObjectContainer*>(parentObject.accessibilityContainer);
649 
650  XCTAssertTrue(childObject.accessibilityRespondsToUserInteraction);
651  XCTAssertTrue(CGRectEqualToRect(container.accessibilityFrame, UIScreen.mainScreen.bounds));
652 }
653 
654 - (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
656  mock->isVoiceOverRunningValue = true;
657  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
658  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
659 
660  flutter::SemanticsNode node;
661  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
662  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
663  [object setSemanticsNode:&node];
664  XCTAssertTrue(object.accessibilityAttributedLabel == nil);
665 }
666 
667 - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
669  mock->isVoiceOverRunningValue = true;
670  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
671  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
672 
673  flutter::SemanticsNode parent;
674  parent.id = 0;
675  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
676  parent.label = "label";
677  parent.value = "value";
678  parent.hint = "hint";
679 
680  flutter::SemanticsNode node;
681  node.id = 1;
682  node.flags.hasImplicitScrolling = true;
683  node.actions = flutter::kHorizontalScrollSemanticsActions;
684  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
685  node.label = "label";
686  node.value = "value";
687  node.hint = "hint";
688  node.scrollExtentMax = 100.0;
689  node.scrollPosition = 0.0;
690  parent.childrenInTraversalOrder.push_back(1);
691 
692  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
693  uid:0];
694  [parentObject setSemanticsNode:&parent];
695 
697  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
698  [scrollable setSemanticsNode:&node];
699  UIScrollView* scrollView = [scrollable nativeAccessibility];
700 
701  parentObject.children = @[ scrollable ];
702  [parentObject accessibilityBridgeDidFinishUpdate];
704  XCTAssertTrue(scrollView.isAccessibilityElement);
705  SemanticsObjectContainer* container =
706  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
707  XCTAssertEqual(container.semanticsObject, parentObject);
708 }
709 
710 - (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
711  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
713  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
714 
715  flutter::SemanticsNode node;
716  node.flags.hasImplicitScrolling = true;
717  node.actions = flutter::kHorizontalScrollSemanticsActions;
718  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
719  node.scrollExtentMax = 100.0;
720  node.scrollPosition = 0.0;
721 
723  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
724  [scrollable setSemanticsNode:&node];
726  UIScrollView* scrollView = [scrollable nativeAccessibility];
727 
728  XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
729  XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
730  XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior,
731  UIScrollViewContentInsetAdjustmentNever);
732  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero));
733 }
734 
735 - (void)testSemanticsObjectBuildsAttributedString {
736  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
738  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
739  flutter::SemanticsNode node;
740  node.label = "label";
741  std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
742  std::make_shared<flutter::SpellOutStringAttribute>();
743  attribute->start = 1;
744  attribute->end = 2;
745  attribute->type = flutter::StringAttributeType::kSpellOut;
746  node.labelAttributes.push_back(attribute);
747  node.value = "value";
748  attribute = std::make_shared<flutter::SpellOutStringAttribute>();
749  attribute->start = 2;
750  attribute->end = 3;
751  attribute->type = flutter::StringAttributeType::kSpellOut;
752  node.valueAttributes.push_back(attribute);
753  node.hint = "hint";
754  std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
755  std::make_shared<flutter::LocaleStringAttribute>();
756  local_attribute->start = 3;
757  local_attribute->end = 4;
758  local_attribute->type = flutter::StringAttributeType::kLocale;
759  local_attribute->locale = "en-MX";
760  node.hintAttributes.push_back(local_attribute);
761  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
762  [object setSemanticsNode:&node];
763  NSMutableAttributedString* expectedAttributedLabel =
764  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
765  NSDictionary* attributeDict = @{
766  UIAccessibilitySpeechAttributeSpellOut : @YES,
767  };
768  [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
769  XCTAssertTrue(
770  [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
771 
772  NSMutableAttributedString* expectedAttributedValue =
773  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
774  attributeDict = @{
775  UIAccessibilitySpeechAttributeSpellOut : @YES,
776  };
777  [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
778  XCTAssertTrue(
779  [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
780 
781  NSMutableAttributedString* expectedAttributedHint =
782  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
783  attributeDict = @{
784  UIAccessibilitySpeechAttributeLanguage : @"en-MX",
785  };
786  [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
787  XCTAssertTrue(
788  [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
789 }
790 
791 - (void)testShouldTriggerAnnouncement {
792  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
794  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
795  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
796 
797  // Handle nil with no node set.
798  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
799 
800  // Handle initial setting of node with liveRegion
801  flutter::SemanticsNode node;
802  node.flags.isLiveRegion = true;
803  node.label = "foo";
804  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
805 
806  // Handle nil with node set.
807  [object setSemanticsNode:&node];
808  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
809 
810  // Handle new node, still has live region, same label.
811  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
812 
813  // Handle update node with new label, still has live region.
814  flutter::SemanticsNode updatedNode;
815  updatedNode.flags.isLiveRegion = true;
816  updatedNode.label = "bar";
817  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
818 
819  // Handle dropping the live region flag.
820  updatedNode.flags = flutter::SemanticsFlags{};
821  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
822 
823  // Handle adding the flag when the label has not changed.
824  updatedNode.label = "foo";
825  [object setSemanticsNode:&updatedNode];
826  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
827 }
828 
829 - (void)testShouldDispatchShowOnScreenActionForHeader {
830  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
832  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
833  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
834 
835  // Handle initial setting of node with header.
836  flutter::SemanticsNode node;
837  node.flags.isHeader = true;
838  node.label = "foo";
839 
840  [object setSemanticsNode:&node];
841 
842  // Simulate accessibility focus.
843  [object accessibilityElementDidBecomeFocused];
844 
845  XCTAssertTrue(bridge->observations.size() == 1);
846  XCTAssertTrue(bridge->observations[0].id == 1);
847  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
848 }
849 
850 - (void)testShouldDispatchShowOnScreenActionForHidden {
851  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
853  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
854  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
855 
856  // Handle initial setting of node with hidden.
857  flutter::SemanticsNode node;
858  node.flags.isHidden = true;
859  node.label = "foo";
860 
861  [object setSemanticsNode:&node];
862 
863  // Simulate accessibility focus.
864  [object accessibilityElementDidBecomeFocused];
865 
866  XCTAssertTrue(bridge->observations.size() == 1);
867  XCTAssertTrue(bridge->observations[0].id == 1);
868  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
869 }
870 
871 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
872  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
874  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
875  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
876  uid:1];
877 
878  // Handle initial setting of node with header.
879  flutter::SemanticsNode node;
880  node.flags.isToggled = flutter::SemanticsTristate::kTrue;
881  node.flags.isEnabled = flutter::SemanticsTristate::kTrue;
882  node.label = "foo";
883  [object setSemanticsNode:&node];
884  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
885  UISwitch* nativeSwitch = [[UISwitch alloc] init];
886  nativeSwitch.on = YES;
887 
888  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
889  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
890 
891  // Set the toggled to false;
892  flutter::SemanticsNode update;
893  update.flags.isToggled = flutter::SemanticsTristate::kFalse;
894  update.flags.isEnabled = flutter::SemanticsTristate::kTrue;
895 
896  update.label = "foo";
897  [object setSemanticsNode:&update];
898 
899  nativeSwitch.on = NO;
900 
901  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
902  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
903 }
904 
905 - (void)testFlutterSemanticsObjectOfRadioButton {
906  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
908  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
909  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
910 
911  // Handle initial setting of node with header.
912  flutter::SemanticsNode node;
913  node.flags.isInMutuallyExclusiveGroup = true;
914  node.flags.isChecked = flutter::SemanticsCheckState::kFalse;
915  node.flags.isEnabled = flutter::SemanticsTristate::kTrue;
916  node.label = "foo";
917  [object setSemanticsNode:&node];
918  XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
919  XCTAssertNil(object.accessibilityValue);
920 }
921 
922 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
923  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
925  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
926  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
927  uid:1];
928 
929  // Handle initial setting of node with header.
930  flutter::SemanticsNode node;
931  node.flags.isToggled = flutter::SemanticsTristate::kTrue;
932  node.label = "foo";
933  [object setSemanticsNode:&node];
934  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
935  UISwitch* nativeSwitch = [[UISwitch alloc] init];
936  nativeSwitch.on = YES;
937  nativeSwitch.enabled = NO;
938 
939  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
940  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
941 }
942 
943 - (void)testSemanticsObjectDeallocated {
944  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
946  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
947  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
948  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
949  parent.children = @[ child ];
950  // Validate SemanticsObject deallocation does not crash.
951  // https://github.com/flutter/flutter/issues/66032
952  __weak SemanticsObject* weakObject = parent;
953  parent = nil;
954  XCTAssertNil(weakObject);
955 }
956 
957 - (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
958  FlutterSemanticsObject* parentObject;
960  FlutterSemanticsObject* object2;
961 
962  flutter::SemanticsNode parent;
963  parent.id = 0;
964  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
965  parent.label = "label";
966  parent.value = "value";
967  parent.hint = "hint";
968 
969  flutter::SemanticsNode node;
970  node.id = 1;
971  node.flags.hasImplicitScrolling = true;
972  node.actions = flutter::kHorizontalScrollSemanticsActions;
973  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
974  node.label = "label";
975  node.value = "value";
976  node.hint = "hint";
977  node.scrollExtentMax = 100.0;
978  node.scrollPosition = 0.0;
979  parent.childrenInTraversalOrder.push_back(1);
980 
981  flutter::SemanticsNode node2;
982  node2.id = 2;
983  node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
984  node2.label = "label";
985  node2.value = "value";
986  node2.hint = "hint";
987  node2.scrollExtentMax = 100.0;
988  node2.scrollPosition = 0.0;
989  parent.childrenInTraversalOrder.push_back(2);
990 
991  {
994  mock->isVoiceOverRunningValue = true;
995  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
996  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
997 
998  parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
999  [parentObject setSemanticsNode:&parent];
1000 
1001  scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
1002  [scrollable setSemanticsNode:&node];
1004 
1005  object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
1006  [object2 setSemanticsNode:&node2];
1007 
1008  parentObject.children = @[ scrollable, object2 ];
1009  [parentObject accessibilityBridgeDidFinishUpdate];
1012 
1013  // Returns the correct container if the bridge is alive.
1014  SemanticsObjectContainer* container =
1015  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
1016  XCTAssertEqual(container.semanticsObject, parentObject);
1017  SemanticsObjectContainer* container2 =
1018  static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
1019  XCTAssertEqual(container2.semanticsObject, parentObject);
1020  }
1021  // The bridge pointer went out of scope and was deallocated.
1022 
1023  XCTAssertNil(scrollable.accessibilityContainer);
1024  XCTAssertNil(object2.accessibilityContainer);
1025 }
1026 
1027 - (void)testAccessibilityHitTestSearchCanReturnPlatformView {
1028  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1030  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1031  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1032  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1033  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
1034  FlutterTouchInterceptingView* platformView =
1035  [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
1036  FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
1037  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1038  uid:1
1039  platformView:platformView];
1040 
1041  object0.children = @[ object1 ];
1042  object0.childrenInHitTestOrder = @[ object1 ];
1043  object1.children = @[ platformViewSemanticsContainer, object3 ];
1044  object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
1045 
1046  flutter::SemanticsNode node0;
1047  node0.id = 0;
1048  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1049  node0.label = "0";
1050  [object0 setSemanticsNode:&node0];
1051 
1052  flutter::SemanticsNode node1;
1053  node1.id = 1;
1054  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1055  node1.label = "1";
1056  [object1 setSemanticsNode:&node1];
1057 
1058  flutter::SemanticsNode node2;
1059  node2.id = 2;
1060  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1061  node2.label = "2";
1062  [platformViewSemanticsContainer setSemanticsNode:&node2];
1063 
1064  flutter::SemanticsNode node3;
1065  node3.id = 3;
1066  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1067  node3.label = "3";
1068  [object3 setSemanticsNode:&node3];
1069 
1070  CGPoint point = CGPointMake(10, 10);
1071  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1072 
1073  XCTAssertEqual(hitTestResult, platformView);
1074 }
1075 
1076 - (void)testFlutterPlatformViewSemanticsContainer {
1077  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
1079  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1080  __weak FlutterTouchInterceptingView* weakPlatformView;
1081  __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1082  @autoreleasepool {
1083  FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1084  weakPlatformView = platformView;
1085 
1086  @autoreleasepool {
1088  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1089  uid:1
1090  platformView:platformView];
1091  weakContainer = container;
1092  XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1093  XCTAssertNotNil(weakPlatformView);
1094  XCTAssertNotNil(weakContainer);
1095  }
1096  // Check the variables are still lived.
1097  // `container` is `retain` in `platformView`, so it will not be nil here.
1098  XCTAssertNotNil(weakPlatformView);
1099  XCTAssertNotNil(weakContainer);
1100  }
1101  // Check if there's no more strong references to `platformView` after container and platformView
1102  // are released.
1103  XCTAssertNil(weakPlatformView);
1104  XCTAssertNil(weakContainer);
1105 }
1106 
1107 - (void)testTextInputSemanticsObject {
1108  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1110  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1111 
1112  flutter::SemanticsNode node;
1113  node.label = "foo";
1114  node.flags.isTextField = true;
1115  node.flags.isReadOnly = true;
1116  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1117  [object setSemanticsNode:&node];
1119  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1120 }
1121 
1122 - (void)testTextInputSemanticsObject_canPerformAction {
1123  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1125  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1126 
1127  flutter::SemanticsNode node;
1128  node.label = "foo";
1129  node.flags.isTextField = true;
1130  node.flags.isReadOnly = true;
1131  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1132  [object setSemanticsNode:&node];
1134 
1135  id textInputSurrogate = OCMClassMock([UIResponder class]);
1136  id partialSemanticsObject = OCMPartialMock(object);
1137  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1138 
1139  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1140  .andReturn(YES);
1141  XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1142 
1143  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1144  .andReturn(NO);
1145  XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1146 }
1147 
1148 - (void)testTextInputSemanticsObject_editActions {
1149  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1151  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1152 
1153  flutter::SemanticsNode node;
1154  node.label = "foo";
1155 
1156  node.flags.isTextField = true;
1157  node.flags.isReadOnly = true;
1158  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1159  [object setSemanticsNode:&node];
1161 
1162  id textInputSurrogate = OCMClassMock([UIResponder class]);
1163  id partialSemanticsObject = OCMPartialMock(object);
1164  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1165 
1166  XCTestExpectation* copyExpectation =
1167  [self expectationWithDescription:@"Surrogate's copy method is called."];
1168  XCTestExpectation* cutExpectation =
1169  [self expectationWithDescription:@"Surrogate's cut method is called."];
1170  XCTestExpectation* pasteExpectation =
1171  [self expectationWithDescription:@"Surrogate's paste method is called."];
1172  XCTestExpectation* selectAllExpectation =
1173  [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1174  XCTestExpectation* deleteExpectation =
1175  [self expectationWithDescription:@"Surrogate's delete method is called."];
1176 
1177  OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1178  [copyExpectation fulfill];
1179  });
1180  OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1181  [cutExpectation fulfill];
1182  });
1183  OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1184  [pasteExpectation fulfill];
1185  });
1186  OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1187  [selectAllExpectation fulfill];
1188  });
1189  OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1190  [deleteExpectation fulfill];
1191  });
1192 
1193  [partialSemanticsObject copy:nil];
1194  [partialSemanticsObject cut:nil];
1195  [partialSemanticsObject paste:nil];
1196  [partialSemanticsObject selectAll:nil];
1197  [partialSemanticsObject delete:nil];
1198 
1199  [self waitForExpectationsWithTimeout:1 handler:nil];
1200 }
1201 
1202 - (void)testSliderSemanticsObject {
1203  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1205  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1206 
1207  flutter::SemanticsNode node;
1208  node.flags.isSlider = true;
1209  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1210  [object setSemanticsNode:&node];
1212  XCTAssertEqual([object accessibilityActivate], YES);
1213 }
1214 
1215 - (void)testUIFocusItemConformance {
1216  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1218  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1219  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1220  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1221  parent.children = @[ child ];
1222 
1223  // parentFocusEnvironment
1224  XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]);
1225  XCTAssertEqual(child.parentFocusEnvironment, child.parent);
1226 
1227  // canBecomeFocused
1228  flutter::SemanticsNode childNode;
1229  childNode.flags.isHidden = true;
1230  childNode.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
1231  [child setSemanticsNode:&childNode];
1232  XCTAssertFalse(child.canBecomeFocused);
1233  childNode.flags = flutter::SemanticsFlags{};
1234  [child setSemanticsNode:&childNode];
1235  XCTAssertTrue(child.canBecomeFocused);
1236  childNode.actions = 0;
1237  [child setSemanticsNode:&childNode];
1238  XCTAssertFalse(child.canBecomeFocused);
1239 
1240  CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale;
1241 
1242  childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale);
1243  [child setSemanticsNode:&childNode];
1244  flutter::SemanticsNode parentNode;
1245  parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1246  [parent setSemanticsNode:&parentNode];
1247 
1248  XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100)));
1249 }
1250 
1251 - (void)testUIFocusItemContainerConformance {
1252  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1254  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1255  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1256  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1257  SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
1258  parent.childrenInHitTestOrder = @[ child1, child2 ];
1259 
1260  // focusItemsInRect
1261  NSArray<id<UIFocusItem>>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)];
1262  XCTAssertEqual(itemsInRect.count, (unsigned long)2);
1263  XCTAssertTrue([itemsInRect containsObject:child1]);
1264  XCTAssertTrue([itemsInRect containsObject:child2]);
1265 }
1266 
1267 - (void)testUIFocusItemScrollableContainerConformance {
1268  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1270  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1271  FlutterScrollableSemanticsObject* scrollable =
1272  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1273 
1274  // setContentOffset
1275  CGPoint p = CGPointMake(123.0, 456.0);
1276  [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView
1277  withVelocity:CGPointZero
1278  targetContentOffset:&p];
1279  scrollable.scrollView.contentOffset = p;
1280  [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView];
1281  XCTAssertEqual(bridge->observations.size(), (size_t)1);
1282  XCTAssertEqual(bridge->observations[0].id, 5);
1283  XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset);
1284 
1285  std::vector<uint8_t> args = bridge->observations[0].args;
1286  XCTAssertEqual(args.size(), 3 * sizeof(CGFloat));
1287 
1288  NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()];
1290  CGPoint point = CGPointZero;
1291  memcpy(&point, decoded.data.bytes, decoded.data.length);
1292  XCTAssertTrue(CGPointEqualToPoint(point, p));
1293 }
1294 
1295 - (void)testUIFocusItemScrollableContainerNoFeedbackLoops {
1296  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1298  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1299  FlutterScrollableSemanticsObject* scrollable =
1300  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1301 
1302  // setContentOffset
1303  const CGPoint p = CGPointMake(0.0, 456.0);
1304  scrollable.scrollView.contentOffset = p;
1305  bridge->observations.clear();
1306 
1307  const SkScalar scrollPosition = p.y + 0.0000000000000001;
1308  flutter::SemanticsNode node;
1309  node.flags.hasImplicitScrolling = true;
1310  node.actions = flutter::kVerticalScrollSemanticsActions;
1311  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1312  node.scrollExtentMax = 10000;
1313  node.scrollPosition = scrollPosition;
1314  node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0};
1315  [scrollable setSemanticsNode:&node];
1317 
1318  XCTAssertEqual(bridge->observations.size(), (size_t)0);
1319 }
1320 @end
constexpr float kScrollExtentMaxForInf
FLUTTER_ASSERT_ARC const float kFloatCompareEpsilon
UIView< UITextInput > * textInputSurrogate()
FlutterSemanticsScrollView * scrollView
SemanticsObject * semanticsObject
id _accessibilityHitTest:withEvent:(CGPoint point,[withEvent] UIEvent *event)
SemanticsObject * parent
NSArray< SemanticsObject * > * childrenInHitTestOrder
BOOL accessibilityScrollToVisibleWithChild:(id child)
void accessibilityBridgeDidFinishUpdate()
BOOL accessibilityScrollToVisible()
NSArray< SemanticsObject * > * children
void replaceChildAtIndex:withChild:(NSInteger index,[withChild] SemanticsObject *child)
void setSemanticsNode:(const flutter::SemanticsNode *NS_REQUIRES_SUPER)
instancetype sharedInstance()