blob: 0ad11af0e3e8f66cd249067e16d9c471708b0c46 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/testing/testing.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
namespace flutter::testing {
namespace {
class AccessibilityBridgeMacSpy : public AccessibilityBridgeMac {
public:
using AccessibilityBridgeMac::OnAccessibilityEvent;
AccessibilityBridgeMacSpy(__weak FlutterEngine* flutter_engine,
__weak FlutterViewController* view_controller)
: AccessibilityBridgeMac(flutter_engine, view_controller) {}
std::unordered_map<std::string, gfx::NativeViewAccessible> actual_notifications;
private:
void DispatchMacOSNotification(gfx::NativeViewAccessible native_node,
NSAccessibilityNotificationName mac_notification) override {
actual_notifications[[mac_notification UTF8String]] = native_node;
}
};
} // namespace
} // namespace flutter::testing
@interface AccessibilityBridgeTestEngine : FlutterEngine
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
createAccessibilityBridge:(nonnull FlutterEngine*)engine
viewController:(nonnull FlutterViewController*)viewController;
@end
@implementation AccessibilityBridgeTestEngine
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
createAccessibilityBridge:(nonnull FlutterEngine*)engine
viewController:(nonnull FlutterViewController*)viewController {
return std::make_shared<flutter::testing::AccessibilityBridgeMacSpy>(engine, viewController);
}
@end
namespace flutter::testing {
namespace {
// Returns an engine configured for the text fixture resource configuration.
FlutterEngine* CreateTestEngine() {
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
return [[AccessibilityBridgeTestEngine alloc] initWithName:@"test"
project:project
allowHeadlessExecution:true];
}
} // namespace
TEST(AccessibilityBridgeMacTest, sendsAccessibilityCreateNotificationToWindowOfFlutterView) {
FlutterEngine* engine = CreateTestEngine();
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
[engine setViewController:viewController];
NSWindow* expectedTarget = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
expectedTarget.contentView = viewController.view;
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
// can query semantics information from.
engine.semanticsEnabled = YES;
auto bridge =
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
FlutterSemanticsNode root;
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.actions = static_cast<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;
root.label = "root";
root.hint = "";
root.value = "";
root.increased_value = "";
root.decreased_value = "";
root.tooltip = "";
root.child_count = 0;
root.custom_accessibility_actions_count = 0;
bridge->AddFlutterSemanticsNodeUpdate(&root);
bridge->CommitUpdates();
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
// Creates a targeted event.
ui::AXTree tree;
ui::AXNode ax_node(&tree, nullptr, 0, 0);
ui::AXNodeData node_data;
node_data.id = 0;
ax_node.SetData(node_data);
std::vector<ui::AXEventIntent> intent;
ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
ax::mojom::EventFrom::kNone, intent);
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
bridge->OnAccessibilityEvent(targeted_event);
EXPECT_EQ(bridge->actual_notifications.size(), 1u);
EXPECT_EQ(
bridge->actual_notifications.find([NSAccessibilityCreatedNotification UTF8String])->second,
expectedTarget);
[engine shutDownEngine];
}
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenHeadless) {
FlutterEngine* engine = CreateTestEngine();
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
[engine setViewController:viewController];
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
// can query semantics information from.
engine.semanticsEnabled = YES;
auto bridge =
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
FlutterSemanticsNode root;
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.actions = static_cast<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;
root.label = "root";
root.hint = "";
root.value = "";
root.increased_value = "";
root.decreased_value = "";
root.tooltip = "";
root.child_count = 0;
root.custom_accessibility_actions_count = 0;
bridge->AddFlutterSemanticsNodeUpdate(&root);
bridge->CommitUpdates();
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
// Creates a targeted event.
ui::AXTree tree;
ui::AXNode ax_node(&tree, nullptr, 0, 0);
ui::AXNodeData node_data;
node_data.id = 0;
ax_node.SetData(node_data);
std::vector<ui::AXEventIntent> intent;
ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
ax::mojom::EventFrom::kNone, intent);
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
bridge->OnAccessibilityEvent(targeted_event);
// Does not send any notification if the engine is headless.
EXPECT_EQ(bridge->actual_notifications.size(), 0u);
[engine shutDownEngine];
}
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenNoWindow) {
FlutterEngine* engine = CreateTestEngine();
// Create a view controller without attaching it to a window.
NSString* fixtures = @(testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
[engine setViewController:viewController];
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
// can query semantics information from.
engine.semanticsEnabled = YES;
auto bridge =
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
FlutterSemanticsNode root;
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.actions = static_cast<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;
root.label = "root";
root.hint = "";
root.value = "";
root.increased_value = "";
root.decreased_value = "";
root.tooltip = "";
root.child_count = 0;
root.custom_accessibility_actions_count = 0;
bridge->AddFlutterSemanticsNodeUpdate(&root);
bridge->CommitUpdates();
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
// Creates a targeted event.
ui::AXTree tree;
ui::AXNode ax_node(&tree, nullptr, 0, 0);
ui::AXNodeData node_data;
node_data.id = 0;
ax_node.SetData(node_data);
std::vector<ui::AXEventIntent> intent;
ui::AXEventGenerator::EventParams event_params(ui::AXEventGenerator::Event::CHILDREN_CHANGED,
ax::mojom::EventFrom::kNone, intent);
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
bridge->OnAccessibilityEvent(targeted_event);
// Does not send any notification if the flutter view is not attached to a NSWindow.
EXPECT_EQ(bridge->actual_notifications.size(), 0u);
[engine shutDownEngine];
}
} // namespace flutter::testing