Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
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  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling) |
249  static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
250  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
251  [object setSemanticsNode:&node];
252  XCTAssertEqual(object.isAccessibilityElement, YES);
253 }
254 
255 - (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
256  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
258  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
259  flutter::SemanticsNode node;
260  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
261  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
262  [object setSemanticsNode:&node];
263  XCTAssertEqual(object.isAccessibilityElement, NO);
264 }
265 
266 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
267  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
269  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
270  flutter::SemanticsNode node;
271  node.label = "foo";
272  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
273  SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
274  object.children = @[ child1 ];
275  [object setSemanticsNode:&node];
276  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
277 }
278 
279 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
280  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
282  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
283  flutter::SemanticsNode node;
284  node.label = "foo";
285  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField);
286  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
287  [object setSemanticsNode:&node];
288  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
289 }
290 
291 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
292  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
294  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
295  flutter::SemanticsNode node;
296  node.label = "foo";
297  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsButton);
298  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
299  [object setSemanticsNode:&node];
300  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
301 }
302 
303 - (void)testVerticalFlutterScrollableSemanticsObject {
304  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
306  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
307 
308  float transformScale = 0.5f;
309  float screenScale = [[bridge->view() window] screen].scale;
310  float effectivelyScale = transformScale / screenScale;
311  float x = 10;
312  float y = 10;
313  float w = 100;
314  float h = 200;
315  float scrollExtentMax = 500.0;
316  float scrollPosition = 150.0;
317 
318  flutter::SemanticsNode node;
319  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
320  node.actions = flutter::kVerticalScrollSemanticsActions;
321  node.rect = SkRect::MakeXYWH(x, y, w, h);
322  node.scrollExtentMax = scrollExtentMax;
323  node.scrollPosition = scrollPosition;
324  node.transform = {
325  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
327  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
328  [scrollable setSemanticsNode:&node];
330  UIScrollView* scrollView = [scrollable nativeAccessibility];
331 
332  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
333  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
334  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
336  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
338 
339  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
341  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
342  (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
343 
344  XCTAssertEqual(scrollView.contentOffset.x, 0);
345  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
347 }
348 
349 - (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
350  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
352  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
353 
354  float transformScale = 0.5f;
355  float x = 10;
356  float y = 10;
357  float w = 100;
358  float h = 200;
359  float scrollExtentMax = 500.0;
360  float scrollPosition = 150.0;
361 
362  flutter::SemanticsNode node;
363  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
364  node.actions = flutter::kVerticalScrollSemanticsActions;
365  node.rect = SkRect::MakeXYWH(x, y, w, h);
366  node.scrollExtentMax = scrollExtentMax;
367  node.scrollPosition = scrollPosition;
368  node.transform = {
369  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
371  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
372  [scrollable setSemanticsNode:&node];
374  XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
375 }
376 
377 - (void)testHorizontalFlutterScrollableSemanticsObject {
378  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
380  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
381 
382  float transformScale = 0.5f;
383  float screenScale = [[bridge->view() window] screen].scale;
384  float effectivelyScale = transformScale / screenScale;
385  float x = 10;
386  float y = 10;
387  float w = 100;
388  float h = 200;
389  float scrollExtentMax = 500.0;
390  float scrollPosition = 150.0;
391 
392  flutter::SemanticsNode node;
393  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
394  node.actions = flutter::kHorizontalScrollSemanticsActions;
395  node.rect = SkRect::MakeXYWH(x, y, w, h);
396  node.scrollExtentMax = scrollExtentMax;
397  node.scrollPosition = scrollPosition;
398  node.transform = {
399  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
401  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
402  [scrollable setSemanticsNode:&node];
404  UIScrollView* scrollView = [scrollable nativeAccessibility];
405 
406  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
407  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
408  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
410  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
412 
413  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
415  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
417 
418  XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
420  XCTAssertEqual(scrollView.contentOffset.y, 0);
421 }
422 
423 - (void)testCanHandleInfiniteScrollExtent {
424  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
426  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
427 
428  float transformScale = 0.5f;
429  float screenScale = [[bridge->view() window] screen].scale;
430  float effectivelyScale = transformScale / screenScale;
431  float x = 10;
432  float y = 10;
433  float w = 100;
434  float h = 200;
435  float scrollExtentMax = INFINITY;
436  float scrollPosition = 150.0;
437 
438  flutter::SemanticsNode node;
439  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
440  node.actions = flutter::kVerticalScrollSemanticsActions;
441  node.rect = SkRect::MakeXYWH(x, y, w, h);
442  node.scrollExtentMax = scrollExtentMax;
443  node.scrollPosition = scrollPosition;
444  node.transform = {
445  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
447  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
448  [scrollable setSemanticsNode:&node];
450  UIScrollView* scrollView = [scrollable nativeAccessibility];
451  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
452  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
453  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
455  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
457 
458  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
460  XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
461  (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
463 
464  XCTAssertEqual(scrollView.contentOffset.x, 0);
465  XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
467 }
468 
469 - (void)testCanHandleNaNScrollExtentAndScrollPoisition {
470  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
472  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
473 
474  float transformScale = 0.5f;
475  float screenScale = [[bridge->view() window] screen].scale;
476  float effectivelyScale = transformScale / screenScale;
477  float x = 10;
478  float y = 10;
479  float w = 100;
480  float h = 200;
481  float scrollExtentMax = std::nan("");
482  float scrollPosition = std::nan("");
483 
484  flutter::SemanticsNode node;
485  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
486  node.actions = flutter::kVerticalScrollSemanticsActions;
487  node.rect = SkRect::MakeXYWH(x, y, w, h);
488  node.scrollExtentMax = scrollExtentMax;
489  node.scrollPosition = scrollPosition;
490  node.transform = {
491  transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
493  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
494  [scrollable setSemanticsNode:&node];
496  UIScrollView* scrollView = [scrollable nativeAccessibility];
497 
498  XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
499  XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
500  XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
502  XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
504 
505  // Content size equal to the scrollable size.
506  XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
508  XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
510 
511  XCTAssertEqual(scrollView.contentOffset.x, 0);
512  XCTAssertEqual(scrollView.contentOffset.y, 0);
513 }
514 
515 - (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
516  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
518  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
519 
520  flutter::SemanticsNode node;
521  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
522  node.actions = flutter::kHorizontalScrollSemanticsActions;
523  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
524  node.scrollExtentMax = 100.0;
525  node.scrollPosition = 0.0;
526 
528  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
529  [scrollable setSemanticsNode:&node];
531  UIScrollView* scrollView = [scrollable nativeAccessibility];
532  XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
533 }
534 
535 - (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
537  mock->isVoiceOverRunningValue = false;
538  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
539  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
540 
541  flutter::SemanticsNode node;
542  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
543  node.actions = flutter::kHorizontalScrollSemanticsActions;
544  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
545  node.scrollExtentMax = 100.0;
546  node.scrollPosition = 0.0;
547 
549  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
550  [scrollable setSemanticsNode:&node];
552  UIScrollView* scrollView = [scrollable nativeAccessibility];
553  XCTAssertTrue(scrollView.isAccessibilityElement);
554  mock->isVoiceOverRunningValue = true;
555  XCTAssertFalse(scrollView.isAccessibilityElement);
556 }
557 
558 - (void)testFlutterSemanticsObjectHasIdentifier {
560  mock->isVoiceOverRunningValue = true;
561  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
562  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
563 
564  flutter::SemanticsNode node;
565  node.identifier = "identifier";
566 
567  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
568  [object setSemanticsNode:&node];
569  XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
570 }
571 
572 - (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
574  mock->isVoiceOverRunningValue = true;
575  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
576  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
577 
578  flutter::SemanticsNode node;
579  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
580  node.actions = flutter::kHorizontalScrollSemanticsActions;
581  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
582  node.label = "label";
583  node.value = "value";
584  node.hint = "hint";
585  node.scrollExtentMax = 100.0;
586  node.scrollPosition = 0.0;
587 
589  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
590  [scrollable setSemanticsNode:&node];
592  UIScrollView* scrollView = [scrollable nativeAccessibility];
593  XCTAssertTrue(scrollView.isAccessibilityElement);
594  XCTAssertTrue(
595  [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
596  XCTAssertTrue(
597  [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
598  XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
599 }
600 
601 - (void)testFlutterSemanticsObjectMergeTooltipToLabel {
603  mock->isVoiceOverRunningValue = true;
604  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
605  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
606 
607  flutter::SemanticsNode node;
608  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
609  node.label = "label";
610  node.tooltip = "tooltip";
611  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
612  [object setSemanticsNode:&node];
613  XCTAssertTrue(object.isAccessibilityElement);
614  XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
615 }
616 
617 - (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
619  mock->isVoiceOverRunningValue = true;
620  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
621  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
622 
623  flutter::SemanticsNode node;
624  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
625  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
626  [object setSemanticsNode:&node];
627  XCTAssertTrue(object.accessibilityAttributedLabel == nil);
628 }
629 
630 - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
632  mock->isVoiceOverRunningValue = true;
633  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
634  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
635 
636  flutter::SemanticsNode parent;
637  parent.id = 0;
638  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
639  parent.label = "label";
640  parent.value = "value";
641  parent.hint = "hint";
642 
643  flutter::SemanticsNode node;
644  node.id = 1;
645  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
646  node.actions = flutter::kHorizontalScrollSemanticsActions;
647  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
648  node.label = "label";
649  node.value = "value";
650  node.hint = "hint";
651  node.scrollExtentMax = 100.0;
652  node.scrollPosition = 0.0;
653  parent.childrenInTraversalOrder.push_back(1);
654 
655  FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
656  uid:0];
657  [parentObject setSemanticsNode:&parent];
658 
660  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
661  [scrollable setSemanticsNode:&node];
662  UIScrollView* scrollView = [scrollable nativeAccessibility];
663 
664  parentObject.children = @[ scrollable ];
665  [parentObject accessibilityBridgeDidFinishUpdate];
667  XCTAssertTrue(scrollView.isAccessibilityElement);
668  SemanticsObjectContainer* container =
669  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
670  XCTAssertEqual(container.semanticsObject, parentObject);
671 }
672 
673 - (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
674  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
676  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
677 
678  flutter::SemanticsNode node;
679  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
680  node.actions = flutter::kHorizontalScrollSemanticsActions;
681  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
682  node.scrollExtentMax = 100.0;
683  node.scrollPosition = 0.0;
684 
686  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
687  [scrollable setSemanticsNode:&node];
689  UIScrollView* scrollView = [scrollable nativeAccessibility];
690 
691  XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
692  XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
693  XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior,
694  UIScrollViewContentInsetAdjustmentNever);
695  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero));
696 }
697 
698 - (void)testSemanticsObjectBuildsAttributedString {
699  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
701  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
702  flutter::SemanticsNode node;
703  node.label = "label";
704  std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
705  std::make_shared<flutter::SpellOutStringAttribute>();
706  attribute->start = 1;
707  attribute->end = 2;
708  attribute->type = flutter::StringAttributeType::kSpellOut;
709  node.labelAttributes.push_back(attribute);
710  node.value = "value";
711  attribute = std::make_shared<flutter::SpellOutStringAttribute>();
712  attribute->start = 2;
713  attribute->end = 3;
714  attribute->type = flutter::StringAttributeType::kSpellOut;
715  node.valueAttributes.push_back(attribute);
716  node.hint = "hint";
717  std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
718  std::make_shared<flutter::LocaleStringAttribute>();
719  local_attribute->start = 3;
720  local_attribute->end = 4;
721  local_attribute->type = flutter::StringAttributeType::kLocale;
722  local_attribute->locale = "en-MX";
723  node.hintAttributes.push_back(local_attribute);
724  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
725  [object setSemanticsNode:&node];
726  NSMutableAttributedString* expectedAttributedLabel =
727  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
728  NSDictionary* attributeDict = @{
729  UIAccessibilitySpeechAttributeSpellOut : @YES,
730  };
731  [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
732  XCTAssertTrue(
733  [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
734 
735  NSMutableAttributedString* expectedAttributedValue =
736  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
737  attributeDict = @{
738  UIAccessibilitySpeechAttributeSpellOut : @YES,
739  };
740  [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
741  XCTAssertTrue(
742  [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
743 
744  NSMutableAttributedString* expectedAttributedHint =
745  [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
746  attributeDict = @{
747  UIAccessibilitySpeechAttributeLanguage : @"en-MX",
748  };
749  [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
750  XCTAssertTrue(
751  [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
752 }
753 
754 - (void)testShouldTriggerAnnouncement {
755  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
757  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
758  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
759 
760  // Handle nil with no node set.
761  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
762 
763  // Handle initial setting of node with liveRegion
764  flutter::SemanticsNode node;
765  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
766  node.label = "foo";
767  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
768 
769  // Handle nil with node set.
770  [object setSemanticsNode:&node];
771  XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
772 
773  // Handle new node, still has live region, same label.
774  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
775 
776  // Handle update node with new label, still has live region.
777  flutter::SemanticsNode updatedNode;
778  updatedNode.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
779  updatedNode.label = "bar";
780  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
781 
782  // Handle dropping the live region flag.
783  updatedNode.flags = 0;
784  XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
785 
786  // Handle adding the flag when the label has not changed.
787  updatedNode.label = "foo";
788  [object setSemanticsNode:&updatedNode];
789  XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
790 }
791 
792 - (void)testShouldDispatchShowOnScreenActionForHeader {
793  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
795  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
796  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
797 
798  // Handle initial setting of node with header.
799  flutter::SemanticsNode node;
800  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHeader);
801  node.label = "foo";
802 
803  [object setSemanticsNode:&node];
804 
805  // Simulate accessibility focus.
806  [object accessibilityElementDidBecomeFocused];
807 
808  XCTAssertTrue(bridge->observations.size() == 1);
809  XCTAssertTrue(bridge->observations[0].id == 1);
810  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
811 }
812 
813 - (void)testShouldDispatchShowOnScreenActionForHidden {
814  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
816  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
817  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
818 
819  // Handle initial setting of node with hidden.
820  flutter::SemanticsNode node;
821  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
822  node.label = "foo";
823 
824  [object setSemanticsNode:&node];
825 
826  // Simulate accessibility focus.
827  [object accessibilityElementDidBecomeFocused];
828 
829  XCTAssertTrue(bridge->observations.size() == 1);
830  XCTAssertTrue(bridge->observations[0].id == 1);
831  XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
832 }
833 
834 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
835  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
837  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
838  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
839  uid:1];
840 
841  // Handle initial setting of node with header.
842  flutter::SemanticsNode node;
843  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
844  static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled) |
845  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
846  node.label = "foo";
847  [object setSemanticsNode:&node];
848  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
849  UISwitch* nativeSwitch = [[UISwitch alloc] init];
850  nativeSwitch.on = YES;
851 
852  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
853  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
854 
855  // Set the toggled to false;
856  flutter::SemanticsNode update;
857  update.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
858  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
859  update.label = "foo";
860  [object setSemanticsNode:&update];
861 
862  nativeSwitch.on = NO;
863 
864  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
865  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
866 }
867 
868 - (void)testFlutterSemanticsObjectOfRadioButton {
869  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
871  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
872  FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
873 
874  // Handle initial setting of node with header.
875  flutter::SemanticsNode node;
876  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
877  static_cast<int32_t>(flutter::SemanticsFlags::kHasCheckedState) |
878  static_cast<int32_t>(flutter::SemanticsFlags::kHasEnabledState) |
879  static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
880  node.label = "foo";
881  [object setSemanticsNode:&node];
882  XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
883  XCTAssertNil(object.accessibilityValue);
884 }
885 
886 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
887  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
889  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
890  FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
891  uid:1];
892 
893  // Handle initial setting of node with header.
894  flutter::SemanticsNode node;
895  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
896  static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled);
897  node.label = "foo";
898  [object setSemanticsNode:&node];
899  // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
900  UISwitch* nativeSwitch = [[UISwitch alloc] init];
901  nativeSwitch.on = YES;
902  nativeSwitch.enabled = NO;
903 
904  XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
905  XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
906 }
907 
908 - (void)testSemanticsObjectDeallocated {
909  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
911  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
912  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
913  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
914  parent.children = @[ child ];
915  // Validate SemanticsObject deallocation does not crash.
916  // https://github.com/flutter/flutter/issues/66032
917  __weak SemanticsObject* weakObject = parent;
918  parent = nil;
919  XCTAssertNil(weakObject);
920 }
921 
922 - (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
923  FlutterSemanticsObject* parentObject;
925  FlutterSemanticsObject* object2;
926 
927  flutter::SemanticsNode parent;
928  parent.id = 0;
929  parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
930  parent.label = "label";
931  parent.value = "value";
932  parent.hint = "hint";
933 
934  flutter::SemanticsNode node;
935  node.id = 1;
936  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
937  node.actions = flutter::kHorizontalScrollSemanticsActions;
938  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
939  node.label = "label";
940  node.value = "value";
941  node.hint = "hint";
942  node.scrollExtentMax = 100.0;
943  node.scrollPosition = 0.0;
944  parent.childrenInTraversalOrder.push_back(1);
945 
946  flutter::SemanticsNode node2;
947  node2.id = 2;
948  node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
949  node2.label = "label";
950  node2.value = "value";
951  node2.hint = "hint";
952  node2.scrollExtentMax = 100.0;
953  node2.scrollPosition = 0.0;
954  parent.childrenInTraversalOrder.push_back(2);
955 
956  {
959  mock->isVoiceOverRunningValue = true;
960  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
961  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
962 
963  parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
964  [parentObject setSemanticsNode:&parent];
965 
966  scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
967  [scrollable setSemanticsNode:&node];
969 
970  object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
971  [object2 setSemanticsNode:&node2];
972 
973  parentObject.children = @[ scrollable, object2 ];
974  [parentObject accessibilityBridgeDidFinishUpdate];
977 
978  // Returns the correct container if the bridge is alive.
979  SemanticsObjectContainer* container =
980  static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
981  XCTAssertEqual(container.semanticsObject, parentObject);
982  SemanticsObjectContainer* container2 =
983  static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
984  XCTAssertEqual(container2.semanticsObject, parentObject);
985  }
986  // The bridge pointer went out of scope and was deallocated.
987 
988  XCTAssertNil(scrollable.accessibilityContainer);
989  XCTAssertNil(object2.accessibilityContainer);
990 }
991 
992 - (void)testAccessibilityHitTestSearchCanReturnPlatformView {
993  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
995  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
996  SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
997  SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
998  SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
999  FlutterTouchInterceptingView* platformView =
1000  [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
1001  FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
1002  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1003  uid:1
1004  platformView:platformView];
1005 
1006  object0.children = @[ object1 ];
1007  object0.childrenInHitTestOrder = @[ object1 ];
1008  object1.children = @[ platformViewSemanticsContainer, object3 ];
1009  object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
1010 
1011  flutter::SemanticsNode node0;
1012  node0.id = 0;
1013  node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1014  node0.label = "0";
1015  [object0 setSemanticsNode:&node0];
1016 
1017  flutter::SemanticsNode node1;
1018  node1.id = 1;
1019  node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1020  node1.label = "1";
1021  [object1 setSemanticsNode:&node1];
1022 
1023  flutter::SemanticsNode node2;
1024  node2.id = 2;
1025  node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1026  node2.label = "2";
1027  [platformViewSemanticsContainer setSemanticsNode:&node2];
1028 
1029  flutter::SemanticsNode node3;
1030  node3.id = 3;
1031  node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1032  node3.label = "3";
1033  [object3 setSemanticsNode:&node3];
1034 
1035  CGPoint point = CGPointMake(10, 10);
1036  id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1037 
1038  XCTAssertEqual(hitTestResult, platformView);
1039 }
1040 
1041 - (void)testFlutterPlatformViewSemanticsContainer {
1042  fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
1044  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1045  __weak FlutterTouchInterceptingView* weakPlatformView;
1046  __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1047  @autoreleasepool {
1048  FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1049  weakPlatformView = platformView;
1050 
1051  @autoreleasepool {
1053  [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1054  uid:1
1055  platformView:platformView];
1056  weakContainer = container;
1057  XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1058  XCTAssertNotNil(weakPlatformView);
1059  XCTAssertNotNil(weakContainer);
1060  }
1061  // Check the variables are still lived.
1062  // `container` is `retain` in `platformView`, so it will not be nil here.
1063  XCTAssertNotNil(weakPlatformView);
1064  XCTAssertNotNil(weakContainer);
1065  }
1066  // Check if there's no more strong references to `platformView` after container and platformView
1067  // are released.
1068  XCTAssertNil(weakPlatformView);
1069  XCTAssertNil(weakContainer);
1070 }
1071 
1072 - (void)testTextInputSemanticsObject {
1073  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1075  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1076 
1077  flutter::SemanticsNode node;
1078  node.label = "foo";
1079  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1080  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1081  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1082  [object setSemanticsNode:&node];
1084  XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1085 }
1086 
1087 - (void)testTextInputSemanticsObject_canPerformAction {
1088  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1090  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1091 
1092  flutter::SemanticsNode node;
1093  node.label = "foo";
1094  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1095  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1096  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1097  [object setSemanticsNode:&node];
1099 
1100  id textInputSurrogate = OCMClassMock([UIResponder class]);
1101  id partialSemanticsObject = OCMPartialMock(object);
1102  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1103 
1104  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1105  .andReturn(YES);
1106  XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1107 
1108  OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1109  .andReturn(NO);
1110  XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1111 }
1112 
1113 - (void)testTextInputSemanticsObject_editActions {
1114  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1116  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1117 
1118  flutter::SemanticsNode node;
1119  node.label = "foo";
1120  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1121  static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1122  TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1123  [object setSemanticsNode:&node];
1125 
1126  id textInputSurrogate = OCMClassMock([UIResponder class]);
1127  id partialSemanticsObject = OCMPartialMock(object);
1128  OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1129 
1130  XCTestExpectation* copyExpectation =
1131  [self expectationWithDescription:@"Surrogate's copy method is called."];
1132  XCTestExpectation* cutExpectation =
1133  [self expectationWithDescription:@"Surrogate's cut method is called."];
1134  XCTestExpectation* pasteExpectation =
1135  [self expectationWithDescription:@"Surrogate's paste method is called."];
1136  XCTestExpectation* selectAllExpectation =
1137  [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1138  XCTestExpectation* deleteExpectation =
1139  [self expectationWithDescription:@"Surrogate's delete method is called."];
1140 
1141  OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1142  [copyExpectation fulfill];
1143  });
1144  OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1145  [cutExpectation fulfill];
1146  });
1147  OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1148  [pasteExpectation fulfill];
1149  });
1150  OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1151  [selectAllExpectation fulfill];
1152  });
1153  OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1154  [deleteExpectation fulfill];
1155  });
1156 
1157  [partialSemanticsObject copy:nil];
1158  [partialSemanticsObject cut:nil];
1159  [partialSemanticsObject paste:nil];
1160  [partialSemanticsObject selectAll:nil];
1161  [partialSemanticsObject delete:nil];
1162 
1163  [self waitForExpectationsWithTimeout:1 handler:nil];
1164 }
1165 
1166 - (void)testSliderSemanticsObject {
1167  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1169  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1170 
1171  flutter::SemanticsNode node;
1172  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsSlider);
1173  SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1174  [object setSemanticsNode:&node];
1176  XCTAssertEqual([object accessibilityActivate], YES);
1177 }
1178 
1179 - (void)testUIFocusItemConformance {
1180  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1182  fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1183  SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1184  SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1185  parent.children = @[ child ];
1186 
1187  // parentFocusEnvironment
1188  XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]);
1189  XCTAssertEqual(child.parentFocusEnvironment, child.parent);
1190 
1191  // canBecomeFocused
1192  flutter::SemanticsNode childNode;
1193  childNode.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
1194  childNode.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
1195  [child setSemanticsNode:&childNode];
1196  XCTAssertFalse(child.canBecomeFocused);
1197  childNode.flags = 0;
1198  [child setSemanticsNode:&childNode];
1199  XCTAssertTrue(child.canBecomeFocused);
1200  childNode.actions = 0;
1201  [child setSemanticsNode:&childNode];
1202  XCTAssertFalse(child.canBecomeFocused);
1203 
1204  CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale;
1205 
1206  childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale);
1207  [child setSemanticsNode:&childNode];
1208  flutter::SemanticsNode parentNode;
1209  parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1210  [parent setSemanticsNode:&parentNode];
1211 
1212  XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100)));
1213 }
1214 
1215 - (void)testUIFocusItemContainerConformance {
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* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1221  SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
1222  parent.childrenInHitTestOrder = @[ child1, child2 ];
1223 
1224  // focusItemsInRect
1225  NSArray<id<UIFocusItem>>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)];
1226  XCTAssertEqual(itemsInRect.count, (unsigned long)2);
1227  XCTAssertTrue([itemsInRect containsObject:child1]);
1228  XCTAssertTrue([itemsInRect containsObject:child2]);
1229 }
1230 
1231 - (void)testUIFocusItemScrollableContainerConformance {
1232  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1234  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1235  FlutterScrollableSemanticsObject* scrollable =
1236  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1237 
1238  // setContentOffset
1239  CGPoint p = CGPointMake(123.0, 456.0);
1240  [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView
1241  withVelocity:CGPointZero
1242  targetContentOffset:&p];
1243  scrollable.scrollView.contentOffset = p;
1244  [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView];
1245  XCTAssertEqual(bridge->observations.size(), (size_t)1);
1246  XCTAssertEqual(bridge->observations[0].id, 5);
1247  XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset);
1248 
1249  std::vector<uint8_t> args = bridge->observations[0].args;
1250  XCTAssertEqual(args.size(), 3 * sizeof(CGFloat));
1251 
1252  NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()];
1254  CGPoint point = CGPointZero;
1255  memcpy(&point, decoded.data.bytes, decoded.data.length);
1256  XCTAssertTrue(CGPointEqualToPoint(point, p));
1257 }
1258 
1259 - (void)testUIFocusItemScrollableContainerNoFeedbackLoops {
1260  fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1262  fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1263  FlutterScrollableSemanticsObject* scrollable =
1264  [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1265 
1266  // setContentOffset
1267  const CGPoint p = CGPointMake(0.0, 456.0);
1268  scrollable.scrollView.contentOffset = p;
1269  bridge->observations.clear();
1270 
1271  const SkScalar scrollPosition = p.y + 0.0000000000000001;
1272  flutter::SemanticsNode node;
1273  node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1274  node.actions = flutter::kVerticalScrollSemanticsActions;
1275  node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1276  node.scrollExtentMax = 10000;
1277  node.scrollPosition = scrollPosition;
1278  node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0};
1279  [scrollable setSemanticsNode:&node];
1281 
1282  XCTAssertEqual(bridge->observations.size(), (size_t)0);
1283 }
1284 @end
-[FlutterTouchInterceptingView accessibilityContainer]
id accessibilityContainer()
-[SemanticsObject accessibilityScrollToVisible]
BOOL accessibilityScrollToVisible()
-[SemanticsObject replaceChildAtIndex:withChild:]
void replaceChildAtIndex:withChild:(NSInteger index,[withChild] SemanticsObject *child)
Definition: SemanticsObject.mm:374
-[TextInputSemanticsObject(Test) textInputSurrogate]
UIView< UITextInput > * textInputSurrogate()
SemanticsObject::parent
SemanticsObject * parent
Definition: SemanticsObject.h:42
SemanticsObjectTestMocks.h
TextInputSemanticsObject(Test)
Definition: SemanticsObjectTest.mm:27
flutter::testing::MockAccessibilityBridge
Definition: SemanticsObjectTestMocks.h:32
kFloatCompareEpsilon
const FLUTTER_ASSERT_ARC float kFloatCompareEpsilon
Definition: SemanticsObjectTest.mm:18
FlutterSemanticsScrollView.h
SemanticsObjectContainer::semanticsObject
SemanticsObject * semanticsObject
Definition: SemanticsObject.h:235
-[SemanticsObject accessibilityScrollToVisibleWithChild:]
BOOL accessibilityScrollToVisibleWithChild:(id child)
TextInputSemanticsObject.h
SemanticsObject(UIFocusSystem)
Definition: SemanticsObject+UIFocusSystem.mm:32
-[SemanticsObject setSemanticsNode:]
void setSemanticsNode:(const flutter::SemanticsNode *NS_REQUIRES_SUPER)
Definition: SemanticsObject.mm:333
FlutterMacros.h
FlutterScrollableSemanticsObject::scrollView
FlutterSemanticsScrollView * scrollView
Definition: SemanticsObject.h:190
FlutterStandardMessageCodec
Definition: FlutterCodecs.h:209
FlutterSemanticsObject
Definition: SemanticsObject.h:155
SemanticsObject::childrenInHitTestOrder
NSArray< SemanticsObject * > * childrenInHitTestOrder
Definition: SemanticsObject.h:74
kScrollExtentMaxForInf
constexpr float kScrollExtentMaxForInf
Definition: SemanticsObject.h:18
-[SemanticsObject _accessibilityHitTest:withEvent:]
id _accessibilityHitTest:withEvent:(CGPoint point,[withEvent] UIEvent *event)
FlutterPlatformViews_Internal.h
FlutterSwitchSemanticsObject
Definition: SemanticsObject.h:183
SemanticsObject::children
NSArray< SemanticsObject * > * children
Definition: SemanticsObject.h:68
SemanticsObject::nativeAccessibility
id nativeAccessibility
Definition: SemanticsObject.h:83
SemanticsObject.h
TextInputSemanticsObject
Definition: TextInputSemanticsObject.h:20
FlutterStandardTypedData
Definition: FlutterCodecs.h:300
FlutterPlatformViewSemanticsContainer
Definition: SemanticsObject.h:169
FlutterStandardTypedData::data
NSData * data
Definition: FlutterCodecs.h:344
FlutterTouchInterceptingView
Definition: FlutterPlatformViews.mm:521
flutter::testing::MockAccessibilityBridgeNoWindow
Definition: SemanticsObjectTestMocks.h:63
FlutterTouchInterceptingView_Test.h
FlutterScrollableSemanticsObject(UIFocusItemScrollableContainer)
Definition: SemanticsObjectTest.mm:23
-[SemanticsObject accessibilityBridgeDidFinishUpdate]
void accessibilityBridgeDidFinishUpdate()
Definition: SemanticsObject.mm:337
SemanticsObjectContainer
Definition: SemanticsObject.h:227
SemanticsObjectTest
Definition: SemanticsObjectTest.mm:31
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
flutter::testing::MockAccessibilityBridge::isVoiceOverRunningValue
bool isVoiceOverRunningValue
Definition: SemanticsObjectTestMocks.h:56
FlutterScrollableSemanticsObject
Definition: SemanticsObject.h:189
+[FlutterMessageCodec-p sharedInstance]
instancetype sharedInstance()
SemanticsObject
Definition: SemanticsObject.h:31