Flutter Engine
 
Loading...
Searching...
No Matches
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
18const float kFloatCompareEpsilon = 0.001;
19
20@interface SemanticsObject (UIFocusSystem) <UIFocusItem, UIFocusItemContainer>
21@end
22
23@interface FlutterScrollableSemanticsObject (UIFocusItemScrollableContainer) <
24 UIFocusItemScrollableContainer>
25@end
26
27@interface TextInputSemanticsObject (Test)
28- (UIView<UITextInput>*)textInputSurrogate;
29@end
30
31@interface SemanticsObjectTest : XCTestCase
32@end
33
34@implementation SemanticsObjectTest
35
36- (void)testCreate {
40 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
41 XCTAssertNotNil(object);
42}
43
44- (void)testSetChildren {
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 {
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
70 node0.id = 0;
71 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
72 node0.label = "0";
73 [object0 setSemanticsNode:&node0];
74
76 node1.id = 1;
77 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
78 node1.label = "1";
79 [object1 setSemanticsNode:&node1];
80
82 node2.id = 2;
83 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
84 node2.label = "2";
85 [object2 setSemanticsNode:&node2];
86
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 {
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
114 node0.id = 0;
115 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
116 [object0 setSemanticsNode:&node0];
117
119 node1.id = 1;
120 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
121 [object1 setSemanticsNode:&node1];
122
124 node2.id = 2;
125 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
126 [object2 setSemanticsNode:&node2];
127
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 {
143 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
144
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 {
161 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
162
164 node3.id = 3;
165 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
166 [object3 setSemanticsNode:&node3];
167
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 {
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
189 node0.id = 0;
190 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
191 node0.label = "0";
192 [object0 setSemanticsNode:&node0];
193
195 node1.id = 1;
196 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
197 node1.label = "1";
198 [object1 setSemanticsNode:&node1];
199
201 node2.id = 2;
202 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
203 node2.label = "2";
204 [object2 setSemanticsNode:&node2];
205
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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
321 node.flags.hasImplicitScrolling = true;
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 {
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
365 node.flags.hasImplicitScrolling = true;
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 {
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
395 node.flags.hasImplicitScrolling = true;
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 {
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
441 node.flags.hasImplicitScrolling = true;
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 {
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
487 node.flags.hasImplicitScrolling = true;
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 {
521
523 node.flags.hasImplicitScrolling = true;
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;
542
544 node.flags.hasImplicitScrolling = true;
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;
565
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;
579
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;
593
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;
607
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;
624
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;
637
639 node.flags.hasImplicitScrolling = true;
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;
666
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;
682
684 parent.id = 0;
685 parent.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
686
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 ];
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;
717
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;
730
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
739 node.id = 1;
740 node.flags.hasImplicitScrolling = true;
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 ];
762 XCTAssertTrue(scrollView.isAccessibilityElement);
763 SemanticsObjectContainer* container =
764 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
765 XCTAssertEqual(container.semanticsObject, parentObject);
766}
767
768- (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
772
774 node.flags.hasImplicitScrolling = true;
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 {
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;
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;
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 {
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
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 {
891 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
892
893 // Handle initial setting of node with header.
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 {
912 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
913
914 // Handle initial setting of node with hidden.
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 {
933 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
934 uid:1];
935
936 // Handle initial setting of node with header.
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;
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 {
967 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
968
969 // Handle initial setting of node with header.
974 node.label = "foo";
975 [object setSemanticsNode:&node];
976 XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
977 XCTAssertNil(object.accessibilityValue);
978}
979
980- (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
984 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
985 uid:1];
986
987 // Handle initial setting of node with header.
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 {
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
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
1028 node.id = 1;
1029 node.flags.hasImplicitScrolling = true;
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
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;
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 ];
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 {
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
1105 node0.id = 0;
1106 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1107 node0.label = "0";
1108 [object0 setSemanticsNode:&node0];
1109
1111 node1.id = 1;
1112 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1113 node1.label = "1";
1114 [object1 setSemanticsNode:&node1];
1115
1117 node2.id = 2;
1118 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1119 node2.label = "2";
1120 [platformViewSemanticsContainer setSemanticsNode:&node2];
1121
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 {
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 {
1169
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 {
1184
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 {
1210
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 {
1264
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 {
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 {
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 {
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 {
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;
1367 node.flags.hasImplicitScrolling = true;
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
const float kFloatCompareEpsilon
constexpr float kScrollExtentMaxForInf
FLUTTER_ASSERT_ARC const float kFloatCompareEpsilon
WeakPtr< T > GetWeakPtr() const
Definition weak_ptr.h:271
int32_t x
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
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)
static MockEpoxy * mock
Definition mock_epoxy.cc:53
double y
constexpr int kHorizontalScrollSemanticsActions
constexpr int kVerticalScrollSemanticsActions
instancetype sharedInstance()
SemanticsCheckState isChecked
SemanticsTristate isToggled
SemanticsTristate isEnabled
StringAttributes hintAttributes
StringAttributes valueAttributes
StringAttributes labelAttributes
std::vector< int32_t > childrenInTraversalOrder