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)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden {
290
291 node.flags.hasImplicitScrolling = true;
292 node.flags.isHidden = true;
293 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
294 [object setSemanticsNode:&node];
295 XCTAssertEqual(object.isAccessibilityElement, YES);
296}
297
298- (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
303 node.flags.hasImplicitScrolling = true;
304 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
305 [object setSemanticsNode:&node];
306 XCTAssertEqual(object.isAccessibilityElement, NO);
307}
308
309- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
314 node.label = "foo";
315 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
316 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
317 object.children = @[ child1 ];
318 [object setSemanticsNode:&node];
319 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
320}
321
322- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
327 node.label = "foo";
328 node.flags.isTextField = true;
329 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
330 [object setSemanticsNode:&node];
331 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
332}
333
334- (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
339 node.label = "foo";
340
341 node.flags.isButton = true;
342 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
343 [object setSemanticsNode:&node];
344 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
345}
346
347- (void)testVerticalFlutterScrollableSemanticsObject {
351
352 float transformScale = 0.5f;
353 float screenScale = [[bridge->view() window] screen].scale;
354 float effectivelyScale = transformScale / screenScale;
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
363 node.flags.hasImplicitScrolling = true;
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 UIScrollView* scrollView = [scrollable nativeAccessibility];
375
376 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
377 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
378 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
380 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
382
383 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
385 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
386 (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
387
388 XCTAssertEqual(scrollView.contentOffset.x, 0);
389 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
391}
392
393- (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
397
398 float transformScale = 0.5f;
399 float x = 10;
400 float y = 10;
401 float w = 100;
402 float h = 200;
403 float scrollExtentMax = 500.0;
404 float scrollPosition = 150.0;
405
407 node.flags.hasImplicitScrolling = true;
409 node.rect = SkRect::MakeXYWH(x, y, w, h);
410 node.scrollExtentMax = scrollExtentMax;
411 node.scrollPosition = scrollPosition;
412 node.transform = {
413 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
415 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
416 [scrollable setSemanticsNode:&node];
418 XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
419}
420
421- (void)testHorizontalFlutterScrollableSemanticsObject {
425
426 float transformScale = 0.5f;
427 float screenScale = [[bridge->view() window] screen].scale;
428 float effectivelyScale = transformScale / screenScale;
429 float x = 10;
430 float y = 10;
431 float w = 100;
432 float h = 200;
433 float scrollExtentMax = 500.0;
434 float scrollPosition = 150.0;
435
437 node.flags.hasImplicitScrolling = true;
439 node.rect = SkRect::MakeXYWH(x, y, w, h);
440 node.scrollExtentMax = scrollExtentMax;
441 node.scrollPosition = scrollPosition;
442 node.transform = {
443 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
445 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
446 [scrollable setSemanticsNode:&node];
448 UIScrollView* scrollView = [scrollable nativeAccessibility];
449
450 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
451 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
452 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
454 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
456
457 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
459 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
461
462 XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
464 XCTAssertEqual(scrollView.contentOffset.y, 0);
465}
466
467- (void)testCanHandleInfiniteScrollExtent {
471
472 float transformScale = 0.5f;
473 float screenScale = [[bridge->view() window] screen].scale;
474 float effectivelyScale = transformScale / screenScale;
475 float x = 10;
476 float y = 10;
477 float w = 100;
478 float h = 200;
479 float scrollExtentMax = INFINITY;
480 float scrollPosition = 150.0;
481
483 node.flags.hasImplicitScrolling = true;
485 node.rect = SkRect::MakeXYWH(x, y, w, h);
486 node.scrollExtentMax = scrollExtentMax;
487 node.scrollPosition = scrollPosition;
488 node.transform = {
489 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
491 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
492 [scrollable setSemanticsNode:&node];
494 UIScrollView* scrollView = [scrollable nativeAccessibility];
495 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
496 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
497 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
499 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
501
502 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
504 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
505 (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
507
508 XCTAssertEqual(scrollView.contentOffset.x, 0);
509 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
511}
512
513- (void)testCanHandleNaNScrollExtentAndScrollPoisition {
517
518 float transformScale = 0.5f;
519 float screenScale = [[bridge->view() window] screen].scale;
520 float effectivelyScale = transformScale / screenScale;
521 float x = 10;
522 float y = 10;
523 float w = 100;
524 float h = 200;
525 float scrollExtentMax = std::nan("");
526 float scrollPosition = std::nan("");
527
529 node.flags.hasImplicitScrolling = true;
531 node.rect = SkRect::MakeXYWH(x, y, w, h);
532 node.scrollExtentMax = scrollExtentMax;
533 node.scrollPosition = scrollPosition;
534 node.transform = {
535 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
537 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
538 [scrollable setSemanticsNode:&node];
540 UIScrollView* scrollView = [scrollable nativeAccessibility];
541
542 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
543 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
544 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
546 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
548
549 // Content size equal to the scrollable size.
550 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
552 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
554
555 XCTAssertEqual(scrollView.contentOffset.x, 0);
556 XCTAssertEqual(scrollView.contentOffset.y, 0);
557}
558
559- (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
563
565 node.flags.hasImplicitScrolling = true;
567 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
568 node.scrollExtentMax = 100.0;
569 node.scrollPosition = 0.0;
570
572 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
573 [scrollable setSemanticsNode:&node];
575 UIScrollView* scrollView = [scrollable nativeAccessibility];
576 XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
577}
578
579- (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
581 mock->isVoiceOverRunningValue = false;
584
586 node.flags.hasImplicitScrolling = true;
588 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
589 node.scrollExtentMax = 100.0;
590 node.scrollPosition = 0.0;
591
593 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
594 [scrollable setSemanticsNode:&node];
596 UIScrollView* scrollView = [scrollable nativeAccessibility];
597 XCTAssertTrue(scrollView.isAccessibilityElement);
598 mock->isVoiceOverRunningValue = true;
599 XCTAssertFalse(scrollView.isAccessibilityElement);
600}
601
602- (void)testFlutterSemanticsObjectHasIdentifier {
604 mock->isVoiceOverRunningValue = true;
607
609 node.identifier = "identifier";
610
611 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
612 [object setSemanticsNode:&node];
613 XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
614}
615
616- (void)testFlutterSemanticsObjectHasLocale {
618 mock->isVoiceOverRunningValue = true;
621
623 node.locale = "es-MX";
624
625 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
626 [object setSemanticsNode:&node];
627 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
628}
629
630- (void)testFlutterSemanticsObjectUseDefaultLocale {
632 mock->isVoiceOverRunningValue = true;
635
637 mock->mockedLocale = @"es-MX";
638
639 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
640 [object setSemanticsNode:&node];
641 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]);
642}
643
644- (void)testFlutterSemanticsObjectPrioritizedSectionLocale {
646 mock->isVoiceOverRunningValue = true;
649
651 // Set both locales.
652 mock->mockedLocale = @"es-MX";
653 node.locale = "zh-TW";
654
655 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
656 [object setSemanticsNode:&node];
657 // node.locale takes priority.
658 XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"zh-TW"]);
659}
660
661- (void)testFlutterSemanticsObjectLocaleNil {
663 mock->isVoiceOverRunningValue = true;
666
668
669 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
670 [object setSemanticsNode:&node];
671 XCTAssertTrue(object.accessibilityLanguage == nil);
672}
673
674- (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
676 mock->isVoiceOverRunningValue = true;
679
681 node.flags.hasImplicitScrolling = true;
683 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
684 node.label = "label";
685 node.value = "value";
686 node.hint = "hint";
687 node.scrollExtentMax = 100.0;
688 node.scrollPosition = 0.0;
689
691 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
692 [scrollable setSemanticsNode:&node];
694 UIScrollView* scrollView = [scrollable nativeAccessibility];
695 XCTAssertTrue(scrollView.isAccessibilityElement);
696 XCTAssertTrue(
697 [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
698 XCTAssertTrue(
699 [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
700 XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
701}
702
703- (void)testFlutterSemanticsObjectMergeTooltipToLabel {
705 mock->isVoiceOverRunningValue = true;
708
710 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
711 node.label = "label";
712 node.tooltip = "tooltip";
713 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
714 [object setSemanticsNode:&node];
715 XCTAssertTrue(object.isAccessibilityElement);
716 XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
717}
718
719- (void)testSemanticsObjectContainerAccessibilityFrameCoversEntireScreen {
721 mock->isVoiceOverRunningValue = true;
724
726 parent.id = 0;
727 parent.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
728
730 child.id = 1;
731 child.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
732 child.rect = SkRect::MakeXYWH(0, 0, 100, 100);
733 parent.childrenInTraversalOrder.push_back(1);
734
735 FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
736 uid:0];
737 [parentObject setSemanticsNode:&parent];
738
739 FlutterSemanticsObject* childObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
740 uid:1];
741 [childObject setSemanticsNode:&child];
742
743 parentObject.children = @[ childObject ];
746
747 SemanticsObjectContainer* container =
748 static_cast<SemanticsObjectContainer*>(parentObject.accessibilityContainer);
749
750 XCTAssertTrue(childObject.accessibilityRespondsToUserInteraction);
751 XCTAssertTrue(CGRectEqualToRect(container.accessibilityFrame, UIScreen.mainScreen.bounds));
752}
753
754- (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
756 mock->isVoiceOverRunningValue = true;
759
761 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
762 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
763 [object setSemanticsNode:&node];
764 XCTAssertTrue(object.accessibilityAttributedLabel == nil);
765}
766
767- (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
769 mock->isVoiceOverRunningValue = true;
772
774 parent.id = 0;
775 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
776 parent.label = "label";
777 parent.value = "value";
778 parent.hint = "hint";
779
781 node.id = 1;
782 node.flags.hasImplicitScrolling = true;
784 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
785 node.label = "label";
786 node.value = "value";
787 node.hint = "hint";
788 node.scrollExtentMax = 100.0;
789 node.scrollPosition = 0.0;
790 parent.childrenInTraversalOrder.push_back(1);
791
792 FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
793 uid:0];
794 [parentObject setSemanticsNode:&parent];
795
797 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
798 [scrollable setSemanticsNode:&node];
799 UIScrollView* scrollView = [scrollable nativeAccessibility];
800
801 parentObject.children = @[ scrollable ];
804 XCTAssertTrue(scrollView.isAccessibilityElement);
805 SemanticsObjectContainer* container =
806 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
807 XCTAssertEqual(container.semanticsObject, parentObject);
808}
809
810- (void)testFlutterScrollableSemanticsObjectNoScrollBarOrContentInsets {
814
816 node.flags.hasImplicitScrolling = true;
818 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
819 node.scrollExtentMax = 100.0;
820 node.scrollPosition = 0.0;
821
823 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
824 [scrollable setSemanticsNode:&node];
826 UIScrollView* scrollView = [scrollable nativeAccessibility];
827
828 XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
829 XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
830 XCTAssertEqual(scrollView.contentInsetAdjustmentBehavior,
831 UIScrollViewContentInsetAdjustmentNever);
832 XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, UIEdgeInsetsZero));
833}
834
835- (void)testSemanticsObjectBuildsAttributedString {
840 node.label = "label";
841 std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
842 std::make_shared<flutter::SpellOutStringAttribute>();
843 attribute->start = 1;
844 attribute->end = 2;
846 node.labelAttributes.push_back(attribute);
847 node.value = "value";
848 attribute = std::make_shared<flutter::SpellOutStringAttribute>();
849 attribute->start = 2;
850 attribute->end = 3;
852 node.valueAttributes.push_back(attribute);
853 node.hint = "hint";
854 std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
855 std::make_shared<flutter::LocaleStringAttribute>();
856 local_attribute->start = 3;
857 local_attribute->end = 4;
858 local_attribute->type = flutter::StringAttributeType::kLocale;
859 local_attribute->locale = "en-MX";
860 node.hintAttributes.push_back(local_attribute);
861 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
862 [object setSemanticsNode:&node];
863 NSMutableAttributedString* expectedAttributedLabel =
864 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
865 NSDictionary* attributeDict = @{
866 UIAccessibilitySpeechAttributeSpellOut : @YES,
867 };
868 [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
869 XCTAssertTrue(
870 [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
871
872 NSMutableAttributedString* expectedAttributedValue =
873 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
874 attributeDict = @{
875 UIAccessibilitySpeechAttributeSpellOut : @YES,
876 };
877 [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
878 XCTAssertTrue(
879 [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
880
881 NSMutableAttributedString* expectedAttributedHint =
882 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
883 attributeDict = @{
884 UIAccessibilitySpeechAttributeLanguage : @"en-MX",
885 };
886 [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
887 XCTAssertTrue(
888 [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
889}
890
891- (void)testShouldTriggerAnnouncement {
895 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
896
897 // Handle nil with no node set.
898 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
899
900 // Handle initial setting of node with liveRegion
902 node.flags.isLiveRegion = true;
903 node.label = "foo";
904 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
905
906 // Handle nil with node set.
907 [object setSemanticsNode:&node];
908 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
909
910 // Handle new node, still has live region, same label.
911 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
912
913 // Handle update node with new label, still has live region.
914 flutter::SemanticsNode updatedNode;
915 updatedNode.flags.isLiveRegion = true;
916 updatedNode.label = "bar";
917 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
918
919 // Handle dropping the live region flag.
920 updatedNode.flags = flutter::SemanticsFlags{};
921 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
922
923 // Handle adding the flag when the label has not changed.
924 updatedNode.label = "foo";
925 [object setSemanticsNode:&updatedNode];
926 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
927}
928
929- (void)testShouldDispatchShowOnScreenActionForHeader {
933 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
934
935 // Handle initial setting of node with header.
937 node.flags.isHeader = true;
938 node.label = "foo";
939
940 [object setSemanticsNode:&node];
941
942 // Simulate accessibility focus.
943 [object accessibilityElementDidBecomeFocused];
944
945 XCTAssertTrue(bridge->observations.size() == 1);
946 XCTAssertTrue(bridge->observations[0].id == 1);
947 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
948}
949
950- (void)testShouldDispatchShowOnScreenActionForHidden {
954 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
955
956 // Handle initial setting of node with hidden.
958 node.flags.isHidden = true;
959 node.label = "foo";
960
961 [object setSemanticsNode:&node];
962
963 // Simulate accessibility focus.
964 [object accessibilityElementDidBecomeFocused];
965
966 XCTAssertTrue(bridge->observations.size() == 1);
967 XCTAssertTrue(bridge->observations[0].id == 1);
968 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
969}
970
971- (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
975 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
976 uid:1];
977
978 // Handle initial setting of node with header.
982 node.label = "foo";
983 [object setSemanticsNode:&node];
984 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
985 UISwitch* nativeSwitch = [[UISwitch alloc] init];
986 nativeSwitch.on = YES;
987
988 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
989 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
990
991 // Set the toggled to false;
995
996 update.label = "foo";
997 [object setSemanticsNode:&update];
998
999 nativeSwitch.on = NO;
1000
1001 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
1002 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
1003}
1004
1005- (void)testFlutterSemanticsObjectOfRadioButton {
1009 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
1010
1011 // Handle initial setting of node with header.
1016 node.label = "foo";
1017 [object setSemanticsNode:&node];
1018 XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
1019 XCTAssertNil(object.accessibilityValue);
1020}
1021
1022- (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
1026 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
1027 uid:1];
1028
1029 // Handle initial setting of node with header.
1032 node.label = "foo";
1033 [object setSemanticsNode:&node];
1034 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
1035 UISwitch* nativeSwitch = [[UISwitch alloc] init];
1036 nativeSwitch.on = YES;
1037 nativeSwitch.enabled = NO;
1038
1039 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
1040 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
1041}
1042
1043- (void)testSemanticsObjectDeallocated {
1047 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1048 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1049 parent.children = @[ child ];
1050 // Validate SemanticsObject deallocation does not crash.
1051 // https://github.com/flutter/flutter/issues/66032
1052 __weak SemanticsObject* weakObject = parent;
1053 parent = nil;
1054 XCTAssertNil(weakObject);
1055}
1056
1057- (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
1058 FlutterSemanticsObject* parentObject;
1060 FlutterSemanticsObject* object2;
1061
1063 parent.id = 0;
1064 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1065 parent.label = "label";
1066 parent.value = "value";
1067 parent.hint = "hint";
1068
1070 node.id = 1;
1071 node.flags.hasImplicitScrolling = true;
1073 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1074 node.label = "label";
1075 node.value = "value";
1076 node.hint = "hint";
1077 node.scrollExtentMax = 100.0;
1078 node.scrollPosition = 0.0;
1079 parent.childrenInTraversalOrder.push_back(1);
1080
1082 node2.id = 2;
1083 node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1084 node2.label = "label";
1085 node2.value = "value";
1086 node2.hint = "hint";
1087 node2.scrollExtentMax = 100.0;
1088 node2.scrollPosition = 0.0;
1089 parent.childrenInTraversalOrder.push_back(2);
1090
1091 {
1094 mock->isVoiceOverRunningValue = true;
1097
1098 parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
1099 [parentObject setSemanticsNode:&parent];
1100
1101 scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
1102 [scrollable setSemanticsNode:&node];
1104
1105 object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
1106 [object2 setSemanticsNode:&node2];
1107
1108 parentObject.children = @[ scrollable, object2 ];
1112
1113 // Returns the correct container if the bridge is alive.
1114 SemanticsObjectContainer* container =
1115 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
1116 XCTAssertEqual(container.semanticsObject, parentObject);
1117 SemanticsObjectContainer* container2 =
1118 static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
1119 XCTAssertEqual(container2.semanticsObject, parentObject);
1120 }
1121 // The bridge pointer went out of scope and was deallocated.
1122
1123 XCTAssertNil(scrollable.accessibilityContainer);
1124 XCTAssertNil(object2.accessibilityContainer);
1125}
1126
1127- (void)testAccessibilityHitTestSearchCanReturnPlatformView {
1131 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1132 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1133 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
1134 FlutterTouchInterceptingView* platformView =
1135 [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
1136 FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
1137 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1138 uid:1
1139 platformView:platformView];
1140
1141 object0.children = @[ object1 ];
1142 object0.childrenInHitTestOrder = @[ object1 ];
1143 object1.children = @[ platformViewSemanticsContainer, object3 ];
1144 object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
1145
1147 node0.id = 0;
1148 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1149 node0.label = "0";
1150 [object0 setSemanticsNode:&node0];
1151
1153 node1.id = 1;
1154 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1155 node1.label = "1";
1156 [object1 setSemanticsNode:&node1];
1157
1159 node2.id = 2;
1160 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1161 node2.label = "2";
1162 [platformViewSemanticsContainer setSemanticsNode:&node2];
1163
1165 node3.id = 3;
1166 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1167 node3.label = "3";
1168 [object3 setSemanticsNode:&node3];
1169
1170 CGPoint point = CGPointMake(10, 10);
1171 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1172
1173 XCTAssertEqual(hitTestResult, platformView);
1174}
1175
1176- (void)testFlutterPlatformViewSemanticsContainer {
1180 __weak FlutterTouchInterceptingView* weakPlatformView;
1181 __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1182 @autoreleasepool {
1183 FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1184 weakPlatformView = platformView;
1185
1186 @autoreleasepool {
1188 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1189 uid:1
1190 platformView:platformView];
1191 weakContainer = container;
1192 XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1193 XCTAssertNotNil(weakPlatformView);
1194 XCTAssertNotNil(weakContainer);
1195 }
1196 // Check the variables are still lived.
1197 // `container` is `retain` in `platformView`, so it will not be nil here.
1198 XCTAssertNotNil(weakPlatformView);
1199 XCTAssertNotNil(weakContainer);
1200 }
1201 // Check if there's no more strong references to `platformView` after container and platformView
1202 // are released.
1203 XCTAssertNil(weakPlatformView);
1204 XCTAssertNil(weakContainer);
1205}
1206
1207- (void)testTextInputSemanticsObject {
1211
1213 node.label = "foo";
1214 node.flags.isTextField = true;
1215 node.flags.isReadOnly = true;
1216 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1217 [object setSemanticsNode:&node];
1219 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1220}
1221
1222- (void)testTextInputSemanticsObject_canPerformAction {
1226
1228 node.label = "foo";
1229 node.flags.isTextField = true;
1230 node.flags.isReadOnly = true;
1231 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1232 [object setSemanticsNode:&node];
1234
1235 id textInputSurrogate = OCMClassMock([UIResponder class]);
1236 id partialSemanticsObject = OCMPartialMock(object);
1237 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1238
1239 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1240 .andReturn(YES);
1241 XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1242
1243 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1244 .andReturn(NO);
1245 XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1246}
1247
1248- (void)testTextInputSemanticsObject_editActions {
1252
1254 node.label = "foo";
1255
1256 node.flags.isTextField = true;
1257 node.flags.isReadOnly = true;
1258 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1259 [object setSemanticsNode:&node];
1261
1262 id textInputSurrogate = OCMClassMock([UIResponder class]);
1263 id partialSemanticsObject = OCMPartialMock(object);
1264 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1265
1266 XCTestExpectation* copyExpectation =
1267 [self expectationWithDescription:@"Surrogate's copy method is called."];
1268 XCTestExpectation* cutExpectation =
1269 [self expectationWithDescription:@"Surrogate's cut method is called."];
1270 XCTestExpectation* pasteExpectation =
1271 [self expectationWithDescription:@"Surrogate's paste method is called."];
1272 XCTestExpectation* selectAllExpectation =
1273 [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1274 XCTestExpectation* deleteExpectation =
1275 [self expectationWithDescription:@"Surrogate's delete method is called."];
1276
1277 OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1278 [copyExpectation fulfill];
1279 });
1280 OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1281 [cutExpectation fulfill];
1282 });
1283 OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1284 [pasteExpectation fulfill];
1285 });
1286 OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1287 [selectAllExpectation fulfill];
1288 });
1289 OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1290 [deleteExpectation fulfill];
1291 });
1292
1293 [partialSemanticsObject copy:nil];
1294 [partialSemanticsObject cut:nil];
1295 [partialSemanticsObject paste:nil];
1296 [partialSemanticsObject selectAll:nil];
1297 [partialSemanticsObject delete:nil];
1298
1299 [self waitForExpectationsWithTimeout:1 handler:nil];
1300}
1301
1302- (void)testSliderSemanticsObject {
1306
1308 node.flags.isSlider = true;
1309 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1310 [object setSemanticsNode:&node];
1312 XCTAssertEqual([object accessibilityActivate], YES);
1313}
1314
1315- (void)testUIFocusItemConformance {
1319 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1320 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1321 parent.children = @[ child ];
1322
1323 // parentFocusEnvironment
1324 XCTAssertTrue([parent.parentFocusEnvironment isKindOfClass:[UIView class]]);
1325 XCTAssertEqual(child.parentFocusEnvironment, child.parent);
1326
1327 // canBecomeFocused
1328 flutter::SemanticsNode childNode;
1329 childNode.flags.isHidden = true;
1330 childNode.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
1331 [child setSemanticsNode:&childNode];
1332 XCTAssertFalse(child.canBecomeFocused);
1333 childNode.flags = flutter::SemanticsFlags{};
1334 [child setSemanticsNode:&childNode];
1335 XCTAssertTrue(child.canBecomeFocused);
1336 childNode.actions = 0;
1337 [child setSemanticsNode:&childNode];
1338 XCTAssertFalse(child.canBecomeFocused);
1339
1340 CGFloat scale = ((bridge->view().window.screen ?: UIScreen.mainScreen)).scale;
1341
1342 childNode.rect = SkRect::MakeXYWH(0, 0, 100 * scale, 100 * scale);
1343 [child setSemanticsNode:&childNode];
1344 flutter::SemanticsNode parentNode;
1345 parentNode.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1346 [parent setSemanticsNode:&parentNode];
1347
1348 XCTAssertTrue(CGRectEqualToRect(child.frame, CGRectMake(0, 0, 100, 100)));
1349}
1350
1351- (void)testUIFocusItemContainerConformance {
1355 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
1356 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
1357 SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
1358 parent.childrenInHitTestOrder = @[ child1, child2 ];
1359
1360 // focusItemsInRect
1361 NSArray<id<UIFocusItem>>* itemsInRect = [parent focusItemsInRect:CGRectMake(0, 0, 100, 100)];
1362 XCTAssertEqual(itemsInRect.count, (unsigned long)2);
1363 XCTAssertTrue([itemsInRect containsObject:child1]);
1364 XCTAssertTrue([itemsInRect containsObject:child2]);
1365}
1366
1367- (void)testUIFocusItemScrollableContainerConformance {
1372 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1373
1374 // setContentOffset
1375 CGPoint p = CGPointMake(123.0, 456.0);
1376 [scrollable.scrollView scrollViewWillEndDragging:scrollable.scrollView
1377 withVelocity:CGPointZero
1378 targetContentOffset:&p];
1379 scrollable.scrollView.contentOffset = p;
1380 [scrollable.scrollView scrollViewDidEndDecelerating:scrollable.scrollView];
1381 XCTAssertEqual(bridge->observations.size(), (size_t)1);
1382 XCTAssertEqual(bridge->observations[0].id, 5);
1383 XCTAssertEqual(bridge->observations[0].action, flutter::SemanticsAction::kScrollToOffset);
1384
1385 std::vector<uint8_t> args = bridge->observations[0].args;
1386 XCTAssertEqual(args.size(), 3 * sizeof(CGFloat));
1387
1388 NSData* encoded = [NSData dataWithBytes:args.data() length:args.size()];
1390 CGPoint point = CGPointZero;
1391 memcpy(&point, decoded.data.bytes, decoded.data.length);
1392 XCTAssertTrue(CGPointEqualToPoint(point, p));
1393}
1394
1395- (void)testUIFocusItemScrollableContainerNoFeedbackLoops {
1400 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:5];
1401
1402 // setContentOffset
1403 const CGPoint p = CGPointMake(0.0, 456.0);
1404 scrollable.scrollView.contentOffset = p;
1405 bridge->observations.clear();
1406
1407 const SkScalar scrollPosition = p.y + 0.0000000000000001;
1409 node.flags.hasImplicitScrolling = true;
1411 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
1412 node.scrollExtentMax = 10000;
1413 node.scrollPosition = scrollPosition;
1414 node.transform = {1.0, 0, 0, 0, 0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, scrollPosition, 0, 1.0};
1415 [scrollable setSemanticsNode:&node];
1417
1418 XCTAssertEqual(bridge->observations.size(), (size_t)0);
1419}
1420@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