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