| Flutter iOS Embedder |

SemanticsObjectTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7
8 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
14
15 FLUTTER_ASSERT_ARC
16
17 const float kFloatCompareEpsilon = 0.001;
18
19 @interface TextInputSemanticsObject (Test)
20 - (UIView<UITextInput>*)textInputSurrogate;
21 @end
22
23 @interface SemanticsObjectTest : XCTestCase
24 @end
25
26 @implementation SemanticsObjectTest
27
28 - (void)testCreate {
29 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
30 new flutter::testing::MockAccessibilityBridge());
31 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
32 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
33 XCTAssertNotNil(object);
34 }
35
36 - (void)testSetChildren {
37 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
38 new flutter::testing::MockAccessibilityBridge());
39 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
40 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
41 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
42 parent.children = @[ child ];
43 XCTAssertEqual(parent, child.parent);
44 parent.children = @[];
45 XCTAssertNil(child.parent);
46 }
47
48 - (void)testAccessibilityHitTestFocusAtLeaf {
49 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
50 new flutter::testing::MockAccessibilityBridge());
51 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
52 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
53 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
54 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
55 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
56 object0.children = @[ object1 ];
57 object0.childrenInHitTestOrder = @[ object1 ];
58 object1.children = @[ object2, object3 ];
59 object1.childrenInHitTestOrder = @[ object2, object3 ];
60
61 flutter::SemanticsNode node0;
62 node0.id = 0;
63 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
64 node0.label = "0";
65 [object0 setSemanticsNode:&node0];
66
67 flutter::SemanticsNode node1;
68 node1.id = 1;
69 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
70 node1.label = "1";
71 [object1 setSemanticsNode:&node1];
72
73 flutter::SemanticsNode node2;
74 node2.id = 2;
75 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
76 node2.label = "2";
77 [object2 setSemanticsNode:&node2];
78
79 flutter::SemanticsNode node3;
80 node3.id = 3;
81 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
82 node3.label = "3";
83 [object3 setSemanticsNode:&node3];
84
85 CGPoint point = CGPointMake(10, 10);
86 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
87
88 // Focus to object2 because it's the first object in hit test order
89 XCTAssertEqual(hitTestResult, object2);
90 }
91
92 - (void)testAccessibilityHitTestNoFocusableItem {
93 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
94 new flutter::testing::MockAccessibilityBridge());
95 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
96 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
97 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
98 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
99 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
100 object0.children = @[ object1 ];
101 object0.childrenInHitTestOrder = @[ object1 ];
102 object1.children = @[ object2, object3 ];
103 object1.childrenInHitTestOrder = @[ object2, object3 ];
104
105 flutter::SemanticsNode node0;
106 node0.id = 0;
107 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
108 [object0 setSemanticsNode:&node0];
109
110 flutter::SemanticsNode node1;
111 node1.id = 1;
112 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
113 [object1 setSemanticsNode:&node1];
114
115 flutter::SemanticsNode node2;
116 node2.id = 2;
117 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
118 [object2 setSemanticsNode:&node2];
119
120 flutter::SemanticsNode node3;
121 node3.id = 3;
122 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
123 [object3 setSemanticsNode:&node3];
124
125 CGPoint point = CGPointMake(10, 10);
126 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
127
128 XCTAssertNil(hitTestResult);
129 }
130
131 - (void)testAccessibilityScrollToVisible {
132 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
133 new flutter::testing::MockAccessibilityBridge());
134 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
135 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
136
137 flutter::SemanticsNode node3;
138 node3.id = 3;
139 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
140 [object3 setSemanticsNode:&node3];
141
142 [object3 accessibilityScrollToVisible];
143
144 XCTAssertTrue(bridge->observations.size() == 1);
145 XCTAssertTrue(bridge->observations[0].id == 3);
146 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
147 }
148
149 - (void)testAccessibilityScrollToVisibleWithChild {
150 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
151 new flutter::testing::MockAccessibilityBridge());
152 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
153 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
154
155 flutter::SemanticsNode node3;
156 node3.id = 3;
157 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
158 [object3 setSemanticsNode:&node3];
159
160 [object3 accessibilityScrollToVisibleWithChild:object3];
161
162 XCTAssertTrue(bridge->observations.size() == 1);
163 XCTAssertTrue(bridge->observations[0].id == 3);
164 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
165 }
166
167 - (void)testAccessibilityHitTestOutOfRect {
168 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
169 new flutter::testing::MockAccessibilityBridge());
170 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
171 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
172 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
173 SemanticsObject* object2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
174 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
175 object0.children = @[ object1 ];
176 object0.childrenInHitTestOrder = @[ object1 ];
177 object1.children = @[ object2, object3 ];
178 object1.childrenInHitTestOrder = @[ object2, object3 ];
179
180 flutter::SemanticsNode node0;
181 node0.id = 0;
182 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
183 node0.label = "0";
184 [object0 setSemanticsNode:&node0];
185
186 flutter::SemanticsNode node1;
187 node1.id = 1;
188 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
189 node1.label = "1";
190 [object1 setSemanticsNode:&node1];
191
192 flutter::SemanticsNode node2;
193 node2.id = 2;
194 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
195 node2.label = "2";
196 [object2 setSemanticsNode:&node2];
197
198 flutter::SemanticsNode node3;
199 node3.id = 3;
200 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
201 node3.label = "3";
202 [object3 setSemanticsNode:&node3];
203
204 CGPoint point = CGPointMake(300, 300);
205 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
206
207 XCTAssertNil(hitTestResult);
208 }
209
210 - (void)testReplaceChildAtIndex {
211 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
212 new flutter::testing::MockAccessibilityBridge());
213 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
214 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
215 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
216 SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2];
217 parent.children = @[ child1 ];
218 [parent replaceChildAtIndex:0 withChild:child2];
219 XCTAssertNil(child1.parent);
220 XCTAssertEqual(parent, child2.parent);
221 XCTAssertEqualObjects(parent.children, @[ child2 ]);
222 }
223
224 - (void)testPlainSemanticsObjectWithLabelHasStaticTextTrait {
225 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
226 new flutter::testing::MockAccessibilityBridge());
227 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
228 flutter::SemanticsNode node;
229 node.label = "foo";
230 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
231 [object setSemanticsNode:&node];
232 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitStaticText);
233 }
234
235 - (void)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden {
236 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
237 new flutter::testing::MockAccessibilityBridge());
238 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
239 flutter::SemanticsNode node;
240 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling) |
241 static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
242 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
243 [object setSemanticsNode:&node];
244 XCTAssertEqual(object.isAccessibilityElement, YES);
245 }
246
247 - (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden {
248 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
249 new flutter::testing::MockAccessibilityBridge());
250 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
251 flutter::SemanticsNode node;
252 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
253 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
254 [object setSemanticsNode:&node];
255 XCTAssertEqual(object.isAccessibilityElement, NO);
256 }
257
258 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait {
259 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
260 new flutter::testing::MockAccessibilityBridge());
261 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
262 flutter::SemanticsNode node;
263 node.label = "foo";
264 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
265 SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
266 object.children = @[ child1 ];
267 [object setSemanticsNode:&node];
268 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
269 }
270
271 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 {
272 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
273 new flutter::testing::MockAccessibilityBridge());
274 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
275 flutter::SemanticsNode node;
276 node.label = "foo";
277 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField);
278 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
279 [object setSemanticsNode:&node];
280 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
281 }
282
283 - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 {
284 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
285 new flutter::testing::MockAccessibilityBridge());
286 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
287 flutter::SemanticsNode node;
288 node.label = "foo";
289 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsButton);
290 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
291 [object setSemanticsNode:&node];
292 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton);
293 }
294
295 - (void)testVerticalFlutterScrollableSemanticsObject {
296 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
297 new flutter::testing::MockAccessibilityBridge());
298 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
299
300 float transformScale = 0.5f;
301 float screenScale = [[bridge->view() window] screen].scale;
302 float effectivelyScale = transformScale / screenScale;
303 float x = 10;
304 float y = 10;
305 float w = 100;
306 float h = 200;
307 float scrollExtentMax = 500.0;
308 float scrollPosition = 150.0;
309
310 flutter::SemanticsNode node;
311 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
312 node.actions = flutter::kVerticalScrollSemanticsActions;
313 node.rect = SkRect::MakeXYWH(x, y, w, h);
314 node.scrollExtentMax = scrollExtentMax;
315 node.scrollPosition = scrollPosition;
316 node.transform = {
317 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
318 FlutterScrollableSemanticsObject* scrollable =
319 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
320 [scrollable setSemanticsNode:&node];
321 [scrollable accessibilityBridgeDidFinishUpdate];
322 UIScrollView* scrollView = [scrollable nativeAccessibility];
323
324 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
325 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
326 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
327 kFloatCompareEpsilon);
328 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
329 kFloatCompareEpsilon);
330
331 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
332 kFloatCompareEpsilon);
333 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
334 (h + scrollExtentMax) * effectivelyScale, kFloatCompareEpsilon);
335
336 XCTAssertEqual(scrollView.contentOffset.x, 0);
337 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
338 kFloatCompareEpsilon);
339 }
340
341 - (void)testVerticalFlutterScrollableSemanticsObjectNoWindowDoesNotCrash {
342 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
343 new flutter::testing::MockAccessibilityBridgeNoWindow());
344 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
345
346 float transformScale = 0.5f;
347 float x = 10;
348 float y = 10;
349 float w = 100;
350 float h = 200;
351 float scrollExtentMax = 500.0;
352 float scrollPosition = 150.0;
353
354 flutter::SemanticsNode node;
355 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
356 node.actions = flutter::kVerticalScrollSemanticsActions;
357 node.rect = SkRect::MakeXYWH(x, y, w, h);
358 node.scrollExtentMax = scrollExtentMax;
359 node.scrollPosition = scrollPosition;
360 node.transform = {
361 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
362 FlutterScrollableSemanticsObject* scrollable =
363 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
364 [scrollable setSemanticsNode:&node];
365 [scrollable accessibilityBridgeDidFinishUpdate];
366 XCTAssertNoThrow([scrollable accessibilityBridgeDidFinishUpdate]);
367 }
368
369 - (void)testHorizontalFlutterScrollableSemanticsObject {
370 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
371 new flutter::testing::MockAccessibilityBridge());
372 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
373
374 float transformScale = 0.5f;
375 float screenScale = [[bridge->view() window] screen].scale;
376 float effectivelyScale = transformScale / screenScale;
377 float x = 10;
378 float y = 10;
379 float w = 100;
380 float h = 200;
381 float scrollExtentMax = 500.0;
382 float scrollPosition = 150.0;
383
384 flutter::SemanticsNode node;
385 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
386 node.actions = flutter::kHorizontalScrollSemanticsActions;
387 node.rect = SkRect::MakeXYWH(x, y, w, h);
388 node.scrollExtentMax = scrollExtentMax;
389 node.scrollPosition = scrollPosition;
390 node.transform = {
391 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
392 FlutterScrollableSemanticsObject* scrollable =
393 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
394 [scrollable setSemanticsNode:&node];
395 [scrollable accessibilityBridgeDidFinishUpdate];
396 UIScrollView* scrollView = [scrollable nativeAccessibility];
397
398 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
399 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
400 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
401 kFloatCompareEpsilon);
402 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
403 kFloatCompareEpsilon);
404
405 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, (w + scrollExtentMax) * effectivelyScale,
406 kFloatCompareEpsilon);
407 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
408 kFloatCompareEpsilon);
409
410 XCTAssertEqualWithAccuracy(scrollView.contentOffset.x, scrollPosition * effectivelyScale,
411 kFloatCompareEpsilon);
412 XCTAssertEqual(scrollView.contentOffset.y, 0);
413 }
414
415 - (void)testCanHandleInfiniteScrollExtent {
416 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
417 new flutter::testing::MockAccessibilityBridge());
418 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
419
420 float transformScale = 0.5f;
421 float screenScale = [[bridge->view() window] screen].scale;
422 float effectivelyScale = transformScale / screenScale;
423 float x = 10;
424 float y = 10;
425 float w = 100;
426 float h = 200;
427 float scrollExtentMax = INFINITY;
428 float scrollPosition = 150.0;
429
430 flutter::SemanticsNode node;
431 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
432 node.actions = flutter::kVerticalScrollSemanticsActions;
433 node.rect = SkRect::MakeXYWH(x, y, w, h);
434 node.scrollExtentMax = scrollExtentMax;
435 node.scrollPosition = scrollPosition;
436 node.transform = {
437 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
438 FlutterScrollableSemanticsObject* scrollable =
439 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
440 [scrollable setSemanticsNode:&node];
441 [scrollable accessibilityBridgeDidFinishUpdate];
442 UIScrollView* scrollView = [scrollable nativeAccessibility];
443 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
444 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
445 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
446 kFloatCompareEpsilon);
447 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
448 kFloatCompareEpsilon);
449
450 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
451 kFloatCompareEpsilon);
452 XCTAssertEqualWithAccuracy(scrollView.contentSize.height,
453 (h + kScrollExtentMaxForInf + scrollPosition) * effectivelyScale,
454 kFloatCompareEpsilon);
455
456 XCTAssertEqual(scrollView.contentOffset.x, 0);
457 XCTAssertEqualWithAccuracy(scrollView.contentOffset.y, scrollPosition * effectivelyScale,
458 kFloatCompareEpsilon);
459 }
460
461 - (void)testCanHandleNaNScrollExtentAndScrollPoisition {
462 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
463 new flutter::testing::MockAccessibilityBridge());
464 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
465
466 float transformScale = 0.5f;
467 float screenScale = [[bridge->view() window] screen].scale;
468 float effectivelyScale = transformScale / screenScale;
469 float x = 10;
470 float y = 10;
471 float w = 100;
472 float h = 200;
473 float scrollExtentMax = std::nan("");
474 float scrollPosition = std::nan("");
475
476 flutter::SemanticsNode node;
477 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
478 node.actions = flutter::kVerticalScrollSemanticsActions;
479 node.rect = SkRect::MakeXYWH(x, y, w, h);
480 node.scrollExtentMax = scrollExtentMax;
481 node.scrollPosition = scrollPosition;
482 node.transform = {
483 transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
484 FlutterScrollableSemanticsObject* scrollable =
485 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
486 [scrollable setSemanticsNode:&node];
487 [scrollable accessibilityBridgeDidFinishUpdate];
488 UIScrollView* scrollView = [scrollable nativeAccessibility];
489
490 XCTAssertEqualWithAccuracy(scrollView.frame.origin.x, x * effectivelyScale, kFloatCompareEpsilon);
491 XCTAssertEqualWithAccuracy(scrollView.frame.origin.y, y * effectivelyScale, kFloatCompareEpsilon);
492 XCTAssertEqualWithAccuracy(scrollView.frame.size.width, w * effectivelyScale,
493 kFloatCompareEpsilon);
494 XCTAssertEqualWithAccuracy(scrollView.frame.size.height, h * effectivelyScale,
495 kFloatCompareEpsilon);
496
497 // Content size equal to the scrollable size.
498 XCTAssertEqualWithAccuracy(scrollView.contentSize.width, w * effectivelyScale,
499 kFloatCompareEpsilon);
500 XCTAssertEqualWithAccuracy(scrollView.contentSize.height, h * effectivelyScale,
501 kFloatCompareEpsilon);
502
503 XCTAssertEqual(scrollView.contentOffset.x, 0);
504 XCTAssertEqual(scrollView.contentOffset.y, 0);
505 }
506
507 - (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
508 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
509 new flutter::testing::MockAccessibilityBridge());
510 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
511
512 flutter::SemanticsNode node;
513 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
514 node.actions = flutter::kHorizontalScrollSemanticsActions;
515 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
516 node.scrollExtentMax = 100.0;
517 node.scrollPosition = 0.0;
518
519 FlutterScrollableSemanticsObject* scrollable =
520 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
521 [scrollable setSemanticsNode:&node];
522 [scrollable accessibilityBridgeDidFinishUpdate];
523 UIScrollView* scrollView = [scrollable nativeAccessibility];
524 XCTAssertEqual([scrollView hitTest:CGPointMake(10, 10) withEvent:nil], nil);
525 }
526
527 - (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
528 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
529 mock->isVoiceOverRunningValue = false;
530 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
531 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
532
533 flutter::SemanticsNode node;
534 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
535 node.actions = flutter::kHorizontalScrollSemanticsActions;
536 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
537 node.scrollExtentMax = 100.0;
538 node.scrollPosition = 0.0;
539
540 FlutterScrollableSemanticsObject* scrollable =
541 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
542 [scrollable setSemanticsNode:&node];
543 [scrollable accessibilityBridgeDidFinishUpdate];
544 UIScrollView* scrollView = [scrollable nativeAccessibility];
545 XCTAssertTrue(scrollView.isAccessibilityElement);
546 mock->isVoiceOverRunningValue = true;
547 XCTAssertFalse(scrollView.isAccessibilityElement);
548 }
549
550 - (void)testFlutterSemanticsObjectHasIdentifier {
551 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
552 mock->isVoiceOverRunningValue = true;
553 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
554 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
555
556 flutter::SemanticsNode node;
557 node.identifier = "identifier";
558
559 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
560 [object setSemanticsNode:&node];
561 XCTAssertTrue([object.accessibilityIdentifier isEqualToString:@"identifier"]);
562 }
563
564 - (void)testFlutterScrollableSemanticsObjectWithLabelValueHintIsNotHiddenWhenVoiceOverIsRunning {
565 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
566 mock->isVoiceOverRunningValue = true;
567 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
568 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
569
570 flutter::SemanticsNode node;
571 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
572 node.actions = flutter::kHorizontalScrollSemanticsActions;
573 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
574 node.label = "label";
575 node.value = "value";
576 node.hint = "hint";
577 node.scrollExtentMax = 100.0;
578 node.scrollPosition = 0.0;
579
580 FlutterScrollableSemanticsObject* scrollable =
581 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
582 [scrollable setSemanticsNode:&node];
583 [scrollable accessibilityBridgeDidFinishUpdate];
584 UIScrollView* scrollView = [scrollable nativeAccessibility];
585 XCTAssertTrue(scrollView.isAccessibilityElement);
586 XCTAssertTrue(
587 [scrollView.accessibilityLabel isEqualToString:NSLocalizedString(@"label", @"test")]);
588 XCTAssertTrue(
589 [scrollView.accessibilityValue isEqualToString:NSLocalizedString(@"value", @"test")]);
590 XCTAssertTrue([scrollView.accessibilityHint isEqualToString:NSLocalizedString(@"hint", @"test")]);
591 }
592
593 - (void)testFlutterSemanticsObjectMergeTooltipToLabel {
594 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
595 mock->isVoiceOverRunningValue = true;
596 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
597 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
598
599 flutter::SemanticsNode node;
600 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
601 node.label = "label";
602 node.tooltip = "tooltip";
603 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
604 [object setSemanticsNode:&node];
605 XCTAssertTrue(object.isAccessibilityElement);
606 XCTAssertTrue([object.accessibilityLabel isEqualToString:@"label\ntooltip"]);
607 }
608
609 - (void)testFlutterSemanticsObjectAttributedStringsDoNotCrashWhenEmpty {
610 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
611 mock->isVoiceOverRunningValue = true;
612 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
613 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
614
615 flutter::SemanticsNode node;
616 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
617 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
618 [object setSemanticsNode:&node];
619 XCTAssertTrue(object.accessibilityAttributedLabel == nil);
620 }
621
622 - (void)testFlutterScrollableSemanticsObjectReturnsParentContainerIfNoChildren {
623 flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge();
624 mock->isVoiceOverRunningValue = true;
625 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
626 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
627
628 flutter::SemanticsNode parent;
629 parent.id = 0;
630 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
631 parent.label = "label";
632 parent.value = "value";
633 parent.hint = "hint";
634
635 flutter::SemanticsNode node;
636 node.id = 1;
637 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
638 node.actions = flutter::kHorizontalScrollSemanticsActions;
639 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
640 node.label = "label";
641 node.value = "value";
642 node.hint = "hint";
643 node.scrollExtentMax = 100.0;
644 node.scrollPosition = 0.0;
645 parent.childrenInTraversalOrder.push_back(1);
646
647 FlutterSemanticsObject* parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge
648 uid:0];
649 [parentObject setSemanticsNode:&parent];
650
651 FlutterScrollableSemanticsObject* scrollable =
652 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
653 [scrollable setSemanticsNode:&node];
654 UIScrollView* scrollView = [scrollable nativeAccessibility];
655
656 parentObject.children = @[ scrollable ];
657 [parentObject accessibilityBridgeDidFinishUpdate];
658 [scrollable accessibilityBridgeDidFinishUpdate];
659 XCTAssertTrue(scrollView.isAccessibilityElement);
660 SemanticsObjectContainer* container =
661 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
662 XCTAssertEqual(container.semanticsObject, parentObject);
663 }
664
665 - (void)testFlutterScrollableSemanticsObjectHidesScrollBar {
666 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
667 new flutter::testing::MockAccessibilityBridge());
668 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
669
670 flutter::SemanticsNode node;
671 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
672 node.actions = flutter::kHorizontalScrollSemanticsActions;
673 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
674 node.scrollExtentMax = 100.0;
675 node.scrollPosition = 0.0;
676
677 FlutterScrollableSemanticsObject* scrollable =
678 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:0];
679 [scrollable setSemanticsNode:&node];
680 [scrollable accessibilityBridgeDidFinishUpdate];
681 UIScrollView* scrollView = [scrollable nativeAccessibility];
682
683 XCTAssertFalse(scrollView.showsHorizontalScrollIndicator);
684 XCTAssertFalse(scrollView.showsVerticalScrollIndicator);
685 }
686
687 - (void)testSemanticsObjectBuildsAttributedString {
688 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
689 new flutter::testing::MockAccessibilityBridge());
690 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
691 flutter::SemanticsNode node;
692 node.label = "label";
693 std::shared_ptr<flutter::SpellOutStringAttribute> attribute =
694 std::make_shared<flutter::SpellOutStringAttribute>();
695 attribute->start = 1;
696 attribute->end = 2;
697 attribute->type = flutter::StringAttributeType::kSpellOut;
698 node.labelAttributes.push_back(attribute);
699 node.value = "value";
700 attribute = std::make_shared<flutter::SpellOutStringAttribute>();
701 attribute->start = 2;
702 attribute->end = 3;
703 attribute->type = flutter::StringAttributeType::kSpellOut;
704 node.valueAttributes.push_back(attribute);
705 node.hint = "hint";
706 std::shared_ptr<flutter::LocaleStringAttribute> local_attribute =
707 std::make_shared<flutter::LocaleStringAttribute>();
708 local_attribute->start = 3;
709 local_attribute->end = 4;
710 local_attribute->type = flutter::StringAttributeType::kLocale;
711 local_attribute->locale = "en-MX";
712 node.hintAttributes.push_back(local_attribute);
713 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
714 [object setSemanticsNode:&node];
715 NSMutableAttributedString* expectedAttributedLabel =
716 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"label", @"test")];
717 NSDictionary* attributeDict = @{
718 UIAccessibilitySpeechAttributeSpellOut : @YES,
719 };
720 [expectedAttributedLabel setAttributes:attributeDict range:NSMakeRange(1, 1)];
721 XCTAssertTrue(
722 [object.accessibilityAttributedLabel isEqualToAttributedString:expectedAttributedLabel]);
723
724 NSMutableAttributedString* expectedAttributedValue =
725 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"value", @"test")];
726 attributeDict = @{
727 UIAccessibilitySpeechAttributeSpellOut : @YES,
728 };
729 [expectedAttributedValue setAttributes:attributeDict range:NSMakeRange(2, 1)];
730 XCTAssertTrue(
731 [object.accessibilityAttributedValue isEqualToAttributedString:expectedAttributedValue]);
732
733 NSMutableAttributedString* expectedAttributedHint =
734 [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"hint", @"test")];
735 attributeDict = @{
736 UIAccessibilitySpeechAttributeLanguage : @"en-MX",
737 };
738 [expectedAttributedHint setAttributes:attributeDict range:NSMakeRange(3, 1)];
739 XCTAssertTrue(
740 [object.accessibilityAttributedHint isEqualToAttributedString:expectedAttributedHint]);
741 }
742
743 - (void)testShouldTriggerAnnouncement {
744 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
745 new flutter::testing::MockAccessibilityBridge());
746 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
747 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
748
749 // Handle nil with no node set.
750 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
751
752 // Handle initial setting of node with liveRegion
753 flutter::SemanticsNode node;
754 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
755 node.label = "foo";
756 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
757
758 // Handle nil with node set.
759 [object setSemanticsNode:&node];
760 XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]);
761
762 // Handle new node, still has live region, same label.
763 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]);
764
765 // Handle update node with new label, still has live region.
766 flutter::SemanticsNode updatedNode;
767 updatedNode.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion);
768 updatedNode.label = "bar";
769 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]);
770
771 // Handle dropping the live region flag.
772 updatedNode.flags = 0;
773 XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]);
774
775 // Handle adding the flag when the label has not changed.
776 updatedNode.label = "foo";
777 [object setSemanticsNode:&updatedNode];
778 XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]);
779 }
780
781 - (void)testShouldDispatchShowOnScreenActionForHeader {
782 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
783 new flutter::testing::MockAccessibilityBridge());
784 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
785 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
786
787 // Handle initial setting of node with header.
788 flutter::SemanticsNode node;
789 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHeader);
790 node.label = "foo";
791
792 [object setSemanticsNode:&node];
793
794 // Simulate accessibility focus.
795 [object accessibilityElementDidBecomeFocused];
796
797 XCTAssertTrue(bridge->observations.size() == 1);
798 XCTAssertTrue(bridge->observations[0].id == 1);
799 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
800 }
801
802 - (void)testShouldDispatchShowOnScreenActionForHidden {
803 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
804 new flutter::testing::MockAccessibilityBridge());
805 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
806 SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
807
808 // Handle initial setting of node with hidden.
809 flutter::SemanticsNode node;
810 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden);
811 node.label = "foo";
812
813 [object setSemanticsNode:&node];
814
815 // Simulate accessibility focus.
816 [object accessibilityElementDidBecomeFocused];
817
818 XCTAssertTrue(bridge->observations.size() == 1);
819 XCTAssertTrue(bridge->observations[0].id == 1);
820 XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
821 }
822
823 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
824 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
825 new flutter::testing::MockAccessibilityBridge());
826 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
827 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
828 uid:1];
829
830 // Handle initial setting of node with header.
831 flutter::SemanticsNode node;
832 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
833 static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled) |
834 static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
835 node.label = "foo";
836 [object setSemanticsNode:&node];
837 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
838 UISwitch* nativeSwitch = [[UISwitch alloc] init];
839 nativeSwitch.on = YES;
840
841 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
842 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
843
844 // Set the toggled to false;
845 flutter::SemanticsNode update;
846 update.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
847 static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
848 update.label = "foo";
849 [object setSemanticsNode:&update];
850
851 nativeSwitch.on = NO;
852
853 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
854 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
855 }
856
857 - (void)testFlutterSemanticsObjectOfRadioButton {
858 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
859 new flutter::testing::MockAccessibilityBridge());
860 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
861 FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
862
863 // Handle initial setting of node with header.
864 flutter::SemanticsNode node;
865 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
866 static_cast<int32_t>(flutter::SemanticsFlags::kHasCheckedState) |
867 static_cast<int32_t>(flutter::SemanticsFlags::kHasEnabledState) |
868 static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled);
869 node.label = "foo";
870 [object setSemanticsNode:&node];
871 XCTAssertTrue((object.accessibilityTraits & UIAccessibilityTraitButton) > 0);
872 XCTAssertNil(object.accessibilityValue);
873 }
874
875 - (void)testFlutterSwitchSemanticsObjectMatchesUISwitchDisabled {
876 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
877 new flutter::testing::MockAccessibilityBridge());
878 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
879 FlutterSwitchSemanticsObject* object = [[FlutterSwitchSemanticsObject alloc] initWithBridge:bridge
880 uid:1];
881
882 // Handle initial setting of node with header.
883 flutter::SemanticsNode node;
884 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasToggledState) |
885 static_cast<int32_t>(flutter::SemanticsFlags::kIsToggled);
886 node.label = "foo";
887 [object setSemanticsNode:&node];
888 // Create ab real UISwitch to compare the FlutterSwitchSemanticsObject with.
889 UISwitch* nativeSwitch = [[UISwitch alloc] init];
890 nativeSwitch.on = YES;
891 nativeSwitch.enabled = NO;
892
893 XCTAssertEqual(object.accessibilityTraits, nativeSwitch.accessibilityTraits);
894 XCTAssertEqualObjects(object.accessibilityValue, nativeSwitch.accessibilityValue);
895 }
896
897 - (void)testSemanticsObjectDeallocated {
898 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
899 new flutter::testing::MockAccessibilityBridge());
900 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
901 SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
902 SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
903 parent.children = @[ child ];
904 // Validate SemanticsObject deallocation does not crash.
905 // https://github.com/flutter/flutter/issues/66032
906 __weak SemanticsObject* weakObject = parent;
907 parent = nil;
908 XCTAssertNil(weakObject);
909 }
910
911 - (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive {
912 FlutterSemanticsObject* parentObject;
913 FlutterScrollableSemanticsObject* scrollable;
914 FlutterSemanticsObject* object2;
915
916 flutter::SemanticsNode parent;
917 parent.id = 0;
918 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
919 parent.label = "label";
920 parent.value = "value";
921 parent.hint = "hint";
922
923 flutter::SemanticsNode node;
924 node.id = 1;
925 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
926 node.actions = flutter::kHorizontalScrollSemanticsActions;
927 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
928 node.label = "label";
929 node.value = "value";
930 node.hint = "hint";
931 node.scrollExtentMax = 100.0;
932 node.scrollPosition = 0.0;
933 parent.childrenInTraversalOrder.push_back(1);
934
935 flutter::SemanticsNode node2;
936 node2.id = 2;
937 node2.rect = SkRect::MakeXYWH(0, 0, 100, 200);
938 node2.label = "label";
939 node2.value = "value";
940 node2.hint = "hint";
941 node2.scrollExtentMax = 100.0;
942 node2.scrollPosition = 0.0;
943 parent.childrenInTraversalOrder.push_back(2);
944
945 {
946 flutter::testing::MockAccessibilityBridge* mock =
947 new flutter::testing::MockAccessibilityBridge();
948 mock->isVoiceOverRunningValue = true;
949 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
950 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
951
952 parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
953 [parentObject setSemanticsNode:&parent];
954
955 scrollable = [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge uid:1];
956 [scrollable setSemanticsNode:&node];
957 [scrollable accessibilityBridgeDidFinishUpdate];
958
959 object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2];
960 [object2 setSemanticsNode:&node2];
961
962 parentObject.children = @[ scrollable, object2 ];
963 [parentObject accessibilityBridgeDidFinishUpdate];
964 [scrollable accessibilityBridgeDidFinishUpdate];
965 [object2 accessibilityBridgeDidFinishUpdate];
966
967 // Returns the correct container if the bridge is alive.
968 SemanticsObjectContainer* container =
969 static_cast<SemanticsObjectContainer*>(scrollable.accessibilityContainer);
970 XCTAssertEqual(container.semanticsObject, parentObject);
971 SemanticsObjectContainer* container2 =
972 static_cast<SemanticsObjectContainer*>(object2.accessibilityContainer);
973 XCTAssertEqual(container2.semanticsObject, parentObject);
974 }
975 // The bridge pointer went out of scope and was deallocated.
976
977 XCTAssertNil(scrollable.accessibilityContainer);
978 XCTAssertNil(object2.accessibilityContainer);
979 }
980
981 - (void)testAccessibilityHitTestSearchCanReturnPlatformView {
982 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
983 new flutter::testing::MockAccessibilityBridge());
984 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
985 SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0];
986 SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
987 SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
988 FlutterTouchInterceptingView* platformView =
989 [[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
990 FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer =
991 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
992 uid:1
993 platformView:platformView];
994
995 object0.children = @[ object1 ];
996 object0.childrenInHitTestOrder = @[ object1 ];
997 object1.children = @[ platformViewSemanticsContainer, object3 ];
998 object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ];
999
1000 flutter::SemanticsNode node0;
1001 node0.id = 0;
1002 node0.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1003 node0.label = "0";
1004 [object0 setSemanticsNode:&node0];
1005
1006 flutter::SemanticsNode node1;
1007 node1.id = 1;
1008 node1.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1009 node1.label = "1";
1010 [object1 setSemanticsNode:&node1];
1011
1012 flutter::SemanticsNode node2;
1013 node2.id = 2;
1014 node2.rect = SkRect::MakeXYWH(0, 0, 100, 100);
1015 node2.label = "2";
1016 [platformViewSemanticsContainer setSemanticsNode:&node2];
1017
1018 flutter::SemanticsNode node3;
1019 node3.id = 3;
1020 node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
1021 node3.label = "3";
1022 [object3 setSemanticsNode:&node3];
1023
1024 CGPoint point = CGPointMake(10, 10);
1025 id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil];
1026
1027 XCTAssertEqual(hitTestResult, platformView);
1028 }
1029
1030 - (void)testFlutterPlatformViewSemanticsContainer {
1031 fml::WeakPtrFactory<flutter::testing::MockAccessibilityBridge> factory(
1032 new flutter::testing::MockAccessibilityBridge());
1033 fml::WeakPtr<flutter::testing::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
1034 __weak FlutterTouchInterceptingView* weakPlatformView;
1035 __weak FlutterPlatformViewSemanticsContainer* weakContainer;
1036 @autoreleasepool {
1037 FlutterTouchInterceptingView* platformView = [[FlutterTouchInterceptingView alloc] init];
1038 weakPlatformView = platformView;
1039
1040 @autoreleasepool {
1041 FlutterPlatformViewSemanticsContainer* container =
1042 [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
1043 uid:1
1044 platformView:platformView];
1045 weakContainer = container;
1046 XCTAssertEqualObjects(platformView.accessibilityContainer, container);
1047 XCTAssertNotNil(weakPlatformView);
1048 XCTAssertNotNil(weakContainer);
1049 }
1050 // Check the variables are still lived.
1051 // `container` is `retain` in `platformView`, so it will not be nil here.
1052 XCTAssertNotNil(weakPlatformView);
1053 XCTAssertNotNil(weakContainer);
1054 }
1055 // Check if there's no more strong references to `platformView` after container and platformView
1056 // are released.
1057 XCTAssertNil(weakPlatformView);
1058 XCTAssertNil(weakContainer);
1059 }
1060
1061 - (void)testTextInputSemanticsObject {
1062 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1063 new flutter::testing::MockAccessibilityBridge());
1064 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1065
1066 flutter::SemanticsNode node;
1067 node.label = "foo";
1068 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1069 static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1070 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1071 [object setSemanticsNode:&node];
1072 [object accessibilityBridgeDidFinishUpdate];
1073 XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
1074 }
1075
1076 - (void)testTextInputSemanticsObject_canPerformAction {
1077 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1078 new flutter::testing::MockAccessibilityBridge());
1079 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1080
1081 flutter::SemanticsNode node;
1082 node.label = "foo";
1083 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1084 static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1085 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1086 [object setSemanticsNode:&node];
1087 [object accessibilityBridgeDidFinishUpdate];
1088
1089 id textInputSurrogate = OCMClassMock([UIResponder class]);
1090 id partialSemanticsObject = OCMPartialMock(object);
1091 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1092
1093 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1094 .andReturn(YES);
1095 XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1096
1097 OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1098 .andReturn(NO);
1099 XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1100 }
1101
1102 - (void)testTextInputSemanticsObject_editActions {
1103 fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1104 new flutter::testing::MockAccessibilityBridge());
1105 fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1106
1107 flutter::SemanticsNode node;
1108 node.label = "foo";
1109 node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1110 static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1111 TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1112 [object setSemanticsNode:&node];
1113 [object accessibilityBridgeDidFinishUpdate];
1114
1115 id textInputSurrogate = OCMClassMock([UIResponder class]);
1116 id partialSemanticsObject = OCMPartialMock(object);
1117 OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1118
1119 XCTestExpectation* copyExpectation =
1120 [self expectationWithDescription:@"Surrogate's copy method is called."];
1121 XCTestExpectation* cutExpectation =
1122 [self expectationWithDescription:@"Surrogate's cut method is called."];
1123 XCTestExpectation* pasteExpectation =
1124 [self expectationWithDescription:@"Surrogate's paste method is called."];
1125 XCTestExpectation* selectAllExpectation =
1126 [self expectationWithDescription:@"Surrogate's selectAll method is called."];
1127 XCTestExpectation* deleteExpectation =
1128 [self expectationWithDescription:@"Surrogate's delete method is called."];
1129
1130 OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1131 [copyExpectation fulfill];
1132 });
1133 OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1134 [cutExpectation fulfill];
1135 });
1136 OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1137 [pasteExpectation fulfill];
1138 });
1139 OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1140 [selectAllExpectation fulfill];
1141 });
1142 OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1143 [deleteExpectation fulfill];
1144 });
1145
1146 [partialSemanticsObject copy:nil];
1147 [partialSemanticsObject cut:nil];
1148 [partialSemanticsObject paste:nil];
1149 [partialSemanticsObject selectAll:nil];
1150 [partialSemanticsObject delete:nil];
1151
1152 [self waitForExpectationsWithTimeout:1 handler:nil];
1153 }
1154
1155 @end
-[SemanticsObject accessibilityScrollToVisible]
BOOL accessibilityScrollToVisible()
-[SemanticsObject replaceChildAtIndex:withChild:]
void replaceChildAtIndex:withChild:(NSInteger index,[withChild] SemanticsObject *child)
Definition: SemanticsObject.mm:369
-[FlutterTouchInterceptingView accessibilityContainer]
id accessibilityContainer()
-[TextInputSemanticsObject(Test) textInputSurrogate]
UIView< UITextInput > * textInputSurrogate()
SemanticsObject::parent
SemanticsObject * parent
Definition: SemanticsObject.h:41
SemanticsObjectTestMocks.h
TextInputSemanticsObject(Test)
Definition: SemanticsObjectTest.mm:19
flutter::testing::MockAccessibilityBridge
Definition: SemanticsObjectTestMocks.h:24
kFloatCompareEpsilon
const FLUTTER_ASSERT_ARC float kFloatCompareEpsilon
Definition: SemanticsObjectTest.mm:17
SemanticsObjectContainer::semanticsObject
SemanticsObject * semanticsObject
Definition: SemanticsObject.h:234
-[SemanticsObject accessibilityScrollToVisibleWithChild:]
BOOL accessibilityScrollToVisibleWithChild:(id child)
TextInputSemanticsObject.h
-[SemanticsObject setSemanticsNode:]
void setSemanticsNode:(const flutter::SemanticsNode *NS_REQUIRES_SUPER)
Definition: SemanticsObject.mm:328
FlutterMacros.h
FlutterSemanticsObject
Definition: SemanticsObject.h:154
SemanticsObject::childrenInHitTestOrder
NSArray< SemanticsObject * > * childrenInHitTestOrder
Definition: SemanticsObject.h:73
kScrollExtentMaxForInf
constexpr float kScrollExtentMaxForInf
Definition: SemanticsObject.h:17
-[SemanticsObject _accessibilityHitTest:withEvent:]
id _accessibilityHitTest:withEvent:(CGPoint point,[withEvent] UIEvent *event)
FlutterPlatformViews_Internal.h
FlutterSwitchSemanticsObject
Definition: SemanticsObject.h:182
SemanticsObject::children
NSArray< SemanticsObject * > * children
Definition: SemanticsObject.h:67
SemanticsObject::nativeAccessibility
id nativeAccessibility
Definition: SemanticsObject.h:82
SemanticsObject.h
TextInputSemanticsObject
Definition: TextInputSemanticsObject.h:20
FlutterPlatformViewSemanticsContainer
Definition: SemanticsObject.h:168
flutter::testing::MockAccessibilityBridgeNoWindow
Definition: SemanticsObjectTestMocks.h:57
FlutterTouchInterceptingView_Test.h
-[SemanticsObject accessibilityBridgeDidFinishUpdate]
void accessibilityBridgeDidFinishUpdate()
Definition: SemanticsObject.mm:332
SemanticsObjectContainer
Definition: SemanticsObject.h:226
SemanticsObjectTest
Definition: SemanticsObjectTest.mm:23
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterTouchInterceptingView
Definition: FlutterPlatformViews_Internal.h:138
flutter::testing::MockAccessibilityBridge::isVoiceOverRunningValue
bool isVoiceOverRunningValue
Definition: SemanticsObjectTestMocks.h:50
FlutterScrollableSemanticsObject
Definition: SemanticsObject.h:188
SemanticsObject
Definition: SemanticsObject.h:30