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)testFlutterSemanticsObjectHasLocale {
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.locale = "es-MX";
582 
583  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
584  [object setSemanticsNode:&node];
585  XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
586 }
587 
588 - (void)testFlutterSemanticsObjectUseDefaultLocale {
590  mock->isVoiceOverRunningValue = true;
591  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
592  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
593 
594  flutter::SemanticsNode node;
595  mock->mockedLocale = @"es-MX";
596 
597  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
598  [object setSemanticsNode:&node];
599  XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
600 }
601 
602 - (void)testFlutterSemanticsObjectPrioritizedSectionLocale {
604  mock->isVoiceOverRunningValue = true;
605  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
606  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
607 
608  flutter::SemanticsNode node;
609  // Set both locales.
610  mock->mockedLocale = @"es-MX";
611  node.locale = "zh-TW";
612 
613  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
614  [object setSemanticsNode:&node];
615  // node.locale takes priority.
616  XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"zh-TW"]);
617 }
618 
619 - (void)testFlutterSemanticsObjectLocaleNil {
621  mock->isVoiceOverRunningValue = true;
622  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
623  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
624 
625  flutter::SemanticsNode node;
626 
627  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
628  [object setSemanticsNode:&node];
629  XCTAssertTrue(object.accessibilityLanguage == nil);
630 }
631 
632 - (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
634  mock->isVoiceOverRunningValue = true;
635  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
636  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
637 
638  flutter::SemanticsNode node;
639  node.flags.hasImplicitScrolling = true;
640  node.actions = flutter::kHorizontalScrollSemanticsActions;
641  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
642  node.label = "label";
643  node.value = "value";
644  node.hint = "hint";
645  node.scrollExtentMax = 100.0;
646  node.scrollPosition = 0.0;
647 
649  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
650  [scrollable setSemanticsNode:&node];
652  UIScrollView* scrollView = [scrollable nativeAccessibility];
653  XCTAssertTrue(scrollView.isAccessibilityElement);
654  XCTAssertTrue(
655  [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
656  XCTAssertTrue(
657  [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
658  XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
659 }
660 
661 - (void)testFlutterSemanticsObjectMergeTooltipToLabel {
663  mock->isVoiceOverRunningValue = true;
664  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
665  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
666 
667  flutter::SemanticsNode node;
668  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
669  node.label = "label";
670  node.tooltip = "tooltip";
671  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
672  [object setSemanticsNode:&node];
673  XCTAssertTrue(object.isAccessibilityElement);
674  XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
675 }
676 
677 - (void)testSemanticsObjectContainerAccessibilityFrameCoversEntireScreen {
679  mock->isVoiceOverRunningValue = true;
680  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
681  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
682 
683  flutter::SemanticsNode parent;
684  parent.id = 0;
685  parent.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
686 
687  flutter::SemanticsNode child;
688  child.id = 1;
689  child.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
690  child.rect = SkRect::MakeXYWH(0, 0, 100, 100);
691  parent.childrenInTraversalOrder.push_back(1);
692 
693  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
694  uid:0];
695  [parentObject setSemanticsNode:&parent];
696 
697  FlutterSemanticsObject* childObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
698  uid:1];
699  [childObject setSemanticsNode:&child];
700 
701  parentObject.children = @[ childObject ];
702  [parentObject accessibilityBridgeDidFinishUpdate];
704 
705  SemanticsObjectContainer* container =
706  static_cast<SemanticsObjectContainer*>(parentObject.accessibilityContainer);
707 
708  XCTAssertTrue(childObject.accessibilityRespondsToUserInteraction);
709  XCTAssertTrue(CGRectEqualToRect(container.accessibilityFrame, UIScreen.mainScreen.bounds));
710 }
711 
712 - (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
714  mock->isVoiceOverRunningValue = true;
715  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
716  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
717 
718  flutter::SemanticsNode node;
719  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
720  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
721  [object setSemanticsNode:&node];
722  XCTAssertTrue(object.accessibilityAttributedLabel == nil);
723 }
724 
725 - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
727  mock->isVoiceOverRunningValue = true;
728  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
729  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
730 
731  flutter::SemanticsNode parent;
732  parent.id = 0;
733  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
734  parent.label = "label";
735  parent.value = "value";
736  parent.hint = "hint";
737 
738  flutter::SemanticsNode node;
739  node.id = 1;
740  node.flags.hasImplicitScrolling = true;
741  node.actions = flutter::kHorizontalScrollSemanticsActions;
742  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
743  node.label = "label";
744  node.value = "value";
745  node.hint = "hint";
746  node.scrollExtentMax = 100.0;
747  node.scrollPosition = 0.0;
748  parent.childrenInTraversalOrder.push_back(1);
749 
750  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
751  uid:0];
752  [parentObject setSemanticsNode:&parent];
753 
755  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
756  [scrollable setSemanticsNode:&node];
757  UIScrollView* scrollView = [scrollable nativeAccessibility];
758 
759  parentObject.children = @[ scrollable ];
760  [parentObject accessibilityBridgeDidFinishUpdate];
762  XCTAssertTrue(scrollView.isAccessibilityElement);
763  SemanticsObjectContainer* container =
764  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
765  XCTAssertEqual(container.semanticsObject, parentObject);
766 }
767 
768 - (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
769  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
771  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
772 
773  flutter::SemanticsNode node;
774  node.flags.hasImplicitScrolling = true;
775  node.actions = flutter::kHorizontalScrollSemanticsActions;
776  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
777  node.scrollExtentMax = 100.0;
778  node.scrollPosition = 0.0;
779 
781  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
782  [scrollable setSemanticsNode:&node];
784  UIScrollView* scrollView = [scrollable nativeAccessibility];
785 
786  XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
787  XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
788  XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior,
789  UIScrollViewContentInsetAdjustmentNever);
790  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero));
791 }
792 
793 - (void)testSemanticsObjectBuildsAttributedString {
794  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
796  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
797  flutter::SemanticsNode node;
798  node.label = "label";
799  std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
800  std::make_shared<flutter::SpellOutStringAttribute>();
801  attribute->start = 1;
802  attribute->end = 2;
803  attribute->type = flutter::StringAttributeType::kSpellOut;
804  node.labelAttributes.push_back(attribute);
805  node.value = "value";
806  attribute = std::make_shared<flutter::SpellOutStringAttribute>();
807  attribute->start = 2;
808  attribute->end = 3;
809  attribute->type = flutter::StringAttributeType::kSpellOut;
810  node.valueAttributes.push_back(attribute);
811  node.hint = "hint";
812  std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
813  std::make_shared<flutter::LocaleStringAttribute>();
814  local_attribute->start = 3;
815  local_attribute->end = 4;
816  local_attribute->type = flutter::StringAttributeType::kLocale;
817  local_attribute->locale = "en-MX";
818  node.hintAttributes.push_back(local_attribute);
819  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
820  [object setSemanticsNode:&node];
821  NSMutableAttributedString* expectedAttributedLabel =
822  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
823  NSDictionary* attributeDict = @{
824  UIAccessibilitySpeechAttributeSpellOut : @YES,
825  };
826  [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
827  XCTAssertTrue(
828  [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
829 
830  NSMutableAttributedString* expectedAttributedValue =
831  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
832  attributeDict = @{
833  UIAccessibilitySpeechAttributeSpellOut : @YES,
834  };
835  [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
836  XCTAssertTrue(
837  [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
838 
839  NSMutableAttributedString* expectedAttributedHint =
840  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
841  attributeDict = @{
842  UIAccessibilitySpeechAttributeLanguage : @"en-MX",
843  };
844  [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
845  XCTAssertTrue(
846  [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
847 }
848 
849 - (void)testShouldTriggerAnnouncement {
850  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
852  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
853  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
854 
855  // Handle nil with no node set.
856  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
857 
858  // Handle initial setting of node with liveRegion
859  flutter::SemanticsNode node;
860  node.flags.isLiveRegion = true;
861  node.label = "foo";
862  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
863 
864  // Handle nil with node set.
865  [object setSemanticsNode:&node];
866  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
867 
868  // Handle new node, still has live region, same label.
869  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
870 
871  // Handle update node with new label, still has live region.
872  flutter::SemanticsNode updatedNode;
873  updatedNode.flags.isLiveRegion = true;
874  updatedNode.label = "bar";
875  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
876 
877  // Handle dropping the live region flag.
878  updatedNode.flags = flutter::SemanticsFlags{};
879  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
880 
881  // Handle adding the flag when the label has not changed.
882  updatedNode.label = "foo";
883  [object setSemanticsNode:&updatedNode];
884  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
885 }
886 
887 - (void)testShouldDispatchShowOnScreenActionForHeader {
888  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
890  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
891  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
892 
893  // Handle initial setting of node with header.
894  flutter::SemanticsNode node;
895  node.flags.isHeader = true;
896  node.label = "foo";
897 
898  [object setSemanticsNode:&node];
899 
900  // Simulate accessibility focus.
901  [object accessibilityElementDidBecomeFocused];
902 
903  XCTAssertTrue(bridge->observations.size() == 1);
904  XCTAssertTrue(bridge->observations[0].id == 1);
905  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
906 }
907 
908 - (void)testShouldDispatchShowOnScreenActionForHidden {
909  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
911  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
912  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
913 
914  // Handle initial setting of node with hidden.
915  flutter::SemanticsNode node;
916  node.flags.isHidden = true;
917  node.label = "foo";
918 
919  [object setSemanticsNode:&node];
920 
921  // Simulate accessibility focus.
922  [object accessibilityElementDidBecomeFocused];
923 
924  XCTAssertTrue(bridge->observations.size() == 1);
925  XCTAssertTrue(bridge->observations[0].id == 1);
926  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
927 }
928 
929 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
930  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
932  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
933  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
934  uid:1];
935 
936  // Handle initial setting of node with header.
937  flutter::SemanticsNode node;
938  node.flags.isToggled = flutter::SemanticsTristate::kTrue;
939  node.flags.isEnabled = flutter::SemanticsTristate::kTrue;
940  node.label = "foo";
941  [object setSemanticsNode:&node];
942  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
943  UISwitch* nativeSwitch = [[UISwitch alloc] init];
944  nativeSwitch.on = YES;
945 
946  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
947  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
948 
949  // Set the toggled to false;
950  flutter::SemanticsNode update;
951  update.flags.isToggled = flutter::SemanticsTristate::kFalse;
952  update.flags.isEnabled = flutter::SemanticsTristate::kTrue;
953 
954  update.label = "foo";
955  [object setSemanticsNode:&update];
956 
957  nativeSwitch.on = NO;
958 
959  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
960  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
961 }
962 
963 - (void)testFlutterSemanticsObjectOfRadioButton {
964  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
966  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
967  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
968 
969  // Handle initial setting of node with header.
970  flutter::SemanticsNode node;
971  node.flags.isInMutuallyExclusiveGroup = true;
972  node.flags.isChecked = flutter::SemanticsCheckState::kFalse;
973  node.flags.isEnabled = flutter::SemanticsTristate::kTrue;
974  node.label = "foo";
975  [object setSemanticsNode:&node];
976  XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
977  XCTAssertNil(object.accessibilityValue);
978 }
979 
980 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
981  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
983  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
984  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
985  uid:1];
986 
987  // Handle initial setting of node with header.
988  flutter::SemanticsNode node;
989  node.flags.isToggled = flutter::SemanticsTristate::kTrue;
990  node.label = "foo";
991  [object setSemanticsNode:&node];
992  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
993  UISwitch* nativeSwitch = [[UISwitch alloc] init];
994  nativeSwitch.on = YES;
995  nativeSwitch.enabled = NO;
996 
997  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
998  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
999 }
1000 
1001 - (void)testSemanticsObjectDeallocated {
1002  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1004  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1005  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1006  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1007  parent.children = @[ child ];
1008  // Validate SemanticsObject deallocation does not crash.
1009  // https://github.com/flutter/flutter/issues/66032
1010  __weak SemanticsObject* weakObject = parent;
1011  parent = nil;
1012  XCTAssertNil(weakObject);
1013 }
1014 
1015 - (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
1016  FlutterSemanticsObject* parentObject;
1018  FlutterSemanticsObject* object2;
1019 
1020  flutter::SemanticsNode parent;
1021  parent.id = 0;
1022  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1023  parent.label = "label";
1024  parent.value = "value";
1025  parent.hint = "hint";
1026 
1027  flutter::SemanticsNode node;
1028  node.id = 1;
1029  node.flags.hasImplicitScrolling = true;
1030  node.actions = flutter::kHorizontalScrollSemanticsActions;
1031  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1032  node.label = "label";
1033  node.value = "value";
1034  node.hint = "hint";
1035  node.scrollExtentMax = 100.0;
1036  node.scrollPosition = 0.0;
1037  parent.childrenInTraversalOrder.push_back(1);
1038 
1039  flutter::SemanticsNode node2;
1040  node2.id = 2;
1041  node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1042  node2.label = "label";
1043  node2.value = "value";
1044  node2.hint = "hint";
1045  node2.scrollExtentMax = 100.0;
1046  node2.scrollPosition = 0.0;
1047  parent.childrenInTraversalOrder.push_back(2);
1048 
1049  {
1052  mock->isVoiceOverRunningValue = true;
1053  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
1054  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1055 
1056  parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
1057  [parentObject setSemanticsNode:&parent];
1058 
1059  scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
1060  [scrollable setSemanticsNode:&node];
1062 
1063  object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
1064  [object2 setSemanticsNode:&node2];
1065 
1066  parentObject.children = @[ scrollable, object2 ];
1067  [parentObject accessibilityBridgeDidFinishUpdate];
1070 
1071  // Returns the correct container if the bridge is alive.
1072  SemanticsObjectContainer* container =
1073  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
1074  XCTAssertEqual(container.semanticsObject, parentObject);
1075  SemanticsObjectContainer* container2 =
1076  static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
1077  XCTAssertEqual(container2.semanticsObject, parentObject);
1078  }
1079  // The bridge pointer went out of scope and was deallocated.
1080 
1081  XCTAssertNil(scrollable.accessibilityContainer);
1082  XCTAssertNil(object2.accessibilityContainer);
1083 }
1084 
1085 - (void)testAccessibilityHitTestSearchCanReturnPlatformView {
1086  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1088  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1089  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1090  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1091  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
1092  FlutterTouchInterceptingView* platformView =
1093  [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
1094  FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
1095  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1096  uid:1
1097  platformView:platformView];
1098 
1099  object0.children = @[ object1 ];
1100  object0.childrenInHitTestOrder = @[ object1 ];
1101  object1.children = @[ platformViewSemanticsContainer, object3 ];
1102  object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
1103 
1104  flutter::SemanticsNode node0;
1105  node0.id = 0;
1106  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1107  node0.label = "0";
1108  [object0 setSemanticsNode:&node0];
1109 
1110  flutter::SemanticsNode node1;
1111  node1.id = 1;
1112  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1113  node1.label = "1";
1114  [object1 setSemanticsNode:&node1];
1115 
1116  flutter::SemanticsNode node2;
1117  node2.id = 2;
1118  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1119  node2.label = "2";
1120  [platformViewSemanticsContainer setSemanticsNode:&node2];
1121 
1122  flutter::SemanticsNode node3;
1123  node3.id = 3;
1124  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1125  node3.label = "3";
1126  [object3 setSemanticsNode:&node3];
1127 
1128  CGPoint point = CGPointMake(10, 10);
1129  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1130 
1131  XCTAssertEqual(hitTestResult, platformView);
1132 }
1133 
1134 - (void)testFlutterPlatformViewSemanticsContainer {
1135  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
1137  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1138  __weak FlutterTouchInterceptingView* weakPlatformView;
1139  __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1140  @autoreleasepool {
1141  FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1142  weakPlatformView = platformView;
1143 
1144  @autoreleasepool {
1146  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1147  uid:1
1148  platformView:platformView];
1149  weakContainer = container;
1150  XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1151  XCTAssertNotNil(weakPlatformView);
1152  XCTAssertNotNil(weakContainer);
1153  }
1154  // Check the variables are still lived.
1155  // `container` is `retain` in `platformView`, so it will not be nil here.
1156  XCTAssertNotNil(weakPlatformView);
1157  XCTAssertNotNil(weakContainer);
1158  }
1159  // Check if there's no more strong references to `platformView` after container and platformView
1160  // are released.
1161  XCTAssertNil(weakPlatformView);
1162  XCTAssertNil(weakContainer);
1163 }
1164 
1165 - (void)testTextInputSemanticsObject {
1166  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1168  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1169 
1170  flutter::SemanticsNode node;
1171  node.label = "foo";
1172  node.flags.isTextField = true;
1173  node.flags.isReadOnly = true;
1174  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1175  [object setSemanticsNode:&node];
1177  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1178 }
1179 
1180 - (void)testTextInputSemanticsObject_canPerformAction {
1181  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1183  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1184 
1185  flutter::SemanticsNode node;
1186  node.label = "foo";
1187  node.flags.isTextField = true;
1188  node.flags.isReadOnly = true;
1189  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1190  [object setSemanticsNode:&node];
1192 
1193  id textInputSurrogate = OCMClassMock([UIResponder class]);
1194  id partialSemanticsObject = OCMPartialMock(object);
1195  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1196 
1197  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1198  .andReturn(YES);
1199  XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1200 
1201  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1202  .andReturn(NO);
1203  XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1204 }
1205 
1206 - (void)testTextInputSemanticsObject_editActions {
1207  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1209  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1210 
1211  flutter::SemanticsNode node;
1212  node.label = "foo";
1213 
1214  node.flags.isTextField = true;
1215  node.flags.isReadOnly = true;
1216  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1217  [object setSemanticsNode:&node];
1219 
1220  id textInputSurrogate = OCMClassMock([UIResponder class]);
1221  id partialSemanticsObject = OCMPartialMock(object);
1222  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1223 
1224  XCTestExpectation* copyExpectation =
1225  [self expectationWithDescription:@"Surrogate's copy method is called."];
1226  XCTestExpectation* cutExpectation =
1227  [self expectationWithDescription:@"Surrogate's cut method is called."];
1228  XCTestExpectation* pasteExpectation =
1229  [self expectationWithDescription:@"Surrogate's paste method is called."];
1230  XCTestExpectation* selectAllExpectation =
1231  [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1232  XCTestExpectation* deleteExpectation =
1233  [self expectationWithDescription:@"Surrogate's delete method is called."];
1234 
1235  OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1236  [copyExpectation fulfill];
1237  });
1238  OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1239  [cutExpectation fulfill];
1240  });
1241  OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1242  [pasteExpectation fulfill];
1243  });
1244  OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1245  [selectAllExpectation fulfill];
1246  });
1247  OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1248  [deleteExpectation fulfill];
1249  });
1250 
1251  [partialSemanticsObject copy:nil];
1252  [partialSemanticsObject cut:nil];
1253  [partialSemanticsObject paste:nil];
1254  [partialSemanticsObject selectAll:nil];
1255  [partialSemanticsObject delete:nil];
1256 
1257  [self waitForExpectationsWithTimeout:1 handler:nil];
1258 }
1259 
1260 - (void)testSliderSemanticsObject {
1261  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1263  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1264 
1265  flutter::SemanticsNode node;
1266  node.flags.isSlider = true;
1267  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1268  [object setSemanticsNode:&node];
1270  XCTAssertEqual([object accessibilityActivate], YES);
1271 }
1272 
1273 - (void)testUIFocusItemConformance {
1274  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1276  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1277  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1278  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1279  parent.children = @[ child ];
1280 
1281  // parentFocusEnvironment
1282  XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]);
1283  XCTAssertEqual(child.parentFocusEnvironment, child.parent);
1284 
1285  // canBecomeFocused
1286  flutter::SemanticsNode childNode;
1287  childNode.flags.isHidden = true;
1288  childNode.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
1289  [child setSemanticsNode:&childNode];
1290  XCTAssertFalse(child.canBecomeFocused);
1291  childNode.flags = flutter::SemanticsFlags{};
1292  [child setSemanticsNode:&childNode];
1293  XCTAssertTrue(child.canBecomeFocused);
1294  childNode.actions = 0;
1295  [child setSemanticsNode:&childNode];
1296  XCTAssertFalse(child.canBecomeFocused);
1297 
1298  CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale;
1299 
1300  childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale);
1301  [child setSemanticsNode:&childNode];
1302  flutter::SemanticsNode parentNode;
1303  parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1304  [parent setSemanticsNode:&parentNode];
1305 
1306  XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100)));
1307 }
1308 
1309 - (void)testUIFocusItemContainerConformance {
1310  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1312  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1313  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1314  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1315  SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
1316  parent.childrenInHitTestOrder = @[ child1, child2 ];
1317 
1318  // focusItemsInRect
1319  NSArray<id<UIFocusItem>>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)];
1320  XCTAssertEqual(itemsInRect.count, (unsigned long)2);
1321  XCTAssertTrue([itemsInRect containsObject:child1]);
1322  XCTAssertTrue([itemsInRect containsObject:child2]);
1323 }
1324 
1325 - (void)testUIFocusItemScrollableContainerConformance {
1326  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1328  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1329  FlutterScrollableSemanticsObject* scrollable =
1330  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1331 
1332  // setContentOffset
1333  CGPoint p = CGPointMake(123.0, 456.0);
1334  [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView
1335  withVelocity:CGPointZero
1336  targetContentOffset:&p];
1337  scrollable.scrollView.contentOffset = p;
1338  [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView];
1339  XCTAssertEqual(bridge->observations.size(), (size_t)1);
1340  XCTAssertEqual(bridge->observations[0].id, 5);
1341  XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset);
1342 
1343  std::vector<uint8_t> args = bridge->observations[0].args;
1344  XCTAssertEqual(args.size(), 3 * sizeof(CGFloat));
1345 
1346  NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()];
1348  CGPoint point = CGPointZero;
1349  memcpy(&point, decoded.data.bytes, decoded.data.length);
1350  XCTAssertTrue(CGPointEqualToPoint(point, p));
1351 }
1352 
1353 - (void)testUIFocusItemScrollableContainerNoFeedbackLoops {
1354  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1356  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1357  FlutterScrollableSemanticsObject* scrollable =
1358  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1359 
1360  // setContentOffset
1361  const CGPoint p = CGPointMake(0.0, 456.0);
1362  scrollable.scrollView.contentOffset = p;
1363  bridge->observations.clear();
1364 
1365  const SkScalar scrollPosition = p.y + 0.0000000000000001;
1366  flutter::SemanticsNode node;
1367  node.flags.hasImplicitScrolling = true;
1368  node.actions = flutter::kVerticalScrollSemanticsActions;
1369  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1370  node.scrollExtentMax = 10000;
1371  node.scrollPosition = scrollPosition;
1372  node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0};
1373  [scrollable setSemanticsNode:&node];
1375 
1376  XCTAssertEqual(bridge->observations.size(), (size_t)0);
1377 }
1378 @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()