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