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 
10 
11 namespace flutter::testing {
12 
13 namespace {
14 
15 class AccessibilityBridgeMacSpy : public AccessibilityBridgeMac {
16  public:
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 
36 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
37  (nonnull FlutterEngine*)engine;
38 @end
39 
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