text: // 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/shell/platform/windows/accessibility_bridge_delegate_win32.h"
  
  #include <comdef.h>
  #include <comutil.h>
  #include <oleacc.h>
  
  #include <vector>
  
  #include "flutter/shell/platform/embedder/embedder.h"
  #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
  #include "flutter/shell/platform/windows/flutter_platform_node_delegate_win32.h"
  #include "flutter/shell/platform/windows/flutter_windows_engine.h"
  #include "flutter/shell/platform/windows/flutter_windows_view.h"
  #include "flutter/shell/platform/windows/testing/engine_modifier.h"
  #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
  #include "flutter/shell/platform/windows/testing/test_keyboard.h"
  #include "gmock/gmock.h"
  #include "gtest/gtest.h"
  
  namespace flutter {
  namespace testing {
  
  namespace {
  
  // A structure representing a Win32 MSAA event targeting a specified node.
  struct MsaaEvent {
    std::shared_ptr<FlutterPlatformNodeDelegateWin32> node_delegate;
    DWORD event_type;
  };
  
  // Accessibility bridge delegate that captures events dispatched to the OS.
  class AccessibilityBridgeDelegateWin32Spy
      : public AccessibilityBridgeDelegateWin32 {
   public:
    explicit AccessibilityBridgeDelegateWin32Spy(FlutterWindowsEngine* engine)
        : AccessibilityBridgeDelegateWin32(engine) {}
  
    void DispatchWinAccessibilityEvent(
        std::shared_ptr<FlutterPlatformNodeDelegateWin32> node_delegate,
        DWORD event_type) override {
      dispatched_events_.push_back({node_delegate, event_type});
    }
  
    void SetFocus(std::shared_ptr<FlutterPlatformNodeDelegateWin32> node_delegate)
        override {
      focused_nodes_.push_back(node_delegate->GetAXNode()->id());
    }
  
    void Reset() {
      dispatched_events_.clear();
      focused_nodes_.clear();
    }
  
    const std::vector<MsaaEvent>& dispatched_events() const {
      return dispatched_events_;
    }
  
    const std::vector<int32_t> focused_nodes() const { return focused_nodes_; }
  
   private:
    std::vector<MsaaEvent> dispatched_events_;
    std::vector<int32_t> focused_nodes_;
  };
  
  // Returns an engine instance configured with dummy project path values, and
  // overridden methods for sending platform messages, so that the engine can
  // respond as if the framework were connected.
  std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
    FlutterDesktopEngineProperties properties = {};
    properties.assets_path = L"C:\\foo\\flutter_assets";
    properties.icu_data_path = L"C:\\foo\\icudtl.dat";
    properties.aot_library_path = L"C:\\foo\\aot.so";
    FlutterProjectBundle project(properties);
    auto engine = std::make_unique<FlutterWindowsEngine>(project);
  
    EngineModifier modifier(engine.get());
    modifier.embedder_api().UpdateSemanticsEnabled =
        [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
          return kSuccess;
        };
  
    MockEmbedderApiForKeyboard(modifier,
                               std::make_shared<MockKeyResponseController>());
  
    engine->RunWithEntrypoint(nullptr);
    return engine;
  }
  
  // Populates the AXTree associated with the specified bridge with test data.
  //
  //        node0
  //        /   \
  //    node1    node2
  //             /   \
  //         node3   node4
  //
  // node0 and node2 are grouping nodes. node1 and node2 are static text nodes.
  // node4 is a static text node with no text, and hence has the "ignored" state.
  void PopulateAXTree(std::shared_ptr<AccessibilityBridge> bridge) {
    // Add node 0: root.
    FlutterSemanticsNode node0{sizeof(FlutterSemanticsNode), 0};
    std::vector<int32_t> node0_children{1, 2};
    node0.child_count = node0_children.size();
    node0.children_in_traversal_order = node0_children.data();
    node0.children_in_hit_test_order = node0_children.data();
  
    // Add node 1: text child of node 0.
    FlutterSemanticsNode node1{sizeof(FlutterSemanticsNode), 1};
    node1.label = "prefecture";
    node1.value = "Kyoto";
  
    // Add node 2: subtree child of node 0.
    FlutterSemanticsNode node2{sizeof(FlutterSemanticsNode), 2};
    std::vector<int32_t> node2_children{3, 4};
    node2.child_count = node2_children.size();
    node2.children_in_traversal_order = node2_children.data();
    node2.children_in_hit_test_order = node2_children.data();
  
    // Add node 3: text child of node 2.
    FlutterSemanticsNode node3{sizeof(FlutterSemanticsNode), 3};
    node3.label = "city";
    node3.value = "Uji";
  
    // Add node 4: text child (with no text) of node 2.
    FlutterSemanticsNode node4{sizeof(FlutterSemanticsNode), 4};
  
    bridge->AddFlutterSemanticsNodeUpdate(&node0);
    bridge->AddFlutterSemanticsNodeUpdate(&node1);
    bridge->AddFlutterSemanticsNodeUpdate(&node2);
    bridge->AddFlutterSemanticsNodeUpdate(&node3);
    bridge->AddFlutterSemanticsNodeUpdate(&node4);
    bridge->CommitUpdates();
  }
  
  ui::AXNode* AXNodeFromID(std::shared_ptr<AccessibilityBridge> bridge,
                           int32_t id) {
    auto node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(id).lock();
    return node_delegate ? node_delegate->GetAXNode() : nullptr;
  }
  
  void ExpectWinEventFromAXEvent(int32_t node_id,
                                 ui::AXEventGenerator::Event ax_event,
                                 DWORD expected_event) {
    auto window_binding_handler =
        std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
    FlutterWindowsView view(std::move(window_binding_handler));
    view.SetEngine(GetTestEngine());
    view.OnUpdateSemanticsEnabled(true);
  
    auto bridge = view.GetEngine()->accessibility_bridge().lock();
    PopulateAXTree(bridge);
  
    AccessibilityBridgeDelegateWin32Spy spy(view.GetEngine());
    spy.OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
                              {ax_event, ax::mojom::EventFrom::kNone, {}}});
    ASSERT_EQ(spy.dispatched_events().size(), 1);
    EXPECT_EQ(spy.dispatched_events()[0].event_type, expected_event);
  }
  
  }  // namespace
  
  TEST(AccessibilityBridgeDelegateWin32, GetParent) {
    auto window_binding_handler =
        std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
    FlutterWindowsView view(std::move(window_binding_handler));
    view.SetEngine(GetTestEngine());
    view.OnUpdateSemanticsEnabled(true);
  
    auto bridge = view.GetEngine()->accessibility_bridge().lock();
    PopulateAXTree(bridge);
  
    auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
    auto node1_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
    EXPECT_EQ(node0_delegate->GetNativeViewAccessible(),
              node1_delegate->GetParent());
  }
  
  TEST(AccessibilityBridgeDelegateWin32, GetParentOnRootRetunsNullptr) {
    auto window_binding_handler =
        std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
    FlutterWindowsView view(std::move(window_binding_handler));
    view.SetEngine(GetTestEngine());
    view.OnUpdateSemanticsEnabled(true);
  
    auto bridge = view.GetEngine()->accessibility_bridge().lock();
    PopulateAXTree(bridge);
  
    auto node0_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
    ASSERT_TRUE(node0_delegate->GetParent() == nullptr);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, DispatchAccessibilityAction) {
    auto window_binding_handler =
        std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
    FlutterWindowsView view(std::move(window_binding_handler));
    view.SetEngine(GetTestEngine());
    view.OnUpdateSemanticsEnabled(true);
  
    auto bridge = view.GetEngine()->accessibility_bridge().lock();
    PopulateAXTree(bridge);
  
    FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
    EngineModifier modifier(view.GetEngine());
    modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC(
        DispatchSemanticsAction,
        ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t id,
                          FlutterSemanticsAction action, const uint8_t* data,
                          size_t data_length) {
          actual_action = action;
          return kSuccess;
        }));
  
    AccessibilityBridgeDelegateWin32 delegate(view.GetEngine());
    delegate.DispatchAccessibilityAction(1, kFlutterSemanticsActionCopy, {});
    EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityEventAlert) {
    ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
                              EVENT_SYSTEM_ALERT);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityEventChildrenChanged) {
    ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
                              EVENT_OBJECT_REORDER);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityEventFocusChanged) {
    auto window_binding_handler =
        std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
    FlutterWindowsView view(std::move(window_binding_handler));
    view.SetEngine(GetTestEngine());
    view.OnUpdateSemanticsEnabled(true);
  
    auto bridge = view.GetEngine()->accessibility_bridge().lock();
    PopulateAXTree(bridge);
  
    AccessibilityBridgeDelegateWin32Spy spy(view.GetEngine());
    spy.OnAccessibilityEvent({AXNodeFromID(bridge, 1),
                              {ui::AXEventGenerator::Event::FOCUS_CHANGED,
                               ax::mojom::EventFrom::kNone,
                               {}}});
    ASSERT_EQ(spy.dispatched_events().size(), 1);
    EXPECT_EQ(spy.dispatched_events()[0].event_type, EVENT_OBJECT_FOCUS);
  
    ASSERT_EQ(spy.focused_nodes().size(), 1);
    EXPECT_EQ(spy.focused_nodes()[0], 1);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityEventIgnoredChanged) {
    // Static test nodes with no text, hint, or scrollability are ignored.
    ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
                              EVENT_OBJECT_HIDE);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityImageAnnotationChanged) {
    ExpectWinEventFromAXEvent(
        1, ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED,
        EVENT_OBJECT_NAMECHANGE);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityLiveRegionChanged) {
    ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
                              EVENT_OBJECT_LIVEREGIONCHANGED);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityNameChanged) {
    ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
                              EVENT_OBJECT_NAMECHANGE);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityHScrollPosChanged) {
    ExpectWinEventFromAXEvent(
        1, ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED,
        EVENT_SYSTEM_SCROLLINGEND);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityVScrollPosChanged) {
    ExpectWinEventFromAXEvent(
        1, ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED,
        EVENT_SYSTEM_SCROLLINGEND);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilitySelectedChanged) {
    ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
                              EVENT_OBJECT_VALUECHANGE);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilitySelectedChildrenChanged) {
    ExpectWinEventFromAXEvent(
        2, ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED,
        EVENT_OBJECT_SELECTIONWITHIN);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilitySubtreeCreated) {
    ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
                              EVENT_OBJECT_SHOW);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityValueChanged) {
    ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
                              EVENT_OBJECT_VALUECHANGE);
  }
  
  TEST(AccessibilityBridgeDelegateWin32, OnAccessibilityStateChanged) {
    ExpectWinEventFromAXEvent(
        1, ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED,
        EVENT_OBJECT_STATECHANGE);
  }
  
  }  // namespace testing
  }  // namespace flutter
  