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 
6 
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:
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 
38 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
39  (nonnull FlutterEngine*)engine;
40 @end
41 
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  root.id = 0;
103  root.flags = static_cast<FlutterSemanticsFlag>(0);
104  root.actions = static_cast<FlutterSemanticsAction>(0);
105  root.text_selection_base = -1;
106  root.text_selection_extent = -1;
107  root.label = "root";
108  root.hint = "";
109  root.value = "";
110  root.increased_value = "";
111  root.decreased_value = "";
112  root.tooltip = "";
113  root.child_count = 0;
114  root.custom_accessibility_actions_count = 0;
115  bridge->AddFlutterSemanticsNodeUpdate(root);
116 
117  bridge->CommitUpdates();
118  auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
119 
120  // Creates a targeted event.
121  ui::AXTree tree;
122  ui::AXNode ax_node(&tree, nullptr, 0, 0);
123  ui::AXNodeData node_data;
124  node_data.id = 0;
125  ax_node.SetData(node_data);
126  std::vector<ui::AXEventIntent> intent;
127  ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
128  ax::mojom::EventFrom::kNone, intent);
129  ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
130 
131  bridge->OnAccessibilityEvent(targeted_event);
132 
133  ASSERT_EQ(bridge->actual_notifications.size(), 1u);
134  auto target = bridge->actual_notifications.find([NSAccessibilityCreatedNotification UTF8String]);
135  ASSERT_NE(target, bridge->actual_notifications.end());
136  EXPECT_EQ(target->second, expectedTarget);
137  [engine shutDownEngine];
138 }
139 
140 // Flutter used to assume that the accessibility root had ID 0.
141 // In a multi-view world, each view has its own accessibility root
142 // with a globally unique node ID.
143 //
144 // node1
145 // |
146 // node2
147 TEST_F(AccessibilityBridgeMacWindowTest, NonZeroRootNodeId) {
148  FlutterViewController* viewController = CreateTestViewController();
149  FlutterEngine* engine = viewController.engine;
150  NSWindow* expectedTarget = GetWindow();
151  expectedTarget.contentView = viewController.view;
152 
153  // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
154  // can query semantics information from.
155  engine.semanticsEnabled = YES;
156  auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
157  viewController.accessibilityBridge.lock());
158 
159  FlutterSemanticsNode2 node1;
160  std::vector<int32_t> node1_children{2};
161  node1.id = 1;
162  node1.flags = static_cast<FlutterSemanticsFlag>(0);
163  node1.actions = static_cast<FlutterSemanticsAction>(0);
164  node1.text_selection_base = -1;
165  node1.text_selection_extent = -1;
166  node1.label = "node1";
167  node1.hint = "";
168  node1.value = "";
169  node1.increased_value = "";
170  node1.decreased_value = "";
171  node1.tooltip = "";
172  node1.child_count = node1_children.size();
173  node1.children_in_traversal_order = node1_children.data();
174  node1.children_in_hit_test_order = node1_children.data();
175  node1.custom_accessibility_actions_count = 0;
176 
177  FlutterSemanticsNode2 node2;
178  node2.id = 2;
179  node2.flags = static_cast<FlutterSemanticsFlag>(0);
180  node2.actions = static_cast<FlutterSemanticsAction>(0);
181  node2.text_selection_base = -1;
182  node2.text_selection_extent = -1;
183  node2.label = "node2";
184  node2.hint = "";
185  node2.value = "";
186  node2.increased_value = "";
187  node2.decreased_value = "";
188  node2.tooltip = "";
189  node2.child_count = 0;
190  node2.custom_accessibility_actions_count = 0;
191 
192  bridge->AddFlutterSemanticsNodeUpdate(node1);
193  bridge->AddFlutterSemanticsNodeUpdate(node2);
194  bridge->CommitUpdates();
195 
196  // Look up the root node delegate.
197  auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
198  ASSERT_TRUE(root_delegate);
199  ASSERT_EQ(root_delegate->GetChildCount(), 1);
200 
201  // Look up the child node delegate.
202  auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
203  ASSERT_TRUE(child_delegate);
204  ASSERT_EQ(child_delegate->GetChildCount(), 0);
205 
206  // Ensure a node with ID 0 does not exist.
207  auto invalid_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
208  ASSERT_FALSE(invalid_delegate);
209 
210  [engine shutDownEngine];
211 }
212 
213 TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenHeadless) {
214  FlutterViewController* viewController = CreateTestViewController();
215  FlutterEngine* engine = viewController.engine;
216 
217  // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
218  // can query semantics information from.
219  engine.semanticsEnabled = YES;
220  auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
221  viewController.accessibilityBridge.lock());
222  FlutterSemanticsNode2 root;
223  root.id = 0;
224  root.flags = static_cast<FlutterSemanticsFlag>(0);
225  root.actions = static_cast<FlutterSemanticsAction>(0);
226  root.text_selection_base = -1;
227  root.text_selection_extent = -1;
228  root.label = "root";
229  root.hint = "";
230  root.value = "";
231  root.increased_value = "";
232  root.decreased_value = "";
233  root.tooltip = "";
234  root.child_count = 0;
235  root.custom_accessibility_actions_count = 0;
236  bridge->AddFlutterSemanticsNodeUpdate(root);
237 
238  bridge->CommitUpdates();
239  auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
240 
241  // Creates a targeted event.
242  ui::AXTree tree;
243  ui::AXNode ax_node(&tree, nullptr, 0, 0);
244  ui::AXNodeData node_data;
245  node_data.id = 0;
246  ax_node.SetData(node_data);
247  std::vector<ui::AXEventIntent> intent;
248  ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
249  ax::mojom::EventFrom::kNone, intent);
250  ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
251 
252  bridge->OnAccessibilityEvent(targeted_event);
253 
254  // Does not send any notification if the engine is headless.
255  EXPECT_EQ(bridge->actual_notifications.size(), 0u);
256  [engine shutDownEngine];
257 }
258 
259 TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenNoWindow) {
260  FlutterViewController* viewController = CreateTestViewController();
261  FlutterEngine* engine = viewController.engine;
262 
263  // Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
264  // can query semantics information from.
265  engine.semanticsEnabled = YES;
266  auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
267  viewController.accessibilityBridge.lock());
268  FlutterSemanticsNode2 root;
269  root.id = 0;
270  root.flags = static_cast<FlutterSemanticsFlag>(0);
271  root.actions = static_cast<FlutterSemanticsAction>(0);
272  root.text_selection_base = -1;
273  root.text_selection_extent = -1;
274  root.label = "root";
275  root.hint = "";
276  root.value = "";
277  root.increased_value = "";
278  root.decreased_value = "";
279  root.tooltip = "";
280  root.child_count = 0;
281  root.custom_accessibility_actions_count = 0;
282  bridge->AddFlutterSemanticsNodeUpdate(root);
283 
284  bridge->CommitUpdates();
285  auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
286 
287  // Creates a targeted event.
288  ui::AXTree tree;
289  ui::AXNode ax_node(&tree, nullptr, 0, 0);
290  ui::AXNodeData node_data;
291  node_data.id = 0;
292  ax_node.SetData(node_data);
293  std::vector<ui::AXEventIntent> intent;
294  ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
295  ax::mojom::EventFrom::kNone, intent);
296  ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
297 
298  bridge->OnAccessibilityEvent(targeted_event);
299 
300  // Does not send any notification if the flutter view is not attached to a NSWindow.
301  EXPECT_EQ(bridge->actual_notifications.size(), 0u);
302  [engine shutDownEngine];
303 }
304 
305 } // namespace flutter::testing
FlutterEngine
Definition: FlutterEngine.h:31
flutter::AccessibilityBridgeMac
Definition: AccessibilityBridgeMac.h:28
FlutterViewController
Definition: FlutterViewController.h:73
AccessibilityBridgeTestViewController
Definition: AccessibilityBridgeMacTest.mm:37
FlutterEngine_Internal.h
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
FlutterViewController::engine
FlutterEngine * engine
Definition: FlutterViewController.h:78
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
flutter::testing::TEST_F
TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhenNoWindow)
Definition: AccessibilityBridgeMacTest.mm:259
-[FlutterEngine shutDownEngine]
void shutDownEngine()
Definition: FlutterEngine.mm:1134
FlutterDartProject
Definition: FlutterDartProject.mm:24
actual_notifications
std::unordered_map< std::string, gfx::NativeViewAccessible > actual_notifications
Definition: AccessibilityBridgeMacTest.mm:25