Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
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)testUIFocusSystemMethodsDoNotCrashWhenBridgeIsDead {
46 SemanticsObject* object;
47 {
48 auto mock_bridge = std::make_unique<flutter::testing::MockAccessibilityBridge>();
50 bridge = factory.GetWeakPtr();
51 object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
52 }
53 // Now bridge is nullptr.
54
55 XCTAssertNil([object parentFocusEnvironment]);
56 XCTAssertNil([object coordinateSpace]);
57 XCTAssertTrue(CGRectEqualToRect([object frame], CGRectZero));
58}
59
60- (void)testSetChildren {
64 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
65 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
66 parent.children = @[ child ];
67 XCTAssertEqual(parent, child.parent);
68 parent.children = @[];
69 XCTAssertNil(child.parent);
70}
71
72- (void)testAccessibilityHitTestFocusAtLeaf {
76 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
77 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
78 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
79 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
80 object0.children = @[ object1 ];
81 object0.childrenInHitTestOrder = @[ object1 ];
82 object1.children = @[ object2, object3 ];
83 object1.childrenInHitTestOrder = @[ object2, object3 ];
84
86 node0.id = 0;
87 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
88 node0.label = "0";
89 [object0 setSemanticsNode:&node0];
90
92 node1.id = 1;
93 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
94 node1.label = "1";
95 [object1 setSemanticsNode:&node1];
96
98 node2.id = 2;
99 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
100 node2.label = "2";
101 [object2 setSemanticsNode:&node2];
102
104 node3.id = 3;
105 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
106 node3.label = "3";
107 [object3 setSemanticsNode:&node3];
108
109 CGPoint point = CGPointMake(10, 10);
110 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
111
112 // Focus to object2 because it's the first object in hit test order
113 XCTAssertEqual(hitTestResult, object2);
114}
115
116- (void)testAccessibilityHitTestNoFocusableItem {
120 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
121 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
122 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
123 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
124 object0.children = @[ object1 ];
125 object0.childrenInHitTestOrder = @[ object1 ];
126 object1.children = @[ object2, object3 ];
127 object1.childrenInHitTestOrder = @[ object2, object3 ];
128
130 node0.id = 0;
131 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
132 [object0 setSemanticsNode:&node0];
133
135 node1.id = 1;
136 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
137 [object1 setSemanticsNode:&node1];
138
140 node2.id = 2;
141 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
142 [object2 setSemanticsNode:&node2];
143
145 node3.id = 3;
146 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
147 [object3 setSemanticsNode:&node3];
148
149 CGPoint point = CGPointMake(10, 10);
150 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
151
152 XCTAssertNil(hitTestResult);
153}
154
155- (void)testAccessibilityScrollToVisible {
159 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
160
162 node3.id = 3;
163 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
164 [object3 setSemanticsNode:&node3];
165
167
168 XCTAssertTrue(bridge->observations.size() == 1);
169 XCTAssertTrue(bridge->observations[0].id == 3);
170 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
171}
172
173- (void)testFlutterScrollableSemanticsObjectIsNotAccessibilityElementWhenVoiceOverIsRunning {
175 mock->isVoiceOverRunningValue = true;
178
180 node.flags.hasImplicitScrolling = true;
181
183 static_cast<int32_t>(flutter::SemanticsAction::kCustomAction);
184
185 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
186 node.scrollExtentMax = 100.0;
187 node.scrollPosition = 0.0;
188
190 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
191 [scrollable setSemanticsNode:&node];
193
194 UIScrollView* scrollView = [scrollable nativeAccessibility];
195
196 XCTAssertFalse(scrollView.isAccessibilityElement);
197}
198
199- (void)testAccessibilityScrollToVisibleWithChild {
203 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
204
206 node3.id = 3;
207 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
208 [object3 setSemanticsNode:&node3];
209
211
212 XCTAssertTrue(bridge->observations.size() == 1);
213 XCTAssertTrue(bridge->observations[0].id == 3);
214 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
215}
216
217- (void)testAccessibilityHitTestOutOfRect {
221 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
222 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
223 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
224 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
225 object0.children = @[ object1 ];
226 object0.childrenInHitTestOrder = @[ object1 ];
227 object1.children = @[ object2, object3 ];
228 object1.childrenInHitTestOrder = @[ object2, object3 ];
229
231 node0.id = 0;
232 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
233 node0.label = "0";
234 [object0 setSemanticsNode:&node0];
235
237 node1.id = 1;
238 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
239 node1.label = "1";
240 [object1 setSemanticsNode:&node1];
241
243 node2.id = 2;
244 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
245 node2.label = "2";
246 [object2 setSemanticsNode:&node2];
247
249 node3.id = 3;
250 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
251 node3.label = "3";
252 [object3 setSemanticsNode:&node3];
253
254 CGPoint point = CGPointMake(300, 300);
255 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
256
257 XCTAssertNil(hitTestResult);
258}
259
260- (void)testReplaceChildAtIndex {
264 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
265 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
266 SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
267 parent.children = @[ child1 ];
268 [parent replaceChildAtIndex:0 withChild:child2];
269 XCTAssertNil(child1.parent);
270 XCTAssertEqual(parent, child2.parent);
271 XCTAssertEqualObjects(parent.children, @[ child2 ]);
272}
273
274- (void)testPlainSemanticsObjectWithLabelHasStaticTextTrait {
279 node.label = "foo";
280 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
281 [object setSemanticsNode:&node];
282 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitStaticText);
283}
284
285- (void)testPlainSemanticsObjectWithHeadingLevelHasHeaderTrait {
290 node.headingLevel = 2;
291 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
292 [object setSemanticsNode:&node];
293 XCTAssertTrue(([object accessibilityTraits] & UIAccessibilityTraitHeader) > 0);
294}
295
296- (void)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden {
301
302 node.flags.hasImplicitScrolling = true;
303 node.flags.isHidden = true;
304 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
305 [object setSemanticsNode:&node];
306 XCTAssertEqual(object.isAccessibilityElement, YES);
307}
308
309- (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
314 node.flags.hasImplicitScrolling = true;
315 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
316 [object setSemanticsNode:&node];
317 XCTAssertEqual(object.isAccessibilityElement, NO);
318}
319
320- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
325 node.label = "foo";
326 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
327 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
328 object.children = @[ child1 ];
329 [object setSemanticsNode:&node];
330 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
331}
332
333- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
338 node.label = "foo";
339 node.flags.isTextField = true;
340 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
341 [object setSemanticsNode:&node];
342 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
343}
344
345- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
350 node.label = "foo";
351
352 node.flags.isButton = true;
353 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
354 [object setSemanticsNode:&node];
355 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
356}
357
358- (void)testVerticalFlutterScrollableSemanticsObject {
362
363 float transformScale = 0.5f;
364 float screenScale = [[bridge->view() window] screen].scale;
365 float effectivelyScale = transformScale / screenScale;
366 float x = 10;
367 float y = 10;
368 float w = 100;
369 float h = 200;
370 float scrollExtentMax = 500.0;
371 float scrollPosition = 150.0;
372
374 node.flags.hasImplicitScrolling = true;
376 node.rect = SkRect::MakeXYWH(x, y, w, h);
377 node.scrollExtentMax = scrollExtentMax;
378 node.scrollPosition = scrollPosition;
379 node.transform = {
380 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
382 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
383 [scrollable setSemanticsNode:&node];
385 UIScrollView* scrollView = [scrollable nativeAccessibility];
386
387 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
388 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
389 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
391 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
393
394 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
396 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
397 (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
398
399 XCTAssertEqual(scrollView.contentOffset.x, 0);
400 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
402}
403
404- (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
408
409 float transformScale = 0.5f;
410 float x = 10;
411 float y = 10;
412 float w = 100;
413 float h = 200;
414 float scrollExtentMax = 500.0;
415 float scrollPosition = 150.0;
416
418 node.flags.hasImplicitScrolling = true;
420 node.rect = SkRect::MakeXYWH(x, y, w, h);
421 node.scrollExtentMax = scrollExtentMax;
422 node.scrollPosition = scrollPosition;
423 node.transform = {
424 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
426 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
427 [scrollable setSemanticsNode:&node];
429 XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
430}
431
432- (void)testHorizontalFlutterScrollableSemanticsObject {
436
437 float transformScale = 0.5f;
438 float screenScale = [[bridge->view() window] screen].scale;
439 float effectivelyScale = transformScale / screenScale;
440 float x = 10;
441 float y = 10;
442 float w = 100;
443 float h = 200;
444 float scrollExtentMax = 500.0;
445 float scrollPosition = 150.0;
446
448 node.flags.hasImplicitScrolling = true;
450 node.rect = SkRect::MakeXYWH(x, y, w, h);
451 node.scrollExtentMax = scrollExtentMax;
452 node.scrollPosition = scrollPosition;
453 node.transform = {
454 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
456 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
457 [scrollable setSemanticsNode:&node];
459 UIScrollView* scrollView = [scrollable nativeAccessibility];
460
461 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
462 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
463 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
465 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
467
468 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
470 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
472
473 XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
475 XCTAssertEqual(scrollView.contentOffset.y, 0);
476}
477
478- (void)testCanHandleInfiniteScrollExtent {
482
483 float transformScale = 0.5f;
484 float screenScale = [[bridge->view() window] screen].scale;
485 float effectivelyScale = transformScale / screenScale;
486 float x = 10;
487 float y = 10;
488 float w = 100;
489 float h = 200;
490 float scrollExtentMax = INFINITY;
491 float scrollPosition = 150.0;
492
494 node.flags.hasImplicitScrolling = true;
496 node.rect = SkRect::MakeXYWH(x, y, w, h);
497 node.scrollExtentMax = scrollExtentMax;
498 node.scrollPosition = scrollPosition;
499 node.transform = {
500 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
502 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
503 [scrollable setSemanticsNode:&node];
505 UIScrollView* scrollView = [scrollable nativeAccessibility];
506 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
507 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
508 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
510 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
512
513 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
515 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
516 (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
518
519 XCTAssertEqual(scrollView.contentOffset.x, 0);
520 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
522}
523
524- (void)testCanHandleNaNScrollExtentAndScrollPoisition {
528
529 float transformScale = 0.5f;
530 float screenScale = [[bridge->view() window] screen].scale;
531 float effectivelyScale = transformScale / screenScale;
532 float x = 10;
533 float y = 10;
534 float w = 100;
535 float h = 200;
536 float scrollExtentMax = std::nan("");
537 float scrollPosition = std::nan("");
538
540 node.flags.hasImplicitScrolling = true;
542 node.rect = SkRect::MakeXYWH(x, y, w, h);
543 node.scrollExtentMax = scrollExtentMax;
544 node.scrollPosition = scrollPosition;
545 node.transform = {
546 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
548 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
549 [scrollable setSemanticsNode:&node];
551 UIScrollView* scrollView = [scrollable nativeAccessibility];
552
553 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
554 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
555 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
557 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
559
560 // Content size equal to the scrollable size.
561 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
563 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
565
566 XCTAssertEqual(scrollView.contentOffset.x, 0);
567 XCTAssertEqual(scrollView.contentOffset.y, 0);
568}
569
570- (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
574
576 node.flags.hasImplicitScrolling = true;
578 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
579 node.scrollExtentMax = 100.0;
580 node.scrollPosition = 0.0;
581
583 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
584 [scrollable setSemanticsNode:&node];
586 UIScrollView* scrollView = [scrollable nativeAccessibility];
587 XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
588}
589
590- (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
592 mock->isVoiceOverRunningValue = false;
595
597 node.flags.hasImplicitScrolling = true;
599 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
600 node.scrollExtentMax = 100.0;
601 node.scrollPosition = 0.0;
602
604 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
605 [scrollable setSemanticsNode:&node];
607 UIScrollView* scrollView = [scrollable nativeAccessibility];
608 XCTAssertTrue(scrollView.isAccessibilityElement);
609 mock->isVoiceOverRunningValue = true;
610 XCTAssertFalse(scrollView.isAccessibilityElement);
611}
612
613- (void)testFlutterSemanticsObjectHasIdentifier {
615 mock->isVoiceOverRunningValue = true;
618
620 node.identifier = "identifier";
621
622 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
623 [object setSemanticsNode:&node];
624 XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
625}
626
627- (void)testFlutterSemanticsObjectHasLocale {
629 mock->isVoiceOverRunningValue = true;
632
634 node.locale = "es-MX";
635
636 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
637 [object setSemanticsNode:&node];
638 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
639}
640
641- (void)testFlutterSemanticsObjectUseDefaultLocale {
643 mock->isVoiceOverRunningValue = true;
646
648 mock->mockedLocale = @"es-MX";
649
650 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
651 [object setSemanticsNode:&node];
652 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
653}
654
655- (void)testFlutterSemanticsObjectPrioritizedSectionLocale {
657 mock->isVoiceOverRunningValue = true;
660
662 // Set both locales.
663 mock->mockedLocale = @"es-MX";
664 node.locale = "zh-TW";
665
666 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
667 [object setSemanticsNode:&node];
668 // node.locale takes priority.
669 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"zh-TW"]);
670}
671
672- (void)testFlutterSemanticsObjectLocaleNil {
674 mock->isVoiceOverRunningValue = true;
677
679
680 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
681 [object setSemanticsNode:&node];
682 XCTAssertTrue(object.accessibilityLanguage == nil);
683}
684
685- (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
687 mock->isVoiceOverRunningValue = true;
690
692 node.flags.hasImplicitScrolling = true;
694 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
695 node.label = "label";
696 node.value = "value";
697 node.hint = "hint";
698 node.scrollExtentMax = 100.0;
699 node.scrollPosition = 0.0;
700
702 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
703 [scrollable setSemanticsNode:&node];
705 UIScrollView* scrollView = [scrollable nativeAccessibility];
706 XCTAssertTrue(scrollView.isAccessibilityElement);
707 XCTAssertTrue(
708 [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
709 XCTAssertTrue(
710 [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
711 XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
712}
713
714- (void)testFlutterSemanticsObjectMergeTooltipToLabel {
716 mock->isVoiceOverRunningValue = true;
719
721 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
722 node.label = "label";
723 node.tooltip = "tooltip";
724 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
725 [object setSemanticsNode:&node];
726 XCTAssertTrue(object.isAccessibilityElement);
727 XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
728}
729
730- (void)testSemanticsObjectContainerAccessibilityFrameCoversEntireScreen {
732 mock->isVoiceOverRunningValue = true;
735
737 parent.id = 0;
738 parent.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
739
741 child.id = 1;
742 child.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
743 child.rect = SkRect::MakeXYWH(0, 0, 100, 100);
744 parent.childrenInTraversalOrder.push_back(1);
745
746 FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
747 uid:0];
748 [parentObject setSemanticsNode:&parent];
749
750 FlutterSemanticsObject* childObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
751 uid:1];
752 [childObject setSemanticsNode:&child];
753
754 parentObject.children = @[ childObject ];
757
758 SemanticsObjectContainer* container =
759 static_cast<SemanticsObjectContainer*>(parentObject.accessibilityContainer);
760
761 XCTAssertTrue(childObject.accessibilityRespondsToUserInteraction);
762 XCTAssertTrue(CGRectEqualToRect(container.accessibilityFrame, UIScreen.mainScreen.bounds));
763}
764
765- (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
767 mock->isVoiceOverRunningValue = true;
770
772 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
773 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
774 [object setSemanticsNode:&node];
775 XCTAssertTrue(object.accessibilityAttributedLabel == nil);
776}
777
778- (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
780 mock->isVoiceOverRunningValue = true;
783
785 parent.id = 0;
786 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
787 parent.label = "label";
788 parent.value = "value";
789 parent.hint = "hint";
790
792 node.id = 1;
793 node.flags.hasImplicitScrolling = true;
795 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
796 node.label = "label";
797 node.value = "value";
798 node.hint = "hint";
799 node.scrollExtentMax = 100.0;
800 node.scrollPosition = 0.0;
801 parent.childrenInTraversalOrder.push_back(1);
802
803 FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
804 uid:0];
805 [parentObject setSemanticsNode:&parent];
806
808 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
809 [scrollable setSemanticsNode:&node];
810 UIScrollView* scrollView = [scrollable nativeAccessibility];
811
812 parentObject.children = @[ scrollable ];
815 XCTAssertTrue(scrollView.isAccessibilityElement);
816 SemanticsObjectContainer* container =
817 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
818 XCTAssertEqual(container.semanticsObject, parentObject);
819}
820
821- (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
825
827 node.flags.hasImplicitScrolling = true;
829 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
830 node.scrollExtentMax = 100.0;
831 node.scrollPosition = 0.0;
832
834 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
835 [scrollable setSemanticsNode:&node];
837 UIScrollView* scrollView = [scrollable nativeAccessibility];
838
839 XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
840 XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
841 XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior,
842 UIScrollViewContentInsetAdjustmentNever);
843 XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero));
844}
845
846- (void)testSemanticsObjectBuildsAttributedString {
851 node.label = "label";
852 std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
853 std::make_shared<flutter::SpellOutStringAttribute>();
854 attribute->start = 1;
855 attribute->end = 2;
857 node.labelAttributes.push_back(attribute);
858 node.value = "value";
859 attribute = std::make_shared<flutter::SpellOutStringAttribute>();
860 attribute->start = 2;
861 attribute->end = 3;
863 node.valueAttributes.push_back(attribute);
864 node.hint = "hint";
865 std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
866 std::make_shared<flutter::LocaleStringAttribute>();
867 local_attribute->start = 3;
868 local_attribute->end = 4;
869 local_attribute->type = flutter::StringAttributeType::kLocale;
870 local_attribute->locale = "en-MX";
871 node.hintAttributes.push_back(local_attribute);
872 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
873 [object setSemanticsNode:&node];
874 NSMutableAttributedString* expectedAttributedLabel =
875 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
876 NSDictionary* attributeDict = @{
877 UIAccessibilitySpeechAttributeSpellOut : @YES,
878 };
879 [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
880 XCTAssertTrue(
881 [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
882
883 NSMutableAttributedString* expectedAttributedValue =
884 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
885 attributeDict = @{
886 UIAccessibilitySpeechAttributeSpellOut : @YES,
887 };
888 [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
889 XCTAssertTrue(
890 [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
891
892 NSMutableAttributedString* expectedAttributedHint =
893 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
894 attributeDict = @{
895 UIAccessibilitySpeechAttributeLanguage : @"en-MX",
896 };
897 [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
898 XCTAssertTrue(
899 [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
900}
901
902- (void)testShouldTriggerAnnouncement {
906 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
907
908 // Handle nil with no node set.
909 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
910
911 // Handle initial setting of node with liveRegion
913 node.flags.isLiveRegion = true;
914 node.label = "foo";
915 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
916
917 // Handle nil with node set.
918 [object setSemanticsNode:&node];
919 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
920
921 // Handle new node, still has live region, same label.
922 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
923
924 // Handle update node with new label, still has live region.
925 flutter::SemanticsNode updatedNode;
926 updatedNode.flags.isLiveRegion = true;
927 updatedNode.label = "bar";
928 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
929
930 // Handle dropping the live region flag.
931 updatedNode.flags = flutter::SemanticsFlags{};
932 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
933
934 // Handle adding the flag when the label has not changed.
935 updatedNode.label = "foo";
936 [object setSemanticsNode:&updatedNode];
937 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
938}
939
940- (void)testShouldDispatchShowOnScreenActionForHeader {
944 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
945
946 // Handle initial setting of node with header.
948 node.flags.isHeader = true;
949 node.label = "foo";
950
951 [object setSemanticsNode:&node];
952
953 // Simulate accessibility focus.
954 [object accessibilityElementDidBecomeFocused];
955
956 XCTAssertTrue(bridge->observations.size() == 1);
957 XCTAssertTrue(bridge->observations[0].id == 1);
958 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
959}
960
961- (void)testShouldDispatchShowOnScreenActionForHidden {
965 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
966
967 // Handle initial setting of node with hidden.
969 node.flags.isHidden = true;
970 node.label = "foo";
971
972 [object setSemanticsNode:&node];
973
974 // Simulate accessibility focus.
975 [object accessibilityElementDidBecomeFocused];
976
977 XCTAssertTrue(bridge->observations.size() == 1);
978 XCTAssertTrue(bridge->observations[0].id == 1);
979 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
980}
981
982- (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
986 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
987 uid:1];
988
989 // Handle initial setting of node with header.
993 node.label = "foo";
994 [object setSemanticsNode:&node];
995 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
996 UISwitch* nativeSwitch = [[UISwitch alloc] init];
997 nativeSwitch.on = YES;
998
999 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
1000 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
1001
1002 // Set the toggled to false;
1006
1007 update.label = "foo";
1008 [object setSemanticsNode:&update];
1009
1010 nativeSwitch.on = NO;
1011
1012 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
1013 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
1014}
1015
1016- (void)testFlutterSemanticsObjectOfRadioButton {
1020 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
1021
1022 // Handle initial setting of node with header.
1027 node.label = "foo";
1028 [object setSemanticsNode:&node];
1029 XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
1030 XCTAssertNil(object.accessibilityValue);
1031}
1032
1033- (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
1037 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
1038 uid:1];
1039
1040 // Handle initial setting of node with header.
1043 node.label = "foo";
1044 [object setSemanticsNode:&node];
1045 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
1046 UISwitch* nativeSwitch = [[UISwitch alloc] init];
1047 nativeSwitch.on = YES;
1048 nativeSwitch.enabled = NO;
1049
1050 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
1051 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
1052}
1053
1054- (void)testSemanticsObjectDeallocated {
1058 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1059 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1060 parent.children = @[ child ];
1061 // Validate SemanticsObject deallocation does not crash.
1062 // https://github.com/flutter/flutter/issues/66032
1063 __weak SemanticsObject* weakObject = parent;
1064 parent = nil;
1065 XCTAssertNil(weakObject);
1066}
1067
1068- (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
1069 FlutterSemanticsObject* parentObject;
1071 FlutterSemanticsObject* object2;
1072
1074 parent.id = 0;
1075 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1076 parent.label = "label";
1077 parent.value = "value";
1078 parent.hint = "hint";
1079
1081 node.id = 1;
1082 node.flags.hasImplicitScrolling = true;
1084 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1085 node.label = "label";
1086 node.value = "value";
1087 node.hint = "hint";
1088 node.scrollExtentMax = 100.0;
1089 node.scrollPosition = 0.0;
1090 parent.childrenInTraversalOrder.push_back(1);
1091
1093 node2.id = 2;
1094 node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1095 node2.label = "label";
1096 node2.value = "value";
1097 node2.hint = "hint";
1098 node2.scrollExtentMax = 100.0;
1099 node2.scrollPosition = 0.0;
1100 parent.childrenInTraversalOrder.push_back(2);
1101
1102 {
1105 mock->isVoiceOverRunningValue = true;
1108
1109 parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
1110 [parentObject setSemanticsNode:&parent];
1111
1112 scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
1113 [scrollable setSemanticsNode:&node];
1115
1116 object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
1117 [object2 setSemanticsNode:&node2];
1118
1119 parentObject.children = @[ scrollable, object2 ];
1123
1124 // Returns the correct container if the bridge is alive.
1125 SemanticsObjectContainer* container =
1126 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
1127 XCTAssertEqual(container.semanticsObject, parentObject);
1128 SemanticsObjectContainer* container2 =
1129 static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
1130 XCTAssertEqual(container2.semanticsObject, parentObject);
1131 }
1132 // The bridge pointer went out of scope and was deallocated.
1133
1134 XCTAssertNil(scrollable.accessibilityContainer);
1135 XCTAssertNil(object2.accessibilityContainer);
1136}
1137
1138- (void)testAccessibilityHitTestSearchCanReturnPlatformView {
1142 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1143 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1144 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
1145 FlutterTouchInterceptingView* platformView =
1146 [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
1147 FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
1148 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1149 uid:1
1150 platformView:platformView];
1151
1152 object0.children = @[ object1 ];
1153 object0.childrenInHitTestOrder = @[ object1 ];
1154 object1.children = @[ platformViewSemanticsContainer, object3 ];
1155 object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
1156
1158 node0.id = 0;
1159 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1160 node0.label = "0";
1161 [object0 setSemanticsNode:&node0];
1162
1164 node1.id = 1;
1165 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1166 node1.label = "1";
1167 [object1 setSemanticsNode:&node1];
1168
1170 node2.id = 2;
1171 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1172 node2.label = "2";
1173 [platformViewSemanticsContainer setSemanticsNode:&node2];
1174
1176 node3.id = 3;
1177 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1178 node3.label = "3";
1179 [object3 setSemanticsNode:&node3];
1180
1181 CGPoint point = CGPointMake(10, 10);
1182 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1183
1184 XCTAssertEqual(hitTestResult, platformView);
1185}
1186
1187- (void)testFlutterPlatformViewSemanticsContainer {
1191 __weak FlutterTouchInterceptingView* weakPlatformView;
1192 __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1193 @autoreleasepool {
1194 FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1195 weakPlatformView = platformView;
1196
1197 @autoreleasepool {
1199 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1200 uid:1
1201 platformView:platformView];
1202 weakContainer = container;
1203 XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1204 XCTAssertNotNil(weakPlatformView);
1205 XCTAssertNotNil(weakContainer);
1206 }
1207 // Check the variables are still lived.
1208 // `container` is `retain` in `platformView`, so it will not be nil here.
1209 XCTAssertNotNil(weakPlatformView);
1210 XCTAssertNotNil(weakContainer);
1211 }
1212 // Check if there's no more strong references to `platformView` after container and platformView
1213 // are released.
1214 XCTAssertNil(weakPlatformView);
1215 XCTAssertNil(weakContainer);
1216}
1217
1218- (void)testTextInputSemanticsObject {
1222
1224 node.label = "foo";
1225 node.flags.isTextField = true;
1226 node.flags.isReadOnly = true;
1227 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1228 [object setSemanticsNode:&node];
1230 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1231}
1232
1233- (void)testTextInputSemanticsObject_canPerformAction {
1237
1239 node.label = "foo";
1240 node.flags.isTextField = true;
1241 node.flags.isReadOnly = true;
1242 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1243 [object setSemanticsNode:&node];
1245
1246 id textInputSurrogate = OCMClassMock([UIResponder class]);
1247 id partialSemanticsObject = OCMPartialMock(object);
1248 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1249
1250 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1251 .andReturn(YES);
1252 XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1253
1254 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1255 .andReturn(NO);
1256 XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1257}
1258
1259- (void)testTextInputSemanticsObject_editActions {
1263
1265 node.label = "foo";
1266
1267 node.flags.isTextField = true;
1268 node.flags.isReadOnly = true;
1269 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1270 [object setSemanticsNode:&node];
1272
1273 id textInputSurrogate = OCMClassMock([UIResponder class]);
1274 id partialSemanticsObject = OCMPartialMock(object);
1275 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1276
1277 XCTestExpectation* copyExpectation =
1278 [self expectationWithDescription:@"Surrogate's copy method is called."];
1279 XCTestExpectation* cutExpectation =
1280 [self expectationWithDescription:@"Surrogate's cut method is called."];
1281 XCTestExpectation* pasteExpectation =
1282 [self expectationWithDescription:@"Surrogate's paste method is called."];
1283 XCTestExpectation* selectAllExpectation =
1284 [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1285 XCTestExpectation* deleteExpectation =
1286 [self expectationWithDescription:@"Surrogate's delete method is called."];
1287
1288 OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1289 [copyExpectation fulfill];
1290 });
1291 OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1292 [cutExpectation fulfill];
1293 });
1294 OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1295 [pasteExpectation fulfill];
1296 });
1297 OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1298 [selectAllExpectation fulfill];
1299 });
1300 OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1301 [deleteExpectation fulfill];
1302 });
1303
1304 [partialSemanticsObject copy:nil];
1305 [partialSemanticsObject cut:nil];
1306 [partialSemanticsObject paste:nil];
1307 [partialSemanticsObject selectAll:nil];
1308 [partialSemanticsObject delete:nil];
1309
1310 [self waitForExpectationsWithTimeout:1 handler:nil];
1311}
1312
1313- (void)testSliderSemanticsObject {
1317
1319 node.flags.isSlider = true;
1320 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1321 [object setSemanticsNode:&node];
1323 XCTAssertEqual([object accessibilityActivate], YES);
1324}
1325
1326- (void)testUIFocusItemConformance {
1330 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1331 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1332 parent.children = @[ child ];
1333
1334 // parentFocusEnvironment
1335 XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]);
1336 XCTAssertEqual(child.parentFocusEnvironment, child.parent);
1337
1338 // canBecomeFocused
1339 flutter::SemanticsNode childNode;
1340 childNode.flags.isHidden = true;
1341 childNode.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
1342 [child setSemanticsNode:&childNode];
1343 XCTAssertFalse(child.canBecomeFocused);
1344 childNode.flags = flutter::SemanticsFlags{};
1345 [child setSemanticsNode:&childNode];
1346 XCTAssertTrue(child.canBecomeFocused);
1347 childNode.actions = 0;
1348 [child setSemanticsNode:&childNode];
1349 XCTAssertFalse(child.canBecomeFocused);
1350
1351 CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale;
1352
1353 childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale);
1354 [child setSemanticsNode:&childNode];
1355 flutter::SemanticsNode parentNode;
1356 parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1357 [parent setSemanticsNode:&parentNode];
1358
1359 XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100)));
1360}
1361
1362- (void)testUIFocusItemContainerConformance {
1366 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1367 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1368 SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
1369 parent.childrenInHitTestOrder = @[ child1, child2 ];
1370
1371 // focusItemsInRect
1372 NSArray<id<UIFocusItem>>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)];
1373 XCTAssertEqual(itemsInRect.count, (unsigned long)2);
1374 XCTAssertTrue([itemsInRect containsObject:child1]);
1375 XCTAssertTrue([itemsInRect containsObject:child2]);
1376}
1377
1378- (void)testUIFocusItemScrollableContainerConformance {
1383 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1384
1385 // setContentOffset
1386 CGPoint p = CGPointMake(123.0, 456.0);
1387 [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView
1388 withVelocity:CGPointZero
1389 targetContentOffset:&p];
1390 scrollable.scrollView.contentOffset = p;
1391 [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView];
1392 XCTAssertEqual(bridge->observations.size(), (size_t)1);
1393 XCTAssertEqual(bridge->observations[0].id, 5);
1394 XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset);
1395
1396 std::vector<uint8_t> args = bridge->observations[0].args;
1397 XCTAssertEqual(args.size(), 3 * sizeof(CGFloat));
1398
1399 NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()];
1401 CGPoint point = CGPointZero;
1402 memcpy(&point, decoded.data.bytes, decoded.data.length);
1403 XCTAssertTrue(CGPointEqualToPoint(point, p));
1404}
1405
1406- (void)testUIFocusItemScrollableContainerNoFeedbackLoops {
1411 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1412
1413 // setContentOffset
1414 const CGPoint p = CGPointMake(0.0, 456.0);
1415 scrollable.scrollView.contentOffset = p;
1416 bridge->observations.clear();
1417
1418 const SkScalar scrollPosition = p.y + 0.0000000000000001;
1420 node.flags.hasImplicitScrolling = true;
1422 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1423 node.scrollExtentMax = 10000;
1424 node.scrollPosition = scrollPosition;
1425 node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0};
1426 [scrollable setSemanticsNode:&node];
1428
1429 XCTAssertEqual(bridge->observations.size(), (size_t)0);
1430}
1431@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