| Flutter macOS Embedder |

AccessibilityBridgeMacTest.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 "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
6
7 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
8 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
9 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
10 #include "flutter/testing/autoreleasepool_test.h"
11 #include "flutter/testing/testing.h"
12
13 namespace flutter::testing {
14
15 namespace {
16
17 class AccessibilityBridgeMacSpy : public AccessibilityBridgeMac {
18 public:
19 using AccessibilityBridgeMac::OnAccessibilityEvent;
20
21 AccessibilityBridgeMacSpy(__weak FlutterEngine* flutter_engine,
22 __weak FlutterViewController* view_controller)
23 : AccessibilityBridgeMac(flutter_engine, view_controller) {}
24
25 std::unordered_map<std::string, gfx::NativeViewAccessible> actual_notifications;
26
27 private:
28 void DispatchMacOSNotification(gfx::NativeViewAccessible native_node,
29 NSAccessibilityNotificationName mac_notification) override {
30 actual_notifications[[mac_notification UTF8String]] = native_node;
31 }
32 };
33
34 } // namespace
35 } // namespace flutter::testing
36
37 @interface AccessibilityBridgeTestViewController : FlutterViewController
38 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
39 (nonnull FlutterEngine*)engine;
40 @end
41
42 @implementation AccessibilityBridgeTestViewController
43 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
44 (nonnull FlutterEngine*)engine {
45 return std::make_shared<flutter::testing::AccessibilityBridgeMacSpy>(engine, self);
46 }
47 @end
48
49 namespace flutter::testing {
50
51 namespace {
52
53 // Returns an engine configured for the text fixture resource configuration.
54 FlutterViewController* CreateTestViewController() {
55 NSString* fixtures = @(testing::GetFixturesPath());
56 FlutterDartProject* project = [[FlutterDartProject alloc]
57 initWithAssetsPath:fixtures
58 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
59 return [[AccessibilityBridgeTestViewController alloc] initWithProject:project];
60 }
61
62 // Test fixture that instantiates and re-uses a single NSWindow across multiple tests.
63 //
64 // Works around: http://www.openradar.me/FB13291861
65 class AccessibilityBridgeMacWindowTest : public AutoreleasePoolTest {
66 public:
67 AccessibilityBridgeMacWindowTest() {
68 if (!gWindow_) {
69 gWindow_ = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
70 styleMask:NSBorderlessWindowMask
71 backing:NSBackingStoreBuffered
72 defer:NO];
73 }
74 }
75
76 NSWindow* GetWindow() const { return gWindow_; }
77
78 private:
79 static NSWindow* gWindow_;
80 FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridgeMacWindowTest);
81 };
82
83 NSWindow* AccessibilityBridgeMacWindowTest::gWindow_ = nil;
84
85 // Test-specific name for AutoreleasePoolTest fixture.
86 using AccessibilityBridgeMacTest = AutoreleasePoolTest;
87
88 } // namespace
89
90 TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlutterViewWindow) {
91 FlutterViewController* viewController = CreateTestViewController();
92 FlutterEngine* engine = viewController.engine;
93 NSWindow* expectedTarget = GetWindow();
94 expectedTarget.contentView = viewController.view;
95
96 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
97 // can query semantics information from.
98 engine.semanticsEnabled = YES;
99 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
100 viewController.accessibilityBridge.lock());
101 FlutterSemanticsNode2 root;
102 FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
103 root.id = 0;
104 root.flags2 = &flags;
105 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
106 root.actions = static_cast<FlutterSemanticsAction>(0);
107 root.text_selection_base = -1;
108 root.text_selection_extent = -1;
109 root.label = "root";
110 root.hint = "";
111 root.value = "";
112 root.increased_value = "";
113 root.decreased_value = "";
114 root.tooltip = "";
115 root.child_count = 0;
116 root.custom_accessibility_actions_count = 0;
117 bridge->AddFlutterSemanticsNodeUpdate(root);
118
119 bridge->CommitUpdates();
120 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
121
122 // Creates a targeted event.
123 ui::AXTree tree;
124 ui::AXNode ax_node(&tree, nullptr, 0, 0);
125 ui::AXNodeData node_data;
126 node_data.id = 0;
127 ax_node.SetData(node_data);
128 std::vector<ui::AXEventIntent> intent;
129 ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
130 ax::mojom::EventFrom::kNone, intent);
131 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
132
133 bridge->OnAccessibilityEvent(targeted_event);
134
135 ASSERT_EQ(bridge->actual_notifications.size(), 1u);
136 auto target = bridge->actual_notifications.find([NSAccessibilityCreatedNotification UTF8String]);
137 ASSERT_NE(target, bridge->actual_notifications.end());
138 EXPECT_EQ(target->second, expectedTarget);
139 [engine shutDownEngine];
140 }
141
142 // Flutter used to assume that the accessibility root had ID 0.
143 // In a multi-view world, each view has its own accessibility root
144 // with a globally unique node ID.
145 //
146 // node1
147 // |
148 // node2
149 TEST_F(AccessibilityBridgeMacWindowTest, NonZeroRootNodeId) {
150 FlutterViewController* viewController = CreateTestViewController();
151 FlutterEngine* engine = viewController.engine;
152 NSWindow* expectedTarget = GetWindow();
153 expectedTarget.contentView = viewController.view;
154
155 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
156 // can query semantics information from.
157 engine.semanticsEnabled = YES;
158 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
159 viewController.accessibilityBridge.lock());
160
161 FlutterSemanticsNode2 node1;
162 FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
163 std::vector<int32_t> node1_children{2};
164 node1.id = 1;
165 node1.flags2 = &flags;
166 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
167 node1.actions = static_cast<FlutterSemanticsAction>(0);
168 node1.text_selection_base = -1;
169 node1.text_selection_extent = -1;
170 node1.label = "node1";
171 node1.hint = "";
172 node1.value = "";
173 node1.increased_value = "";
174 node1.decreased_value = "";
175 node1.tooltip = "";
176 node1.child_count = node1_children.size();
177 node1.children_in_traversal_order = node1_children.data();
178 node1.children_in_hit_test_order = node1_children.data();
179 node1.custom_accessibility_actions_count = 0;
180
181 FlutterSemanticsNode2 node2;
182 node2.id = 2;
183 node2.flags2 = &flags;
184 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
185 node2.actions = static_cast<FlutterSemanticsAction>(0);
186 node2.text_selection_base = -1;
187 node2.text_selection_extent = -1;
188 node2.label = "node2";
189 node2.hint = "";
190 node2.value = "";
191 node2.increased_value = "";
192 node2.decreased_value = "";
193 node2.tooltip = "";
194 node2.child_count = 0;
195 node2.custom_accessibility_actions_count = 0;
196
197 bridge->AddFlutterSemanticsNodeUpdate(node1);
198 bridge->AddFlutterSemanticsNodeUpdate(node2);
199 bridge->CommitUpdates();
200
201 // Look up the root node delegate.
202 auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
203 ASSERT_TRUE(root_delegate);
204 ASSERT_EQ(root_delegate->GetChildCount(), 1);
205
206 // Look up the child node delegate.
207 auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
208 ASSERT_TRUE(child_delegate);
209 ASSERT_EQ(child_delegate->GetChildCount(), 0);
210
211 // Ensure a node with ID 0 does not exist.
212 auto invalid_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
213 ASSERT_FALSE(invalid_delegate);
214
215 [engine shutDownEngine];
216 }
217
218 TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenHeadless) {
219 FlutterViewController* viewController = CreateTestViewController();
220 FlutterEngine* engine = viewController.engine;
221
222 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
223 // can query semantics information from.
224 engine.semanticsEnabled = YES;
225 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
226 viewController.accessibilityBridge.lock());
227 FlutterSemanticsNode2 root;
228 FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
229 root.id = 0;
230 root.flags2 = &flags;
231 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
232 root.actions = static_cast<FlutterSemanticsAction>(0);
233 root.text_selection_base = -1;
234 root.text_selection_extent = -1;
235 root.label = "root";
236 root.hint = "";
237 root.value = "";
238 root.increased_value = "";
239 root.decreased_value = "";
240 root.tooltip = "";
241 root.child_count = 0;
242 root.custom_accessibility_actions_count = 0;
243 bridge->AddFlutterSemanticsNodeUpdate(root);
244
245 bridge->CommitUpdates();
246 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
247
248 // Creates a targeted event.
249 ui::AXTree tree;
250 ui::AXNode ax_node(&tree, nullptr, 0, 0);
251 ui::AXNodeData node_data;
252 node_data.id = 0;
253 ax_node.SetData(node_data);
254 std::vector<ui::AXEventIntent> intent;
255 ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
256 ax::mojom::EventFrom::kNone, intent);
257 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
258
259 bridge->OnAccessibilityEvent(targeted_event);
260
261 // Does not send any notification if the engine is headless.
262 EXPECT_EQ(bridge->actual_notifications.size(), 0u);
263 [engine shutDownEngine];
264 }
265
266 TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenNoWindow) {
267 FlutterViewController* viewController = CreateTestViewController();
268 FlutterEngine* engine = viewController.engine;
269
270 // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
271 // can query semantics information from.
272 engine.semanticsEnabled = YES;
273 auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
274 viewController.accessibilityBridge.lock());
275 FlutterSemanticsNode2 root;
276 FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
277 root.id = 0;
278 root.flags2 = &flags;
279 // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
280 root.actions = static_cast<FlutterSemanticsAction>(0);
281 root.text_selection_base = -1;
282 root.text_selection_extent = -1;
283 root.label = "root";
284 root.hint = "";
285 root.value = "";
286 root.increased_value = "";
287 root.decreased_value = "";
288 root.tooltip = "";
289 root.child_count = 0;
290 root.custom_accessibility_actions_count = 0;
291 bridge->AddFlutterSemanticsNodeUpdate(root);
292
293 bridge->CommitUpdates();
294 auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
295
296 // Creates a targeted event.
297 ui::AXTree tree;
298 ui::AXNode ax_node(&tree, nullptr, 0, 0);
299 ui::AXNodeData node_data;
300 node_data.id = 0;
301 ax_node.SetData(node_data);
302 std::vector<ui::AXEventIntent> intent;
303 ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
304 ax::mojom::EventFrom::kNone, intent);
305 ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
306
307 bridge->OnAccessibilityEvent(targeted_event);
308
309 // Does not send any notification if the flutter view is not attached to a NSWindow.
310 EXPECT_EQ(bridge->actual_notifications.size(), 0u);
311 [engine shutDownEngine];
312 }
313
314 } // namespace flutter::testing
AccessibilityBridgeMac.h
actual_notifications
std::unordered_map< std::string, gfx::NativeViewAccessible > actual_notifications
Definition: AccessibilityBridgeMacTest.mm:25
FlutterDartProject_Internal.h
FlutterEngine_Internal.h
FlutterViewController_Internal.h
FlutterDartProject
Definition: FlutterDartProject.mm:24
flutter::AccessibilityBridgeMac
Definition: AccessibilityBridgeMac.h:28
flutter::AccessibilityBridgeMac::OnAccessibilityEvent
void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override
Handle accessibility events generated due to accessibility tree changes. These events are needed to b...
Definition: AccessibilityBridgeMac.mm:27
AccessibilityBridgeTestViewController
Definition: AccessibilityBridgeMacTest.mm:38
FlutterEngine
Definition: FlutterEngine.h:32
-[FlutterEngine shutDownEngine]
void shutDownEngine()
Definition: FlutterEngine.mm:1323
FlutterViewController
Definition: FlutterViewController.h:73
FlutterViewController::engine
FlutterEngine * engine
Definition: FlutterViewController.h:78
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
flutter::testing::TEST_F
TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlutterViewWindow)
Definition: AccessibilityBridgeMacTest.mm:90