// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"

#include <oleacc.h>
#include <wrl/client.h>

#include <algorithm>
#include <memory>

#include "base/auto_reset.h"
#include "base/check_deref.h"
#include "base/compiler_specific.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/strings/string_util_win.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/iaccessible2/ia2_api_all.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"

using Microsoft::WRL::ComPtr;
using base::win::ScopedBstr;
using base::win::ScopedVariant;

namespace ui {

const std::u16string AXPlatformNodeWinTest::kEmbeddedCharacterAsString = {
    AXPlatformNodeBase::kEmbeddedCharacter};

namespace {

// Most IAccessible functions require a VARIANT set to CHILDID_SELF as
// the first argument.
ScopedVariant SELF(CHILDID_SELF);

// Calls `Release()` on each of the `count` interface pointers in `pointers`.
void ReleasePointers(IUnknown** pointers, LONG count) {
  if (count > 0) {
    std::ranges::for_each(
        UNSAFE_TODO(base::span(pointers, static_cast<size_t>(count))),
        [](IUnknown* ptr) { ptr->Release(); });
  }
}

}  // namespace

// Helper macros for UIAutomation HRESULT expectations
#define EXPECT_UIA_ELEMENTNOTAVAILABLE(expr) \
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
#define EXPECT_UIA_INVALIDOPERATION(expr) \
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
#define EXPECT_UIA_ELEMENTNOTENABLED(expr) \
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
#define EXPECT_UIA_NOTSUPPORTED(expr) \
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))

#define ASSERT_UIA_ELEMENTNOTAVAILABLE(expr) \
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
#define ASSERT_UIA_INVALIDOPERATION(expr) \
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
#define ASSERT_UIA_ELEMENTNOTENABLED(expr) \
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
#define ASSERT_UIA_NOTSUPPORTED(expr) \
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))

// Helper macros for testing UIAutomation property values and maintain
// correct stack tracing and failure causality.
//
// WARNING: These aren't intended to be generic EXPECT_BSTR_EQ macros
// as the logic is specific to extracting and comparing UIA property
// values.
#define EXPECT_UIA_EMPTY(node, property_id)                     \
  {                                                             \
    ScopedVariant actual;                                       \
    ASSERT_HRESULT_SUCCEEDED(                                   \
        node->GetPropertyValue(property_id, actual.Receive())); \
    EXPECT_EQ(VT_EMPTY, actual.type());                         \
  }

#define EXPECT_UIA_VALUE_EQ(node, property_id, expectedVariant) \
  {                                                             \
    ScopedVariant actual;                                       \
    ASSERT_HRESULT_SUCCEEDED(                                   \
        node->GetPropertyValue(property_id, actual.Receive())); \
    EXPECT_EQ(0, actual.Compare(expectedVariant));              \
  }

#define EXPECT_UIA_BSTR_EQ(node, property_id, expected)                  \
  {                                                                      \
    ScopedVariant expectedVariant(expected);                             \
    ASSERT_EQ(VT_BSTR, expectedVariant.type());                          \
    ASSERT_NE(nullptr, expectedVariant.ptr()->bstrVal);                  \
    ScopedVariant actual;                                                \
    ASSERT_HRESULT_SUCCEEDED(                                            \
        node->GetPropertyValue(property_id, actual.Receive()));          \
    ASSERT_EQ(VT_BSTR, actual.type());                                   \
    ASSERT_NE(nullptr, actual.ptr()->bstrVal);                           \
    EXPECT_STREQ(expectedVariant.ptr()->bstrVal, actual.ptr()->bstrVal); \
  }

#define EXPECT_UIA_BOOL_EQ(node, property_id, expected)               \
  {                                                                   \
    ScopedVariant expectedVariant(expected);                          \
    ASSERT_EQ(VT_BOOL, expectedVariant.type());                       \
    ScopedVariant actual;                                             \
    ASSERT_HRESULT_SUCCEEDED(                                         \
        node->GetPropertyValue(property_id, actual.Receive()));       \
    EXPECT_EQ(expectedVariant.ptr()->boolVal, actual.ptr()->boolVal); \
  }

#define EXPECT_UIA_DOUBLE_ARRAY_EQ(node, array_property_id,                 \
                                   expected_property_values)                \
  {                                                                         \
    ScopedVariant array;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                               \
        node->GetPropertyValue(array_property_id, array.Receive()));        \
    ASSERT_EQ(VT_ARRAY | VT_R8, array.type());                              \
    ASSERT_EQ(1u, SafeArrayGetDim(array.ptr()->parray));                    \
    LONG array_lower_bound;                                                 \
    ASSERT_HRESULT_SUCCEEDED(                                               \
        SafeArrayGetLBound(array.ptr()->parray, 1, &array_lower_bound));    \
    LONG array_upper_bound;                                                 \
    ASSERT_HRESULT_SUCCEEDED(                                               \
        SafeArrayGetUBound(array.ptr()->parray, 1, &array_upper_bound));    \
    double* array_data;                                                     \
    ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(                         \
        array.ptr()->parray, reinterpret_cast<void**>(&array_data)));       \
    size_t count = array_upper_bound - array_lower_bound + 1;               \
    ASSERT_EQ(expected_property_values.size(), count);                      \
    for (size_t i = 0; i < count; ++i) {                                    \
      EXPECT_EQ(UNSAFE_TODO(array_data[i]), expected_property_values[i]);   \
    }                                                                       \
    ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(array.ptr()->parray)); \
  }

#define EXPECT_UIA_INT_EQ(node, property_id, expected)              \
  {                                                                 \
    ScopedVariant expectedVariant(expected);                        \
    ASSERT_EQ(VT_I4, expectedVariant.type());                       \
    ScopedVariant actual;                                           \
    ASSERT_HRESULT_SUCCEEDED(                                       \
        node->GetPropertyValue(property_id, actual.Receive()));     \
    EXPECT_EQ(expectedVariant.ptr()->intVal, actual.ptr()->intVal); \
  }

#define EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(array, element_test_property_id,      \
                                         expected_property_values)             \
  {                                                                            \
    ASSERT_EQ(1u, SafeArrayGetDim(array));                                     \
    LONG array_lower_bound;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        SafeArrayGetLBound(array, 1, &array_lower_bound));                     \
    LONG array_upper_bound;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        SafeArrayGetUBound(array, 1, &array_upper_bound));                     \
    IUnknown** array_data;                                                     \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        ::SafeArrayAccessData(array, reinterpret_cast<void**>(&array_data)));  \
    size_t count = array_upper_bound - array_lower_bound + 1;                  \
    ASSERT_EQ(expected_property_values.size(), count);                         \
    for (size_t i = 0; i < count; ++i) {                                       \
      ComPtr<IRawElementProviderSimple> element;                               \
      ASSERT_HRESULT_SUCCEEDED(                                                \
          UNSAFE_TODO(array_data[i])->QueryInterface(IID_PPV_ARGS(&element))); \
      EXPECT_UIA_BSTR_EQ(element, element_test_property_id,                    \
                         expected_property_values[i].c_str());                 \
    }                                                                          \
    ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(array));                  \
  }

#define EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(node, array_property_id,  \
                                                  element_test_property_id, \
                                                  expected_property_values) \
  {                                                                         \
    ScopedVariant array;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                               \
        node->GetPropertyValue(array_property_id, array.Receive()));        \
    ASSERT_EQ(VT_ARRAY | VT_UNKNOWN, array.type());                         \
    EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(array.ptr()->parray,                   \
                                     element_test_property_id,              \
                                     expected_property_values);             \
  }

#define EXPECT_UIA_PROPERTY_UNORDERED_ELEMENT_ARRAY_BSTR_EQ(                   \
    node, array_property_id, element_test_property_id,                         \
    expected_property_values)                                                  \
  {                                                                            \
    ScopedVariant array;                                                       \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        node->GetPropertyValue(array_property_id, array.Receive()));           \
    ASSERT_EQ(VT_ARRAY | VT_UNKNOWN, array.type());                            \
    ASSERT_EQ(1u, SafeArrayGetDim(array.ptr()->parray));                       \
    LONG array_lower_bound;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        SafeArrayGetLBound(array.ptr()->parray, 1, &array_lower_bound));       \
    LONG array_upper_bound;                                                    \
    ASSERT_HRESULT_SUCCEEDED(                                                  \
        SafeArrayGetUBound(array.ptr()->parray, 1, &array_upper_bound));       \
    IUnknown** array_data;                                                     \
    ASSERT_HRESULT_SUCCEEDED(::SafeArrayAccessData(                            \
        array.ptr()->parray, reinterpret_cast<void**>(&array_data)));          \
    size_t count = array_upper_bound - array_lower_bound + 1;                  \
    ASSERT_EQ(expected_property_values.size(), count);                         \
    std::vector<std::wstring> property_values;                                 \
    for (size_t i = 0; i < count; ++i) {                                       \
      ComPtr<IRawElementProviderSimple> element;                               \
      ASSERT_HRESULT_SUCCEEDED(                                                \
          UNSAFE_TODO(array_data[i])->QueryInterface(IID_PPV_ARGS(&element))); \
      ScopedVariant actual;                                                    \
      ASSERT_HRESULT_SUCCEEDED(element->GetPropertyValue(                      \
          element_test_property_id, actual.Receive()));                        \
      ASSERT_EQ(VT_BSTR, actual.type());                                       \
      ASSERT_NE(nullptr, actual.ptr()->bstrVal);                               \
      property_values.push_back(std::wstring(                                  \
          V_BSTR(actual.ptr()), SysStringLen(V_BSTR(actual.ptr()))));          \
    }                                                                          \
    ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(array.ptr()->parray));    \
    EXPECT_THAT(property_values, ::testing::UnorderedElementsAreArray(         \
                                     expected_property_values));               \
  }

MockIRawElementProviderSimple::MockIRawElementProviderSimple() = default;
MockIRawElementProviderSimple::~MockIRawElementProviderSimple() = default;

//
// IRawElementProviderSimple methods.
//
IFACEMETHODIMP MockIRawElementProviderSimple::GetPatternProvider(
    PATTERNID pattern_id,
    IUnknown** result) {
  return E_NOTIMPL;
}

IFACEMETHODIMP MockIRawElementProviderSimple::GetPropertyValue(
    PROPERTYID property_id,
    VARIANT* result) {
  return E_NOTIMPL;
}

IFACEMETHODIMP
MockIRawElementProviderSimple::get_ProviderOptions(enum ProviderOptions* ret) {
  return E_NOTIMPL;
}

IFACEMETHODIMP MockIRawElementProviderSimple::get_HostRawElementProvider(
    IRawElementProviderSimple** provider) {
  return E_NOTIMPL;
}

AXPlatformNodeWinTest::AXPlatformNodeWinTest()
    : ax_embedded_object_behavior_(
          AXEmbeddedObjectBehavior::kExposeCharacterForHypertext) {
  scoped_feature_list_.InitAndEnableFeature(features::kIChromeAccessible);
}

AXPlatformNodeWinTest::~AXPlatformNodeWinTest() {}

void AXPlatformNodeWinTest::TearDown() {
  // Destroy the tree and make sure we're not leaking any objects.
  ax_fragment_root_.reset(nullptr);
  DestroyTree();
}

void AXPlatformNodeWinTest::DestroyTree() {
  TestAXNodeWrapper::SetGlobalIsWebContent(false);
  // Must unregister all observers from the tree before it is destroyed.
  TestAXNodeWrapper::ResetGlobalState();

  TestSingleAXTreeManager::DestroyTree();
}

AXPlatformNode* AXPlatformNodeWinTest::AXPlatformNodeFromNode(AXNode* node) {
  const TestAXNodeWrapper* wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), node);
  return wrapper ? wrapper->ax_platform_node() : nullptr;
}

template <typename T>
ComPtr<T> AXPlatformNodeWinTest::QueryInterfaceFromNodeId(AXNodeID id) {
  return QueryInterfaceFromNode<T>(GetNode(id));
}

template <typename T>
ComPtr<T> AXPlatformNodeWinTest::QueryInterfaceFromNode(AXNode* node) {
  AXPlatformNode* ax_platform_node = AXPlatformNodeFromNode(node);
  if (!ax_platform_node)
    return ComPtr<T>();
  ComPtr<T> result;
  EXPECT_HRESULT_SUCCEEDED(
      ax_platform_node->GetNativeViewAccessible()->QueryInterface(__uuidof(T),
                                                                  &result));
  return result;
}

ComPtr<IRawElementProviderSimple>
AXPlatformNodeWinTest::GetRootIRawElementProviderSimple() {
  return QueryInterfaceFromNode<IRawElementProviderSimple>(GetRoot());
}

ComPtr<IRawElementProviderSimple>
AXPlatformNodeWinTest::GetIRawElementProviderSimpleFromChildIndex(
    int child_index) {
  if (!GetRoot() || child_index < 0 ||
      static_cast<size_t>(child_index) >= GetRoot()->children().size()) {
    return ComPtr<IRawElementProviderSimple>();
  }

  return QueryInterfaceFromNode<IRawElementProviderSimple>(
      GetRoot()->children()[static_cast<size_t>(child_index)]);
}

Microsoft::WRL::ComPtr<IRawElementProviderSimple>
AXPlatformNodeWinTest::GetIRawElementProviderSimpleFromId(
    const AXNodeID node_id) {
  CHECK(GetTree()) << "No tree available";
  return QueryInterfaceFromNode<IRawElementProviderSimple>(
      GetTree()->GetFromId(node_id));
}

ComPtr<IRawElementProviderFragment>
AXPlatformNodeWinTest::GetRootIRawElementProviderFragment() {
  return QueryInterfaceFromNode<IRawElementProviderFragment>(GetRoot());
}

Microsoft::WRL::ComPtr<IRawElementProviderFragment>
AXPlatformNodeWinTest::IRawElementProviderFragmentFromNode(AXNode* node) {
  AXPlatformNode* platform_node = AXPlatformNodeFromNode(node);
  gfx::NativeViewAccessible native_view =
      platform_node->GetNativeViewAccessible();
  ComPtr<IUnknown> unknown_node = native_view;
  ComPtr<IRawElementProviderFragment> fragment_node;
  unknown_node.As(&fragment_node);

  return fragment_node;
}

ComPtr<IAccessible> AXPlatformNodeWinTest::IAccessibleFromNode(AXNode* node) {
  return QueryInterfaceFromNode<IAccessible>(node);
}

ComPtr<IAccessible> AXPlatformNodeWinTest::GetRootIAccessible() {
  return IAccessibleFromNode(GetRoot());
}

ComPtr<IAccessible2> AXPlatformNodeWinTest::ToIAccessible2(
    ComPtr<IUnknown> unknown) {
  CHECK(unknown);
  ComPtr<IServiceProvider> service_provider;
  unknown.As(&service_provider);
  ComPtr<IAccessible2> result;
  CHECK(SUCCEEDED(
      service_provider->QueryService(IID_IAccessible2, IID_PPV_ARGS(&result))));
  return result;
}

ComPtr<IAccessible2> AXPlatformNodeWinTest::ToIAccessible2(
    ComPtr<IAccessible> accessible) {
  CHECK(accessible);
  ComPtr<IServiceProvider> service_provider;
  accessible.As(&service_provider);
  ComPtr<IAccessible2> result;
  CHECK(SUCCEEDED(
      service_provider->QueryService(IID_IAccessible2, IID_PPV_ARGS(&result))));
  return result;
}

ComPtr<IAccessible2_2> AXPlatformNodeWinTest::ToIAccessible2_2(
    ComPtr<IAccessible> accessible) {
  CHECK(accessible);
  ComPtr<IServiceProvider> service_provider;
  accessible.As(&service_provider);
  ComPtr<IAccessible2_2> result;
  CHECK(SUCCEEDED(service_provider->QueryService(IID_IAccessible2_2,
                                                 IID_PPV_ARGS(&result))));
  return result;
}

void AXPlatformNodeWinTest::CheckVariantHasName(const ScopedVariant& variant,
                                                const wchar_t* expected_name) {
  ASSERT_NE(nullptr, variant.ptr());
  ComPtr<IAccessible> accessible;
  ASSERT_HRESULT_SUCCEEDED(
      V_DISPATCH(variant.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
  ScopedBstr name;
  EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
  EXPECT_STREQ(expected_name, name.Get());
}

void AXPlatformNodeWinTest::CheckIUnknownHasName(ComPtr<IUnknown> unknown,
                                                 const wchar_t* expected_name) {
  ComPtr<IAccessible2> accessible = ToIAccessible2(unknown);
  ASSERT_NE(nullptr, accessible.Get());

  ScopedBstr name;
  EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
  EXPECT_STREQ(expected_name, name.Get());
}

ComPtr<IAccessibleTableCell> AXPlatformNodeWinTest::GetCellInTable() {
  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable2> table;
  root_obj.As(&table);
  if (!table)
    return ComPtr<IAccessibleTableCell>();

  ComPtr<IUnknown> cell;
  table->get_cellAt(1, 1, &cell);
  if (!cell)
    return ComPtr<IAccessibleTableCell>();

  ComPtr<IAccessibleTableCell> table_cell;
  cell.As(&table_cell);
  return table_cell;
}

void AXPlatformNodeWinTest::InitFragmentRoot() {
  test_fragment_root_delegate_ = std::make_unique<TestFragmentRootDelegate>();
  ax_fragment_root_.reset(
      InitNodeAsFragmentRoot(GetRoot(), test_fragment_root_delegate_.get()));
}

AXFragmentRootWin* AXPlatformNodeWinTest::InitNodeAsFragmentRoot(
    AXNode* node,
    TestFragmentRootDelegate* delegate) {
  delegate->child_ = AXPlatformNodeFromNode(node)->GetNativeViewAccessible();
  if (node->parent())
    delegate->parent_ =
        AXPlatformNodeFromNode(node->parent())->GetNativeViewAccessible();

  return new AXFragmentRootWin(gfx::kMockAcceleratedWidget, delegate);
}

ComPtr<IRawElementProviderFragmentRoot>
AXPlatformNodeWinTest::GetFragmentRoot() {
  ComPtr<IRawElementProviderFragmentRoot> fragment_root_provider;
  ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
      IID_PPV_ARGS(&fragment_root_provider));
  return fragment_root_provider;
}

AXPlatformNodeWinTest::PatternSet
AXPlatformNodeWinTest::GetSupportedPatternsFromNodeId(AXNodeID id) {
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(id);
  PatternSet supported_patterns;
  static const std::vector<LONG> all_supported_patterns_ = {
      UIA_TextChildPatternId,  UIA_TextEditPatternId,
      UIA_TextPatternId,       UIA_WindowPatternId,
      UIA_InvokePatternId,     UIA_ExpandCollapsePatternId,
      UIA_GridPatternId,       UIA_GridItemPatternId,
      UIA_RangeValuePatternId, UIA_ScrollPatternId,
      UIA_ScrollItemPatternId, UIA_TablePatternId,
      UIA_TableItemPatternId,  UIA_SelectionItemPatternId,
      UIA_SelectionPatternId,  UIA_TogglePatternId,
      UIA_ValuePatternId,
  };
  for (LONG property_id : all_supported_patterns_) {
    ComPtr<IUnknown> provider;
    if (SUCCEEDED(raw_element_provider_simple->GetPatternProvider(property_id,
                                                                  &provider)) &&
        provider) {
      supported_patterns.insert(property_id);
    }
  }
  return supported_patterns;
}

void AXPlatformNodeWinTest::TestGetColumnHeadersForRole(ax::mojom::Role role) {
  ASSERT_TRUE(role == ax::mojom::Role::kTable ||
              role == ax::mojom::Role::kGrid);
  AXNodeData root;
  root.id = 1;
  root.role = role;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData column_header;
  column_header.id = 3;
  column_header.role = ax::mojom::Role::kColumnHeader;
  column_header.SetName(u"column_header");
  row1.child_ids.push_back(column_header.id);

  AXNodeData row_header;
  row_header.id = 4;
  row_header.role = ax::mojom::Role::kRowHeader;
  row_header.SetName(u"row_header");
  row1.child_ids.push_back(row_header.id);

  Init(root, row1, column_header, row_header);

  ComPtr<ITableProvider> root_itableprovider(
      QueryInterfaceFromNode<ITableProvider>(GetRoot()));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetColumnHeaders(safearray.Receive()));
  EXPECT_NE(nullptr, safearray.Get());

  std::vector<std::wstring> expected_names = {L"column_header"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
                                   expected_names);

  // Remove column_header's native event target and verify it's no longer
  // returned.
  TestAXNodeWrapper* column_header_wrapper = TestAXNodeWrapper::GetOrCreate(
      GetTree(), GetRoot()->children()[0]->children()[0]);
  column_header_wrapper->ResetNativeEventTarget();

  safearray.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetColumnHeaders(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());
}

gfx::NativeViewAccessible TestFragmentRootDelegate::GetChildOfAXFragmentRoot() {
  return child_;
}

gfx::NativeViewAccessible
TestFragmentRootDelegate::GetParentOfAXFragmentRoot() {
  return parent_;
}

bool TestFragmentRootDelegate::IsAXFragmentRootAControlElement() {
  return is_control_element_;
}

TEST_F(AXPlatformNodeWinTest, IAccessibleDetachedObject) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("Name");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedBstr name;
  EXPECT_EQ(S_OK, root_obj->get_accName(SELF, name.Receive()));
  EXPECT_STREQ(L"Name", name.Get());

  // Create an empty tree.
  SetTree(std::make_unique<AXTree>());
  ScopedBstr name2;
  EXPECT_EQ(E_FAIL, root_obj->get_accName(SELF, name2.Receive()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleHitTest) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 40, 40);

  AXNodeData node1;
  node1.id = 2;
  node1.role = ax::mojom::Role::kGenericContainer;
  node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
  node1.SetName("Name1");
  root.child_ids.push_back(node1.id);

  AXNodeData node2;
  node2.id = 3;
  node2.role = ax::mojom::Role::kGenericContainer;
  node2.relative_bounds.bounds = gfx::RectF(20, 20, 20, 20);
  node2.SetName("Name2");
  root.child_ids.push_back(node2.id);

  Init(root, node1, node2);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  // This is way outside of the root node.
  ScopedVariant obj_1;
  EXPECT_EQ(S_FALSE, root_obj->accHitTest(50, 50, obj_1.Receive()));
  EXPECT_EQ(VT_EMPTY, obj_1.type());

  // This is directly on node 1.
  EXPECT_EQ(S_OK, root_obj->accHitTest(5, 5, obj_1.Receive()));
  ASSERT_NE(nullptr, obj_1.ptr());
  CheckVariantHasName(obj_1, L"Name1");

  // This is directly on node 2 with a scale factor of 1.5.
  ScopedVariant obj_2;
  std::unique_ptr<base::AutoReset<float>> scale_factor_reset =
      TestAXNodeWrapper::SetScaleFactor(1.5);
  EXPECT_EQ(S_OK, root_obj->accHitTest(38, 38, obj_2.Receive()));
  ASSERT_NE(nullptr, obj_2.ptr());
  CheckVariantHasName(obj_2, L"Name2");
}

TEST_F(AXPlatformNodeWinTest, IAccessibleHitTestDoesNotLoopForever) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 40, 40);

  AXNodeData node1;
  node1.id = 2;
  node1.role = ax::mojom::Role::kGenericContainer;
  node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
  node1.SetName("Name1");
  root.child_ids.push_back(node1.id);

  Init(root, node1);

  // Set up the endless loop.
  TestAXNodeWrapper::SetHitTestResult(1, 2);
  TestAXNodeWrapper::SetHitTestResult(2, 1);

  // Hit testing on the root returns the child. Hit testing on the
  // child returns the root, but that should be rejected rather than
  // looping endlessly.
  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedVariant obj_1;
  EXPECT_EQ(S_OK, root_obj->accHitTest(5, 5, obj_1.Receive()));
  ASSERT_NE(nullptr, obj_1.ptr());
  CheckVariantHasName(obj_1, L"Name1");
}

TEST_F(AXPlatformNodeWinTest, IAccessibleName) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("Name");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedBstr name;
  EXPECT_EQ(S_OK, root_obj->get_accName(SELF, name.Receive()));
  EXPECT_STREQ(L"Name", name.Get());

  EXPECT_EQ(E_INVALIDARG, root_obj->get_accName(SELF, nullptr));
  ScopedVariant bad_id(999);
  ScopedBstr name2;
  EXPECT_EQ(E_INVALIDARG, root_obj->get_accName(bad_id, name2.Receive()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleDescription) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddStringAttribute(ax::mojom::StringAttribute::kDescription,
                          "Description");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedBstr description;
  EXPECT_EQ(S_OK, root_obj->get_accDescription(SELF, description.Receive()));
  EXPECT_STREQ(L"Description", description.Get());

  EXPECT_EQ(E_INVALIDARG, root_obj->get_accDescription(SELF, nullptr));
  ScopedVariant bad_id(999);
  ScopedBstr d2;
  EXPECT_EQ(E_INVALIDARG, root_obj->get_accDescription(bad_id, d2.Receive()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleAccValue) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTextField;
  root.AddState(ax::mojom::State::kEditable);
  root.AddStringAttribute(ax::mojom::StringAttribute::kValue, "Value");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedBstr value;
  EXPECT_EQ(S_OK, root_obj->get_accValue(SELF, value.Receive()));
  EXPECT_STREQ(L"Value", value.Get());

  EXPECT_EQ(E_INVALIDARG, root_obj->get_accValue(SELF, nullptr));
  ScopedVariant bad_id(999);
  ScopedBstr v2;
  EXPECT_EQ(E_INVALIDARG, root_obj->get_accValue(bad_id, v2.Receive()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleShortcut) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts,
                          "Shortcut");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ScopedBstr shortcut;
  EXPECT_EQ(S_OK, root_obj->get_accKeyboardShortcut(SELF, shortcut.Receive()));
  EXPECT_STREQ(L"Shortcut", shortcut.Get());

  EXPECT_EQ(E_INVALIDARG, root_obj->get_accKeyboardShortcut(SELF, nullptr));
  ScopedVariant bad_id(999);
  ScopedBstr k2;
  EXPECT_EQ(E_INVALIDARG,
            root_obj->get_accKeyboardShortcut(bad_id, k2.Receive()));
}

TEST_F(AXPlatformNodeWinTest,
       IAccessibleSelectionListBoxOptionNothingSelected) {
  AXNodeData list;
  list.id = 1;
  list.role = ax::mojom::Role::kListBox;

  AXNodeData list_item_1;
  list_item_1.id = 2;
  list_item_1.role = ax::mojom::Role::kListBoxOption;
  list_item_1.SetName("Name1");

  AXNodeData list_item_2;
  list_item_2.id = 3;
  list_item_2.role = ax::mojom::Role::kListBoxOption;
  list_item_2.SetName("Name2");

  list.child_ids.push_back(list_item_1.id);
  list.child_ids.push_back(list_item_2.id);

  Init(list, list_item_1, list_item_2);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_EMPTY, selection.type());
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionListBoxOptionOneSelected) {
  AXNodeData list;
  list.id = 1;
  list.role = ax::mojom::Role::kListBox;

  AXNodeData list_item_1;
  list_item_1.id = 2;
  list_item_1.role = ax::mojom::Role::kListBoxOption;
  list_item_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  list_item_1.SetName("Name1");

  AXNodeData list_item_2;
  list_item_2.id = 3;
  list_item_2.role = ax::mojom::Role::kListBoxOption;
  list_item_2.SetName("Name2");

  list.child_ids.push_back(list_item_1.id);
  list.child_ids.push_back(list_item_2.id);

  Init(list, list_item_1, list_item_2);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_DISPATCH, selection.type());

  CheckVariantHasName(selection, L"Name1");
}

TEST_F(AXPlatformNodeWinTest,
       IAccessibleSelectionListBoxOptionMultipleSelected) {
  AXNodeData list;
  list.id = 1;
  list.role = ax::mojom::Role::kListBox;

  AXNodeData list_item_1;
  list_item_1.id = 2;
  list_item_1.role = ax::mojom::Role::kListBoxOption;
  list_item_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  list_item_1.SetName("Name1");

  AXNodeData list_item_2;
  list_item_2.id = 3;
  list_item_2.role = ax::mojom::Role::kListBoxOption;
  list_item_2.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  list_item_2.SetName("Name2");

  AXNodeData list_item_3;
  list_item_3.id = 4;
  list_item_3.role = ax::mojom::Role::kListBoxOption;
  list_item_3.SetName("Name3");

  list.child_ids.push_back(list_item_1.id);
  list.child_ids.push_back(list_item_2.id);
  list.child_ids.push_back(list_item_3.id);

  Init(list, list_item_1, list_item_2, list_item_3);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_UNKNOWN, selection.type());
  ASSERT_NE(nullptr, selection.ptr());

  // Loop through the selections and  make sure we have the right ones.
  ComPtr<IEnumVARIANT> accessibles;
  ASSERT_HRESULT_SUCCEEDED(
      V_UNKNOWN(selection.ptr())->QueryInterface(IID_PPV_ARGS(&accessibles)));
  ULONG retrieved_count;

  // Check out the first selected item.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedBstr name;
    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
    EXPECT_STREQ(L"Name1", name.Get());
  }

  // And the second selected element.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedBstr name;
    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
    EXPECT_STREQ(L"Name2", name.Get());
  }

  // There shouldn't be any more selected.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_FALSE, hr);
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionTableNothingSelected) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_EMPTY, selection.type());
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionTableRowOneSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 5 == table_row_1
  update.nodes[5].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_DISPATCH, selection.type());
  ASSERT_NE(nullptr, selection.ptr());

  ComPtr<IAccessible> row;
  ASSERT_HRESULT_SUCCEEDED(
      V_DISPATCH(selection.ptr())->QueryInterface(IID_PPV_ARGS(&row)));

  ScopedVariant role;
  EXPECT_HRESULT_SUCCEEDED(row->get_accRole(SELF, role.Receive()));
  EXPECT_EQ(ROLE_SYSTEM_ROW, V_I4(role.ptr()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionTableRowMultipleSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 5 == table_row_1
  // 9 == table_row_2
  update.nodes[5].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[9].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ScopedVariant selection;
  EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_UNKNOWN, selection.type());
  ASSERT_NE(nullptr, selection.ptr());

  // Loop through the selections and  make sure we have the right ones.
  ComPtr<IEnumVARIANT> accessibles;
  ASSERT_HRESULT_SUCCEEDED(
      V_UNKNOWN(selection.ptr())->QueryInterface(IID_PPV_ARGS(&accessibles)));
  ULONG retrieved_count;

  // Check out the first selected row.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedVariant role;
    EXPECT_HRESULT_SUCCEEDED(accessible->get_accRole(SELF, role.Receive()));
    EXPECT_EQ(ROLE_SYSTEM_ROW, V_I4(role.ptr()));
  }

  // And the second selected element.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedVariant role;
    EXPECT_HRESULT_SUCCEEDED(accessible->get_accRole(SELF, role.Receive()));
    EXPECT_EQ(ROLE_SYSTEM_ROW, V_I4(role.ptr()));
  }

  // There shouldn't be any more selected.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_FALSE, hr);
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionTableCellOneSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ComPtr<IDispatch> row2;
  ASSERT_HRESULT_SUCCEEDED(root_obj->get_accChild(ScopedVariant(2), &row2));
  ComPtr<IAccessible> row2_accessible;
  ASSERT_HRESULT_SUCCEEDED(row2.As(&row2_accessible));

  ScopedVariant selection;
  EXPECT_EQ(S_OK, row2_accessible->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_DISPATCH, selection.type());
  ASSERT_NE(nullptr, selection.ptr());

  ComPtr<IAccessible> cell;
  ASSERT_HRESULT_SUCCEEDED(
      V_DISPATCH(selection.ptr())->QueryInterface(IID_PPV_ARGS(&cell)));

  ScopedVariant role;
  EXPECT_HRESULT_SUCCEEDED(cell->get_accRole(SELF, role.Receive()));
  EXPECT_EQ(ROLE_SYSTEM_CELL, V_I4(role.ptr()));

  ScopedBstr name;
  EXPECT_EQ(S_OK, cell->get_accName(SELF, name.Receive()));
  EXPECT_STREQ(L"1", name.Get());
}

TEST_F(AXPlatformNodeWinTest, IAccessibleSelectionTableCellMultipleSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 11 == table_cell_3
  // 12 == table_cell_4
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ASSERT_NE(nullptr, root_obj.Get());

  ComPtr<IDispatch> row3;
  ASSERT_HRESULT_SUCCEEDED(root_obj->get_accChild(ScopedVariant(3), &row3));
  ComPtr<IAccessible> row3_accessible;
  ASSERT_HRESULT_SUCCEEDED(row3.As(&row3_accessible));

  ScopedVariant selection;
  EXPECT_EQ(S_OK, row3_accessible->get_accSelection(selection.Receive()));
  EXPECT_EQ(VT_UNKNOWN, selection.type());
  ASSERT_NE(nullptr, selection.ptr());

  // Loop through the selections and  make sure we have the right ones.
  ComPtr<IEnumVARIANT> accessibles;
  ASSERT_HRESULT_SUCCEEDED(
      V_UNKNOWN(selection.ptr())->QueryInterface(IID_PPV_ARGS(&accessibles)));
  ULONG retrieved_count;

  // Check out the first selected cell.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedBstr name;
    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
    EXPECT_STREQ(L"3", name.Get());
  }

  // And the second selected cell.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_OK, hr);

    ComPtr<IAccessible> accessible;
    ASSERT_HRESULT_SUCCEEDED(
        V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible)));
    ScopedBstr name;
    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
    EXPECT_STREQ(L"4", name.Get());
  }

  // There shouldn't be any more selected.
  {
    ScopedVariant item;
    HRESULT hr = accessibles->Next(1, item.Receive(), &retrieved_count);
    EXPECT_EQ(S_FALSE, hr);
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleRole) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids.push_back(2);

  AXNodeData child;
  child.id = 2;

  Init(root, child);
  AXNode* child_node = GetRoot()->children()[0];
  ComPtr<IAccessible> child_iaccessible(IAccessibleFromNode(child_node));

  ScopedVariant role;

  child.role = ax::mojom::Role::kAlert;
  child_node->SetData(child);
  EXPECT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive()));
  EXPECT_EQ(ROLE_SYSTEM_ALERT, V_I4(role.ptr()));

  child.role = ax::mojom::Role::kButton;
  child_node->SetData(child);
  EXPECT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive()));
  EXPECT_EQ(ROLE_SYSTEM_PUSHBUTTON, V_I4(role.ptr()));

  child.role = ax::mojom::Role::kPopUpButton;
  child_node->SetData(child);
  EXPECT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive()));
  EXPECT_EQ(ROLE_SYSTEM_BUTTONMENU, V_I4(role.ptr()));

  EXPECT_EQ(E_INVALIDARG, child_iaccessible->get_accRole(SELF, nullptr));
  ScopedVariant bad_id(999);
  EXPECT_EQ(E_INVALIDARG,
            child_iaccessible->get_accRole(bad_id, role.Receive()));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleLocation) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(10, 40, 800, 600);
  Init(root);

  TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(100, 200));

  LONG x_left, y_top, width, height;
  EXPECT_EQ(S_OK, GetRootIAccessible()->accLocation(&x_left, &y_top, &width,
                                                    &height, SELF));
  EXPECT_EQ(110, x_left);
  EXPECT_EQ(240, y_top);
  EXPECT_EQ(800, width);
  EXPECT_EQ(600, height);

  EXPECT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation(
                              nullptr, &y_top, &width, &height, SELF));
  EXPECT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation(
                              &x_left, nullptr, &width, &height, SELF));
  EXPECT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation(
                              &x_left, &y_top, nullptr, &height, SELF));
  EXPECT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation(
                              &x_left, &y_top, &width, nullptr, SELF));
  ScopedVariant bad_id(999);
  EXPECT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation(
                              &x_left, &y_top, &width, &height, bad_id));

  // Un-set the global offset so that it doesn't affect subsequent tests.
  TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(0, 0));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleChildAndParent) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids.push_back(2);
  root.child_ids.push_back(3);

  AXNodeData button;
  button.role = ax::mojom::Role::kButton;
  button.id = 2;

  AXNodeData checkbox;
  checkbox.role = ax::mojom::Role::kCheckBox;
  checkbox.id = 3;

  Init(root, button, checkbox);
  AXNode* button_node = GetRoot()->children()[0];
  AXNode* checkbox_node = GetRoot()->children()[1];
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible> button_iaccessible(IAccessibleFromNode(button_node));
  ComPtr<IAccessible> checkbox_iaccessible(IAccessibleFromNode(checkbox_node));

  LONG child_count;
  EXPECT_EQ(S_OK, root_iaccessible->get_accChildCount(&child_count));
  EXPECT_EQ(2L, child_count);
  EXPECT_EQ(S_OK, button_iaccessible->get_accChildCount(&child_count));
  EXPECT_EQ(0L, child_count);
  EXPECT_EQ(S_OK, checkbox_iaccessible->get_accChildCount(&child_count));
  EXPECT_EQ(0L, child_count);

  {
    ComPtr<IDispatch> result;
    EXPECT_EQ(S_OK, root_iaccessible->get_accChild(SELF, &result));
    EXPECT_EQ(result.Get(), root_iaccessible.Get());
  }

  {
    ComPtr<IDispatch> result;
    ScopedVariant child1(1);
    EXPECT_EQ(S_OK, root_iaccessible->get_accChild(child1, &result));
    EXPECT_EQ(result.Get(), button_iaccessible.Get());
  }

  {
    ComPtr<IDispatch> result;
    ScopedVariant child2(2);
    EXPECT_EQ(S_OK, root_iaccessible->get_accChild(child2, &result));
    EXPECT_EQ(result.Get(), checkbox_iaccessible.Get());
  }

  {
    // Asking for child id 3 should fail.
    ComPtr<IDispatch> result;
    ScopedVariant child3(3);
    EXPECT_EQ(E_INVALIDARG, root_iaccessible->get_accChild(child3, &result));
  }

  // We should be able to ask for the button by its unique id too.
  LONG button_unique_id;
  ComPtr<IAccessible2> button_iaccessible2 = ToIAccessible2(button_iaccessible);
  button_iaccessible2->get_uniqueID(&button_unique_id);
  ASSERT_LT(button_unique_id, 0);
  {
    ComPtr<IDispatch> result;
    ScopedVariant button_id_variant(button_unique_id);
    EXPECT_EQ(S_OK, root_iaccessible->get_accChild(button_id_variant, &result));
    EXPECT_EQ(result.Get(), button_iaccessible.Get());
  }

  // We shouldn't be able to ask for the root node by its unique ID
  // from one of its children, though.
  LONG root_unique_id;
  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
  root_iaccessible2->get_uniqueID(&root_unique_id);
  ASSERT_LT(root_unique_id, 0);
  {
    ComPtr<IDispatch> result;
    ScopedVariant root_id_variant(root_unique_id);
    EXPECT_EQ(E_INVALIDARG,
              button_iaccessible->get_accChild(root_id_variant, &result));
  }

  // Now check parents.
  {
    ComPtr<IDispatch> result;
    EXPECT_EQ(S_OK, button_iaccessible->get_accParent(&result));
    EXPECT_EQ(result.Get(), root_iaccessible.Get());
  }

  {
    ComPtr<IDispatch> result;
    EXPECT_EQ(S_OK, checkbox_iaccessible->get_accParent(&result));
    EXPECT_EQ(result.Get(), root_iaccessible.Get());
  }

  {
    ComPtr<IDispatch> result;
    EXPECT_EQ(S_FALSE, root_iaccessible->get_accParent(&result));
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessible2IndexInParent) {
  AXNodeData root;
  root.id = 1;
  root.child_ids.push_back(2);
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids.push_back(3);

  AXNodeData left;
  left.id = 2;

  AXNodeData right;
  right.id = 3;

  Init(root, left, right);
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
  ComPtr<IAccessible> left_iaccessible(
      IAccessibleFromNode(GetRoot()->children()[0]));
  ComPtr<IAccessible2> left_iaccessible2 = ToIAccessible2(left_iaccessible);
  ComPtr<IAccessible> right_iaccessible(
      IAccessibleFromNode(GetRoot()->children()[1]));
  ComPtr<IAccessible2> right_iaccessible2 = ToIAccessible2(right_iaccessible);

  LONG index;
  EXPECT_EQ(E_FAIL, root_iaccessible2->get_indexInParent(&index));

  EXPECT_EQ(S_OK, left_iaccessible2->get_indexInParent(&index));
  EXPECT_EQ(0, index);

  EXPECT_EQ(S_OK, right_iaccessible2->get_indexInParent(&index));
  EXPECT_EQ(1, index);
}

TEST_F(AXPlatformNodeWinTest, AccNavigate) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;
  root.child_ids.push_back(2);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;
  root.child_ids.push_back(3);

  Init(root, child1, child2);
  ComPtr<IAccessible> ia_root(GetRootIAccessible());
  ComPtr<IDispatch> disp_root;
  ASSERT_HRESULT_SUCCEEDED(ia_root.As(&disp_root));
  ScopedVariant var_root(disp_root.Get());
  ComPtr<IAccessible> ia_child1(IAccessibleFromNode(GetRoot()->children()[0]));
  ComPtr<IDispatch> disp_child1;
  ASSERT_HRESULT_SUCCEEDED(ia_child1.As(&disp_child1));
  ScopedVariant var_child1(disp_child1.Get());
  ComPtr<IAccessible> ia_child2(IAccessibleFromNode(GetRoot()->children()[1]));
  ComPtr<IDispatch> disp_child2;
  ASSERT_HRESULT_SUCCEEDED(ia_child2.As(&disp_child2));
  ScopedVariant var_child2(disp_child2.Get());
  ScopedVariant end;

  // Invalid arguments.
  EXPECT_EQ(
      E_INVALIDARG,
      ia_root->accNavigate(NAVDIR_NEXT, ScopedVariant::kEmptyVariant, nullptr));
  EXPECT_EQ(E_INVALIDARG,
            ia_child1->accNavigate(NAVDIR_NEXT, ScopedVariant::kEmptyVariant,
                                   end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());

  // Navigating to first/last child should only be from self.
  EXPECT_EQ(E_INVALIDARG,
            ia_root->accNavigate(NAVDIR_FIRSTCHILD, var_root, end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());
  EXPECT_EQ(E_INVALIDARG,
            ia_root->accNavigate(NAVDIR_LASTCHILD, var_root, end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());

  // Spatial directions are not supported.
  EXPECT_EQ(E_NOTIMPL, ia_child1->accNavigate(NAVDIR_UP, SELF, end.Receive()));
  EXPECT_EQ(E_NOTIMPL, ia_root->accNavigate(NAVDIR_DOWN, SELF, end.Receive()));
  EXPECT_EQ(E_NOTIMPL,
            ia_child1->accNavigate(NAVDIR_RIGHT, SELF, end.Receive()));
  EXPECT_EQ(E_NOTIMPL,
            ia_child2->accNavigate(NAVDIR_LEFT, SELF, end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());

  // Logical directions should be supported.
  EXPECT_EQ(S_OK, ia_root->accNavigate(NAVDIR_FIRSTCHILD, SELF, end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child1.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  EXPECT_EQ(S_OK, ia_root->accNavigate(NAVDIR_LASTCHILD, SELF, end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child2.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  EXPECT_EQ(S_OK, ia_child1->accNavigate(NAVDIR_NEXT, SELF, end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child2.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  EXPECT_EQ(S_OK, ia_child2->accNavigate(NAVDIR_PREVIOUS, SELF, end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child1.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  // Child indices can also be passed by variant.
  // Indices are one-based.
  EXPECT_EQ(S_OK,
            ia_root->accNavigate(NAVDIR_NEXT, ScopedVariant(1), end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child2.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  EXPECT_EQ(S_OK, ia_root->accNavigate(NAVDIR_PREVIOUS, ScopedVariant(2),
                                       end.Receive()));
  EXPECT_EQ(VT_DISPATCH, end.type());
  EXPECT_EQ(V_DISPATCH(var_child1.ptr()), V_DISPATCH(end.ptr()));
  end.Reset();

  // Test out-of-bounds.
  EXPECT_EQ(S_FALSE,
            ia_child1->accNavigate(NAVDIR_PREVIOUS, SELF, end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());
  EXPECT_EQ(S_FALSE, ia_child2->accNavigate(NAVDIR_NEXT, SELF, end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());

  EXPECT_EQ(S_FALSE, ia_root->accNavigate(NAVDIR_PREVIOUS, ScopedVariant(1),
                                          end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());
  EXPECT_EQ(S_FALSE,
            ia_root->accNavigate(NAVDIR_NEXT, ScopedVariant(2), end.Receive()));
  EXPECT_EQ(VT_EMPTY, end.type());
}

TEST_F(AXPlatformNodeWinTest, IAccessible2TextFieldSetSelection) {
  Init(BuildTextField());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  EXPECT_HRESULT_SUCCEEDED(text_field->setSelection(0, 0, 1));
  EXPECT_HRESULT_SUCCEEDED(text_field->setSelection(0, 1, 0));
  EXPECT_HRESULT_SUCCEEDED(text_field->setSelection(0, 2, 2));
  EXPECT_HRESULT_SUCCEEDED(text_field->setSelection(0, IA2_TEXT_OFFSET_CARET,
                                                    IA2_TEXT_OFFSET_LENGTH));

  EXPECT_HRESULT_FAILED(text_field->setSelection(1, 0, 0));
  EXPECT_HRESULT_FAILED(text_field->setSelection(0, 0, 50));
}

TEST_F(AXPlatformNodeWinTest, IAccessible2ContentEditableSetSelection) {
  Init(BuildContentEditable());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> content_editable;
  ia2_text_field.As(&content_editable);
  ASSERT_NE(nullptr, content_editable.Get());

  EXPECT_HRESULT_SUCCEEDED(content_editable->setSelection(0, 0, 1));
  EXPECT_HRESULT_SUCCEEDED(content_editable->setSelection(0, 1, 0));
  EXPECT_HRESULT_SUCCEEDED(content_editable->setSelection(0, 2, 2));
  EXPECT_HRESULT_SUCCEEDED(content_editable->setSelection(
      0, IA2_TEXT_OFFSET_CARET, IA2_TEXT_OFFSET_LENGTH));

  EXPECT_HRESULT_FAILED(content_editable->setSelection(1, 0, 0));
  EXPECT_HRESULT_FAILED(content_editable->setSelection(0, 0, 50));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetAccessibilityAt) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  ComPtr<IUnknown> cell_1;
  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 1, &cell_1));
  CheckIUnknownHasName(cell_1, L"1");

  ComPtr<IUnknown> cell_2;
  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 2, &cell_2));
  CheckIUnknownHasName(cell_2, L"2");

  ComPtr<IUnknown> cell_3;
  EXPECT_EQ(S_OK, result->get_accessibleAt(2, 1, &cell_3));
  CheckIUnknownHasName(cell_3, L"3");

  ComPtr<IUnknown> cell_4;
  EXPECT_EQ(S_OK, result->get_accessibleAt(2, 2, &cell_4));
  CheckIUnknownHasName(cell_4, L"4");
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetAccessibilityAtOutOfBounds) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(E_INVALIDARG, result->get_accessibleAt(-1, -1, &cell));
  }

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(E_INVALIDARG, result->get_accessibleAt(0, 5, &cell));
  }

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(E_INVALIDARG, result->get_accessibleAt(5, 0, &cell));
  }

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(E_INVALIDARG, result->get_accessibleAt(10, 10, &cell));
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableQueryInterfaceOnNonTable) {
  ComPtr<IAccessibleTable> table;
  ComPtr<IAccessibleTable2> table2;

  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  Init(root);

  ComPtr<IAccessible> root_obj = GetRootIAccessible();
  EXPECT_EQ(E_NOINTERFACE, root_obj->QueryInterface(IID_PPV_ARGS(&table)));
  EXPECT_EQ(E_NOINTERFACE, root_obj->QueryInterface(IID_PPV_ARGS(&table2)));

  AXTreeUpdate update = Build3X3Table();
  update.node_id_to_clear = 1;
  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());
  EXPECT_EQ(E_NOINTERFACE, cell->QueryInterface(IID_PPV_ARGS(&table)));
  EXPECT_EQ(E_NOINTERFACE, cell->QueryInterface(IID_PPV_ARGS(&table2)));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellQueryInterfaceOnNonCell) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj = GetRootIAccessible();
  ComPtr<IAccessibleTableCell> cell;
  EXPECT_EQ(E_NOINTERFACE, root_obj->QueryInterface(IID_PPV_ARGS(&cell)));
}

TEST_F(AXPlatformNodeWinTest, IAccessible2ScrollToPoint) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 2000, 2000);

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;
  child1.relative_bounds.bounds = gfx::RectF(10, 10, 10, 10);
  root.child_ids.push_back(2);

  Init(root, child1);

  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  LONG x_left, y_top, width, height;
  EXPECT_EQ(S_OK,
            ax_child1->accLocation(&x_left, &y_top, &width, &height, SELF));
  EXPECT_EQ(10, x_left);
  EXPECT_EQ(10, y_top);
  EXPECT_EQ(10, width);
  EXPECT_EQ(10, height);

  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
  EXPECT_EQ(S_OK, root_iaccessible2->scrollToPoint(
                      IA2_COORDTYPE_SCREEN_RELATIVE, 600, 650));

  EXPECT_EQ(S_OK,
            ax_child1->accLocation(&x_left, &y_top, &width, &height, SELF));
  EXPECT_EQ(610, x_left);
  EXPECT_EQ(660, y_top);
  EXPECT_EQ(10, width);
  EXPECT_EQ(10, height);

  EXPECT_EQ(S_OK, root_iaccessible2->scrollToPoint(
                      IA2_COORDTYPE_PARENT_RELATIVE, 0, 0));

  EXPECT_EQ(S_OK,
            ax_child1->accLocation(&x_left, &y_top, &width, &height, SELF));
  EXPECT_EQ(10, x_left);
  EXPECT_EQ(10, y_top);
  EXPECT_EQ(10, width);
  EXPECT_EQ(10, height);
}

TEST_F(AXPlatformNodeWinTest, IAccessible2ScrollTo) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 2000, 2000);

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;
  child1.relative_bounds.bounds = gfx::RectF(10, 10, 10, 10);
  root.child_ids.push_back(2);

  Init(root, child1);

  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  LONG x_left, y_top, width, height;
  EXPECT_EQ(S_OK,
            ax_child1->accLocation(&x_left, &y_top, &width, &height, SELF));
  EXPECT_EQ(10, x_left);
  EXPECT_EQ(10, y_top);
  EXPECT_EQ(10, width);
  EXPECT_EQ(10, height);

  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
  EXPECT_EQ(S_OK, ax_child1->scrollTo(IA2_SCROLL_TYPE_ANYWHERE));

  EXPECT_EQ(S_OK,
            ax_child1->accLocation(&x_left, &y_top, &width, &height, SELF));
  EXPECT_EQ(0, x_left);
  EXPECT_EQ(0, y_top);
  EXPECT_EQ(10, width);
  EXPECT_EQ(10, height);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetChildIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG id;
  EXPECT_EQ(S_OK, result->get_childIndex(0, 0, &id));
  EXPECT_EQ(id, 0);

  EXPECT_EQ(S_OK, result->get_childIndex(0, 1, &id));
  EXPECT_EQ(id, 1);

  EXPECT_EQ(S_OK, result->get_childIndex(1, 0, &id));
  EXPECT_EQ(id, 3);

  EXPECT_EQ(S_OK, result->get_childIndex(1, 1, &id));
  EXPECT_EQ(id, 4);

  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(-1, -1, &id));
  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(0, 5, &id));
  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(5, 0, &id));
  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(5, 5, &id));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetColumnDescription) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  {
    ScopedBstr name;
    EXPECT_EQ(S_FALSE, result->get_columnDescription(0, name.Receive()));
  }
  {
    ScopedBstr name;
    EXPECT_EQ(S_OK, result->get_columnDescription(1, name.Receive()));
    EXPECT_STREQ(L"column header 1", name.Get());
  }

  {
    ScopedBstr name;
    EXPECT_EQ(S_OK, result->get_columnDescription(2, name.Receive()));
    EXPECT_STREQ(L"column header 2", name.Get());
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetColumnExtentAt) {
  // TODO(dougt) This table doesn't have any spanning cells. This test
  // tests get_columnExtentAt for (1) and an invalid input.
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG columns_spanned;
  EXPECT_EQ(S_OK, result->get_columnExtentAt(1, 1, &columns_spanned));
  EXPECT_EQ(columns_spanned, 1);

  EXPECT_EQ(E_INVALIDARG, result->get_columnExtentAt(-1, -1, &columns_spanned));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetColumnIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG index;
  EXPECT_EQ(S_OK, result->get_columnIndex(2, &index));
  EXPECT_EQ(index, 2);
  EXPECT_EQ(S_OK, result->get_columnIndex(3, &index));
  EXPECT_EQ(index, 0);

  EXPECT_EQ(E_INVALIDARG, result->get_columnIndex(-1, &index));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNColumns) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG count;
  EXPECT_EQ(S_OK, result->get_nColumns(&count));
  EXPECT_EQ(count, 3);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNRows) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG count;
  EXPECT_EQ(S_OK, result->get_nRows(&count));
  EXPECT_EQ(count, 3);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetRowDescription) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  {
    ScopedBstr name;
    EXPECT_EQ(S_FALSE, result->get_rowDescription(0, name.Receive()));
  }

  {
    ScopedBstr name;
    EXPECT_EQ(S_OK, result->get_rowDescription(1, name.Receive()));
    EXPECT_STREQ(L"row header 1", name.Get());
  }

  {
    ScopedBstr name;
    EXPECT_EQ(S_OK, result->get_rowDescription(2, name.Receive()));
    EXPECT_STREQ(L"row header 2", name.Get());
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetRowExtentAt) {
  // TODO(dougt) This table doesn't have any spanning cells. This test
  // tests get_rowExtentAt for (1) and an invalid input.
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG rows_spanned;
  EXPECT_EQ(S_OK, result->get_rowExtentAt(0, 1, &rows_spanned));
  EXPECT_EQ(1, rows_spanned);

  EXPECT_EQ(E_INVALIDARG, result->get_columnExtentAt(-1, -1, &rows_spanned));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetRowIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG index;
  EXPECT_EQ(S_OK, result->get_rowIndex(2, &index));
  EXPECT_EQ(0, index);
  EXPECT_EQ(S_OK, result->get_rowIndex(3, &index));
  EXPECT_EQ(1, index);

  EXPECT_EQ(E_INVALIDARG, result->get_rowIndex(-1, &index));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetRowColumnExtentsAtIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG row, column, row_extents, column_extents;
  BOOLEAN is_selected = false;
  EXPECT_EQ(S_OK,
            result->get_rowColumnExtentsAtIndex(0, &row, &column, &row_extents,
                                                &column_extents, &is_selected));

  EXPECT_EQ(0, row);
  EXPECT_EQ(0, column);
  EXPECT_EQ(1, row_extents);
  EXPECT_EQ(1, column_extents);
  EXPECT_FALSE(is_selected);

  EXPECT_EQ(E_INVALIDARG,
            result->get_rowColumnExtentsAtIndex(-1, &row, &column, &row_extents,
                                                &column_extents, &is_selected));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetCellAt) {
  Init(Build3X3Table());

  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  ComPtr<IAccessibleTable2> result;
  root_obj.As(&result);
  ASSERT_NE(nullptr, result.Get());

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(S_OK, result->get_cellAt(1, 1, &cell));
    CheckIUnknownHasName(cell, L"1");
  }

  {
    ComPtr<IUnknown> cell;
    EXPECT_EQ(E_INVALIDARG, result->get_cellAt(-1, -1, &cell));
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetColumnExtent) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  LONG column_spanned;
  EXPECT_EQ(S_OK, cell->get_columnExtent(&column_spanned));
  EXPECT_EQ(1, column_spanned);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetColumnHeaderCells) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  base::win::ScopedCoMem<IUnknown*> cell_accessibles;
  LONG number_cells = 0;

  EXPECT_EQ(S_OK,
            cell->get_columnHeaderCells(&cell_accessibles, &number_cells));
  EXPECT_EQ(1, number_cells);
  ReleasePointers(cell_accessibles.get(), number_cells);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetColumnIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  LONG index;
  EXPECT_EQ(S_OK, cell->get_columnIndex(&index));
  EXPECT_EQ(index, 1);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetRowExtent) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  LONG rows_spanned;
  EXPECT_EQ(S_OK, cell->get_rowExtent(&rows_spanned));
  EXPECT_EQ(rows_spanned, 1);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetRowHeaderCells) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  base::win::ScopedCoMem<IUnknown*> cell_accessibles;
  LONG number_cells = 0;

  EXPECT_EQ(S_OK, cell->get_rowHeaderCells(&cell_accessibles, &number_cells));
  EXPECT_EQ(number_cells, 1);
  ReleasePointers(cell_accessibles.get(), number_cells);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetRowIndex) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  LONG index;
  EXPECT_EQ(S_OK, cell->get_rowIndex(&index));
  EXPECT_EQ(index, 1);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetRowColumnExtent) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  LONG row, column, row_extents, column_extents;
  BOOLEAN is_selected = false;
  EXPECT_EQ(S_OK, cell->get_rowColumnExtents(&row, &column, &row_extents,
                                             &column_extents, &is_selected));
  EXPECT_EQ(1, row);
  EXPECT_EQ(1, column);
  EXPECT_EQ(1, row_extents);
  EXPECT_EQ(1, column_extents);
  EXPECT_FALSE(is_selected);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableCellGetTable) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  // Check to make sure that this is the right table by checking one cell.
  ComPtr<IUnknown> cell_1;
  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 1, &cell_1));
  CheckIUnknownHasName(cell_1, L"1");
}

TEST_F(AXPlatformNodeWinTest, IAccessible2GetNRelations) {
  // This is is a duplicated of
  // BrowserAccessibilityTest::TestIAccessible2Relations but without the
  // specific COM/BrowserAccessibility knowledge.
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  // Add 999 as a target relation id to test that invalid relations are dropped.
  std::vector<AXNodeID> describedby_ids = {1, 2, 3, 999};
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kDescribedbyIds,
                           describedby_ids);

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;

  root.child_ids.push_back(2);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;

  root.child_ids.push_back(3);

  Init(root, child1, child2);
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);

  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
  ComPtr<IAccessible2> ax_child2;
  EXPECT_EQ(S_OK, result.As(&ax_child2));
  result.Reset();

  LONG n_relations = 0;
  LONG n_targets = 0;
  ScopedBstr relation_type;
  ComPtr<IAccessibleRelation> describedby_relation;
  ComPtr<IAccessibleRelation> description_for_relation;
  ComPtr<IUnknown> target;

  EXPECT_HRESULT_SUCCEEDED(root_iaccessible2->get_nRelations(&n_relations));
  EXPECT_EQ(1, n_relations);

  EXPECT_HRESULT_SUCCEEDED(
      root_iaccessible2->get_relation(0, &describedby_relation));

  EXPECT_HRESULT_SUCCEEDED(
      describedby_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"describedBy", std::wstring(relation_type.Get()));

  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_nTargets(&n_targets));
  EXPECT_EQ(2, n_targets);

  EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_target(0, &target));
  target.Reset();

  EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_target(1, &target));
  target.Reset();

  describedby_relation.Reset();

  // Test the reverse relations.
  EXPECT_HRESULT_SUCCEEDED(ax_child1->get_nRelations(&n_relations));
  EXPECT_EQ(1, n_relations);

  EXPECT_HRESULT_SUCCEEDED(
      ax_child1->get_relation(0, &description_for_relation));
  EXPECT_HRESULT_SUCCEEDED(
      description_for_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"descriptionFor", std::wstring(relation_type.Get()));
  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_nTargets(&n_targets));
  EXPECT_EQ(1, n_targets);

  EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_target(0, &target));
  target.Reset();
  description_for_relation.Reset();

  EXPECT_HRESULT_SUCCEEDED(ax_child2->get_nRelations(&n_relations));
  EXPECT_EQ(1, n_relations);

  EXPECT_HRESULT_SUCCEEDED(
      ax_child2->get_relation(0, &description_for_relation));
  EXPECT_HRESULT_SUCCEEDED(
      description_for_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"descriptionFor", std::wstring(relation_type.Get()));
  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_nTargets(&n_targets));
  EXPECT_EQ(1, n_targets);

  EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_target(0, &target));
  target.Reset();

  // TODO(dougt): Try adding one more relation.
}

TEST_F(AXPlatformNodeWinTest,
       IAccessible2TestPopupForRelationMapsToControlledByRelation) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kTextField;
  child1.AddState(ax::mojom::State::kEditable);
  child1.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, {3});
  root.child_ids.push_back(2);

  // Add listbox that is popup for the textfield.
  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kListBox;
  child2.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, 2);
  root.child_ids.push_back(3);

  Init(root, child1, child2);
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);

  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
  ComPtr<IAccessible2> ax_child2;
  EXPECT_EQ(S_OK, result.As(&ax_child2));
  result.Reset();

  LONG n_relations = 0;
  LONG n_targets = 0;
  ScopedBstr relation_type;
  ComPtr<IAccessibleRelation> controls_relation;
  ComPtr<IAccessibleRelation> controlled_by_relation;
  ComPtr<IUnknown> target;

  EXPECT_HRESULT_SUCCEEDED(ax_child1->get_nRelations(&n_relations));
  EXPECT_EQ(1, n_relations);

  EXPECT_HRESULT_SUCCEEDED(ax_child1->get_relation(0, &controls_relation));

  EXPECT_HRESULT_SUCCEEDED(
      controls_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"controllerFor", std::wstring(relation_type.Get()));

  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(controls_relation->get_nTargets(&n_targets));
  EXPECT_EQ(1, n_targets);

  EXPECT_HRESULT_SUCCEEDED(controls_relation->get_target(0, &target));
  target.Reset();

  controls_relation.Reset();

  // Test the controlled by relation, mapped from the popup for relation.
  EXPECT_HRESULT_SUCCEEDED(ax_child2->get_nRelations(&n_relations));
  // The test is currently outsmarting us, and automatically mapping the
  // reverse relation in addition to mapping the popup for -> controlled by.
  // Therefore, the same relation will exist twice in this test, which
  // actually shows that the popup for -> controlled by relation is working.
  // As a result, both relations should have the same result in this test.
  EXPECT_EQ(2, n_relations);

  // Both relations should have the same result in this test.
  EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(0, &controlled_by_relation));
  EXPECT_HRESULT_SUCCEEDED(
      controlled_by_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"controlledBy", std::wstring(relation_type.Get()));
  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
  EXPECT_EQ(1, n_targets);

  EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
  target.Reset();
  controlled_by_relation.Reset();

  // Both relations should have the same result in this test.
  EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(1, &controlled_by_relation));
  EXPECT_HRESULT_SUCCEEDED(
      controlled_by_relation->get_relationType(relation_type.Receive()));
  EXPECT_EQ(L"controlledBy", std::wstring(relation_type.Get()));
  relation_type.Reset();

  EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
  EXPECT_EQ(1, n_targets);

  EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
  target.Reset();
}

TEST_F(AXPlatformNodeWinTest, IAccessible2_TestRelationTargetsOfType) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds, {2, 3});

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;

  root.child_ids.push_back(2);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;
  std::vector<AXNodeID> labelledby_ids = {1, 4};
  child2.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                             labelledby_ids);

  root.child_ids.push_back(3);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kStaticText;
  child3.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds, {2});

  root.child_ids.push_back(4);

  Init(root, child1, child2, child3);
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible2_2> root_iaccessible2 = ToIAccessible2_2(root_iaccessible);

  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2_2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
  ComPtr<IAccessible2_2> ax_child2;
  EXPECT_EQ(S_OK, result.As(&ax_child2));
  result.Reset();

  {
    ScopedBstr type(L"details");
    base::win::ScopedCoMem<IUnknown*> targets;
    LONG n_targets;
    EXPECT_EQ(S_OK, root_iaccessible2->get_relationTargetsOfType(
                        type.Get(), 0, &targets, &n_targets));
    ASSERT_EQ(2, n_targets);
    ReleasePointers(targets.get(), n_targets);
    targets.Reset(nullptr);

    EXPECT_EQ(S_OK, root_iaccessible2->get_relationTargetsOfType(
                        type.Get(), 1, &targets, &n_targets));
    ASSERT_EQ(1, n_targets);
    ReleasePointers(targets.get(), n_targets);
  }
}

TEST_F(AXPlatformNodeWinTest, TestRelationTargetsOfType) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds, {2, 3});

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;

  root.child_ids.push_back(2);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;
  std::vector<AXNodeID> labelledby_ids = {1, 4};
  child2.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                             labelledby_ids);

  root.child_ids.push_back(3);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kStaticText;
  child3.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds, {2});

  root.child_ids.push_back(4);

  Init(root, child1, child2, child3);
  ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
  ComPtr<IAccessible2_2> root_iaccessible2 = ToIAccessible2_2(root_iaccessible);

  ComPtr<IDispatch> result;
  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
  ComPtr<IAccessible2_2> ax_child1;
  EXPECT_EQ(S_OK, result.As(&ax_child1));
  result.Reset();

  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
  ComPtr<IAccessible2_2> ax_child2;
  EXPECT_EQ(S_OK, result.As(&ax_child2));
  result.Reset();

  EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(3), &result));
  ComPtr<IAccessible2_2> ax_child3;
  EXPECT_EQ(S_OK, result.As(&ax_child3));
  result.Reset();

  {
    ScopedBstr type(L"details");
    base::win::ScopedCoMem<IUnknown*> targets;
    LONG n_targets;
    EXPECT_EQ(S_OK, root_iaccessible2->get_relationTargetsOfType(
                        type.Get(), 1, &targets, &n_targets));
    ASSERT_EQ(1, n_targets);
    EXPECT_EQ(ax_child1.Get(), targets[0]);
    ReleasePointers(targets.get(), n_targets);
  }

  {
    ScopedBstr type(IA2_RELATION_LABELLED_BY);
    base::win::ScopedCoMem<IUnknown*> targets;
    LONG n_targets;
    EXPECT_EQ(S_OK, ax_child2->get_relationTargetsOfType(type.Get(), 0,
                                                         &targets, &n_targets));
    ASSERT_EQ(2, n_targets);
    EXPECT_EQ(root_iaccessible2.Get(), targets[0]);
    EXPECT_EQ(ax_child3.Get(), UNSAFE_TODO(targets[1]));
    ReleasePointers(targets.get(), n_targets);
  }

  {
    ScopedBstr type(L"detailsFor");
    base::win::ScopedCoMem<IUnknown*> targets;
    LONG n_targets;
    EXPECT_EQ(S_OK, ax_child1->get_relationTargetsOfType(type.Get(), 0,
                                                         &targets, &n_targets));
    ASSERT_EQ(2, n_targets);
    EXPECT_EQ(root_iaccessible2.Get(), targets[0]);
    EXPECT_EQ(ax_child3.Get(), UNSAFE_TODO(targets[1]));
    ReleasePointers(targets.get(), n_targets);
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedChildrenZero) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedChildren;
  EXPECT_EQ(S_OK, result->get_nSelectedChildren(&selectedChildren));
  EXPECT_EQ(0, selectedChildren);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedChildrenOne) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedChildren;
  EXPECT_EQ(S_OK, result->get_nSelectedChildren(&selectedChildren));
  EXPECT_EQ(1, selectedChildren);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedChildrenMany) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  // 8 == table_cell_2
  // 11 == table_cell_3
  // 12 == table_cell_4
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedChildren;
  EXPECT_EQ(S_OK, result->get_nSelectedChildren(&selectedChildren));
  EXPECT_EQ(4, selectedChildren);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedColumnsZero) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedColumns;
  EXPECT_EQ(S_OK, result->get_nSelectedColumns(&selectedColumns));
  EXPECT_EQ(0, selectedColumns);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedColumnsOne) {
  AXTreeUpdate update = Build3X3Table();

  // 3 == table_column_header_2
  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedColumns;
  EXPECT_EQ(S_OK, result->get_nSelectedColumns(&selectedColumns));
  EXPECT_EQ(1, selectedColumns);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedColumnsMany) {
  AXTreeUpdate update = Build3X3Table();

  // 3 == table_column_header_2
  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  // 4 == table_column_header_3
  // 8 == table_cell_2
  // 12 == table_cell_4
  update.nodes[4].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedColumns;
  EXPECT_EQ(S_OK, result->get_nSelectedColumns(&selectedColumns));
  EXPECT_EQ(2, selectedColumns);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedRowsZero) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedRows;
  EXPECT_EQ(S_OK, result->get_nSelectedRows(&selectedRows));
  EXPECT_EQ(0, selectedRows);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedRowsOne) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_1
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedRows;
  EXPECT_EQ(S_OK, result->get_nSelectedRows(&selectedRows));
  EXPECT_EQ(1, selectedRows);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetNSelectedRowsMany) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_3
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  // 10 == table_row_header_3
  // 11 == table_cell_1
  // 12 == table_cell_2
  update.nodes[10].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG selectedRows;
  EXPECT_EQ(S_OK, result->get_nSelectedRows(&selectedRows));
  EXPECT_EQ(2, selectedRows);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedChildren) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  // 12 == table_cell_4
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max = 10;
  LONG* indices;
  LONG count;
  EXPECT_EQ(S_OK, result->get_selectedChildren(max, &indices, &count));
  EXPECT_EQ(2, count);
  EXPECT_EQ(4, indices[0]);
  EXPECT_EQ(8, UNSAFE_TODO(indices[1]));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedChildrenZeroMax) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  // 12 == table_cell_4
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG* indices;
  LONG count;
  EXPECT_EQ(E_INVALIDARG, result->get_selectedChildren(0, &indices, &count));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedColumnsZero) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_columns = 10;
  LONG* columns;
  LONG n_columns;
  EXPECT_EQ(S_OK,
            result->get_selectedColumns(max_columns, &columns, &n_columns));
  EXPECT_EQ(0, n_columns);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedColumnsOne) {
  AXTreeUpdate update = Build3X3Table();

  // 3 == table_column_header_2
  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_columns = 10;
  LONG* columns;
  LONG n_columns;
  EXPECT_EQ(S_OK,
            result->get_selectedColumns(max_columns, &columns, &n_columns));
  EXPECT_EQ(1, n_columns);
  EXPECT_EQ(1, columns[0]);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedColumnsMany) {
  AXTreeUpdate update = Build3X3Table();

  // 3 == table_column_header_2
  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  // 4 == table_column_header_3
  // 8 == table_cell_2
  // 12 == table_cell_4
  update.nodes[4].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_columns = 10;
  LONG* columns;
  LONG n_columns;
  EXPECT_EQ(S_OK,
            result->get_selectedColumns(max_columns, &columns, &n_columns));
  EXPECT_EQ(2, n_columns);
  EXPECT_EQ(1, columns[0]);
  EXPECT_EQ(2, UNSAFE_TODO(columns[1]));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedRowsZero) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_rows = 10;
  LONG* rows;
  LONG n_rows;
  EXPECT_EQ(S_OK, result->get_selectedRows(max_rows, &rows, &n_rows));
  EXPECT_EQ(0, n_rows);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedRowsOne) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_1
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_rows = 10;
  LONG* rows;
  LONG n_rows;
  EXPECT_EQ(S_OK, result->get_selectedRows(max_rows, &rows, &n_rows));
  EXPECT_EQ(1, n_rows);
  EXPECT_EQ(1, rows[0]);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableGetSelectedRowsMany) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_3
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  // 10 == table_row_header_3
  // 11 == table_cell_1
  // 12 == table_cell_2
  update.nodes[10].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  LONG max_rows = 10;
  LONG* rows;
  LONG n_rows;
  EXPECT_EQ(S_OK, result->get_selectedRows(max_rows, &rows, &n_rows));
  EXPECT_EQ(2, n_rows);
  EXPECT_EQ(1, rows[0]);
  EXPECT_EQ(2, UNSAFE_TODO(rows[1]));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableIsColumnSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 3 == table_column_header_2
  // 7 == table_cell_1
  // 11 == table_cell_3
  update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[11].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  BOOLEAN selected = false;
  EXPECT_EQ(S_OK, result->get_isColumnSelected(0, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(S_OK, result->get_isColumnSelected(1, &selected));
  EXPECT_TRUE(selected);

  EXPECT_EQ(S_OK, result->get_isColumnSelected(2, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(E_INVALIDARG, result->get_isColumnSelected(3, &selected));
  EXPECT_EQ(E_INVALIDARG, result->get_isColumnSelected(-3, &selected));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableIsRowSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_3
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  BOOLEAN selected;
  EXPECT_EQ(S_OK, result->get_isRowSelected(0, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(S_OK, result->get_isRowSelected(1, &selected));
  EXPECT_TRUE(selected);

  EXPECT_EQ(S_OK, result->get_isRowSelected(2, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(E_INVALIDARG, result->get_isRowSelected(3, &selected));
  EXPECT_EQ(E_INVALIDARG, result->get_isRowSelected(-3, &selected));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTableIsSelected) {
  AXTreeUpdate update = Build3X3Table();

  // 6 == table_row_header_3
  // 7 == table_cell_1
  // 8 == table_cell_2
  update.nodes[6].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[8].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  BOOLEAN selected = false;
  EXPECT_EQ(S_OK, result->get_isSelected(0, 0, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(S_OK, result->get_isSelected(0, 1, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(S_OK, result->get_isSelected(0, 2, &selected));
  EXPECT_FALSE(selected);

  EXPECT_EQ(E_INVALIDARG, result->get_isSelected(0, 4, &selected));

  EXPECT_EQ(S_OK, result->get_isSelected(1, 0, &selected));
  EXPECT_TRUE(selected);

  EXPECT_EQ(S_OK, result->get_isSelected(1, 1, &selected));
  EXPECT_TRUE(selected);

  EXPECT_EQ(S_OK, result->get_isSelected(1, 2, &selected));
  EXPECT_TRUE(selected);

  EXPECT_EQ(E_INVALIDARG, result->get_isSelected(1, 4, &selected));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTable2GetSelectedChildrenZero) {
  Init(Build3X3Table());

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable2> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  base::win::ScopedCoMem<IUnknown*> cell_accessibles;
  LONG count;
  EXPECT_EQ(S_OK, result->get_selectedCells(&cell_accessibles, &count));
  EXPECT_EQ(0, count);
  ReleasePointers(cell_accessibles.get(), count);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTable2GetSelectedChildren) {
  AXTreeUpdate update = Build3X3Table();

  // 7 == table_cell_1
  // 12 == table_cell_4
  update.nodes[7].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);

  Init(update);

  ComPtr<IAccessibleTableCell> cell = GetCellInTable();
  ASSERT_NE(nullptr, cell.Get());

  ComPtr<IUnknown> table;
  EXPECT_EQ(S_OK, cell->get_table(&table));

  ComPtr<IAccessibleTable2> result;
  table.As(&result);
  ASSERT_NE(nullptr, result.Get());

  base::win::ScopedCoMem<IUnknown*> cell_accessibles;
  LONG count;
  EXPECT_EQ(S_OK, result->get_selectedCells(&cell_accessibles, &count));
  EXPECT_EQ(2, count);

  CheckIUnknownHasName(cell_accessibles.get()[0], L"1");
  CheckIUnknownHasName(UNSAFE_TODO(cell_accessibles.get()[1]), L"4");
  ReleasePointers(cell_accessibles.get(), count);
}

TEST_F(AXPlatformNodeWinTest, IAccessible2GetGroupPosition) {
  AXNodeData root;
  root.id = 1;
  root.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 1);
  root.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 1);
  root.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 1);
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ComPtr<IAccessible2> iaccessible2 = ToIAccessible2(root_obj);
  LONG level, similar, position;
  EXPECT_EQ(S_OK, iaccessible2->get_groupPosition(&level, &similar, &position));
  EXPECT_EQ(1, level);
  EXPECT_EQ(0, similar);
  EXPECT_EQ(0, position);

  EXPECT_EQ(E_INVALIDARG,
            iaccessible2->get_groupPosition(nullptr, nullptr, nullptr));
}

TEST_F(AXPlatformNodeWinTest, IAccessible2GetLocalizedExtendedRole) {
  AXNodeData root;
  root.id = 1;
  root.AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
                          "extended role");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ComPtr<IAccessible2> iaccessible2 = ToIAccessible2(root_obj);
  ScopedBstr role;
  EXPECT_EQ(S_OK, iaccessible2->get_localizedExtendedRole(role.Receive()));
  EXPECT_STREQ(L"extended role", role.Get());
}

TEST_F(AXPlatformNodeWinTest, UnlabeledImageRoleDescription) {
  AXTreeUpdate tree;
  tree.root_id = 1;
  tree.nodes.resize(3);
  tree.nodes[0].id = 1;
  tree.nodes[0].child_ids = {2, 3};

  tree.nodes[1].id = 2;
  tree.nodes[1].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
  tree.nodes[1].role = ax::mojom::Role::kImage;

  tree.nodes[2].id = 3;
  tree.nodes[2].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation);
  tree.nodes[2].role = ax::mojom::Role::kImage;

  Init(tree);
  ComPtr<IAccessible> ia2_root_obj(GetRootIAccessible());
  auto* uia_root_node(GetRoot());

  for (int child_index = 0;
       child_index < static_cast<int>(tree.nodes[0].child_ids.size());
       ++child_index) {
    ComPtr<IDispatch> child_dispatch;
    ASSERT_HRESULT_SUCCEEDED(ia2_root_obj->get_accChild(
        ScopedVariant(child_index + 1), &child_dispatch));
    ComPtr<IAccessible> child;
    ASSERT_HRESULT_SUCCEEDED(child_dispatch.As(&child));
    ComPtr<IAccessible2> ia2_child = ToIAccessible2(child);

    ScopedBstr role_description;
    ASSERT_EQ(S_OK,
              ia2_child->get_localizedExtendedRole(role_description.Receive()));
    EXPECT_STREQ(L"Unlabeled image", role_description.Get());

    ComPtr<IRawElementProviderSimple> uia_child =
        QueryInterfaceFromNode<IRawElementProviderSimple>(
            uia_root_node->children()[child_index]);
    EXPECT_UIA_BSTR_EQ(uia_child, UIA_LocalizedControlTypePropertyId,
                       L"Unlabeled image");
  }
}

TEST_F(AXPlatformNodeWinTest, UnlabeledImageAttributes) {
  AXTreeUpdate tree;
  tree.root_id = 1;
  tree.nodes.resize(3);
  tree.nodes[0].id = 1;
  tree.nodes[0].child_ids = {2, 3};

  tree.nodes[1].id = 2;
  tree.nodes[1].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
  tree.nodes[1].role = ax::mojom::Role::kImage;

  tree.nodes[2].id = 3;
  tree.nodes[2].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation);
  tree.nodes[2].role = ax::mojom::Role::kImage;

  Init(tree);
  ComPtr<IAccessible> root_obj(GetRootIAccessible());

  for (int child_index = 0;
       child_index < static_cast<int>(tree.nodes[0].child_ids.size());
       ++child_index) {
    ComPtr<IDispatch> child_dispatch;
    ASSERT_HRESULT_SUCCEEDED(root_obj->get_accChild(
        ScopedVariant(child_index + 1), &child_dispatch));
    ComPtr<IAccessible> child;
    ASSERT_HRESULT_SUCCEEDED(child_dispatch.As(&child));
    ComPtr<IAccessible2> ia2_child = ToIAccessible2(child);

    ScopedBstr attributes_bstr;
    ASSERT_EQ(S_OK, ia2_child->get_attributes(attributes_bstr.Receive()));
    std::wstring attributes(attributes_bstr.Get());

    std::vector<std::wstring> attribute_vector = base::SplitString(
        attributes, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
    EXPECT_TRUE(std::ranges::contains(attribute_vector,
                                      L"roledescription:Unlabeled image"));
  }
}

TEST_F(AXPlatformNodeWinTest, AnnotatedImageName) {
  std::vector<const wchar_t*> expected_names;

  AXTreeUpdate tree;
  tree.root_id = 1;
  tree.nodes.resize(11);
  tree.nodes[0].id = 1;
  tree.nodes[0].child_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

  // If the status is EligibleForAnnotation and there's no existing label,
  // the name should be the discoverability string.
  tree.nodes[1].id = 2;
  tree.nodes[1].role = ax::mojom::Role::kImage;
  tree.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[1].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
  expected_names.push_back(
      L"To get missing image descriptions, open the context menu.");

  // If the status is EligibleForAnnotation, the discoverability string
  // should be appended to the existing name.
  tree.nodes[2].id = 3;
  tree.nodes[2].role = ax::mojom::Role::kImage;
  tree.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[2].SetName("ExistingLabel");
  tree.nodes[2].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
  expected_names.push_back(
      L"ExistingLabel. To get missing image descriptions, open the context "
      L"menu.");

  // If the status is SilentlyEligibleForAnnotation, the discoverability string
  // should not be appended to the existing name.
  tree.nodes[3].id = 4;
  tree.nodes[3].role = ax::mojom::Role::kImage;
  tree.nodes[3].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[3].SetName("ExistingLabel");
  tree.nodes[3].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation);
  expected_names.push_back(L"ExistingLabel");

  // If the status is IneligibleForAnnotation, nothing should be appended.
  tree.nodes[4].id = 5;
  tree.nodes[4].role = ax::mojom::Role::kImage;
  tree.nodes[4].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[4].SetName("ExistingLabel");
  tree.nodes[4].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
  expected_names.push_back(L"ExistingLabel");

  // If the status is AnnotationPending, pending text should be appended
  // to the name.
  tree.nodes[5].id = 6;
  tree.nodes[5].role = ax::mojom::Role::kImage;
  tree.nodes[5].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[5].SetName("ExistingLabel");
  tree.nodes[5].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationPending);
  expected_names.push_back(L"ExistingLabel. Getting description...");

  // If the status is AnnotationSucceeded, and there's no annotation,
  // nothing should be appended. (Ideally this shouldn't happen.)
  tree.nodes[6].id = 7;
  tree.nodes[6].role = ax::mojom::Role::kImage;
  tree.nodes[6].SetName("ExistingLabel");
  tree.nodes[6].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
  expected_names.push_back(L"ExistingLabel");

  // If the status is AnnotationSucceeded, the annotation should be appended
  // to the existing label.
  tree.nodes[7].id = 8;
  tree.nodes[7].role = ax::mojom::Role::kImage;
  tree.nodes[7].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[7].SetName("ExistingLabel");
  tree.nodes[7].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
  expected_names.push_back(L"ExistingLabel. Annotation");

  // If the status is AnnotationEmpty, failure text should be added to the
  // name.
  tree.nodes[8].id = 9;
  tree.nodes[8].role = ax::mojom::Role::kImage;
  tree.nodes[8].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[8].SetName("ExistingLabel");
  tree.nodes[8].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
  expected_names.push_back(L"ExistingLabel. No description available.");

  // If the status is AnnotationAdult, appropriate text should be appended
  // to the name.
  tree.nodes[9].id = 10;
  tree.nodes[9].role = ax::mojom::Role::kImage;
  tree.nodes[9].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                                   "Annotation");
  tree.nodes[9].SetName("ExistingLabel");
  tree.nodes[9].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationAdult);
  expected_names.push_back(
      L"ExistingLabel. Appears to contain adult content. No description "
      L"available.");

  // If the status is AnnotationProcessFailed, failure text should be added
  // to the name.
  tree.nodes[10].id = 11;
  tree.nodes[10].role = ax::mojom::Role::kImage;
  tree.nodes[10].AddStringAttribute(
      ax::mojom::StringAttribute::kImageAnnotation, "Annotation");
  tree.nodes[10].SetName("ExistingLabel");
  tree.nodes[10].SetImageAnnotationStatus(
      ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed);
  expected_names.push_back(L"ExistingLabel. No description available.");

  // We should have one expected name per child of the root.
  ASSERT_EQ(expected_names.size(), tree.nodes[0].child_ids.size());
  int child_count = static_cast<int>(expected_names.size());

  Init(tree);

  ComPtr<IAccessible> ia2_root_obj(GetRootIAccessible());
  auto* uia_root_node(GetRoot());

  for (int child_index = 0; child_index < child_count; child_index++) {
    ComPtr<IDispatch> child_dispatch;
    ASSERT_HRESULT_SUCCEEDED(ia2_root_obj->get_accChild(
        ScopedVariant(child_index + 1), &child_dispatch));
    ComPtr<IAccessible> ia2_child;
    ASSERT_HRESULT_SUCCEEDED(child_dispatch.As(&ia2_child));

    ScopedBstr ia2_name;
    EXPECT_EQ(S_OK, ia2_child->get_accName(SELF, ia2_name.Receive()));
    EXPECT_STREQ(expected_names[child_index], ia2_name.Get());

    ComPtr<IRawElementProviderSimple> uia_child =
        QueryInterfaceFromNode<IRawElementProviderSimple>(
            uia_root_node->children()[child_index]);
    EXPECT_UIA_BSTR_EQ(uia_child, UIA_NamePropertyId,
                       expected_names[child_index]);
  }
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextGetNCharacters) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData node;
  node.id = 2;
  node.role = ax::mojom::Role::kStaticText;
  node.SetName("Name");
  root.child_ids.push_back(node.id);

  Init(root, node);

  AXNode* child_node = GetRoot()->children()[0];
  ComPtr<IAccessible> child_iaccessible(IAccessibleFromNode(child_node));
  ASSERT_NE(nullptr, child_iaccessible.Get());

  ComPtr<IAccessibleText> text;
  child_iaccessible.As(&text);
  ASSERT_NE(nullptr, text.Get());

  LONG count;
  EXPECT_HRESULT_SUCCEEDED(text->get_nCharacters(&count));
  EXPECT_EQ(4, count);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextGetOffsetAtPoint) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 300, 200);
  root.child_ids = {2, 3};

  AXNodeData button;
  button.id = 2;
  button.role = ax::mojom::Role::kButton;
  button.SetName("button");
  button.relative_bounds.bounds = gfx::RectF(20, 0, 10, 10);

  AXNodeData static_text1;
  static_text1.id = 3;
  static_text1.role = ax::mojom::Role::kStaticText;
  static_text1.SetName("line 1");
  static_text1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
  static_text1.child_ids = {4};

  AXNodeData inline_box1;
  inline_box1.id = 4;
  inline_box1.role = ax::mojom::Role::kInlineTextBox;
  inline_box1.SetName("line 1");
  inline_box1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
  std::vector<int32_t> character_offsets1;
  // The width of each character is 5px, height is 10px.
  character_offsets1.push_back(5);   // "l" {0, 20, 5x10}
  character_offsets1.push_back(10);  // "i" {5, 20, 5x10}
  character_offsets1.push_back(15);  // "n" {10, 20, 5x10}
  character_offsets1.push_back(20);  // "e" {15, 20, 5x10}
  character_offsets1.push_back(25);  // " " {20, 20, 5x10}
  character_offsets1.push_back(30);  // "1" {25, 20, 5x10}

  inline_box1.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
  inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                  std::vector<int32_t>{0, 5});
  inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                  std::vector<int32_t>{4, 6});

  Init(root, button, static_text1, inline_box1);

  LONG offset_result;
  ComPtr<IAccessible> root_iaccessible(IAccessibleFromNode(GetRoot()));
  ASSERT_NE(nullptr, root_iaccessible.Get());

  ComPtr<IAccessibleText> root_text;
  root_iaccessible.As(&root_text);
  ASSERT_NE(nullptr, root_text.Get());

  // Test point(0, 0) with coordinate parent relative, which is not supported.
  // Expected result: S_FALSE.
  EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
                         0, 0, IA2CoordinateType::IA2_COORDTYPE_PARENT_RELATIVE,
                         &offset_result));
  EXPECT_EQ(-1, offset_result);

  // Test point(0, 0) retrieved from IAccessibleText of the root web area is
  // outside of any text. Expected result: S_FALSE.
  EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
                         0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
                         &offset_result));
  EXPECT_EQ(-1, offset_result);

  // Test point(25, 5) retrieved from IAccessibleText of the root web area is
  // on button, and outside of any text. Expected result: S_FALSE.
  EXPECT_EQ(S_FALSE,
            root_text->get_offsetAtPoint(
                25, 5, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
                &offset_result));
  EXPECT_EQ(-1, offset_result);

  // Test point(0, 20) retrieved from IAccessibleText of the root web area is
  // on "line 1", character="l". Expected result: S_OK, offset=0.
  EXPECT_EQ(S_OK, root_text->get_offsetAtPoint(
                      0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
                      &offset_result));
  EXPECT_EQ(0, offset_result);

  AXNode* static_text1_node = GetRoot()->children()[1];
  ComPtr<IAccessible> text1_iaccessible(IAccessibleFromNode(static_text1_node));
  ASSERT_NE(nullptr, text1_iaccessible.Get());

  ComPtr<IAccessibleText> text1;
  text1_iaccessible.As(&text1);
  ASSERT_NE(nullptr, text1.Get());

  // "l" 4 points of bounds {(0, 20), (5, 20), (0, 30), (5, 30)}
  // "i" 4 points of bounds {(5, 20), (10, 20), (5, 30), (10, 30)}
  // "n" 4 points of bounds {(10, 20), (15, 20), (10, 30), (15, 30)}
  // "e" 4 points of bounds {(15, 20), (20, 20), (15, 30), (20, 30)}
  // " " 4 points of bounds {(20, 20), (25, 20), (20, 30), (25, 30)}
  // "1" 4 points of bounds {(25, 20), (30, 20), (25, 30), (30, 30)}

  // Test point(0, 0) outside of any character bounds and text.
  EXPECT_EQ(S_FALSE, text1->get_offsetAtPoint(
                         0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
                         &offset_result));
  EXPECT_EQ(-1, offset_result);

  // Test point(30, 30) outside of any character bounds but on the text.
  EXPECT_EQ(S_OK, text1->get_offsetAtPoint(
                      30, 30, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
                      &offset_result));
  EXPECT_EQ(-1, offset_result);

  // Test point(0, 20) inside bounds of "l", text offset=0
  // character bounds={(0, 20), (5, 20), (0, 30), (5, 30)}
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
  EXPECT_EQ(0, offset_result);

  // Test point(9, 20) inside bounds of "i", text offset=1
  // character bounds={(5, 20), (10, 20), (5, 30), (10, 30)}
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      9, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
  EXPECT_EQ(1, offset_result);

  // Test point(10, 30) inside bounds of "n", text offset=2
  // character bounds={(10, 20), (15, 20), (10, 30), (15, 30)}
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      10, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
      &offset_result));
  EXPECT_EQ(2, offset_result);

  // Test point(19, 29) inside bounds of "e", text offset=3
  // character bounds={(15, 20), (20, 20), (15, 30), (20, 30)
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      19, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
      &offset_result));
  EXPECT_EQ(3, offset_result);

  // Test point(23, 25) inside bounds of " ", text offset=4
  // character bounds={(20, 20), (25, 20), (20, 30), (25, 30)}
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      23, 25, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
      &offset_result));
  EXPECT_EQ(4, offset_result);

  // Test point(25, 20) inside bounds of "1", text offset=5
  // character bounds={(25, 20), (30, 20), (25, 30), (30, 30)}
  EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
      25, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
      &offset_result));
  EXPECT_EQ(5, offset_result);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldRemoveSelection) {
  Init(BuildTextFieldWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);

  EXPECT_HRESULT_SUCCEEDED(text_field->removeSelection(0));

  // There is no selection, just a caret.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(0, &start_offset, &end_offset));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextContentEditableRemoveSelection) {
  Init(BuildTextFieldWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);

  EXPECT_HRESULT_SUCCEEDED(text_field->removeSelection(0));

  // There is no selection, just a caret.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(0, &start_offset, &end_offset));
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetSelected) {
  Init(BuildTextFieldWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;

  // We only care about selection_index of zero, so passing anything but 0 as
  // the first parameter should fail.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(1, &start_offset, &end_offset));

  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetSelectedBackward) {
  Init(BuildTextFieldWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleContentEditabledGetSelected) {
  Init(BuildContentEditableWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;

  // We only care about selection_index of zero, so passing anything but 0 as
  // the first parameter should fail.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(1, &start_offset, &end_offset));

  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleContentEditabledGetSelectedBackward) {
  Init(BuildContentEditableWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldAddSelection) {
  Init(BuildTextField());
  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  // There is no selection, just a caret.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(0, &start_offset, &end_offset));

  EXPECT_HRESULT_SUCCEEDED(text_field->addSelection(1, 2));

  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextContentEditableAddSelection) {
  Init(BuildContentEditable());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG start_offset, end_offset;
  // There is no selection, just a caret.
  EXPECT_EQ(E_INVALIDARG,
            text_field->get_selection(0, &start_offset, &end_offset));

  EXPECT_HRESULT_SUCCEEDED(text_field->addSelection(1, 2));

  EXPECT_HRESULT_SUCCEEDED(
      text_field->get_selection(0, &start_offset, &end_offset));
  EXPECT_EQ(1, start_offset);
  EXPECT_EQ(2, end_offset);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetNSelectionsZero) {
  Init(BuildTextField());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG selections;
  EXPECT_HRESULT_SUCCEEDED(text_field->get_nSelections(&selections));
  EXPECT_EQ(0, selections);
}

TEST_F(AXPlatformNodeWinTest,
       IAccessibleTextContentEditableGetNSelectionsZero) {
  Init(BuildContentEditable());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG selections;
  EXPECT_HRESULT_SUCCEEDED(text_field->get_nSelections(&selections));
  EXPECT_EQ(0, selections);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextContentEditableGetNSelections) {
  Init(BuildContentEditableWithSelectionRange(1, 2));
  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG selections;
  EXPECT_HRESULT_SUCCEEDED(text_field->get_nSelections(&selections));
  EXPECT_EQ(1, selections);
}

TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetCaretOffsetNoCaret) {
  Init(BuildTextField());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG offset;
  EXPECT_EQ(S_FALSE, text_field->get_caretOffset(&offset));
  EXPECT_EQ(0, offset);
}

TEST_F(AXPlatformNodeWinTest,
       IAccessibleTextContextEditableGetCaretOffsetNoCaret) {
  Init(BuildContentEditable());

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG offset;
  EXPECT_EQ(S_FALSE, text_field->get_caretOffset(&offset));
  EXPECT_EQ(0, offset);
}

TEST_F(AXPlatformNodeWinTest,
       IAccessibleTextContentEditableGetCaretOffsetHasCaret) {
  Init(BuildContentEditableWithSelectionRange(1, 2));

  ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
  ComPtr<IAccessibleText> text_field;
  ia2_text_field.As(&text_field);
  ASSERT_NE(nullptr, text_field.Get());

  LONG offset;
  EXPECT_HRESULT_SUCCEEDED(text_field->get_caretOffset(&offset));
  EXPECT_EQ(2, offset);
}

TEST_F(AXPlatformNodeWinTest, IGridProviderGetRowCount) {
  Init(BuildAriaColumnAndRowCountGrids());

  // Empty Grid.
  ComPtr<IGridProvider> grid1_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[0]);

  // Grid with a cell that defines aria-rowindex (4) and aria-colindex (5).
  ComPtr<IGridProvider> grid2_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[1]);

  // Grid that specifies aria-rowcount (2) and aria-colcount (3).
  ComPtr<IGridProvider> grid3_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[2]);

  // Grid that specifies aria-rowcount and aria-colcount are both (-1).
  ComPtr<IGridProvider> grid4_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[3]);

  int row_count;

  EXPECT_HRESULT_SUCCEEDED(grid1_provider->get_RowCount(&row_count));
  EXPECT_EQ(row_count, 0);

  EXPECT_HRESULT_SUCCEEDED(grid2_provider->get_RowCount(&row_count));
  EXPECT_EQ(row_count, 4);

  EXPECT_HRESULT_SUCCEEDED(grid3_provider->get_RowCount(&row_count));
  EXPECT_EQ(row_count, 2);

  EXPECT_EQ(E_UNEXPECTED, grid4_provider->get_RowCount(&row_count));
}

TEST_F(AXPlatformNodeWinTest, IGridProviderGetColumnCount) {
  Init(BuildAriaColumnAndRowCountGrids());

  // Empty Grid.
  ComPtr<IGridProvider> grid1_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[0]);

  // Grid with a cell that defines aria-rowindex (4) and aria-colindex (5).
  ComPtr<IGridProvider> grid2_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[1]);

  // Grid that specifies aria-rowcount (2) and aria-colcount (3).
  ComPtr<IGridProvider> grid3_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[2]);

  // Grid that specifies aria-rowcount and aria-colcount are both (-1).
  ComPtr<IGridProvider> grid4_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[3]);

  int column_count;

  EXPECT_HRESULT_SUCCEEDED(grid1_provider->get_ColumnCount(&column_count));
  EXPECT_EQ(column_count, 0);

  EXPECT_HRESULT_SUCCEEDED(grid2_provider->get_ColumnCount(&column_count));
  EXPECT_EQ(column_count, 5);

  EXPECT_HRESULT_SUCCEEDED(grid3_provider->get_ColumnCount(&column_count));
  EXPECT_EQ(column_count, 3);

  EXPECT_EQ(E_UNEXPECTED, grid4_provider->get_ColumnCount(&column_count));
}

TEST_F(AXPlatformNodeWinTest, IGridProviderGetItem) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kGrid;
  root.AddIntAttribute(ax::mojom::IntAttribute::kAriaRowCount, 1);
  root.AddIntAttribute(ax::mojom::IntAttribute::kAriaColumnCount, 1);

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData cell1;
  cell1.id = 3;
  cell1.role = ax::mojom::Role::kCell;
  row1.child_ids.push_back(cell1.id);

  Init(root, row1, cell1);

  ComPtr<IGridProvider> root_igridprovider(
      QueryInterfaceFromNode<IGridProvider>(GetRoot()));

  ComPtr<IRawElementProviderSimple> cell1_irawelementprovidersimple(
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]->children()[0]));

  ComPtr<IRawElementProviderSimple> grid_item;
  EXPECT_HRESULT_SUCCEEDED(root_igridprovider->GetItem(0, 0, &grid_item));
  EXPECT_NE(nullptr, grid_item);
  EXPECT_EQ(cell1_irawelementprovidersimple.Get(), grid_item.Get());
}

TEST_F(AXPlatformNodeWinTest, IGridProviderGetItemCustomAria) {
  Init(BuildAriaColumnAndRowCountGrids());

  // Empty Grid.
  ComPtr<IGridProvider> grid1_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[0]);

  // Grid with a cell that defines aria-rowindex (4) and aria-colindex (5).
  ComPtr<IGridProvider> grid2_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[1]);

  // Grid that specifies aria-rowcount (2) and aria-colcount (3).
  ComPtr<IGridProvider> grid3_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[2]);

  // Grid that specifies aria-rowcount and aria-colcount are both (-1).
  ComPtr<IGridProvider> grid4_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot()->children()[3]);

  ComPtr<IRawElementProviderSimple> grid_item;
  EXPECT_HRESULT_FAILED(grid1_provider->GetItem(0, 0, &grid_item));
  EXPECT_HRESULT_SUCCEEDED(grid2_provider->GetItem(3, 4, &grid_item));
  EXPECT_EQ(QueryInterfaceFromNodeId<IRawElementProviderSimple>(5).Get(),
            grid_item.Get());
  // This is an empty grid, so we should not be able to get any items.
  EXPECT_HRESULT_FAILED(grid3_provider->GetItem(1, 2, &grid_item));
  EXPECT_HRESULT_FAILED(grid4_provider->GetItem(-1, -1, &grid_item));
}

TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeadersForTable) {
  TestGetColumnHeadersForRole(ax::mojom::Role::kTable);
}

TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeadersForGrid) {
  TestGetColumnHeadersForRole(ax::mojom::Role::kGrid);
}

TEST_F(AXPlatformNodeWinTest, ITableProviderNoHeaders) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  Init(root, row1);

  ComPtr<ITableProvider> root_itableprovider(
      QueryInterfaceFromNode<ITableProvider>(GetRoot()));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetColumnHeaders(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());

  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetRowHeaders(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());
}

TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeadersMultipleHeaders) {
  // Build a table like this:
  //   header_r1c1  | header_r1c2 | header_r1c3
  //    cell_r2c1   | cell_r2c2   | cell_r2c3
  //    cell_r3c1   | header_r3c2 |

  // <table>
  //   <tr aria-label="row1">
  //     <th>header_r1c1</th>
  //     <th>header_r1c2</th>
  //     <th>header_r1c3</th>
  //   </tr>
  //   <tr aria-label="row2">
  //     <td>cell_r2c1</td>
  //     <td>cell_r2c2</td>
  //     <td>cell_r2c3</td>
  //   </tr>
  //   <tr aria-label="row3">
  //     <td>cell_r3c1</td>
  //     <th>header_r3c2</th>
  //   </tr>
  // </table>

  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData row2;
  row2.id = 3;
  row2.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row2.id);

  AXNodeData row3;
  row3.id = 4;
  row3.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row3.id);

  // <tr aria-label="row1">
  //   <th>header_r1c1</th> <th>header_r1c2</th> <th>header_r1c3</th>
  // </tr>
  AXNodeData header_r1c1;
  header_r1c1.id = 5;
  header_r1c1.role = ax::mojom::Role::kColumnHeader;
  header_r1c1.SetName(u"header_r1c1");
  row1.child_ids.push_back(header_r1c1.id);

  AXNodeData header_r1c2;
  header_r1c2.id = 6;
  header_r1c2.role = ax::mojom::Role::kColumnHeader;
  header_r1c2.SetName(u"header_r1c2");
  row1.child_ids.push_back(header_r1c2.id);

  AXNodeData header_r1c3;
  header_r1c3.id = 7;
  header_r1c3.role = ax::mojom::Role::kColumnHeader;
  header_r1c3.SetName(u"header_r1c3");
  row1.child_ids.push_back(header_r1c3.id);

  // <tr aria-label="row2">
  //   <td>cell_r2c1</td> <td>cell_r2c2</td> <td>cell_r2c3</td>
  // </tr>
  AXNodeData cell_r2c1;
  cell_r2c1.id = 8;
  cell_r2c1.role = ax::mojom::Role::kCell;
  cell_r2c1.SetName(u"cell_r2c1");
  row2.child_ids.push_back(cell_r2c1.id);

  AXNodeData cell_r2c2;
  cell_r2c2.id = 9;
  cell_r2c2.role = ax::mojom::Role::kCell;
  cell_r2c2.SetName(u"cell_r2c2");
  row2.child_ids.push_back(cell_r2c2.id);

  AXNodeData cell_r2c3;
  cell_r2c3.id = 10;
  cell_r2c3.role = ax::mojom::Role::kCell;
  cell_r2c3.SetName(u"cell_r2c3");
  row2.child_ids.push_back(cell_r2c3.id);

  // <tr aria-label="row3">
  //   <td>cell_r3c1</td> <th>header_r3c2</th>
  // </tr>
  AXNodeData cell_r3c1;
  cell_r3c1.id = 11;
  cell_r3c1.role = ax::mojom::Role::kCell;
  cell_r3c1.SetName(u"cell_r3c1");
  row3.child_ids.push_back(cell_r3c1.id);

  AXNodeData header_r3c2;
  header_r3c2.id = 12;
  header_r3c2.role = ax::mojom::Role::kColumnHeader;
  header_r3c2.SetName(u"header_r3c2");
  row3.child_ids.push_back(header_r3c2.id);

  Init(root, row1, row2, row3, header_r1c1, header_r1c2, header_r1c3, cell_r2c1,
       cell_r2c2, cell_r2c3, cell_r3c1, header_r3c2);

  ComPtr<ITableProvider> root_itableprovider(
      QueryInterfaceFromNode<ITableProvider>(GetRoot()));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetColumnHeaders(safearray.Receive()));
  EXPECT_NE(nullptr, safearray.Get());

  // Validate that we retrieve all column headers of the table and in the order
  // below.
  std::vector<std::wstring> expected_names = {L"header_r1c1", L"header_r1c2",
                                              L"header_r3c2", L"header_r1c3"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
                                   expected_names);
}

TEST_F(AXPlatformNodeWinTest, ITableProviderGetRowHeaders) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData column_header;
  column_header.id = 3;
  column_header.role = ax::mojom::Role::kColumnHeader;
  column_header.SetName(u"column_header");
  row1.child_ids.push_back(column_header.id);

  AXNodeData row_header;
  row_header.id = 4;
  row_header.role = ax::mojom::Role::kRowHeader;
  row_header.SetName(u"row_header");
  row1.child_ids.push_back(row_header.id);

  Init(root, row1, column_header, row_header);

  ComPtr<ITableProvider> root_itableprovider(
      QueryInterfaceFromNode<ITableProvider>(GetRoot()));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetRowHeaders(safearray.Receive()));
  EXPECT_NE(nullptr, safearray.Get());
  std::vector<std::wstring> expected_names = {L"row_header"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
                                   expected_names);

  // Remove row_header's native event target and verify it's no longer returned.
  TestAXNodeWrapper* row_header_wrapper = TestAXNodeWrapper::GetOrCreate(
      GetTree(), GetRoot()->children()[0]->children()[1]);
  row_header_wrapper->ResetNativeEventTarget();

  safearray.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->GetRowHeaders(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());
}

TEST_F(AXPlatformNodeWinTest, ITableProviderGetRowOrColumnMajor) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  Init(root);

  ComPtr<ITableProvider> root_itableprovider(
      QueryInterfaceFromNode<ITableProvider>(GetRoot()));

  RowOrColumnMajor row_or_column_major;
  EXPECT_HRESULT_SUCCEEDED(
      root_itableprovider->get_RowOrColumnMajor(&row_or_column_major));
  EXPECT_EQ(row_or_column_major, RowOrColumnMajor_RowMajor);
}

TEST_F(AXPlatformNodeWinTest, ITableItemProviderGetColumnHeaderItems) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData column_header_1;
  column_header_1.id = 3;
  column_header_1.role = ax::mojom::Role::kColumnHeader;
  column_header_1.SetName(u"column_header_1");
  row1.child_ids.push_back(column_header_1.id);

  AXNodeData column_header_2;
  column_header_2.id = 4;
  column_header_2.role = ax::mojom::Role::kColumnHeader;
  column_header_2.SetName(u"column_header_2");
  row1.child_ids.push_back(column_header_2.id);

  AXNodeData row2;
  row2.id = 5;
  row2.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row2.id);

  AXNodeData cell;
  cell.id = 6;
  cell.role = ax::mojom::Role::kCell;
  row2.child_ids.push_back(cell.id);

  Init(root, row1, column_header_1, column_header_2, row2, cell);

  TestAXNodeWrapper* root_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot());
  root_wrapper->BuildAllWrappers(GetTree(), GetRoot());

  ComPtr<ITableItemProvider> cell_itableitemprovider(
      QueryInterfaceFromNode<ITableItemProvider>(
          GetRoot()->children()[1]->children()[0]));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      cell_itableitemprovider->GetColumnHeaderItems(safearray.Receive()));
  EXPECT_NE(nullptr, safearray.Get());

  std::vector<std::wstring> expected_names = {L"column_header_1"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
                                   expected_names);

  // Remove column_header_1's native event target and verify it's no longer
  // returned.
  TestAXNodeWrapper* column_header_wrapper = TestAXNodeWrapper::GetOrCreate(
      GetTree(), GetRoot()->children()[0]->children()[0]);
  column_header_wrapper->ResetNativeEventTarget();

  safearray.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      cell_itableitemprovider->GetColumnHeaderItems(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());
}

TEST_F(AXPlatformNodeWinTest, ITableItemProviderGetRowHeaderItems) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData row_header_1;
  row_header_1.id = 3;
  row_header_1.role = ax::mojom::Role::kRowHeader;
  row_header_1.SetName(u"row_header_1");
  row1.child_ids.push_back(row_header_1.id);

  AXNodeData cell;
  cell.id = 4;
  cell.role = ax::mojom::Role::kCell;
  row1.child_ids.push_back(cell.id);

  AXNodeData row2;
  row2.id = 5;
  row2.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row2.id);

  AXNodeData row_header_2;
  row_header_2.id = 6;
  row_header_2.role = ax::mojom::Role::kRowHeader;
  row_header_2.SetName(u"row_header_2");
  row2.child_ids.push_back(row_header_2.id);

  Init(root, row1, row_header_1, cell, row2, row_header_2);

  TestAXNodeWrapper* root_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot());
  root_wrapper->BuildAllWrappers(GetTree(), GetRoot());

  ComPtr<ITableItemProvider> cell_itableitemprovider(
      QueryInterfaceFromNode<ITableItemProvider>(
          GetRoot()->children()[0]->children()[1]));

  base::win::ScopedSafearray safearray;
  EXPECT_HRESULT_SUCCEEDED(
      cell_itableitemprovider->GetRowHeaderItems(safearray.Receive()));
  EXPECT_NE(nullptr, safearray.Get());
  std::vector<std::wstring> expected_names = {L"row_header_1"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
                                   expected_names);

  // Remove row_header_1's native event target and verify it's no longer
  // returned.
  TestAXNodeWrapper* row_header_wrapper = TestAXNodeWrapper::GetOrCreate(
      GetTree(), GetRoot()->children()[0]->children()[0]);
  row_header_wrapper->ResetNativeEventTarget();

  safearray.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      cell_itableitemprovider->GetRowHeaderItems(safearray.Receive()));
  EXPECT_EQ(nullptr, safearray.Get());
}

TEST_F(AXPlatformNodeWinTest, IA2GetAttribute) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kButton;
  root.AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
                          "Potato");
  Init(root);

  ComPtr<IAccessible> root_obj(GetRootIAccessible());
  ComPtr<IAccessible2_2> iaccessible2 = ToIAccessible2_2(root_obj);

  ScopedBstr roledescription(L"roledescription");
  ScopedVariant result;
  ASSERT_EQ(S_OK, iaccessible2->get_attribute(roledescription.Get(),
                                              result.Receive()));
  ScopedBstr expected_bstr(L"Potato");
  ASSERT_EQ(VT_BSTR, result.type());
  EXPECT_STREQ(expected_bstr.Get(), result.ptr()->bstrVal);

  ScopedBstr smoothness(L"smoothness");
  ScopedVariant result2;
  EXPECT_EQ(S_FALSE,
            iaccessible2->get_attribute(smoothness.Get(), result2.Receive()));

  EXPECT_EQ(E_INVALIDARG, iaccessible2->get_attribute(nullptr, nullptr));
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
  AXNodeData root;
  root.role = ax::mojom::Role::kList;
  root.SetName("fake name");
  root.AddStringAttribute(ax::mojom::StringAttribute::kAccessKey, "Ctrl+Q");
  root.AddStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts, "Alt+F4");
  root.AddStringAttribute(ax::mojom::StringAttribute::kDescription,
                          "fake description");
  root.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 2);
  root.AddIntAttribute(ax::mojom::IntAttribute::kInvalidState, 1);
  root.AddIntAttribute(ax::mojom::IntAttribute::kAriaCurrentState,
                       static_cast<int>(ax::mojom::AriaCurrentState::kNone));
  root.id = 1;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kListItem;
  child1.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 1);
  child1.AddIntAttribute(ax::mojom::IntAttribute::kAriaCurrentState,
                         static_cast<int>(ax::mojom::AriaCurrentState::kPage));
  child1.SetName("child1");
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kGenericContainer;
  child2.AddIntAttribute(ax::mojom::IntAttribute::kAriaCurrentState,
                         static_cast<int>(ax::mojom::AriaCurrentState::kFalse));
  root.child_ids.push_back(child2.id);

  Init(root, child1, child2);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  ScopedVariant uia_id;
  EXPECT_UIA_BSTR_EQ(root_node, UIA_AccessKeyPropertyId, L"Ctrl+Q");
  EXPECT_UIA_BSTR_EQ(root_node, UIA_AcceleratorKeyPropertyId, L"Alt+F4");
  ASSERT_HRESULT_SUCCEEDED(root_node->GetPropertyValue(
      UIA_AutomationIdPropertyId, uia_id.Receive()));
  EXPECT_UIA_BSTR_EQ(root_node, UIA_AutomationIdPropertyId,
                     uia_id.ptr()->bstrVal);
  EXPECT_UIA_BSTR_EQ(root_node, UIA_FullDescriptionPropertyId,
                     L"fake description");
  EXPECT_UIA_BSTR_EQ(root_node, UIA_AriaRolePropertyId, L"list");
  EXPECT_UIA_BSTR_EQ(
      root_node, UIA_AriaPropertiesPropertyId,
      L"readonly=true;expanded=false;multiline=false;"
      L"multiselectable=false;required=false;setsize=2;hasactions=false");
  EXPECT_UIA_BSTR_EQ(root_node, UIA_NamePropertyId, L"fake name");
  EXPECT_UIA_INT_EQ(root_node, UIA_ControlTypePropertyId,
                    int{UIA_ListControlTypeId});
  EXPECT_UIA_INT_EQ(root_node, UIA_OrientationPropertyId,
                    int{OrientationType_None});
  EXPECT_UIA_INT_EQ(root_node, UIA_SizeOfSetPropertyId, 2);
  EXPECT_UIA_INT_EQ(root_node, UIA_ToggleToggleStatePropertyId,
                    int{ToggleState_Off});
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsPasswordPropertyId, false);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsEnabledPropertyId, true);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_HasKeyboardFocusPropertyId, false);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsRequiredForFormPropertyId, false);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsDataValidForFormPropertyId, true);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsKeyboardFocusablePropertyId, false);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsOffscreenPropertyId, false);
  ComPtr<IRawElementProviderSimple> child_node1 =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);
  EXPECT_UIA_INT_EQ(child_node1, UIA_PositionInSetPropertyId, 1);
  EXPECT_UIA_BSTR_EQ(
      child_node1, UIA_AriaPropertiesPropertyId,
      L"readonly=true;expanded=false;multiline=false;multiselectable=false;"
      L"posinset=1;required=false;hasactions=false;current=page");

  ComPtr<IRawElementProviderSimple> child_node2 =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[1]);
  // aria-current="false" should not be serialized.
  EXPECT_UIA_BSTR_EQ(
      child_node2, UIA_AriaPropertiesPropertyId,
      L"readonly=true;expanded=false;multiline=false;multiselectable=false;"
      L"required=false;hasactions=false");
}

TEST_F(AXPlatformNodeWinTest, UIAAriaPropertiesForCellIndexText) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kCell;
  root.AddStringAttribute(ax::mojom::StringAttribute::kAriaCellColumnIndexText,
                          "A");
  root.AddStringAttribute(ax::mojom::StringAttribute::kAriaCellRowIndexText,
                          "10");
  Init(root);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  EXPECT_UIA_BSTR_EQ(
      root_node, UIA_AriaPropertiesPropertyId,
      L"colindextext=A;rowindextext=10;readonly=true;expanded=false;"
      L"multiline=false;multiselectable=false;required=false;hasactions=false");
}

TEST_F(AXPlatformNodeWinTest, UIAAriaPropertiesForBrailleAttributes) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kList;
  root.AddStringAttribute(ax::mojom::StringAttribute::kAriaBrailleLabel,
                          "Label");
  root.AddStringAttribute(
      ax::mojom::StringAttribute::kAriaBrailleRoleDescription, "Role");
  Init(root);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  EXPECT_UIA_BSTR_EQ(
      root_node, UIA_AriaPropertiesPropertyId,
      L"braillelabel=Label;brailleroledescription=Role;readonly=true;"
      L"expanded=false;multiline=false;multiselectable=false;required=false;"
      L"hasactions=false");
}

TEST_F(AXPlatformNodeWinTest, UIAControlContentPropertyForTableElements) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData cell1;
  cell1.id = 3;
  cell1.role = ax::mojom::Role::kCell;
  row1.child_ids.push_back(cell1.id);

  AXNodeData generic_container;
  generic_container.id = 4;
  generic_container.role = ax::mojom::Role::kGenericContainer;
  cell1.child_ids.push_back(generic_container.id);

  Init(root, row1, cell1, generic_container);

  // Turn on web content mode for the AXTree.
  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(root_node, UIA_IsContentElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> row =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);
  EXPECT_UIA_BOOL_EQ(row, UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(row, UIA_IsContentElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> cell =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]->children()[0]);
  EXPECT_UIA_BOOL_EQ(cell, UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(cell, UIA_IsContentElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> generic_container_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]->children()[0]->children()[0]);
  EXPECT_UIA_BOOL_EQ(generic_container_provider, UIA_IsControlElementPropertyId,
                     false);
  EXPECT_UIA_BOOL_EQ(generic_container_provider, UIA_IsContentElementPropertyId,
                     false);
}

TEST_F(AXPlatformNodeWinTest, IsUIAControlColorWellInsideTable) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kTable
    ++++++3 kLayoutTableCell
    ++++++++4 kColorWell
    ++++++++++5 kStaticText
    ++++++6 kColumnHeader
    ++++++++7 kColorWell
    ++++++++++8 kStaticText
    ++++++9 kRowHeader
    ++++++++10 kColorWell
    ++++++++++11 kStaticText
  )HTML"));

  Init(update);

  // Turn on web content mode for the AXTree.
  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  AXNode* root_node = GetRoot();
  AXNode* table_node = root_node->children()[0];
  AXNode* st_5_node = table_node->children()[0]->children()[0]->children()[0];
  AXNode* st_8_node = table_node->children()[1]->children()[0]->children()[0];
  AXNode* st_11_node = table_node->children()[2]->children()[0]->children()[0];

  ComPtr<IRawElementProviderSimple> st_5_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(st_5_node);
  EXPECT_UIA_BOOL_EQ(st_5_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> st_8_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(st_8_node);
  EXPECT_UIA_BOOL_EQ(st_8_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> st_11_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(st_11_node);
  EXPECT_UIA_BOOL_EQ(st_11_provider, UIA_IsControlElementPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, IsUIAControlForTextNodes) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kGenericContainer
    ++++++3 kStaticText name="text"
    ++++4 kGroup
    ++++++5 kStaticText name="text"
    ++++6 kHeading
    ++++++7 kStaticText name="text"
  )HTML"));
  Init(update);

  // Turn on web content mode for the AXTree.
  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  AXNode* root_node = GetRoot();
  AXNode* text_3_node = root_node->children()[0]->children()[0];
  AXNode* text_5_node = root_node->children()[1]->children()[0];
  AXNode* text_7_node = root_node->children()[2]->children()[0];

  ComPtr<IRawElementProviderSimple> text_3_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(text_3_node);
  EXPECT_UIA_BOOL_EQ(text_3_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> text_5_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(text_5_node);
  EXPECT_UIA_BOOL_EQ(text_5_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> text_7_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(text_7_node);
  EXPECT_UIA_BOOL_EQ(text_7_provider, UIA_IsControlElementPropertyId, false);
}

TEST_F(AXPlatformNodeWinTest, IsUIAControlForNonFocusableNodesInViews) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kUnknown
    ++++2 kButton
  )HTML"));
  Init(update);

  // Set web content mode to false for the AXTree since we're testing for Views.
  TestAXNodeWrapper::SetGlobalIsWebContent(false);

  AXNode* root_1_node = GetRoot();
  AXNode* button_2_node = root_1_node->children()[0];

  ComPtr<IRawElementProviderSimple> root_1_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(root_1_node);
  EXPECT_UIA_BOOL_EQ(root_1_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> button_2_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(button_2_node);
  EXPECT_UIA_BOOL_EQ(button_2_provider, UIA_IsControlElementPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, IsUIAControlForLiveRegionNodesInViews) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kUnknown
    ++++2 kStatus
  )HTML"));
  Init(update);

  // Set web content mode to false for the AXTree since we're testing for Views.
  TestAXNodeWrapper::SetGlobalIsWebContent(false);

  AXNode* root_1_node = GetRoot();
  AXNode* status_2_node = root_1_node->children()[0];

  ComPtr<IRawElementProviderSimple> root_1_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(root_1_node);
  EXPECT_UIA_BOOL_EQ(root_1_provider, UIA_IsControlElementPropertyId, true);

  ComPtr<IRawElementProviderSimple> status_2_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(status_2_node);
  EXPECT_UIA_BOOL_EQ(status_2_provider, UIA_IsControlElementPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueClickablePoint) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kButton;
  root.relative_bounds.bounds = gfx::RectF(20, 30, 100, 200);
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();

  // The clickable point of a rectangle {20, 30, 100, 200} is the rectangle's
  // center, with coordinates {x: 70, y: 130}.
  std::vector<double> expected_values = {70, 130};
  EXPECT_UIA_DOUBLE_ARRAY_EQ(raw_element_provider_simple,
                             UIA_ClickablePointPropertyId, expected_values);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValue_Histogram) {
  AXNodeData root;
  root.id = 1;
  Init(root);
  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  // Log a known property ID
  {
    base::HistogramTester histogram_tester;
    ScopedVariant property_value;
    root_node->GetPropertyValue(UIA_NamePropertyId, property_value.Receive());
    histogram_tester.ExpectUniqueSample(
        "Accessibility.WinAPIs.GetPropertyValue", UIA_NamePropertyId, 1);
    histogram_tester.ExpectTotalCount(
        "Accessibility.Performance.WinAPIs.UMA_API_GET_PROPERTY_VALUE", 1);
  }

  // Collapse unknown property IDs to zero
  {
    base::HistogramTester histogram_tester;
    ScopedVariant property_value;
    root_node->GetPropertyValue(42, property_value.Receive());
    histogram_tester.ExpectUniqueSample(
        "Accessibility.WinAPIs.GetPropertyValue", 0, 1);
    histogram_tester.ExpectTotalCount(
        "Accessibility.Performance.WinAPIs.UMA_API_GET_PROPERTY_VALUE", 1);
  }
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueIsDialog) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kAlertDialog
    ++++3 kDialog
  )HTML"));
  Init(update);

  EXPECT_UIA_BOOL_EQ(GetRootIRawElementProviderSimple(), UIA_IsDialogPropertyId,
                     false);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                     UIA_IsDialogPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                     UIA_IsDialogPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueHeadingLevel) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kHeading intAttribute=kHierarchicalLevel,1
    ++++3 kHeading intAttribute=kHierarchicalLevel,2
    ++++4 kHeading intAttribute=kHierarchicalLevel,6
    ++++5 kGroup intAttribute=kHierarchicalLevel,3
    ++++6 kHeading
    ++++7 kHeading intAttribute=kHierarchicalLevel,10
    ++++8 kHeading intAttribute=kHierarchicalLevel,100
  )HTML"));
  Init(update);

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel1});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel2});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(2),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel6});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(3),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(4),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(5),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(6),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});
}

TEST_F(AXPlatformNodeWinTest,
       UIAGetPropertyValueHeadingLevelDisclosureTriangle) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kAccessibilityExposeSummaryAsHeading);

  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kDisclosureTriangle intAttribute=kHierarchicalLevel,2
    ++++3 kDisclosureTriangleGrouped intAttribute=kHierarchicalLevel,4
    ++++4 kDisclosureTriangle intAttribute=kHierarchicalLevel,3
  )HTML"));
  Init(update);

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel2});

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel4});
}

TEST_F(AXPlatformNodeWinTest,
       UIAGetPropertyValueHeadingLevelDisclosureTriangleFeatureDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAccessibilityExposeSummaryAsHeading);

  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kDisclosureTriangle intAttribute=kHierarchicalLevel,2
    ++++3 kDisclosureTriangleGrouped intAttribute=kHierarchicalLevel,4
  )HTML"));
  Init(update);

  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});
  EXPECT_UIA_INT_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                    UIA_HeadingLevelPropertyId, int{HeadingLevel_None});
}

TEST_F(AXPlatformNodeWinTest,
       UIAGetPropertyValueIsControlElementIgnoredInvisible) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kButton
    ++++3 kButton states=kIgnored
    ++++4 kButton states=kInvisible
    ++++5 kButton states=kInvisible,kFocusable
    ++++6 kGenericContainer states=kFocusable
    ++++7 kGenericContainer states=kIgnored,kFocusable
    ++++8 kGenericContainer states=kInvisible,kFocusable
  )HTML"));
  Init(update);

  // Turn on web content mode for the AXTree.
  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  // Normal button (id=2), no invisible or ignored state set. Should be a
  // control element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                     UIA_IsControlElementPropertyId, true);

  // Button with ignored state (id=3). Should not be a control element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                     UIA_IsControlElementPropertyId, false);

  // Button with invisible state (id=4). Should not be a control element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(2),
                     UIA_IsControlElementPropertyId, false);

  // Button with invisible state, but focusable (id=5). Should not be a control
  // element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(3),
                     UIA_IsControlElementPropertyId, false);

  // Generic container, focusable (id=6). Should be a control
  // element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(4),
                     UIA_IsControlElementPropertyId, true);

  // Generic container, ignored but focusable (id=7). Should not be a control
  // element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(5),
                     UIA_IsControlElementPropertyId, false);

  // Generic container, invisible and ignored, but focusable (id=8). Should not
  // be a control element.
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(6),
                     UIA_IsControlElementPropertyId, false);
}

TEST_F(AXPlatformNodeWinTest, UIAGetAnnotationObjectsPropertyId) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds, {2, 3});

  AXNodeData highlighted;
  highlighted.id = 2;
  highlighted.role = ax::mojom::Role::kMark;
  highlighted.SetName("highlighted");
  root.child_ids.push_back(highlighted.id);

  AXNodeData comment;
  comment.id = 3;
  comment.role = ax::mojom::Role::kComment;
  comment.SetName("comment");
  root.child_ids.push_back(comment.id);

  Init(root, highlighted, comment);
  ComPtr<IRawElementProviderSimple> root_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(GetRoot());

  ScopedVariant array;
  ASSERT_HRESULT_SUCCEEDED(root_node->GetPropertyValue(
      UIA_AnnotationObjectsPropertyId, array.Receive()));
  ASSERT_EQ(VT_ARRAY | VT_UNKNOWN, array.type());
  std::vector<std::wstring> expected_names = {L"highlighted", L"comment"};
  EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(array.ptr()->parray, UIA_NamePropertyId,
                                   expected_names);
}

TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids = {2, 3, 4, 5, 6};

  AXNodeData tab;
  tab.id = 2;
  tab.role = ax::mojom::Role::kTab;
  tab.SetName("tab");
  std::vector<AXNodeID> controller_ids = {3, 4};
  tab.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds,
                          controller_ids);

  AXNodeData panel1;
  panel1.id = 3;
  panel1.role = ax::mojom::Role::kTabPanel;
  panel1.SetName("panel1");

  AXNodeData panel2;
  panel2.id = 4;
  panel2.role = ax::mojom::Role::kTabPanel;
  panel2.SetName("panel2");

  AXNodeData group1;
  group1.id = 5;
  group1.role = ax::mojom::Role::kGenericContainer;
  group1.AddIntListAttribute(ax::mojom::IntListAttribute::kErrormessageIds,
                             std::vector<AXNodeID>{6});

  AXNodeData text1;
  text1.id = 6;
  text1.role = ax::mojom::Role::kStaticText;
  text1.SetName("text1");

  Init(root, tab, panel1, panel2, group1, text1);
  TestAXNodeWrapper* root_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot());
  root_wrapper->BuildAllWrappers(GetTree(), GetRoot());

  ComPtr<IRawElementProviderSimple> tab_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);

  std::vector<std::wstring> expected_names_1 = {L"panel1", L"panel2"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
      tab_node, UIA_ControllerForPropertyId, UIA_NamePropertyId,
      expected_names_1);

  // Remove panel1's native event target and verify it's no longer returned.
  TestAXNodeWrapper* panel1_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot()->children()[1]);
  panel1_wrapper->ResetNativeEventTarget();
  std::vector<std::wstring> expected_names_2 = {L"panel2"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
      tab_node, UIA_ControllerForPropertyId, UIA_NamePropertyId,
      expected_names_2);

  // The aria-errormessage attribute should be exposed through the ControllerFor
  // UIA property.
  ComPtr<IRawElementProviderSimple> group1_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[3]);

  std::vector<std::wstring> expected_names_3 = {L"text1"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
      group1_node, UIA_ControllerForPropertyId, UIA_NamePropertyId,
      expected_names_3);
}

TEST_F(AXPlatformNodeWinTest, UIAGetDescribedByPropertyId) {
  AXNodeData root;
  std::vector<AXNodeID> describedby_ids = {2, 3, 4};
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds,
                           describedby_ids);
  root.id = 1;
  root.role = ax::mojom::Role::kMarquee;
  root.SetName("root");

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;
  child1.SetName("child1");

  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;
  child2.SetName("child2");

  root.child_ids.push_back(child2.id);

  Init(root, child1, child2);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  std::vector<std::wstring> expected_names = {L"child1", L"child2"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
      root_node, UIA_DescribedByPropertyId, UIA_NamePropertyId, expected_names);
}

TEST_F(AXPlatformNodeWinTest, UIAItemStatusPropertyId) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  row1.AddIntAttribute(ax::mojom::IntAttribute::kSortDirection,
                       static_cast<int>(ax::mojom::SortDirection::kAscending));
  root.child_ids.push_back(row1.id);

  AXNodeData header1;
  header1.id = 3;
  header1.role = ax::mojom::Role::kRowHeader;
  header1.AddIntAttribute(
      ax::mojom::IntAttribute::kSortDirection,
      static_cast<int>(ax::mojom::SortDirection::kAscending));
  row1.child_ids.push_back(header1.id);

  AXNodeData header2;
  header2.id = 4;
  header2.role = ax::mojom::Role::kColumnHeader;
  header2.AddIntAttribute(
      ax::mojom::IntAttribute::kSortDirection,
      static_cast<int>(ax::mojom::SortDirection::kDescending));
  row1.child_ids.push_back(header2.id);

  AXNodeData header3;
  header3.id = 5;
  header3.role = ax::mojom::Role::kColumnHeader;
  header3.AddIntAttribute(ax::mojom::IntAttribute::kSortDirection,
                          static_cast<int>(ax::mojom::SortDirection::kOther));
  row1.child_ids.push_back(header3.id);

  AXNodeData header4;
  header4.id = 6;
  header4.role = ax::mojom::Role::kColumnHeader;
  header4.AddIntAttribute(
      ax::mojom::IntAttribute::kSortDirection,
      static_cast<int>(ax::mojom::SortDirection::kUnsorted));
  row1.child_ids.push_back(header4.id);

  Init(root, row1, header1, header2, header3, header4);

  auto* row_node = GetRoot()->children()[0].get();

  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         row_node->children()[0]),
                     UIA_ItemStatusPropertyId, L"ascending");

  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         row_node->children()[1]),
                     UIA_ItemStatusPropertyId, L"descending");

  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         row_node->children()[2]),
                     UIA_ItemStatusPropertyId, L"other");

  EXPECT_UIA_VALUE_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                          row_node->children()[3]),
                      UIA_ItemStatusPropertyId, ScopedVariant::kEmptyVariant);

  EXPECT_UIA_VALUE_EQ(
      QueryInterfaceFromNode<IRawElementProviderSimple>(row_node),
      UIA_ItemStatusPropertyId, ScopedVariant::kEmptyVariant);
}

TEST_F(AXPlatformNodeWinTest, UIAGetFlowsToPropertyId) {
  AXNodeData root;
  std::vector<AXNodeID> flowto_ids = {2, 3, 4};
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kFlowtoIds, flowto_ids);
  root.id = 1;
  root.role = ax::mojom::Role::kMarquee;
  root.SetName("root");

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kStaticText;
  child1.SetName("child1");

  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kStaticText;
  child2.SetName("child2");

  root.child_ids.push_back(child2.id);

  Init(root, child1, child2);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  std::vector<std::wstring> expected_names = {L"child1", L"child2"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(root_node, UIA_FlowsToPropertyId,
                                            UIA_NamePropertyId, expected_names);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueFlowsFromNone) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");

  Init(root);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  ScopedVariant property_value;
  EXPECT_HRESULT_SUCCEEDED(root_node->GetPropertyValue(
      UIA_FlowsFromPropertyId, property_value.Receive()));
  EXPECT_EQ(VT_ARRAY | VT_UNKNOWN, property_value.type());
  EXPECT_EQ(nullptr, V_ARRAY(property_value.ptr()));
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueFlowsFromSingle) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kFlowtoIds, {2});

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kGenericContainer;
  child1.SetName("child1");
  root.child_ids.push_back(child1.id);

  Init(root, child1);
  ASSERT_NE(nullptr, TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot()));

  ComPtr<IRawElementProviderSimple> child_node1 =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);
  std::vector<std::wstring> expected_names = {L"root"};
  EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
      child_node1, UIA_FlowsFromPropertyId, UIA_NamePropertyId, expected_names);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueFlowsFromMultiple) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  root.AddIntListAttribute(ax::mojom::IntListAttribute::kFlowtoIds, {2, 3});

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kGenericContainer;
  child1.SetName("child1");
  child1.AddIntListAttribute(ax::mojom::IntListAttribute::kFlowtoIds, {3});
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kGenericContainer;
  child2.SetName("child2");
  root.child_ids.push_back(child2.id);

  Init(root, child1, child2);
  ASSERT_NE(nullptr, TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot()));
  ASSERT_NE(nullptr, TestAXNodeWrapper::GetOrCreate(GetTree(),
                                                    GetRoot()->children()[0]));

  ComPtr<IRawElementProviderSimple> child_node2 =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[1]);
  std::vector<std::wstring> expected_names_1 = {L"root", L"child1"};
  EXPECT_UIA_PROPERTY_UNORDERED_ELEMENT_ARRAY_BSTR_EQ(
      child_node2, UIA_FlowsFromPropertyId, UIA_NamePropertyId,
      expected_names_1);

  // Remove child1's native event target and verify it's no longer returned.
  TestAXNodeWrapper* child1_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), GetRoot()->children()[0]);
  child1_wrapper->ResetNativeEventTarget();
  std::vector<std::wstring> expected_names_2 = {L"root"};
  EXPECT_UIA_PROPERTY_UNORDERED_ELEMENT_ARRAY_BSTR_EQ(
      child_node2, UIA_FlowsFromPropertyId, UIA_NamePropertyId,
      expected_names_2);
}

TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueFrameworkId) {
  AXNodeData root_ax_node_data;
  root_ax_node_data.id = 1;
  root_ax_node_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_ax_node_data);

  ComPtr<IRawElementProviderSimple> root_raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  EXPECT_UIA_BSTR_EQ(root_raw_element_provider_simple,
                     UIA_FrameworkIdPropertyId, L"Chrome");
}

TEST_F(AXPlatformNodeWinTest, GetPropertyValue_LabeledByTest) {
  // ++1 root
  // ++++2 kGenericContainer LabeledBy 3
  // ++++++3 kStaticText "Hello"
  // ++++4 kGenericContainer LabeledBy 5
  // ++++++5 kGenericContainer
  // ++++++++6 kStaticText "3.14"
  // ++++7 kAlert LabeledBy 6
  AXNodeData root_1;
  AXNodeData gc_2;
  AXNodeData static_text_3;
  AXNodeData gc_4;
  AXNodeData gc_5;
  AXNodeData static_text_6;
  AXNodeData alert_7;

  root_1.id = 1;
  gc_2.id = 2;
  static_text_3.id = 3;
  gc_4.id = 4;
  gc_5.id = 5;
  static_text_6.id = 6;
  alert_7.id = 7;

  root_1.role = ax::mojom::Role::kRootWebArea;
  root_1.child_ids = {gc_2.id, gc_4.id, alert_7.id};

  gc_2.role = ax::mojom::Role::kGenericContainer;
  gc_2.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                           {static_text_3.id});
  gc_2.child_ids = {static_text_3.id};

  static_text_3.role = ax::mojom::Role::kStaticText;
  static_text_3.SetName("Hello");

  gc_4.role = ax::mojom::Role::kGenericContainer;
  gc_4.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                           {gc_5.id});
  gc_4.child_ids = {gc_5.id};

  gc_5.role = ax::mojom::Role::kGenericContainer;
  gc_5.child_ids = {static_text_6.id};

  static_text_6.role = ax::mojom::Role::kStaticText;
  static_text_6.SetName("3.14");

  alert_7.role = ax::mojom::Role::kAlert;
  alert_7.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                              {static_text_6.id});

  Init(root_1, gc_2, static_text_3, gc_4, gc_5, static_text_6, alert_7);

  AXNode* root_node = GetRoot();
  AXNode* gc_2_node = root_node->children()[0];
  AXNode* static_text_3_node = gc_2_node->children()[0];
  AXNode* gc_4_node = root_node->children()[1];
  AXNode* static_text_6_node = gc_4_node->children()[0]->children()[0];
  AXNode* alert_7_node = root_node->children()[2];

  // Case 1: |gc_2| is labeled by |static_text_3|.

  ComPtr<IRawElementProviderSimple> gc_2_provider =
      GetIRawElementProviderSimpleFromId(gc_2_node->id());
  ScopedVariant property_value;
  EXPECT_EQ(S_OK, gc_2_provider->GetPropertyValue(UIA_LabeledByPropertyId,
                                                  property_value.Receive()));
  ASSERT_EQ(property_value.type(), VT_UNKNOWN);
  ComPtr<IRawElementProviderSimple> static_text_3_provider;
  EXPECT_EQ(S_OK, property_value.ptr()->punkVal->QueryInterface(
                      IID_PPV_ARGS(&static_text_3_provider)));
  EXPECT_UIA_BSTR_EQ(static_text_3_provider, UIA_NamePropertyId, L"Hello");

  // Case 2: |gc_4| is labeled by |gc_5| and should return the first static text
  // child of that node, which is |static_text_6|.

  ComPtr<IRawElementProviderSimple> gc_4_provider =
      GetIRawElementProviderSimpleFromId(gc_4_node->id());
  property_value.Reset();
  EXPECT_EQ(S_OK, gc_4_provider->GetPropertyValue(UIA_LabeledByPropertyId,
                                                  property_value.Receive()));
  ASSERT_EQ(property_value.type(), VT_UNKNOWN);
  ComPtr<IRawElementProviderSimple> static_text_6_provider;
  EXPECT_EQ(S_OK, property_value.ptr()->punkVal->QueryInterface(
                      IID_PPV_ARGS(&static_text_6_provider)));
  EXPECT_UIA_BSTR_EQ(static_text_6_provider, UIA_NamePropertyId, L"3.14");

  // Case 3: Some UIA control types always expect an empty value for this
  // property. The role kAlert corresponds to UIA_TextControlTypeId, which
  // always expects an empty value. |alert_7| is marked as labeled by
  // |static_text_6|, but shouldn't expose it to the UIA_LabeledByPropertyId.

  ComPtr<IRawElementProviderSimple> alert_7_provider =
      GetIRawElementProviderSimpleFromId(alert_7_node->id());
  property_value.Reset();
  EXPECT_EQ(S_OK, alert_7_provider->GetPropertyValue(UIA_LabeledByPropertyId,
                                                     property_value.Receive()));
  ASSERT_EQ(property_value.type(), VT_EMPTY);

  // Remove the referenced nodes' native event targets and verify it's no longer
  // returned.

  // Case 1.
  TestAXNodeWrapper* static_text_3_node_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), static_text_3_node);
  static_text_3_node_wrapper->ResetNativeEventTarget();

  property_value.Reset();
  EXPECT_EQ(S_OK, gc_2_provider->GetPropertyValue(UIA_LabeledByPropertyId,
                                                  property_value.Receive()));
  EXPECT_EQ(property_value.type(), VT_EMPTY);

  // Case 2.
  TestAXNodeWrapper* static_text_6_node_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), static_text_6_node);
  static_text_6_node_wrapper->ResetNativeEventTarget();

  property_value.Reset();
  EXPECT_EQ(S_OK, gc_4_provider->GetPropertyValue(UIA_LabeledByPropertyId,
                                                  property_value.Receive()));
  EXPECT_EQ(property_value.type(), VT_EMPTY);
}

TEST_F(AXPlatformNodeWinTest, GetPropertyValue_HelpText) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kTextField name="name-from-title" state=kEditable stringAttribute=kPlaceholder,placeholder
    ++++3 kTextField state=kEditable name="name-from-title"
    ++++4 kTextField name="name-from-placeholder" state=kEditable
    ++++5 kTextField state=kEditable name="name-from-attribute" stringAttribute=kToolTip,tooltip
    ++++6 kTextField state=kEditable name="name-from-attribute"
  )HTML"));

  update.nodes[1].AddIntAttribute(
      ax::mojom::IntAttribute::kNameFrom,
      static_cast<int>(ax::mojom::NameFrom::kTitle));
  update.nodes[2].AddIntAttribute(
      ax::mojom::IntAttribute::kNameFrom,
      static_cast<int>(ax::mojom::NameFrom::kTitle));
  update.nodes[3].AddIntAttribute(
      ax::mojom::IntAttribute::kNameFrom,
      static_cast<int>(ax::mojom::NameFrom::kPlaceholder));
  update.nodes[4].AddIntAttribute(
      ax::mojom::IntAttribute::kNameFrom,
      static_cast<int>(ax::mojom::NameFrom::kAttribute));
  update.nodes[5].AddIntAttribute(
      ax::mojom::IntAttribute::kNameFrom,
      static_cast<int>(ax::mojom::NameFrom::kAttribute));

  Init(update);

  auto* root_node = GetRoot();
  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         root_node->children()[0]),
                     UIA_HelpTextPropertyId, L"placeholder");
  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         root_node->children()[1]),
                     UIA_HelpTextPropertyId, L"name-from-title");
  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         root_node->children()[2]),
                     UIA_HelpTextPropertyId, L"name-from-placeholder");
  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         root_node->children()[3]),
                     UIA_HelpTextPropertyId, L"tooltip");
  EXPECT_UIA_VALUE_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                          root_node->children()[4]),
                      UIA_HelpTextPropertyId, ScopedVariant::kEmptyVariant);
}

TEST_F(AXPlatformNodeWinTest, GetPropertyValue_LocalizedControlType) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kUnknown stringAttribute=kRoleDescription,rootRoleDescription
    ++++2 kSearchBox stringAttribute=kRoleDescription,child1RoleDescription
    ++++3 kButton
  )HTML"));

  Init(update);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  EXPECT_UIA_BSTR_EQ(root_node, UIA_LocalizedControlTypePropertyId,
                     L"rootRoleDescription");
  EXPECT_UIA_BSTR_EQ(QueryInterfaceFromNode<IRawElementProviderSimple>(
                         GetRoot()->children()[0]),
                     UIA_LocalizedControlTypePropertyId,
                     L"child1RoleDescription");
  EXPECT_UIA_EMPTY(QueryInterfaceFromNode<IRawElementProviderSimple>(
                       GetRoot()->children()[1]),
                   UIA_LocalizedControlTypePropertyId);
}

TEST_F(AXPlatformNodeWinTest, GetPropertyValue_IsControlElement) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kButton
    ++++++3 kStaticText name="someText"
    ++++4 kGenericContainer
    ++++++5 kStaticText name="moreText"
    ++++6 kTable
    ++++7 kList
    ++++8 kForm
    ++++9 kImage
    ++++10 kImage
    ++++11 kArticle
    ++++12 kGenericContainer boolAttribute=kHasAriaAttribute,true
    ++++13 kGenericContainer states=kEditable,kRichlyEditable boolAttribute=kNonAtomicTextFieldRoot,true
    ++++14 kGenericContainer name="name"
    ++++15 kGenericContainer
    ++++16 kGenericContainer state=kFocusable
    ++++17 kForm name="name"
  )HTML"));

  AXTreeID tree_id = AXTreeID::CreateNewAXTreeID();
  update.tree_data.tree_id = tree_id;
  update.has_tree_data = true;
  update.root_id = 1;
  update.nodes[9].SetNameExplicitlyEmpty();
  update.nodes[14].SetDescription("description");

  Init(update);

  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(2),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(3),
                     UIA_IsControlElementPropertyId, false);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(4),
                     UIA_IsControlElementPropertyId, false);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(5),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(6),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(7),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(8),
                     UIA_IsControlElementPropertyId, false);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(9),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(10),
                     UIA_IsControlElementPropertyId, false);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(11),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(12),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(13),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(14),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(15),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(16),
                     UIA_IsControlElementPropertyId, true);
  EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromId(17),
                     UIA_IsControlElementPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, UIAGetProviderOptions) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_data);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  ProviderOptions provider_options = static_cast<ProviderOptions>(0);
  EXPECT_HRESULT_SUCCEEDED(root_node->get_ProviderOptions(&provider_options));
  EXPECT_EQ(ProviderOptions_ServerSideProvider |
                ProviderOptions_UseComThreading |
                ProviderOptions_RefuseNonClientSupport |
                ProviderOptions_HasNativeIAccessible,
            provider_options);
}

TEST_F(AXPlatformNodeWinTest, UIAGetHostRawElementProvider) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_data);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  ComPtr<IRawElementProviderSimple> host_provider;
  EXPECT_HRESULT_SUCCEEDED(
      root_node->get_HostRawElementProvider(&host_provider));
  EXPECT_EQ(nullptr, host_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, UIAGetBoundingRectangle) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;
  root_data.relative_bounds.bounds = gfx::RectF(10, 20, 30, 50);
  Init(root_data);

  ComPtr<IRawElementProviderFragment> root_node =
      GetRootIRawElementProviderFragment();

  UiaRect bounding_rectangle;
  EXPECT_HRESULT_SUCCEEDED(
      root_node->get_BoundingRectangle(&bounding_rectangle));
  EXPECT_EQ(10, bounding_rectangle.left);
  EXPECT_EQ(20, bounding_rectangle.top);
  EXPECT_EQ(30, bounding_rectangle.width);
  EXPECT_EQ(50, bounding_rectangle.height);
}

TEST_F(AXPlatformNodeWinTest, UIAGetFragmentRoot) {
  // This test needs to be run on a child node since AXPlatformRootNodeWin
  // overrides the method.
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;

  AXNodeData element1_data;
  element1_data.id = 2;
  root_data.child_ids.push_back(element1_data.id);

  Init(root_data, element1_data);
  InitFragmentRoot();

  AXNode* root_node = GetRoot();
  AXNode* element1_node = root_node->children()[0];

  ComPtr<IRawElementProviderFragment> element1_provider =
      QueryInterfaceFromNode<IRawElementProviderFragment>(element1_node);
  ComPtr<IRawElementProviderFragmentRoot> expected_fragment_root =
      GetFragmentRoot();

  ComPtr<IRawElementProviderFragmentRoot> actual_fragment_root;
  EXPECT_HRESULT_SUCCEEDED(
      element1_provider->get_FragmentRoot(&actual_fragment_root));
  EXPECT_EQ(expected_fragment_root.Get(), actual_fragment_root.Get());

  // Test the case where the fragment root has gone away.
  ax_fragment_root_.reset();
  actual_fragment_root.Reset();
  EXPECT_UIA_ELEMENTNOTAVAILABLE(
      element1_provider->get_FragmentRoot(&actual_fragment_root));

  // Test the case where the widget has gone away.
  TestAXNodeWrapper* element1_wrapper =
      TestAXNodeWrapper::GetOrCreate(GetTree(), element1_node);
  element1_wrapper->ResetNativeEventTarget();
  EXPECT_UIA_ELEMENTNOTAVAILABLE(
      element1_provider->get_FragmentRoot(&actual_fragment_root));
}

TEST_F(AXPlatformNodeWinTest, UIAGetEmbeddedFragmentRoots) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_data);

  ComPtr<IRawElementProviderFragment> root_provider =
      GetRootIRawElementProviderFragment();

  base::win::ScopedSafearray embedded_fragment_roots;
  EXPECT_HRESULT_SUCCEEDED(root_provider->GetEmbeddedFragmentRoots(
      embedded_fragment_roots.Receive()));
  EXPECT_EQ(nullptr, embedded_fragment_roots.Get());
}

TEST_F(AXPlatformNodeWinTest, UIAGetRuntimeIdForGeneratedId) {
  AXNodeData root_data;
  root_data.id = -99;
  root_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_data);

  ComPtr<IRawElementProviderFragment> root_provider =
      GetRootIRawElementProviderFragment();

  base::win::ScopedSafearray runtime_id;
  EXPECT_HRESULT_SUCCEEDED(root_provider->GetRuntimeId(runtime_id.Receive()));

  LONG array_lower_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetLBound(runtime_id.Get(), 1, &array_lower_bound));
  EXPECT_EQ(0, array_lower_bound);

  LONG array_upper_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetUBound(runtime_id.Get(), 1, &array_upper_bound));
  EXPECT_EQ(3, array_upper_bound);

  int* array_data;
  EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
      runtime_id.Get(), reinterpret_cast<void**>(&array_data)));
  EXPECT_EQ(UiaAppendRuntimeId, array_data[0]);
  EXPECT_NE(-1, UNSAFE_TODO(array_data[1]));
  EXPECT_NE(-1, UNSAFE_TODO(array_data[2]));
  EXPECT_NE(-1, UNSAFE_TODO(array_data[3]));

  EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(runtime_id.Get()));
}

TEST_F(AXPlatformNodeWinTest, UIAGetRuntimeIdForSuppliedId) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;
  Init(root_data);

  ComPtr<IRawElementProviderFragment> root_provider =
      GetRootIRawElementProviderFragment();

  base::win::ScopedSafearray runtime_id;
  EXPECT_HRESULT_SUCCEEDED(root_provider->GetRuntimeId(runtime_id.Receive()));

  LONG array_lower_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetLBound(runtime_id.Get(), 1, &array_lower_bound));
  EXPECT_EQ(0, array_lower_bound);

  LONG array_upper_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetUBound(runtime_id.Get(), 1, &array_upper_bound));
  EXPECT_EQ(3, array_upper_bound);

  int* array_data;
  EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
      runtime_id.Get(), reinterpret_cast<void**>(&array_data)));
  EXPECT_EQ(UiaAppendRuntimeId, array_data[0]);
  EXPECT_EQ(-1, UNSAFE_TODO(array_data[1]));
  EXPECT_EQ(-1, UNSAFE_TODO(array_data[2]));
  EXPECT_EQ(1, UNSAFE_TODO(array_data[3]));

  EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(runtime_id.Get()));
}

TEST_F(AXPlatformNodeWinTest, UIAIWindowProviderGetIsModalUnset) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<IWindowProvider> window_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_WindowPatternId, &window_provider));
  ASSERT_EQ(nullptr, window_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, UIAIWindowProviderGetIsModalFalse) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, false);
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<IWindowProvider> window_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_WindowPatternId, &window_provider));
  ASSERT_NE(nullptr, window_provider.Get());

  BOOL is_modal;
  EXPECT_HRESULT_SUCCEEDED(window_provider->get_IsModal(&is_modal));
  ASSERT_FALSE(is_modal);
}

TEST_F(AXPlatformNodeWinTest, UIAIWindowProviderGetIsModalTrue) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, true);
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<IWindowProvider> window_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_WindowPatternId, &window_provider));
  ASSERT_NE(nullptr, window_provider.Get());

  BOOL is_modal;
  EXPECT_HRESULT_SUCCEEDED(window_provider->get_IsModal(&is_modal));
  ASSERT_TRUE(is_modal);
}

TEST_F(AXPlatformNodeWinTest, UIAIWindowProviderInvalidArgument) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, true);
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<IWindowProvider> window_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_WindowPatternId, &window_provider));
  ASSERT_NE(nullptr, window_provider.Get());

  ASSERT_EQ(E_INVALIDARG, window_provider->WaitForInputIdle(0, nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_CanMaximize(nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_CanMinimize(nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_IsModal(nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_WindowVisualState(nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_WindowInteractionState(nullptr));
  ASSERT_EQ(E_INVALIDARG, window_provider->get_IsTopmost(nullptr));
}

TEST_F(AXPlatformNodeWinTest, UIAIWindowProviderNotSupported) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, true);
  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<IWindowProvider> window_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_WindowPatternId, &window_provider));
  ASSERT_NE(nullptr, window_provider.Get());

  BOOL bool_result;
  WindowVisualState window_visual_state_result;
  WindowInteractionState window_interaction_state_result;

  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->SetVisualState(
                WindowVisualState::WindowVisualState_Normal));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), window_provider->Close());
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->WaitForInputIdle(0, &bool_result));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->get_CanMaximize(&bool_result));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->get_CanMinimize(&bool_result));
  ASSERT_EQ(
      static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
      window_provider->get_WindowVisualState(&window_visual_state_result));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->get_WindowInteractionState(
                &window_interaction_state_result));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED),
            window_provider->get_IsTopmost(&bool_result));
}

TEST_F(AXPlatformNodeWinTest, UIANavigate) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;

  AXNodeData element1_data;
  element1_data.id = 2;
  root_data.child_ids.push_back(element1_data.id);

  AXNodeData element2_data;
  element2_data.id = 3;
  root_data.child_ids.push_back(element2_data.id);

  AXNodeData element3_data;
  element3_data.id = 4;
  element1_data.child_ids.push_back(element3_data.id);

  Init(root_data, element1_data, element2_data, element3_data);

  AXNode* root_node = GetRoot();
  AXNode* element1_node = root_node->children()[0];
  AXNode* element2_node = root_node->children()[1];
  AXNode* element3_node = element1_node->children()[0];

  auto TestNavigate = [this](AXNode* element_node, AXNode* parent,
                             AXNode* next_sibling, AXNode* prev_sibling,
                             AXNode* first_child, AXNode* last_child) {
    ComPtr<IRawElementProviderFragment> element_provider =
        QueryInterfaceFromNode<IRawElementProviderFragment>(element_node);

    auto TestNavigateSingle = [&](NavigateDirection direction,
                                  AXNode* expected_node) {
      ComPtr<IRawElementProviderFragment> expected_provider =
          QueryInterfaceFromNode<IRawElementProviderFragment>(expected_node);

      ComPtr<IRawElementProviderFragment> navigated_to_fragment;
      EXPECT_HRESULT_SUCCEEDED(
          element_provider->Navigate(direction, &navigated_to_fragment));
      EXPECT_EQ(expected_provider.Get(), navigated_to_fragment.Get());
    };

    TestNavigateSingle(NavigateDirection_Parent, parent);
    TestNavigateSingle(NavigateDirection_NextSibling, next_sibling);
    TestNavigateSingle(NavigateDirection_PreviousSibling, prev_sibling);
    TestNavigateSingle(NavigateDirection_FirstChild, first_child);
    TestNavigateSingle(NavigateDirection_LastChild, last_child);
  };

  TestNavigate(root_node,
               nullptr,         // Parent
               nullptr,         // NextSibling
               nullptr,         // PreviousSibling
               element1_node,   // FirstChild
               element2_node);  // LastChild

  TestNavigate(element1_node, root_node, element2_node, nullptr, element3_node,
               element3_node);

  TestNavigate(element2_node, root_node, nullptr, element1_node, nullptr,
               nullptr);

  TestNavigate(element3_node, element1_node, nullptr, nullptr, nullptr,
               nullptr);
}

namespace {

class InconsistentChildDelegate : public AXPlatformNodeDelegate {
 public:
  AXPlatformNodeId GetUniqueId() const override { return unique_id_; }
  size_t GetChildCount() const override { return 1; }

 private:
  const AXUniqueId unique_id_{AXUniqueId::Create()};
};

}  // namespace

TEST_F(AXPlatformNodeWinTest, UIANavigateWithNullChildAccessible) {
  InconsistentChildDelegate delegate;
  AXPlatformNode::Pointer node = AXPlatformNode::Create(delegate);

  ComPtr<IRawElementProviderFragment> provider;
  ASSERT_HRESULT_SUCCEEDED(static_cast<AXPlatformNodeWin*>(node.get())
                               ->QueryInterface(IID_PPV_ARGS(&provider)));

  for (NavigateDirection direction :
       {NavigateDirection_FirstChild, NavigateDirection_LastChild}) {
    ComPtr<IRawElementProviderFragment> child_provider;
    EXPECT_EQ(S_OK, provider->Navigate(direction, &child_provider));
    EXPECT_EQ(nullptr, child_provider.Get());
  }
}

TEST_F(AXPlatformNodeWinTest, IAnnotationProvider) {
  // rootWebArea
  // ++mark detailsIds=comment, footnote, definition
  // ++++staticText name='highlighted 1'
  // ++comment
  // ++++staticText name='comment 1'
  // ++docFootnote
  // ++++staticText name="footnote"
  // ++definition
  // ++button

  AXNodeData root;
  AXNodeData highlighted1;
  AXNodeData highlighted1_text;
  AXNodeData comment1;
  AXNodeData comment1_text;
  AXNodeData footnote;
  AXNodeData footnote_text;
  AXNodeData definition;
  AXNodeData button;

  root.id = 1;
  highlighted1.id = 2;
  highlighted1_text.id = 3;
  comment1.id = 4;
  comment1_text.id = 5;
  footnote.id = 6;
  footnote_text.id = 7;
  definition.id = 8;
  button.id = 9;

  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  root.child_ids = {highlighted1.id, comment1.id, footnote.id, definition.id,
                    button.id};

  highlighted1.role = ax::mojom::Role::kMark;
  highlighted1.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds,
                                   {comment1.id, footnote.id, definition.id});
  highlighted1.child_ids = {highlighted1_text.id};

  highlighted1_text.role = ax::mojom::Role::kStaticText;
  highlighted1_text.SetName("highlighted 1");

  comment1.role = ax::mojom::Role::kComment;
  comment1.child_ids = {comment1_text.id};

  comment1_text.role = ax::mojom::Role::kStaticText;
  comment1_text.SetName("comment 1");

  footnote.role = ax::mojom::Role::kDocFootnote;
  footnote.child_ids = {footnote_text.id};

  footnote_text.role = ax::mojom::Role::kStaticText;
  footnote_text.SetName("highligthed 2");

  definition.role = ax::mojom::Role::kDefinition;
  button.role = ax::mojom::Role::kButton;

  Init(root, highlighted1, highlighted1_text, comment1, comment1_text, footnote,
       footnote_text, definition, button);

  ComPtr<IRawElementProviderSimple> highlighted1_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);
  ComPtr<IRawElementProviderSimple> comment1_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[1]);
  ComPtr<IRawElementProviderSimple> footnote_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[2]);
  ComPtr<IRawElementProviderSimple> definition_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[3]);
  ComPtr<IRawElementProviderSimple> button_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[4]);
  ComPtr<IAnnotationProvider> annotation_provider;
  ComPtr<IRawElementProviderSimple> target;
  int annotation_type;
  ScopedBstr name;

  {
    // |highlighted1_node| with Role::kMark should not support
    // IAnnotationProvider, since it does not have a relation back to the
    // target.
    EXPECT_HRESULT_SUCCEEDED(highlighted1_node->GetPatternProvider(
        UIA_AnnotationPatternId, &annotation_provider));
    ASSERT_EQ(nullptr, annotation_provider.Get());
    annotation_provider.Reset();
  }

  {
    // |comment1_node| with Role::kComment should support IAnnotationProvider.
    EXPECT_HRESULT_SUCCEEDED(comment1_node->GetPatternProvider(
        UIA_AnnotationPatternId, &annotation_provider));
    ASSERT_NE(nullptr, annotation_provider.Get());
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeId(&annotation_type));
    EXPECT_EQ(AnnotationType_Comment, annotation_type);
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeName(name.Receive()));
    // For common annotation type such as Comment, we return empty string for
    // the type name, since UIA will provide a default name.
    EXPECT_EQ(nullptr, name.Get());

    // Retrieve the target from |comment1_node| and verify that it is
    // |highlighted1_node|.
    EXPECT_HRESULT_SUCCEEDED(annotation_provider->get_Target(&target));
    EXPECT_EQ(highlighted1_node.Get(), target.Get());
    annotation_provider.Reset();
    target.Reset();
    name.Reset();
  }

  {
    // |footnote_node| with Role::kDocFootnote should support
    // IAnnotationProvider.
    EXPECT_HRESULT_SUCCEEDED(footnote_node->GetPatternProvider(
        UIA_AnnotationPatternId, &annotation_provider));
    ASSERT_NE(nullptr, annotation_provider.Get());
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeId(&annotation_type));
    EXPECT_EQ(AnnotationType_Footnote, annotation_type);
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeName(name.Receive()));
    // For common annotation type such as Footnote, we return empty string for
    // the type name, since UIA will provide a default name.
    EXPECT_EQ(nullptr, name.Get());

    // Retrieve the target from |footnote_node| and verify that it is
    // |highlighted1_node|.
    EXPECT_HRESULT_SUCCEEDED(annotation_provider->get_Target(&target));
    EXPECT_EQ(highlighted1_node.Get(), target.Get());
    annotation_provider.Reset();
    target.Reset();
    name.Reset();
  }

  {
    // |definition_node| with Role::kDefinition should support
    // IAnnotationProvider.
    EXPECT_HRESULT_SUCCEEDED(definition_node->GetPatternProvider(
        UIA_AnnotationPatternId, &annotation_provider));
    ASSERT_NE(nullptr, annotation_provider.Get());
    // AnnotationType for Role::kDefinition is currently not implemented, so map
    // it to AnnotationType_Unknown.
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeId(&annotation_type));
    EXPECT_EQ(AnnotationType_Unknown, annotation_type);
    EXPECT_HRESULT_SUCCEEDED(
        annotation_provider->get_AnnotationTypeName(name.Receive()));
    // Role::kDefinition has AnnotationType_Unknown, we explicitly return the
    // type name and should be "definition".
    EXPECT_STREQ(L"definition", name.Get());

    // Retrieve the target from |definition_node| and verify that it is
    // |highlighted_node|.
    EXPECT_HRESULT_SUCCEEDED(annotation_provider->get_Target(&target));
    EXPECT_EQ(highlighted1_node.Get(), target.Get());
    annotation_provider.Reset();
    target.Reset();
    name.Reset();
  }

  {
    // |button_node| with Role::kButton should not support IAnnotationProvider.
    EXPECT_HRESULT_SUCCEEDED(button_node->GetPatternProvider(
        UIA_AnnotationPatternId, &annotation_provider));
    ASSERT_EQ(nullptr, annotation_provider.Get());
  }
}

TEST_F(AXPlatformNodeWinTest, IAnnotationProviderMultipleTargets) {
  // rootWebArea
  // ++mark detailsIds=comment
  // ++++staticText name='highlighted 1'
  // ++mark detailsIds=comment
  // ++++staticText name="highlighted 2"
  // ++docEndnote detailsIds=comment
  // ++++staticText name="endnote"
  // ++comment
  // ++++staticText name='comment 1'

  AXNodeData root;
  AXNodeData highlighted1;
  AXNodeData highlighted1_text;
  AXNodeData highlighted2;
  AXNodeData highlighted2_text;
  AXNodeData endnote;
  AXNodeData endnote_text;
  AXNodeData comment1;
  AXNodeData comment1_text;

  root.id = 1;
  highlighted1.id = 2;
  highlighted1_text.id = 3;
  highlighted2.id = 4;
  highlighted2_text.id = 5;
  endnote.id = 6;
  endnote_text.id = 7;
  comment1.id = 8;
  comment1_text.id = 9;

  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  root.child_ids = {highlighted1.id, highlighted2.id, endnote.id, comment1.id};

  highlighted1.role = ax::mojom::Role::kMark;
  highlighted1.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds,
                                   {comment1.id});
  highlighted1.child_ids = {highlighted1_text.id};

  highlighted1_text.role = ax::mojom::Role::kStaticText;
  highlighted1_text.SetName("highlighted 1");

  highlighted2.role = ax::mojom::Role::kMark;
  highlighted2.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds,
                                   {comment1.id});
  highlighted2.child_ids = {highlighted2_text.id};

  highlighted2_text.role = ax::mojom::Role::kStaticText;
  highlighted2_text.SetName("highlighted 2");

  endnote.role = ax::mojom::Role::kDocEndnote;
  endnote.AddIntListAttribute(ax::mojom::IntListAttribute::kDetailsIds,
                              {comment1.id});
  endnote.child_ids = {endnote_text.id};

  endnote_text.role = ax::mojom::Role::kStaticText;
  endnote_text.SetName("endnote");

  comment1.role = ax::mojom::Role::kComment;
  comment1.child_ids = {comment1_text.id};

  comment1_text.role = ax::mojom::Role::kStaticText;
  comment1_text.SetName("comment 1");

  Init(root, highlighted1, highlighted1_text, highlighted2, highlighted2_text,
       endnote, endnote_text, comment1, comment1_text);

  ComPtr<IRawElementProviderSimple> highlighted1_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]);
  ComPtr<IRawElementProviderSimple> comment1_node =
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[3]);

  ComPtr<IAnnotationProvider> annotation_provider;
  ComPtr<IRawElementProviderSimple> target;
  int annotation_type;

  // |comment1_node| with Role::kComment should support IAnnotationProvider.
  EXPECT_HRESULT_SUCCEEDED(comment1_node->GetPatternProvider(
      UIA_AnnotationPatternId, &annotation_provider));
  ASSERT_NE(nullptr, annotation_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(
      annotation_provider->get_AnnotationTypeId(&annotation_type));
  EXPECT_EQ(AnnotationType_Comment, annotation_type);

  // |comment1_node| is set up as the annotation for both |highlighted1_node|
  // and |highlighted2_node|, which means it has two targets. Since get_Target
  // only returns one target, verify that it is |highlighted1_node|.
  EXPECT_HRESULT_SUCCEEDED(annotation_provider->get_Target(&target));
  EXPECT_EQ(highlighted1_node.Get(), target.Get());
}

TEST_F(AXPlatformNodeWinTest, ISelectionProviderCanSelectMultipleDefault) {
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ false,
                    /*option_3_is_selected*/ false, {}));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));

  BOOL multiple = TRUE;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->get_CanSelectMultiple(&multiple));
  EXPECT_FALSE(multiple);
}

TEST_F(AXPlatformNodeWinTest, ISelectionProviderCanSelectMultipleTrue) {
  const std::vector<ax::mojom::State> state = {
      ax::mojom::State::kMultiselectable, ax::mojom::State::kFocusable};
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ false,
                    /*option_3_is_selected*/ false,
                    /*additional_state*/ state));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));

  BOOL multiple = FALSE;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->get_CanSelectMultiple(&multiple));
  EXPECT_TRUE(multiple);
}

TEST_F(AXPlatformNodeWinTest, ISelectionProviderIsSelectionRequiredDefault) {
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ false,
                    /*option_3_is_selected*/ false,
                    /*additional_state*/ {}));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));

  BOOL selection_required = TRUE;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->get_IsSelectionRequired(&selection_required));
  EXPECT_FALSE(selection_required);
}

TEST_F(AXPlatformNodeWinTest, ISelectionProviderIsSelectionRequiredTrue) {
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ false,
                    /*option_3_is_selected*/ false,
                    /*additional_state*/ {ax::mojom::State::kRequired}));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));

  BOOL selection_required = FALSE;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->get_IsSelectionRequired(&selection_required));
  EXPECT_TRUE(selection_required);
}

TEST_F(AXPlatformNodeWinTest, ISelectionProviderGetSelectionNoneSelected) {
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ false,
                    /*option_3_is_selected*/ false,
                    /*additional_state*/ {ax::mojom::State::kFocusable}));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));

  base::win::ScopedSafearray selected_items;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->GetSelection(selected_items.Receive()));
  EXPECT_NE(nullptr, selected_items.Get());

  LONG array_lower_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetLBound(selected_items.Get(), 1, &array_lower_bound));
  EXPECT_EQ(0, array_lower_bound);

  LONG array_upper_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetUBound(selected_items.Get(), 1, &array_upper_bound));
  EXPECT_EQ(-1, array_upper_bound);
}

TEST_F(AXPlatformNodeWinTest,
       ISelectionProviderGetSelectionSingleItemSelected) {
  Init(BuildListBox(/*option_1_is_selected*/ false,
                    /*option_2_is_selected*/ true,
                    /*option_3_is_selected*/ false,
                    /*additional_state*/ {ax::mojom::State::kFocusable}));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));
  ComPtr<IRawElementProviderSimple> option2_provider(
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[1]));

  base::win::ScopedSafearray selected_items;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->GetSelection(selected_items.Receive()));
  EXPECT_NE(nullptr, selected_items.Get());

  LONG array_lower_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetLBound(selected_items.Get(), 1, &array_lower_bound));
  EXPECT_EQ(0, array_lower_bound);

  LONG array_upper_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetUBound(selected_items.Get(), 1, &array_upper_bound));
  EXPECT_EQ(0, array_upper_bound);

  IRawElementProviderSimple** array_data;
  EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
      selected_items.Get(), reinterpret_cast<void**>(&array_data)));
  EXPECT_EQ(option2_provider.Get(), array_data[0]);
  EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(selected_items.Get()));
}

TEST_F(AXPlatformNodeWinTest,
       ISelectionProviderGetSelectionMultipleItemsSelected) {
  const std::vector<ax::mojom::State> state = {
      ax::mojom::State::kMultiselectable, ax::mojom::State::kFocusable};
  Init(BuildListBox(/*option_1_is_selected*/ true,
                    /*option_2_is_selected*/ true,
                    /*option_3_is_selected*/ true,
                    /*additional_state*/ state));

  ComPtr<ISelectionProvider> selection_provider(
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot()));
  ComPtr<IRawElementProviderSimple> option1_provider(
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[0]));
  ComPtr<IRawElementProviderSimple> option2_provider(
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[1]));
  ComPtr<IRawElementProviderSimple> option3_provider(
      QueryInterfaceFromNode<IRawElementProviderSimple>(
          GetRoot()->children()[2]));

  base::win::ScopedSafearray selected_items;
  EXPECT_HRESULT_SUCCEEDED(
      selection_provider->GetSelection(selected_items.Receive()));
  EXPECT_NE(nullptr, selected_items.Get());

  LONG array_lower_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetLBound(selected_items.Get(), 1, &array_lower_bound));
  EXPECT_EQ(0, array_lower_bound);

  LONG array_upper_bound;
  EXPECT_HRESULT_SUCCEEDED(
      ::SafeArrayGetUBound(selected_items.Get(), 1, &array_upper_bound));
  EXPECT_EQ(2, array_upper_bound);

  IRawElementProviderSimple** array_data;
  EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData(
      selected_items.Get(), reinterpret_cast<void**>(&array_data)));
  EXPECT_EQ(option1_provider.Get(), array_data[0]);
  EXPECT_EQ(option2_provider.Get(), UNSAFE_TODO(array_data[1]));
  EXPECT_EQ(option3_provider.Get(), UNSAFE_TODO(array_data[2]));

  EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(selected_items.Get()));
}

TEST_F(AXPlatformNodeWinTest, ComputeUIAControlType) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kTable;
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kLayoutTable;
  root.child_ids.push_back(child2.id);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kTextField;
  child3.AddState(ax::mojom::State::kEditable);
  root.child_ids.push_back(child3.id);

  AXNodeData child4;
  child4.id = 5;
  child4.role = ax::mojom::Role::kSearchBox;
  root.child_ids.push_back(child4.id);

  AXNodeData child5;
  child5.id = 6;
  child5.role = ax::mojom::Role::kTitleBar;
  root.child_ids.push_back(child5.id);

  AXNodeData child6;
  child6.id = 7;
  child6.role = ax::mojom::Role::kDialog;
  root.child_ids.push_back(child6.id);

  AXNodeData child7;
  child7.id = 8;
  child7.role = ax::mojom::Role::kGraphicsObject;
  root.child_ids.push_back(child7.id);

  AXNodeData child8;
  child8.id = 9;
  child8.role = ax::mojom::Role::kSplitter;
  root.child_ids.push_back(child8.id);

  AXNodeData child9;
  child9.id = 10;
  child9.role = ax::mojom::Role::kSplitter;
  child9.AddState(ax::mojom::State::kFocusable);
  root.child_ids.push_back(child9.id);

  AXNodeData child10;
  child10.id = 11;
  child10.role = ax::mojom::Role::kLineBreak;
  root.child_ids.push_back(child10.id);

  AXNodeData child11;
  child11.id = 12;
  child11.role = ax::mojom::Role::kMenuItemSeparator;
  root.child_ids.push_back(child11.id);

  Init(root, child1, child2, child3, child4, child5, child6, child7, child8,
       child9, child10, child11);

  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child1.id),
      UIA_ControlTypePropertyId, int{UIA_TableControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child2.id),
      UIA_ControlTypePropertyId, int{UIA_GroupControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child3.id),
      UIA_ControlTypePropertyId, int{UIA_EditControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child4.id),
      UIA_ControlTypePropertyId, int{UIA_EditControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child5.id),
      UIA_ControlTypePropertyId, int{UIA_TitleBarControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child6.id),
      UIA_ControlTypePropertyId, int{UIA_WindowControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child7.id),
      UIA_ControlTypePropertyId, int{UIA_GroupControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child8.id),
      UIA_ControlTypePropertyId, int{UIA_SeparatorControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child9.id),
      UIA_ControlTypePropertyId, int{UIA_ThumbControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child10.id),
      UIA_ControlTypePropertyId, int{UIA_TextControlTypeId});
  EXPECT_UIA_INT_EQ(
      QueryInterfaceFromNodeId<IRawElementProviderSimple>(child11.id),
      UIA_ControlTypePropertyId, int{UIA_SeparatorControlTypeId});
}

TEST_F(AXPlatformNodeWinTest, IsUIAControlForStatusRole) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kStatus
  )HTML"));

  Init(update);

  // Turn on web content mode for the AXTree.
  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  AXNode* status_node = GetRoot()->children()[0];

  ComPtr<IRawElementProviderSimple> status_node_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple>(status_node);
  EXPECT_UIA_BOOL_EQ(status_node_provider, UIA_IsControlElementPropertyId, true);
}

TEST_F(AXPlatformNodeWinTest, UIALandmarkType) {
  auto TestLandmarkType = [this](ax::mojom::Role node_role,
                                 std::optional<LONG> expected_landmark_type,
                                 const std::string& node_name = {}) {
    AXNodeData root_data;
    root_data.id = 1;
    root_data.role = node_role;
    if (!node_name.empty())
      root_data.SetName(node_name);
    Init(root_data);

    ComPtr<IRawElementProviderSimple> root_provider =
        GetRootIRawElementProviderSimple();

    if (expected_landmark_type) {
      EXPECT_UIA_INT_EQ(root_provider, UIA_LandmarkTypePropertyId,
                        expected_landmark_type.value());
    } else {
      EXPECT_UIA_EMPTY(root_provider, UIA_LandmarkTypePropertyId);
    }
  };

  TestLandmarkType(ax::mojom::Role::kBanner, UIA_CustomLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kComplementary, UIA_CustomLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kContentInfo, UIA_CustomLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kFooter, UIA_CustomLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kMain, UIA_MainLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kNavigation, UIA_NavigationLandmarkTypeId);
  TestLandmarkType(ax::mojom::Role::kSearch, UIA_SearchLandmarkTypeId);

  // Only named forms should be exposed as landmarks.
  TestLandmarkType(ax::mojom::Role::kForm, {});
  TestLandmarkType(ax::mojom::Role::kForm, UIA_FormLandmarkTypeId, "name");

  // Only named regions should be exposed as landmarks.
  // Blink handles this, and will not pass a nameless region as a Role::kRegion.
  TestLandmarkType(ax::mojom::Role::kRegion, UIA_CustomLandmarkTypeId, "name");

  TestLandmarkType(ax::mojom::Role::kGroup, {});
  TestLandmarkType(ax::mojom::Role::kHeading, {});
  TestLandmarkType(ax::mojom::Role::kList, {});
  TestLandmarkType(ax::mojom::Role::kTable, {});
}

TEST_F(AXPlatformNodeWinTest, UIALocalizedLandmarkType) {
  auto TestLocalizedLandmarkType =
      [this](ax::mojom::Role node_role,
             const std::wstring& expected_localized_landmark,
             const std::string& node_name = {}) {
        AXNodeData root_data;
        root_data.id = 1;
        root_data.role = node_role;
        if (!node_name.empty())
          root_data.SetName(node_name);
        Init(root_data);

        ComPtr<IRawElementProviderSimple> root_provider =
            GetRootIRawElementProviderSimple();

        if (expected_localized_landmark.empty()) {
          EXPECT_UIA_EMPTY(root_provider, UIA_LocalizedLandmarkTypePropertyId);
        } else {
          EXPECT_UIA_BSTR_EQ(root_provider, UIA_LocalizedLandmarkTypePropertyId,
                             expected_localized_landmark.c_str());
        }
      };

  TestLocalizedLandmarkType(ax::mojom::Role::kBanner, L"banner");
  TestLocalizedLandmarkType(ax::mojom::Role::kComplementary, L"complementary");
  TestLocalizedLandmarkType(ax::mojom::Role::kContentInfo,
                            L"content information");
  TestLocalizedLandmarkType(ax::mojom::Role::kFooter, L"content information");

  // Only named regions should be exposed as landmarks.
  TestLocalizedLandmarkType(ax::mojom::Role::kRegion, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kRegion, L"region", "name");

  TestLocalizedLandmarkType(ax::mojom::Role::kForm, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kGroup, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kHeading, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kList, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kMain, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kNavigation, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kSearch, {});
  TestLocalizedLandmarkType(ax::mojom::Role::kTable, {});
}

TEST_F(AXPlatformNodeWinTest, IRawElementProviderSimple2ShowContextMenu) {
  AXNodeData root_data;
  root_data.id = 1;
  root_data.role = ax::mojom::Role::kRootWebArea;

  AXNodeData element1_data;
  element1_data.id = 2;
  root_data.child_ids.push_back(element1_data.id);

  AXNodeData element2_data;
  element2_data.id = 3;
  root_data.child_ids.push_back(element2_data.id);

  Init(root_data, element1_data, element2_data);

  AXNode* root_node = GetRoot();
  AXNode* element1_node = root_node->children()[0];
  AXNode* element2_node = root_node->children()[1];

  ComPtr<IRawElementProviderSimple2> root_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple2>(root_node);
  ComPtr<IRawElementProviderSimple2> element1_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple2>(element1_node);
  ComPtr<IRawElementProviderSimple2> element2_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple2>(element2_node);

  EXPECT_HRESULT_SUCCEEDED(element1_provider->ShowContextMenu());
  EXPECT_EQ(element1_node, TestAXNodeWrapper::GetNodeFromLastShowContextMenu());
  EXPECT_HRESULT_SUCCEEDED(element2_provider->ShowContextMenu());
  EXPECT_EQ(element2_node, TestAXNodeWrapper::GetNodeFromLastShowContextMenu());
  EXPECT_HRESULT_SUCCEEDED(root_provider->ShowContextMenu());
  EXPECT_EQ(root_node, TestAXNodeWrapper::GetNodeFromLastShowContextMenu());
}

TEST_F(AXPlatformNodeWinTest, UIAErrorHandling) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  Init(root);

  ComPtr<IRawElementProviderSimple> simple_provider =
      GetRootIRawElementProviderSimple();
  ComPtr<IRawElementProviderSimple2> simple2_provider =
      QueryInterfaceFromNode<IRawElementProviderSimple2>(GetRoot());
  ComPtr<IRawElementProviderFragment> fragment_provider =
      GetRootIRawElementProviderFragment();
  ComPtr<IGridItemProvider> grid_item_provider =
      QueryInterfaceFromNode<IGridItemProvider>(GetRoot());
  ComPtr<IGridProvider> grid_provider =
      QueryInterfaceFromNode<IGridProvider>(GetRoot());
  ComPtr<IScrollItemProvider> scroll_item_provider =
      QueryInterfaceFromNode<IScrollItemProvider>(GetRoot());
  ComPtr<IScrollProvider> scroll_provider =
      QueryInterfaceFromNode<IScrollProvider>(GetRoot());
  ComPtr<ISelectionItemProvider> selection_item_provider =
      QueryInterfaceFromNode<ISelectionItemProvider>(GetRoot());
  ComPtr<ISelectionProvider> selection_provider =
      QueryInterfaceFromNode<ISelectionProvider>(GetRoot());
  ComPtr<ITableItemProvider> table_item_provider =
      QueryInterfaceFromNode<ITableItemProvider>(GetRoot());
  ComPtr<ITableProvider> table_provider =
      QueryInterfaceFromNode<ITableProvider>(GetRoot());
  ComPtr<IExpandCollapseProvider> expand_collapse_provider =
      QueryInterfaceFromNode<IExpandCollapseProvider>(GetRoot());
  ComPtr<IToggleProvider> toggle_provider =
      QueryInterfaceFromNode<IToggleProvider>(GetRoot());
  ComPtr<IValueProvider> value_provider =
      QueryInterfaceFromNode<IValueProvider>(GetRoot());
  ComPtr<IRangeValueProvider> range_value_provider =
      QueryInterfaceFromNode<IRangeValueProvider>(GetRoot());
  ComPtr<IWindowProvider> window_provider =
      QueryInterfaceFromNode<IWindowProvider>(GetRoot());

  // Create an empty tree.
  SetTree(std::make_unique<AXTree>());

  // IGridItemProvider
  int int_result = 0;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_item_provider->get_Column(&int_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_item_provider->get_ColumnSpan(&int_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_item_provider->get_Row(&int_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_item_provider->get_RowSpan(&int_result));

  // IExpandCollapseProvider
  ExpandCollapseState expand_collapse_state;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            expand_collapse_provider->Collapse());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            expand_collapse_provider->Expand());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            expand_collapse_provider->get_ExpandCollapseState(
                &expand_collapse_state));

  // IGridProvider
  ComPtr<IRawElementProviderSimple> temp_simple_provider;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_provider->GetItem(0, 0, &temp_simple_provider));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_provider->get_RowCount(&int_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            grid_provider->get_ColumnCount(&int_result));

  // IScrollItemProvider
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_item_provider->ScrollIntoView());

  // IScrollProvider
  BOOL bool_result = TRUE;
  double double_result = 3.14;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->SetScrollPercent(0, 0));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_HorizontallyScrollable(&bool_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_HorizontalScrollPercent(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_HorizontalViewSize(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_VerticallyScrollable(&bool_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_VerticalScrollPercent(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            scroll_provider->get_VerticalViewSize(&double_result));

  // ISelectionItemProvider
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_item_provider->AddToSelection());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_item_provider->RemoveFromSelection());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_item_provider->Select());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_item_provider->get_IsSelected(&bool_result));
  EXPECT_EQ(
      static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
      selection_item_provider->get_SelectionContainer(&temp_simple_provider));

  // ISelectionProvider
  base::win::ScopedSafearray array_result;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_provider->GetSelection(array_result.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_provider->get_CanSelectMultiple(&bool_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            selection_provider->get_IsSelectionRequired(&bool_result));

  // ITableItemProvider
  RowOrColumnMajor row_or_column_major_result;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            table_item_provider->GetColumnHeaderItems(array_result.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            table_item_provider->GetRowHeaderItems(array_result.Receive()));

  // ITableProvider
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            table_provider->GetColumnHeaders(array_result.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            table_provider->GetRowHeaders(array_result.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            table_provider->get_RowOrColumnMajor(&row_or_column_major_result));

  // IRawElementProviderSimple
  ScopedVariant variant;
  ComPtr<IUnknown> unknown;
  ComPtr<IRawElementProviderSimple> host_provider;
  ProviderOptions options;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            simple_provider->GetPatternProvider(UIA_WindowPatternId, &unknown));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            simple_provider->GetPropertyValue(UIA_FrameworkIdPropertyId,
                                              variant.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            simple_provider->get_ProviderOptions(&options));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            simple_provider->get_HostRawElementProvider(&host_provider));

  // IRawElementProviderSimple2
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            simple2_provider->ShowContextMenu());

  // IRawElementProviderFragment
  ComPtr<IRawElementProviderFragment> navigated_to_fragment;
  base::win::ScopedSafearray safearray;
  UiaRect bounding_rectangle;
  ComPtr<IRawElementProviderFragmentRoot> fragment_root;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->Navigate(NavigateDirection_Parent,
                                        &navigated_to_fragment));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->GetRuntimeId(safearray.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->get_BoundingRectangle(&bounding_rectangle));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->GetEmbeddedFragmentRoots(safearray.Receive()));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->get_FragmentRoot(&fragment_root));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            fragment_provider->SetFocus());

  // IValueProvider
  ScopedBstr bstr_value;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            value_provider->SetValue(L"3.14"));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            value_provider->get_Value(bstr_value.Receive()));

  // IRangeValueProvider
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->SetValue(double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->get_LargeChange(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->get_Maximum(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->get_Minimum(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->get_SmallChange(&double_result));
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            range_value_provider->get_Value(&double_result));

  // IToggleProvider
  ToggleState toggle_state;
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            toggle_provider->Toggle());
  EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            toggle_provider->get_ToggleState(&toggle_state));

  // IWindowProvider
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->SetVisualState(
                WindowVisualState::WindowVisualState_Normal));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->Close());
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->WaitForInputIdle(0, nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_CanMaximize(nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_CanMinimize(nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_IsModal(nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_WindowVisualState(nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_WindowInteractionState(nullptr));
  ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
            window_provider->get_IsTopmost(nullptr));
}

TEST_F(AXPlatformNodeWinTest, GetPatternProviderSupportedPatterns) {
  constexpr AXNodeID root_id = 1;
  constexpr AXNodeID text_field_with_combo_box_id = 2;
  constexpr AXNodeID meter_id = 3;
  constexpr AXNodeID group_with_scroll_id = 4;
  constexpr AXNodeID checkbox_id = 5;
  constexpr AXNodeID link_id = 6;
  constexpr AXNodeID table_without_header_id = 7;
  constexpr AXNodeID table_without_header_cell_id = 8;
  constexpr AXNodeID table_with_header_id = 9;
  constexpr AXNodeID table_with_header_row_1_id = 10;
  constexpr AXNodeID table_with_header_column_header_id = 11;
  constexpr AXNodeID table_with_header_row_2_id = 12;
  constexpr AXNodeID table_with_header_cell_id = 13;
  constexpr AXNodeID grid_without_header_id = 14;
  constexpr AXNodeID grid_without_header_cell_id = 15;
  constexpr AXNodeID grid_with_header_id = 16;
  constexpr AXNodeID grid_with_header_row_1_id = 17;
  constexpr AXNodeID grid_with_header_column_header_id = 18;
  constexpr AXNodeID grid_with_header_row_2_id = 19;
  constexpr AXNodeID grid_with_header_cell_id = 20;
  constexpr AXNodeID button_with_value = 21;
  constexpr AXNodeID button_without_value = 22;
  constexpr AXNodeID tree_item_checked_id = 23;
  constexpr AXNodeID tree_item_unchecked_id = 24;
  constexpr AXNodeID tree_item_id = 25;
  constexpr AXNodeID tab_id = 26;
  constexpr AXNodeID toggle_button_with_popup_id = 27;
  constexpr AXNodeID generic_container_id = 28;
  constexpr AXNodeID row_group_id = 29;
  constexpr AXNodeID row_id = 30;
  constexpr AXNodeID cell_id = 31;
  constexpr AXNodeID button_with_expanded_state = 32;
  constexpr AXNodeID button_without_expanded_state = 33;
  constexpr AXNodeID list_grid_id = 34;
  constexpr AXNodeID list_grid_row_1_id = 35;
  constexpr AXNodeID list_grid_column_header_id = 36;
  constexpr AXNodeID list_grid_row_2_id = 37;
  constexpr AXNodeID list_grid_cell_id = 38;

  AXTreeUpdate update;
  update.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
  update.has_tree_data = true;
  update.root_id = root_id;
  update.nodes.resize(38);
  update.nodes[0].id = root_id;
  update.nodes[0].role = ax::mojom::Role::kRootWebArea;
  update.nodes[0].child_ids = {text_field_with_combo_box_id,
                               meter_id,
                               group_with_scroll_id,
                               checkbox_id,
                               link_id,
                               table_without_header_id,
                               table_with_header_id,
                               grid_without_header_id,
                               grid_with_header_id,
                               button_with_value,
                               button_without_value,
                               tree_item_checked_id,
                               tree_item_unchecked_id,
                               tree_item_id,
                               tab_id,
                               toggle_button_with_popup_id,
                               generic_container_id,
                               button_with_expanded_state,
                               button_without_expanded_state,
                               list_grid_id};
  update.nodes[1].id = text_field_with_combo_box_id;
  update.nodes[1].role = ax::mojom::Role::kTextFieldWithComboBox;
  update.nodes[1].AddState(ax::mojom::State::kEditable);
  update.nodes[2].id = meter_id;
  update.nodes[2].role = ax::mojom::Role::kMeter;
  update.nodes[3].id = group_with_scroll_id;
  update.nodes[3].role = ax::mojom::Role::kGroup;
  update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin, 10);
  update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax, 10);
  update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kScrollX, 10);
  update.nodes[4].id = checkbox_id;
  update.nodes[4].role = ax::mojom::Role::kCheckBox;
  update.nodes[5].id = link_id;
  update.nodes[5].role = ax::mojom::Role::kLink;
  update.nodes[6].id = table_without_header_id;
  update.nodes[6].role = ax::mojom::Role::kTable;
  update.nodes[6].child_ids = {table_without_header_cell_id};
  update.nodes[7].id = table_without_header_cell_id;
  update.nodes[7].role = ax::mojom::Role::kCell;
  update.nodes[8].id = table_with_header_id;
  update.nodes[8].role = ax::mojom::Role::kTable;
  update.nodes[8].child_ids = {table_with_header_row_1_id,
                               table_with_header_row_2_id};
  update.nodes[9].id = table_with_header_row_1_id;
  update.nodes[9].role = ax::mojom::Role::kRow;
  update.nodes[9].child_ids = {table_with_header_column_header_id};
  update.nodes[10].id = table_with_header_column_header_id;
  update.nodes[10].role = ax::mojom::Role::kColumnHeader;
  update.nodes[11].id = table_with_header_row_2_id;
  update.nodes[11].role = ax::mojom::Role::kRow;
  update.nodes[11].child_ids = {table_with_header_cell_id};
  update.nodes[12].id = table_with_header_cell_id;
  update.nodes[12].role = ax::mojom::Role::kCell;
  update.nodes[13].id = grid_without_header_id;
  update.nodes[13].role = ax::mojom::Role::kGrid;
  update.nodes[13].child_ids = {grid_without_header_cell_id};
  update.nodes[14].id = grid_without_header_cell_id;
  update.nodes[14].role = ax::mojom::Role::kGridCell;
  update.nodes[14].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  update.nodes[15].id = grid_with_header_id;
  update.nodes[15].role = ax::mojom::Role::kGrid;
  update.nodes[15].child_ids = {grid_with_header_row_1_id,
                                grid_with_header_row_2_id};
  update.nodes[16].id = grid_with_header_row_1_id;
  update.nodes[16].role = ax::mojom::Role::kRow;
  update.nodes[16].child_ids = {grid_with_header_column_header_id};
  update.nodes[17].id = grid_with_header_column_header_id;
  update.nodes[17].role = ax::mojom::Role::kColumnHeader;
  update.nodes[17].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  update.nodes[18].id = grid_with_header_row_2_id;
  update.nodes[18].role = ax::mojom::Role::kRow;
  update.nodes[18].child_ids = {grid_with_header_cell_id};
  update.nodes[19].id = grid_with_header_cell_id;
  update.nodes[19].role = ax::mojom::Role::kGridCell;
  update.nodes[19].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  update.nodes[20].id = button_with_value;
  update.nodes[20].role = ax::mojom::Role::kButton;
  update.nodes[20].AddStringAttribute(ax::mojom::StringAttribute::kValue,
                                      "test");
  update.nodes[21].id = button_without_value;
  update.nodes[21].role = ax::mojom::Role::kButton;
  update.nodes[21].SetName("button");
  update.nodes[22].id = tree_item_checked_id;
  update.nodes[22].role = ax::mojom::Role::kTreeItem;
  update.nodes[22].AddIntAttribute(
      ax::mojom::IntAttribute::kCheckedState,
      static_cast<int>(ax::mojom::CheckedState::kTrue));
  update.nodes[23].id = tree_item_unchecked_id;
  update.nodes[23].role = ax::mojom::Role::kTreeItem;
  update.nodes[23].AddIntAttribute(
      ax::mojom::IntAttribute::kCheckedState,
      static_cast<int>(ax::mojom::CheckedState::kFalse));
  update.nodes[24].id = tree_item_id;
  update.nodes[24].role = ax::mojom::Role::kTreeItem;
  update.nodes[25].id = tab_id;
  update.nodes[25].role = ax::mojom::Role::kTab;
  update.nodes[26].id = toggle_button_with_popup_id;
  update.nodes[26].role = ax::mojom::Role::kToggleButton;
  update.nodes[26].AddIntAttribute(
      ax::mojom::IntAttribute::kHasPopup,
      static_cast<int32_t>(ax::mojom::HasPopup::kTrue));
  update.nodes[27].role = ax::mojom::Role::kGenericContainer;
  update.nodes[27].id = generic_container_id;
  update.nodes[27].child_ids = {row_group_id};
  update.nodes[28].role = ax::mojom::Role::kRowGroup;
  update.nodes[28].id = row_group_id;
  update.nodes[28].child_ids = {row_id};
  update.nodes[29].role = ax::mojom::Role::kRow;
  update.nodes[29].id = row_id;
  update.nodes[29].child_ids = {cell_id};
  update.nodes[30].role = ax::mojom::Role::kCell;
  update.nodes[30].id = cell_id;
  update.nodes[31].id = button_with_expanded_state;
  update.nodes[31].role = ax::mojom::Role::kButton;
  update.nodes[31].AddState(ax::mojom::State::kExpanded);
  update.nodes[31].AddIntAttribute(
      ax::mojom::IntAttribute::kCheckedState,
      static_cast<int>(ax::mojom::CheckedState::kTrue));
  update.nodes[32].id = button_without_expanded_state;
  update.nodes[32].role = ax::mojom::Role::kButton;
  update.nodes[32].AddIntAttribute(
      ax::mojom::IntAttribute::kCheckedState,
      static_cast<int>(ax::mojom::CheckedState::kTrue));
  update.nodes[33].id = list_grid_id;
  update.nodes[33].role = ax::mojom::Role::kListGrid;
  update.nodes[33].child_ids = {list_grid_row_1_id, list_grid_row_2_id};
  update.nodes[34].id = list_grid_row_1_id;
  update.nodes[34].role = ax::mojom::Role::kRow;
  update.nodes[34].child_ids = {list_grid_column_header_id};
  update.nodes[35].id = list_grid_column_header_id;
  update.nodes[35].role = ax::mojom::Role::kColumnHeader;
  update.nodes[35].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  update.nodes[36].id = list_grid_row_2_id;
  update.nodes[36].role = ax::mojom::Role::kRow;
  update.nodes[36].child_ids = {list_grid_cell_id};
  update.nodes[37].id = list_grid_cell_id;
  update.nodes[37].role = ax::mojom::Role::kGridCell;
  update.nodes[37].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);

  Init(update);

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextEditPatternId,
                        UIA_TextPatternId, UIA_ValuePatternId}),
            GetSupportedPatternsFromNodeId(root_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_ExpandCollapsePatternId, UIA_TextChildPatternId,
                        UIA_TextEditPatternId, UIA_TextPatternId}),
            GetSupportedPatternsFromNodeId(text_field_with_combo_box_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_RangeValuePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(meter_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ScrollPatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(group_with_scroll_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_TextChildPatternId, UIA_TogglePatternId}),
            GetSupportedPatternsFromNodeId(checkbox_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_InvokePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(link_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridPatternId,
                        UIA_TablePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(table_without_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(table_without_header_cell_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridPatternId,
                        UIA_TablePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(table_with_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(table_with_header_column_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(table_with_header_cell_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_SelectionPatternId, UIA_GridPatternId,
                        UIA_TablePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(grid_without_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId,
                        UIA_SelectionItemPatternId}),
            GetSupportedPatternsFromNodeId(grid_without_header_cell_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ValuePatternId,
                        UIA_SelectionPatternId, UIA_GridPatternId,
                        UIA_TablePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(grid_with_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId,
                        UIA_SelectionItemPatternId}),
            GetSupportedPatternsFromNodeId(grid_with_header_column_header_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId, UIA_TextChildPatternId,
                        UIA_SelectionItemPatternId}),
            GetSupportedPatternsFromNodeId(grid_with_header_cell_id));

  EXPECT_EQ(PatternSet({UIA_ValuePatternId, UIA_ScrollItemPatternId,
                        UIA_InvokePatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(button_with_value));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_InvokePatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(button_without_value));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                        UIA_TextChildPatternId, UIA_TogglePatternId}),
            GetSupportedPatternsFromNodeId(tree_item_checked_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                        UIA_TextChildPatternId, UIA_TogglePatternId}),
            GetSupportedPatternsFromNodeId(tree_item_checked_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(tree_item_id));
  EXPECT_EQ(PatternSet({UIA_SelectionItemPatternId, UIA_ScrollItemPatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(tab_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(toggle_button_with_popup_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TableItemPatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(cell_id));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                        UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(button_with_expanded_state));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId,
                        UIA_TogglePatternId}),
            GetSupportedPatternsFromNodeId(button_without_expanded_state));

  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId,
                        UIA_SelectionPatternId, UIA_GridPatternId,
                        UIA_TablePatternId}),
            GetSupportedPatternsFromNodeId(list_grid_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(list_grid_row_1_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId,
                        UIA_SelectionItemPatternId, UIA_GridItemPatternId,
                        UIA_TableItemPatternId}),
            GetSupportedPatternsFromNodeId(list_grid_column_header_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId}),
            GetSupportedPatternsFromNodeId(list_grid_row_2_id));
  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TextChildPatternId,
                        UIA_ValuePatternId, UIA_SelectionItemPatternId,
                        UIA_GridItemPatternId, UIA_TableItemPatternId}),
            GetSupportedPatternsFromNodeId(list_grid_cell_id));
}

TEST_F(AXPlatformNodeWinTest, GetPatternProviderExpandCollapsePattern) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData list_box;
  AXNodeData list_item;
  AXNodeData menu_item;
  AXNodeData menu_list_option;
  AXNodeData tree_item;
  AXNodeData combo_box_grouping;
  AXNodeData combo_box_menu_button;
  AXNodeData disclosure_triangle;
  AXNodeData text_field_with_combo_box;

  list_box.id = 2;
  list_item.id = 3;
  menu_item.id = 4;
  menu_list_option.id = 5;
  tree_item.id = 6;
  combo_box_grouping.id = 7;
  combo_box_menu_button.id = 8;
  disclosure_triangle.id = 9;
  text_field_with_combo_box.id = 10;

  root.child_ids.push_back(list_box.id);
  root.child_ids.push_back(list_item.id);
  root.child_ids.push_back(menu_item.id);
  root.child_ids.push_back(menu_list_option.id);
  root.child_ids.push_back(tree_item.id);
  root.child_ids.push_back(combo_box_grouping.id);
  root.child_ids.push_back(combo_box_menu_button.id);
  root.child_ids.push_back(disclosure_triangle.id);
  root.child_ids.push_back(text_field_with_combo_box.id);

  // list_box HasPopup set to false, does not support expand collapse.
  list_box.role = ax::mojom::Role::kListBoxOption;
  list_box.SetHasPopup(ax::mojom::HasPopup::kFalse);

  // list_item HasPopup set to true, supports expand collapse.
  list_item.role = ax::mojom::Role::kListItem;
  list_item.SetHasPopup(ax::mojom::HasPopup::kTrue);

  // menu_item has expanded state and supports expand collapse.
  menu_item.role = ax::mojom::Role::kMenuItem;
  menu_item.AddState(ax::mojom::State::kExpanded);

  // menu_list_option has collapsed state and supports expand collapse.
  menu_list_option.role = ax::mojom::Role::kMenuListOption;
  menu_list_option.AddState(ax::mojom::State::kCollapsed);

  // These roles by default supports expand collapse.
  tree_item.role = ax::mojom::Role::kTreeItem;
  combo_box_grouping.role = ax::mojom::Role::kComboBoxGrouping;
  combo_box_menu_button.role = ax::mojom::Role::kComboBoxMenuButton;
  disclosure_triangle.role = ax::mojom::Role::kDisclosureTriangle;
  text_field_with_combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;

  Init(root, list_box, list_item, menu_item, menu_list_option, tree_item,
       combo_box_grouping, combo_box_menu_button, disclosure_triangle,
       text_field_with_combo_box);

  // list_box HasPopup set to false, does not support expand collapse.
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetIRawElementProviderSimpleFromChildIndex(0);
  ComPtr<IExpandCollapseProvider> expandcollapse_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_EQ(nullptr, expandcollapse_provider.Get());

  // list_item HasPopup set to true, supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(1);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // menu_item has expanded state and supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(2);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // menu_list_option has collapsed state and supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(3);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // tree_item by default supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(4);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // combo_box_grouping by default supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(5);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // combo_box_menu_button by default supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(6);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // disclosure_triangle by default supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(7);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());

  // text_field_with_combo_box by default supports expand collapse.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(8);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  EXPECT_NE(nullptr, expandcollapse_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, GetPatternProviderInvokePattern) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData link;
  AXNodeData generic_container;
  AXNodeData combo_box_grouping;
  AXNodeData check_box;

  link.id = 2;
  generic_container.id = 3;
  combo_box_grouping.id = 4;
  check_box.id = 5;

  root.child_ids.push_back(link.id);
  root.child_ids.push_back(generic_container.id);
  root.child_ids.push_back(combo_box_grouping.id);
  root.child_ids.push_back(check_box.id);

  // Role link is clickable and neither supports expand collapse nor supports
  // toggle. It should support invoke pattern.
  link.role = ax::mojom::Role::kLink;

  // Role generic container is not clickable. It should not support invoke
  // pattern.
  generic_container.role = ax::mojom::Role::kGenericContainer;

  // Role combo box grouping supports expand collapse. It should not support
  // invoke pattern.
  combo_box_grouping.role = ax::mojom::Role::kComboBoxGrouping;

  // Role check box supports toggle. It should not support invoke pattern.
  check_box.role = ax::mojom::Role::kCheckBox;

  Init(root, link, generic_container, combo_box_grouping, check_box);

  // Role link is clickable and neither supports expand collapse nor supports
  // toggle. It should support invoke pattern.
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetIRawElementProviderSimpleFromChildIndex(0);
  ComPtr<IInvokeProvider> invoke_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_NE(nullptr, invoke_provider.Get());

  // Role generic container is not clickable. It should not support invoke
  // pattern.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(1);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_EQ(nullptr, invoke_provider.Get());

  // Role combo box grouping supports expand collapse. It should not support
  // invoke pattern.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(2);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_EQ(nullptr, invoke_provider.Get());

  // Role check box supports toggle. It should not support invoke pattern.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(3);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_EQ(nullptr, invoke_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, GetPatternProviderGridCellValuePattern) {
  // ++1 kRootWebArea
  // ++++2 kGrid
  // ++++++3 kRow
  // ++++++++4 kCell
  // +++++++++++5 kStaticText
  // ++++++++6 kCell
  // +++++++++++7 kStaticText
  // ++++++++8 kCell
  // +++++++++++9 kStaticText

  AXNodeData root_1;
  AXNodeData table_2;
  AXNodeData row_3;
  AXNodeData cell_4;
  AXNodeData st_5;
  AXNodeData cell_6;
  AXNodeData st_7;
  AXNodeData cell_8;
  AXNodeData st_9;

  root_1.id = 1;
  table_2.id = 2;
  row_3.id = 3;
  cell_4.id = 4;
  st_5.id = 5;
  cell_6.id = 6;
  st_7.id = 7;
  cell_8.id = 8;
  st_9.id = 9;

  root_1.role = ax::mojom::Role::kRootWebArea;
  root_1.child_ids = {table_2.id};

  table_2.role = ax::mojom::Role::kGrid;
  table_2.child_ids = {row_3.id};

  row_3.role = ax::mojom::Role::kRow;
  row_3.child_ids = {cell_4.id, cell_6.id, cell_8.id};

  cell_4.role = ax::mojom::Role::kCell;
  cell_4.AddState(ax::mojom::State::kEditable);
  cell_4.child_ids = {st_5.id};

  cell_6.role = ax::mojom::Role::kCell;
  cell_6.AddState(ax::mojom::State::kEditable);
  cell_6.AddState(ax::mojom::State::kRichlyEditable);
  cell_6.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                         static_cast<int>(ax::mojom::Restriction::kReadOnly));
  cell_6.child_ids = {st_7.id};

  cell_8.role = ax::mojom::Role::kCell;
  cell_8.child_ids = {st_9.id};

  Init(root_1, table_2, row_3, cell_4, st_5, cell_6, st_7, cell_8, st_9);

  AXPlatformNodeWinTest::PatternSet cell_4_patterns =
      GetSupportedPatternsFromNodeId(cell_4.id);
  AXPlatformNodeWinTest::PatternSet cell_6_patterns =
      GetSupportedPatternsFromNodeId(cell_6.id);
  AXPlatformNodeWinTest::PatternSet cell_8_patterns =
      GetSupportedPatternsFromNodeId(cell_8.id);

  // Since the first two gridcell nodes are editable, we should find the
  // ValuePattern exposed in those cells.
  EXPECT_NE(cell_4_patterns.find(UIA_ValuePatternId), cell_4_patterns.end());
  EXPECT_NE(cell_6_patterns.find(UIA_ValuePatternId), cell_6_patterns.end());
  // Since the last gridcell node is not editable, we should not find
  // ValuePattern exposed for it.
  EXPECT_EQ(cell_8_patterns.find(UIA_ValuePatternId), cell_8_patterns.end());
}

TEST_F(AXPlatformNodeWinTest, IExpandCollapsePatternProviderAction) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData combo_box_grouping_has_popup;
  AXNodeData combo_box_grouping_expanded;
  AXNodeData combo_box_grouping_collapsed;
  AXNodeData combo_box_grouping_disabled;
  AXNodeData button_has_popup_menu;
  AXNodeData button_has_popup_menu_pressed;
  AXNodeData button_has_popup_true;
  AXNodeData generic_container_has_popup_menu;

  combo_box_grouping_has_popup.id = 2;
  combo_box_grouping_expanded.id = 3;
  combo_box_grouping_collapsed.id = 4;
  combo_box_grouping_disabled.id = 5;
  button_has_popup_menu.id = 6;
  button_has_popup_menu_pressed.id = 7;
  button_has_popup_true.id = 8;
  generic_container_has_popup_menu.id = 9;

  root.child_ids = {
      combo_box_grouping_has_popup.id, combo_box_grouping_expanded.id,
      combo_box_grouping_collapsed.id, combo_box_grouping_disabled.id,
      button_has_popup_menu.id,        button_has_popup_menu_pressed.id,
      button_has_popup_true.id,        generic_container_has_popup_menu.id};

  // combo_box_grouping HasPopup set to true, can collapse, can expand.
  // state is ExpandCollapseState_LeafNode.
  combo_box_grouping_has_popup.role = ax::mojom::Role::kComboBoxGrouping;
  combo_box_grouping_has_popup.SetHasPopup(ax::mojom::HasPopup::kTrue);

  // combo_box_grouping Expanded set, can collapse, cannot expand.
  // state is ExpandCollapseState_Expanded.
  combo_box_grouping_expanded.role = ax::mojom::Role::kComboBoxGrouping;
  combo_box_grouping_expanded.AddState(ax::mojom::State::kExpanded);

  // combo_box_grouping Collapsed set, can expand, cannot collapse.
  // state is ExpandCollapseState_Collapsed.
  combo_box_grouping_collapsed.role = ax::mojom::Role::kComboBoxGrouping;
  combo_box_grouping_collapsed.AddState(ax::mojom::State::kCollapsed);

  // combo_box_grouping is disabled, can neither expand nor collapse.
  // state is ExpandCollapseState_LeafNode.
  combo_box_grouping_disabled.role = ax::mojom::Role::kComboBoxGrouping;
  combo_box_grouping_disabled.SetRestriction(ax::mojom::Restriction::kDisabled);

  // button_has_popup_menu HasPopup set to kMenu and is not STATE_PRESSED.
  // state is ExpandCollapseState_Collapsed.
  button_has_popup_menu.role = ax::mojom::Role::kButton;
  button_has_popup_menu.SetHasPopup(ax::mojom::HasPopup::kMenu);

  // button_has_popup_menu_pressed HasPopup set to kMenu and is STATE_PRESSED.
  // state is ExpandCollapseState_Expanded.
  button_has_popup_menu_pressed.role = ax::mojom::Role::kButton;
  button_has_popup_menu_pressed.SetHasPopup(ax::mojom::HasPopup::kMenu);
  button_has_popup_menu_pressed.SetCheckedState(ax::mojom::CheckedState::kTrue);

  // button_has_popup_true HasPopup set to true but is not a menu.
  // state is ExpandCollapseState_LeafNode.
  button_has_popup_true.role = ax::mojom::Role::kButton;
  button_has_popup_true.SetHasPopup(ax::mojom::HasPopup::kTrue);

  // generic_container_has_popup_menu HasPopup set to menu but with no expand
  // state set.
  // state is ExpandCollapseState_LeafNode.
  generic_container_has_popup_menu.role = ax::mojom::Role::kGenericContainer;
  generic_container_has_popup_menu.SetHasPopup(ax::mojom::HasPopup::kMenu);

  Init(root, combo_box_grouping_has_popup, combo_box_grouping_disabled,
       combo_box_grouping_expanded, combo_box_grouping_collapsed,
       combo_box_grouping_disabled, button_has_popup_menu,
       button_has_popup_menu_pressed, button_has_popup_true,
       generic_container_has_popup_menu);

  // combo_box_grouping HasPopup set to true, can collapse, can expand.
  // state is ExpandCollapseState_LeafNode.
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetIRawElementProviderSimpleFromChildIndex(0);
  ComPtr<IExpandCollapseProvider> expandcollapse_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(expandcollapse_provider->Collapse());
  EXPECT_HRESULT_SUCCEEDED(expandcollapse_provider->Expand());
  ExpandCollapseState state;
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_LeafNode, state);

  // combo_box_grouping Expanded set, can collapse, cannot expand.
  // state is ExpandCollapseState_Expanded.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(1);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(expandcollapse_provider->Collapse());
  EXPECT_HRESULT_FAILED(expandcollapse_provider->Expand());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_Expanded, state);

  // combo_box_grouping Collapsed set, can expand, cannot collapse.
  // state is ExpandCollapseState_Collapsed.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(2);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_FAILED(expandcollapse_provider->Collapse());
  EXPECT_HRESULT_SUCCEEDED(expandcollapse_provider->Expand());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_Collapsed, state);

  // combo_box_grouping is disabled, can neither expand nor collapse.
  // state is ExpandCollapseState_LeafNode.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(3);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_FAILED(expandcollapse_provider->Collapse());
  EXPECT_HRESULT_FAILED(expandcollapse_provider->Expand());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_LeafNode, state);

  // button_has_popup_menu HasPopup set to kMenu and is not STATE_PRESSED.
  // state is ExpandCollapseState_Collapsed.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(4);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_Collapsed, state);

  // button_has_popup_menu_pressed HasPopup set to kMenu and is STATE_PRESSED.
  // state is ExpandCollapseState_Expanded.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(5);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_Expanded, state);

  // button_has_popup_true HasPopup set to true but is not a menu.
  // state is ExpandCollapseState_LeafNode.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(6);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_LeafNode, state);

  // generic_container_has_popup_menu HasPopup set to menu but with no expand
  // state set.
  // state is ExpandCollapseState_LeafNode.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(7);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_ExpandCollapsePatternId, &expandcollapse_provider));
  ASSERT_NE(nullptr, expandcollapse_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(
      expandcollapse_provider->get_ExpandCollapseState(&state));
  EXPECT_EQ(ExpandCollapseState_LeafNode, state);
}

// If a menu button has an expanded or collapsed state, we want to make sure we
// let that be the deciding factor for `get_ExpandCollapseState()`. Otherwise,
// check if the button is pressed.
TEST_F(AXPlatformNodeWinTest,
       IExpandCollapseProviderExpandedCollapsedStateMenuButton) {
  // Declare a vector that will hold our expected Expanded/Collapsed state for
  // each menu button we'll declare below.
  std::vector<ExpandCollapseState> node_expected_state;

  // Our root. Will host all menu buttons below.
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids = {2, 3, 4, 5, 6, 7};

  // If a pressed menu button is explicitly marked as `kExpanded`, its
  // Expanded/Collapsed state should be expanded.
  AXNodeData expanded_pressed_button;
  expanded_pressed_button.id = 2;
  expanded_pressed_button.role = ax::mojom::Role::kButton;
  expanded_pressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  expanded_pressed_button.SetCheckedState(ax::mojom::CheckedState::kTrue);
  expanded_pressed_button.AddState(ax::mojom::State::kExpanded);
  node_expected_state.push_back(ExpandCollapseState_Expanded);

  // If an unpressed menu button is explicitly marked as `kExpanded`, its
  // Expanded/Collapsed state should be expanded, because this value takes
  // precedence over the fact that the button isn't pressed.
  AXNodeData expanded_unpressed_button;
  expanded_unpressed_button.id = 3;
  expanded_unpressed_button.role = ax::mojom::Role::kButton;
  expanded_unpressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  expanded_unpressed_button.SetCheckedState(ax::mojom::CheckedState::kFalse);
  expanded_unpressed_button.AddState(ax::mojom::State::kExpanded);
  node_expected_state.push_back(ExpandCollapseState_Expanded);

  // If a pressed menu button is explicitly marked as `kCollapsed`, its
  // Expanded/Collapsed state should be collapsed, because this value takes
  // precedence over the fact that the button is pressed.
  AXNodeData collapsed_pressed_button;
  collapsed_pressed_button.id = 4;
  collapsed_pressed_button.role = ax::mojom::Role::kButton;
  collapsed_pressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  collapsed_pressed_button.SetCheckedState(ax::mojom::CheckedState::kTrue);
  collapsed_pressed_button.AddState(ax::mojom::State::kCollapsed);
  node_expected_state.push_back(ExpandCollapseState_Collapsed);

  // If an unpressed menu button is explicitly marked as `kCollapsed`, its
  // Expanded/Collapsed state should be collapsed.
  AXNodeData collapsed_unpressed_button;
  collapsed_unpressed_button.id = 5;
  collapsed_unpressed_button.role = ax::mojom::Role::kButton;
  collapsed_unpressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  collapsed_unpressed_button.SetCheckedState(ax::mojom::CheckedState::kFalse);
  collapsed_unpressed_button.AddState(ax::mojom::State::kCollapsed);
  node_expected_state.push_back(ExpandCollapseState_Collapsed);

  // If a pressed menu button has no explicit Expanded/Collapsed state, its
  // Expanded/Collapsed state should be expanded (since it's pressed).
  AXNodeData pressed_button;
  pressed_button.id = 6;
  pressed_button.role = ax::mojom::Role::kButton;
  pressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  pressed_button.SetCheckedState(ax::mojom::CheckedState::kTrue);
  node_expected_state.push_back(ExpandCollapseState_Expanded);

  // If an unpressed button has no explicit Expanded/Collapsed state, its
  // Expanded/Collapsed state should be collapsed (since it's unpressed).
  AXNodeData unpressed_button;
  unpressed_button.id = 7;
  unpressed_button.role = ax::mojom::Role::kButton;
  unpressed_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
  unpressed_button.SetCheckedState(ax::mojom::CheckedState::kFalse);
  node_expected_state.push_back(ExpandCollapseState_Collapsed);

  // Initialize all our buttons as children of our root.
  Init(root, expanded_pressed_button, expanded_unpressed_button,
       collapsed_pressed_button, collapsed_unpressed_button, pressed_button,
       unpressed_button);

  // Declare all variables we'll use in the below loop.
  ComPtr<IRawElementProviderSimple> raw_element_provider;
  ComPtr<IExpandCollapseProvider> expandcollapse_provider;
  ExpandCollapseState state;

  // Loop through all child buttons and verify that we get the expected
  // Expanded/Collapsed state.
  CHECK_EQ(node_expected_state.size(), root.child_ids.size());
  for (size_t i = 0; i < root.child_ids.size(); ++i) {
    raw_element_provider = GetIRawElementProviderSimpleFromChildIndex(i);
    EXPECT_HRESULT_SUCCEEDED(raw_element_provider->GetPatternProvider(
        UIA_ExpandCollapsePatternId, &expandcollapse_provider));
    EXPECT_NE(expandcollapse_provider.Get(), nullptr);
    EXPECT_HRESULT_SUCCEEDED(
        expandcollapse_provider->get_ExpandCollapseState(&state));
    SCOPED_TRACE(
        ::testing::Message()
        << "node index: " << i << ", Actual Expanded/Collapsed State: " << state
        << ", Expected Expanded/Collapsed State: " << node_expected_state[i]);
    EXPECT_EQ(node_expected_state[i], state);
  }
}

TEST_F(AXPlatformNodeWinTest, IInvokeProviderInvoke) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData button;
  AXNodeData button_disabled;

  button.id = 2;
  button_disabled.id = 3;

  root.child_ids.push_back(button.id);
  root.child_ids.push_back(button_disabled.id);

  // generic button can be invoked.
  button.role = ax::mojom::Role::kButton;

  // disabled button, cannot be invoked.
  button_disabled.role = ax::mojom::Role::kButton;
  button_disabled.SetRestriction(ax::mojom::Restriction::kDisabled);

  Init(root, button, button_disabled);
  AXNode* button_node = GetRoot()->children()[0];

  // generic button can be invoked.
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetIRawElementProviderSimpleFromChildIndex(0);
  ComPtr<IInvokeProvider> invoke_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_NE(nullptr, invoke_provider.Get());
  EXPECT_HRESULT_SUCCEEDED(invoke_provider->Invoke());
  EXPECT_EQ(button_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());

  // disabled button, cannot be invoked.
  raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(1);
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_InvokePatternId, &invoke_provider));
  EXPECT_NE(nullptr, invoke_provider.Get());
  EXPECT_UIA_ELEMENTNOTENABLED(invoke_provider->Invoke());
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderNotSupported) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kNone;

  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<ISelectionItemProvider> selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &selection_item_provider));
  ASSERT_EQ(nullptr, selection_item_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderDisabled) {
  AXNodeData root;
  root.id = 1;
  root.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                       static_cast<int>(ax::mojom::Restriction::kDisabled));
  root.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  root.role = ax::mojom::Role::kTab;

  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<ISelectionItemProvider> selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &selection_item_provider));
  ASSERT_NE(nullptr, selection_item_provider.Get());

  BOOL selected;

  EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->AddToSelection());
  EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->RemoveFromSelection());
  EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderNotSelectable) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kListBoxOption;

  Init(root);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetRootIRawElementProviderSimple();
  ComPtr<ISelectionItemProvider> selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &selection_item_provider));
  ASSERT_EQ(nullptr, selection_item_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderSimple) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kListBox;

  AXNodeData option1;
  option1.id = 2;
  option1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  option1.role = ax::mojom::Role::kListBoxOption;
  root.child_ids.push_back(option1.id);

  Init(root, option1);

  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      GetIRawElementProviderSimpleFromChildIndex(0);
  ComPtr<ISelectionItemProvider> option1_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &option1_provider));
  ASSERT_NE(nullptr, option1_provider.Get());

  BOOL selected;

  // Note: TestAXNodeWrapper::AccessibilityPerformAction will
  // flip kSelected for kListBoxOption when the kDoDefault action is fired.

  // Initial State
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // AddToSelection should fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // AddToSelection should not fire event when selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // RemoveFromSelection should fire event when selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // RemoveFromSelection should not fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // Select should fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Select should not fire event when selected
  EXPECT_HRESULT_SUCCEEDED(option1_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(option1_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderRadioButton) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRadioGroup;

  // CheckedState::kNone
  AXNodeData option1;
  option1.id = 2;
  option1.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kNone));
  option1.role = ax::mojom::Role::kRadioButton;
  root.child_ids.push_back(option1.id);

  // CheckedState::kFalse
  AXNodeData option2;
  option2.id = 3;
  option2.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kFalse));
  option2.role = ax::mojom::Role::kRadioButton;
  root.child_ids.push_back(option2.id);

  // CheckedState::kTrue
  AXNodeData option3;
  option3.id = 4;
  option3.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kTrue));
  option3.role = ax::mojom::Role::kRadioButton;
  root.child_ids.push_back(option3.id);

  // CheckedState::kMixed
  AXNodeData option4;
  option4.id = 5;
  option4.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kMixed));
  option4.role = ax::mojom::Role::kRadioButton;
  root.child_ids.push_back(option4.id);

  Init(root, option1, option2, option3, option4);

  BOOL selected;

  // Option 1, CheckedState::kNone, ISelectionItemProvider is not supported.
  ComPtr<ISelectionItemProvider> option1_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(0)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option1_provider));
  ASSERT_EQ(nullptr, option1_provider.Get());

  // Option 2, CheckedState::kFalse.
  ComPtr<ISelectionItemProvider> option2_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(1)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option2_provider));
  ASSERT_NE(nullptr, option2_provider.Get());

  EXPECT_HRESULT_SUCCEEDED(option2_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  EXPECT_HRESULT_SUCCEEDED(option2_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(option2_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Option 3, CheckedState::kTrue.
  ComPtr<ISelectionItemProvider> option3_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(2)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option3_provider));
  ASSERT_NE(nullptr, option3_provider.Get());

  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  EXPECT_HRESULT_SUCCEEDED(option3_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  EXPECT_HRESULT_SUCCEEDED(option3_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Option 4, CheckedState::kMixed, ISelectionItemProvider is not supported.
  ComPtr<ISelectionItemProvider> option4_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(3)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option4_provider));
  ASSERT_EQ(nullptr, option4_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderMenuItemRadio) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kMenu;

  // CheckedState::kNone
  AXNodeData option1;
  option1.id = 2;
  option1.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kNone));
  option1.role = ax::mojom::Role::kMenuItemRadio;
  root.child_ids.push_back(option1.id);

  // CheckedState::kFalse
  AXNodeData option2;
  option2.id = 3;
  option2.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kFalse));
  option2.role = ax::mojom::Role::kMenuItemRadio;
  root.child_ids.push_back(option2.id);

  // CheckedState::kTrue
  AXNodeData option3;
  option3.id = 4;
  option3.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kTrue));
  option3.role = ax::mojom::Role::kMenuItemRadio;
  root.child_ids.push_back(option3.id);

  // CheckedState::kMixed
  AXNodeData option4;
  option4.id = 5;
  option4.AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
                          static_cast<int>(ax::mojom::CheckedState::kMixed));
  option4.role = ax::mojom::Role::kMenuItemRadio;
  root.child_ids.push_back(option4.id);

  Init(root, option1, option2, option3, option4);

  BOOL selected;

  // Option 1, CheckedState::kNone, ISelectionItemProvider is not supported.
  ComPtr<ISelectionItemProvider> option1_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(0)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option1_provider));
  ASSERT_EQ(nullptr, option1_provider.Get());

  // Option 2, CheckedState::kFalse.
  ComPtr<ISelectionItemProvider> option2_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(1)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option2_provider));
  ASSERT_NE(nullptr, option2_provider.Get());

  EXPECT_HRESULT_SUCCEEDED(option2_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  EXPECT_HRESULT_SUCCEEDED(option2_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(option2_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Option 3, CheckedState::kTrue.
  ComPtr<ISelectionItemProvider> option3_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(2)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option3_provider));
  ASSERT_NE(nullptr, option3_provider.Get());

  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  EXPECT_HRESULT_SUCCEEDED(option3_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  EXPECT_HRESULT_SUCCEEDED(option3_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Option 4, CheckedState::kMixed, ISelectionItemProvider is not supported.
  ComPtr<ISelectionItemProvider> option4_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(3)->GetPatternProvider(
          UIA_SelectionItemPatternId, &option4_provider));
  ASSERT_EQ(nullptr, option4_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, SelectedMenuItemSelectionEventNotifiesFocus) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kMenu;

  AXNodeData menu_item;
  menu_item.id = 2;
  menu_item.role = ax::mojom::Role::kMenuItem;
  menu_item.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  root.child_ids.push_back(menu_item.id);

  Init(root, menu_item);

  bool focus_event_fired = false;
  bool selection_event_fired = false;
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus,
      base::BindLambdaForTesting([&]() { focus_event_fired = true; }));
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection,
      base::BindLambdaForTesting([&]() { selection_event_fired = true; }));

  AXPlatformNodeFromNode(GetRoot()->children()[0])
      ->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);

  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus, base::RepeatingClosure());
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection, base::RepeatingClosure());

  EXPECT_TRUE(focus_event_fired);
  EXPECT_FALSE(selection_event_fired);
}

TEST_F(AXPlatformNodeWinTest,
       SelectedListBoxOptionSelectionEventNotifiesFocus) {
  // Autofill-style popup: the focused option of a single-select listbox carries
  // an explicit selected state. Its selection event must be remapped to a focus
  // event so Windows screen readers announce it (https://crbug.com/525108196).
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kListBox;

  AXNodeData option;
  option.id = 2;
  option.role = ax::mojom::Role::kListBoxOption;
  option.AddState(ax::mojom::State::kFocusable);
  option.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
  root.child_ids.push_back(option.id);

  Init(root, option);

  // Give the option platform focus so the remap's focus guard is satisfied.
  auto* option_node = GetRoot()->children()[0].get();
  ComPtr<IRawElementProviderFragment> option_fragment =
      IRawElementProviderFragmentFromNode(option_node);
  ASSERT_NE(nullptr, option_fragment.Get());
  EXPECT_HRESULT_SUCCEEDED(option_fragment->SetFocus());

  bool focus_event_fired = false;
  bool selection_event_fired = false;
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus,
      base::BindLambdaForTesting([&]() { focus_event_fired = true; }));
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection,
      base::BindLambdaForTesting([&]() { selection_event_fired = true; }));

  AXPlatformNodeFromNode(option_node)
      ->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);

  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus, base::RepeatingClosure());
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection, base::RepeatingClosure());

  EXPECT_TRUE(focus_event_fired);
  EXPECT_FALSE(selection_event_fired);
}

TEST_F(AXPlatformNodeWinTest,
       UnselectedListBoxOptionSelectionEventDoesNotNotifyFocus) {
  // A deselection (explicit selected=false) must stay a selection event and not
  // be remapped to focus, so the highlight moving away is not announced as a
  // focus change (preserves the behavior added in crrev.com/c/7799854).
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kListBox;

  AXNodeData option;
  option.id = 2;
  option.role = ax::mojom::Role::kListBoxOption;
  option.AddState(ax::mojom::State::kFocusable);
  option.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  root.child_ids.push_back(option.id);

  Init(root, option);

  bool focus_event_fired = false;
  bool selection_event_fired = false;
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus,
      base::BindLambdaForTesting([&]() { focus_event_fired = true; }));
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection,
      base::BindLambdaForTesting([&]() { selection_event_fired = true; }));

  AXPlatformNodeFromNode(GetRoot()->children()[0])
      ->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);

  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kFocus, base::RepeatingClosure());
  AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
      ax::mojom::Event::kSelection, base::RepeatingClosure());

  EXPECT_FALSE(focus_event_fired);
  EXPECT_TRUE(selection_event_fired);
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderTable) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTable;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData cell1;
  cell1.id = 3;
  cell1.role = ax::mojom::Role::kCell;
  cell1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  row1.child_ids.push_back(cell1.id);

  Init(root, row1, cell1);

  ComPtr<ISelectionItemProvider> selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(
      GetIRawElementProviderSimpleFromChildIndex(0)->GetPatternProvider(
          UIA_SelectionItemPatternId, &selection_item_provider));
  ASSERT_EQ(nullptr, selection_item_provider.Get());
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderGrid) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kGrid;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData cell1;
  cell1.id = 3;
  cell1.role = ax::mojom::Role::kGridCell;
  cell1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  row1.child_ids.push_back(cell1.id);

  Init(root, row1, cell1);

  const auto* row = GetRoot()->children()[0].get();
  ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
      QueryInterfaceFromNode<IRawElementProviderSimple>(row->children()[0]);

  ComPtr<ISelectionItemProvider> selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &selection_item_provider));
  ASSERT_NE(nullptr, selection_item_provider.Get());

  BOOL selected;

  // Note: TestAXNodeWrapper::AccessibilityPerformAction will
  // flip kSelected for kCell when the kDoDefault action is fired.

  // Initial State
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // AddToSelection should fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // AddToSelection should not fire event when selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->AddToSelection());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // RemoveFromSelection should fire event when selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // RemoveFromSelection should not fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->RemoveFromSelection());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_FALSE(selected);

  // Select should fire event when not selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);

  // Select should not fire event when selected
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->Select());
  EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
  EXPECT_TRUE(selected);
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderGetSelectionContainer) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kGrid;

  AXNodeData row1;
  row1.id = 2;
  row1.role = ax::mojom::Role::kRow;
  root.child_ids.push_back(row1.id);

  AXNodeData cell1;
  cell1.id = 3;
  cell1.role = ax::mojom::Role::kCell;
  cell1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  row1.child_ids.push_back(cell1.id);

  Init(root, row1, cell1);

  ComPtr<IRawElementProviderSimple> container_provider =
      GetRootIRawElementProviderSimple();

  const auto* row = GetRoot()->children()[0].get();
  ComPtr<ISelectionItemProvider> item_provider =
      QueryInterfaceFromNode<ISelectionItemProvider>(row->children()[0]);

  ComPtr<IRawElementProviderSimple> container;
  EXPECT_HRESULT_SUCCEEDED(item_provider->get_SelectionContainer(&container));
  EXPECT_NE(nullptr, container);
  EXPECT_EQ(container, container_provider);
}

TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderSelectFollowFocus) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kTabList;

  AXNodeData tab1;
  tab1.id = 2;
  tab1.role = ax::mojom::Role::kTab;
  tab1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
  tab1.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
  root.child_ids.push_back(tab1.id);

  Init(root, tab1);

  auto* tab1_node = GetRoot()->children()[0].get();
  ComPtr<IRawElementProviderSimple> tab1_raw_element_provider_simple =
      QueryInterfaceFromNode<IRawElementProviderSimple>(tab1_node);
  ASSERT_NE(nullptr, tab1_raw_element_provider_simple.Get());

  ComPtr<IRawElementProviderFragment> tab1_raw_element_provider_fragment =
      IRawElementProviderFragmentFromNode(tab1_node);
  ASSERT_NE(nullptr, tab1_raw_element_provider_fragment.Get());

  ComPtr<ISelectionItemProvider> tab1_selection_item_provider;
  EXPECT_HRESULT_SUCCEEDED(tab1_raw_element_provider_simple->GetPatternProvider(
      UIA_SelectionItemPatternId, &tab1_selection_item_provider));
  ASSERT_NE(nullptr, tab1_selection_item_provider.Get());

  BOOL is_selected;
  // Before setting focus to "tab1", validate that "tab1" has selected=false.
  tab1_selection_item_provider->get_IsSelected(&is_selected);
  EXPECT_FALSE(is_selected);

  // Setting focus on "tab1" will result in selected=true.
  tab1_raw_element_provider_fragment->SetFocus();
  tab1_selection_item_provider->get_IsSelected(&is_selected);
  EXPECT_TRUE(is_selected);

  // Verify that we can still trigger action::kDoDefault through Select().
  EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
  tab1_selection_item_provider->get_IsSelected(&is_selected);
  EXPECT_TRUE(is_selected);
  EXPECT_EQ(tab1_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
  // Verify that after Select(), "tab1" is still selected.
  tab1_selection_item_provider->get_IsSelected(&is_selected);
  EXPECT_TRUE(is_selected);

  // Since last Select() performed |action::kDoDefault|, which set
  // |kSelectedFromFocus| to false. Calling Select() again will not perform
  // |action::kDoDefault| again.
  TestAXNodeWrapper::SetNodeFromLastDefaultAction(nullptr);
  EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
  tab1_selection_item_provider->get_IsSelected(&is_selected);
  EXPECT_TRUE(is_selected);
  // Verify that after Select(),|action::kDoDefault| was not triggered on
  // "tab1".
  EXPECT_EQ(nullptr, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
}

TEST_F(AXPlatformNodeWinTest, IValueProvider_GetValue) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kProgressIndicator;
  child1.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 3.0f);
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kTextField;
  child2.AddState(ax::mojom::State::kEditable);
  child2.AddStringAttribute(ax::mojom::StringAttribute::kValue, "test");
  root.child_ids.push_back(child2.id);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kTextField;
  child3.AddState(ax::mojom::State::kEditable);
  child3.AddStringAttribute(ax::mojom::StringAttribute::kValue, "test");
  child3.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                         static_cast<int>(ax::mojom::Restriction::kReadOnly));
  root.child_ids.push_back(child3.id);

  AXNodeData child4;
  child4.id = 5;
  child4.role = ax::mojom::Role::kButton;
  child4.AddStringAttribute(ax::mojom::StringAttribute::kValue, "test");
  root.child_ids.push_back(child4.id);

  const char url[] = "https://localhost";
  AXTreeUpdate update;
  update.has_tree_data = true;
  update.tree_data.url = url;
  update.root_id = root.id;
  update.nodes.push_back(root);
  update.nodes.push_back(child1);
  update.nodes.push_back(child2);
  update.nodes.push_back(child3);
  update.nodes.push_back(child4);
  Init(update);

  ScopedBstr bstr_value;

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot())->get_Value(
          bstr_value.Receive()));
  EXPECT_STREQ(url,
               base::UTF16ToASCII(base::as_u16cstr(bstr_value.Get())).c_str());
  bstr_value.Reset();

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[0])
          ->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"3", bstr_value.Get());
  bstr_value.Reset();

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[1])
          ->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"test", bstr_value.Get());
  bstr_value.Reset();

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[2])
          ->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"test", bstr_value.Get());
  bstr_value.Reset();

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[3])
          ->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"test", bstr_value.Get());
  bstr_value.Reset();
}

TEST_F(AXPlatformNodeWinTest, IAccessibleValue) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child;
  child.id = 2;
  child.role = ax::mojom::Role::kSlider;
  child.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 3.0f);
  child.AddFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange, 1.0f);
  child.AddFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange, 9.0f);
  root.child_ids.push_back(child.id);

  Init(root, child);

  ScopedVariant value;
  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IAccessibleValue>(GetRoot()->children()[0])
          ->get_currentValue(value.Receive()));
  ASSERT_EQ(VT_R8, value.type());
  EXPECT_DOUBLE_EQ(3.0, V_R8(value.ptr()));
}

TEST_F(AXPlatformNodeWinTest, IValueProvider_SetValue) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kProgressIndicator;
  child1.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 3.0f);
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kTextField;
  child2.AddState(ax::mojom::State::kEditable);
  child2.AddStringAttribute(ax::mojom::StringAttribute::kValue, "test");
  root.child_ids.push_back(child2.id);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kTextField;
  child3.AddState(ax::mojom::State::kEditable);
  child3.AddStringAttribute(ax::mojom::StringAttribute::kValue, "test");
  child3.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                         static_cast<int>(ax::mojom::Restriction::kReadOnly));
  root.child_ids.push_back(child3.id);

  const char url[] = "https://localhost";
  AXTreeUpdate update;
  update.has_tree_data = true;
  update.tree_data.url = url;
  update.root_id = root.id;
  update.nodes.push_back(root);
  update.nodes.push_back(child1);
  update.nodes.push_back(child2);
  update.nodes.push_back(child3);
  Init(update);

  ComPtr<IValueProvider> root_provider =
      QueryInterfaceFromNode<IValueProvider>(GetRoot());
  ComPtr<IValueProvider> provider1 =
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[0]);
  ComPtr<IValueProvider> provider2 =
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[1]);
  ComPtr<IValueProvider> provider3 =
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[2]);

  ScopedBstr bstr_value;

  EXPECT_UIA_ELEMENTNOTENABLED(root_provider->SetValue(L"https://foo"));
  EXPECT_HRESULT_SUCCEEDED(root_provider->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(url,
               base::UTF16ToASCII(base::as_u16cstr(bstr_value.Get())).c_str());
  bstr_value.Reset();

  // Note: TestAXNodeWrapper::AccessibilityPerformAction will
  // modify the value when the kSetValue action is fired.
  EXPECT_UIA_ELEMENTNOTENABLED(provider1->SetValue(L"2"));
  EXPECT_HRESULT_SUCCEEDED(provider1->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"3", bstr_value.Get());
  bstr_value.Reset();

  EXPECT_HRESULT_SUCCEEDED(provider2->SetValue(L"changed"));
  EXPECT_HRESULT_SUCCEEDED(provider2->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"changed", bstr_value.Get());
  bstr_value.Reset();

  EXPECT_UIA_ELEMENTNOTENABLED(provider3->SetValue(L"changed"));
  EXPECT_HRESULT_SUCCEEDED(provider3->get_Value(bstr_value.Receive()));
  EXPECT_STREQ(L"test", bstr_value.Get());
  bstr_value.Reset();
}

TEST_F(AXPlatformNodeWinTest, IValueProvider_IsReadOnly) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  AXNodeData child1;
  child1.id = 2;
  child1.role = ax::mojom::Role::kTextField;
  child1.AddState(ax::mojom::State::kEditable);
  root.child_ids.push_back(child1.id);

  AXNodeData child2;
  child2.id = 3;
  child2.role = ax::mojom::Role::kTextField;
  child2.AddState(ax::mojom::State::kEditable);
  child2.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                         static_cast<int>(ax::mojom::Restriction::kReadOnly));
  root.child_ids.push_back(child2.id);

  AXNodeData child3;
  child3.id = 4;
  child3.role = ax::mojom::Role::kTextField;
  child3.AddState(ax::mojom::State::kEditable);
  child3.AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
                         static_cast<int>(ax::mojom::Restriction::kDisabled));
  root.child_ids.push_back(child3.id);

  AXNodeData child4;
  child4.id = 5;
  child4.role = ax::mojom::Role::kLink;
  root.child_ids.push_back(child4.id);

  Init(root, child1, child2, child3, child4);

  BOOL is_readonly = false;

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot())->get_IsReadOnly(
          &is_readonly));
  EXPECT_TRUE(is_readonly);

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[0])
          ->get_IsReadOnly(&is_readonly));
  EXPECT_FALSE(is_readonly);

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[1])
          ->get_IsReadOnly(&is_readonly));
  EXPECT_TRUE(is_readonly);

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[2])
          ->get_IsReadOnly(&is_readonly));
  EXPECT_TRUE(is_readonly);

  EXPECT_HRESULT_SUCCEEDED(
      QueryInterfaceFromNode<IValueProvider>(GetRoot()->children()[3])
          ->get_IsReadOnly(&is_readonly));
  EXPECT_TRUE(is_readonly);
}

TEST_F(AXPlatformNodeWinTest, IScrollProviderSetScrollPercent) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kGenericContainer;
  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollX, 0);
  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin, 0);
  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax, 100);

  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollY, 60);
  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin, 10);
  root.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax, 60);

  Init(root);

  ComPtr<IScrollProvider> scroll_provider =
      QueryInterfaceFromNode<IScrollProvider>(GetRoot());
  double x_scroll_percent;
  double y_scroll_percent;

  // Set x scroll percent: 0%, y scroll percent: 0%.
  // Expected x scroll percent: 0%, y scroll percent: 0%.
  EXPECT_HRESULT_SUCCEEDED(scroll_provider->SetScrollPercent(0, 0));
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_HorizontalScrollPercent(&x_scroll_percent));
  EXPECT_EQ(x_scroll_percent, 0);
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_VerticalScrollPercent(&y_scroll_percent));
  EXPECT_EQ(y_scroll_percent, 0);

  // Set x scroll percent: 100%, y scroll percent: 100%.
  // Expected x scroll percent: 100%, y scroll percent: 100%.
  EXPECT_HRESULT_SUCCEEDED(scroll_provider->SetScrollPercent(100, 100));
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_HorizontalScrollPercent(&x_scroll_percent));
  EXPECT_EQ(x_scroll_percent, 100);
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_VerticalScrollPercent(&y_scroll_percent));
  EXPECT_EQ(y_scroll_percent, 100);

  // Set x scroll percent: 500%, y scroll percent: 600%.
  // Expected x scroll percent: 100%, y scroll percent: 100%.
  EXPECT_HRESULT_SUCCEEDED(scroll_provider->SetScrollPercent(500, 600));
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_HorizontalScrollPercent(&x_scroll_percent));
  EXPECT_EQ(x_scroll_percent, 100);
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_VerticalScrollPercent(&y_scroll_percent));
  EXPECT_EQ(y_scroll_percent, 100);

  // Set x scroll percent: -100%, y scroll percent: -200%.
  // Expected x scroll percent: 0%, y scroll percent: 0%.
  EXPECT_HRESULT_SUCCEEDED(scroll_provider->SetScrollPercent(-100, -200));
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_HorizontalScrollPercent(&x_scroll_percent));
  EXPECT_EQ(x_scroll_percent, 0);
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_VerticalScrollPercent(&y_scroll_percent));
  EXPECT_EQ(y_scroll_percent, 0);

  // Set x scroll percent: 12%, y scroll percent: 34%.
  // Expected x scroll percent: 12%, y scroll percent: 34%.
  EXPECT_HRESULT_SUCCEEDED(scroll_provider->SetScrollPercent(12, 34));
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_HorizontalScrollPercent(&x_scroll_percent));
  EXPECT_EQ(x_scroll_percent, 12);
  EXPECT_HRESULT_SUCCEEDED(
      scroll_provider->get_VerticalScrollPercent(&y_scroll_percent));
  EXPECT_EQ(y_scroll_percent, 34);
}

TEST_F(AXPlatformNodeWinTest, SanitizeStringAttributeForIA2) {
  std::string input("\\:=,;");
  std::string output;
  AXPlatformNodeWin::SanitizeStringAttributeForIA2(input, &output);
  EXPECT_EQ("\\\\\\:\\=\\,\\;", output);
}

TEST_F(AXPlatformNodeWinTest, AriaRoleForInsertionAndDeletion) {
  TestAXTreeUpdate update(std::string(R"HTML(
    ++1 kRootWebArea
    ++++2 kContentInsertion
    ++++3 kContentDeletion
  )HTML"));
  Init(update);

  EXPECT_UIA_BSTR_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
                     UIA_AriaRolePropertyId, L"insertion");
  EXPECT_UIA_BSTR_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
                     UIA_AriaRolePropertyId, L"deletion");
}

//
// IDispatch tests
//

TEST_F(AXPlatformNodeWinTest, IDispatchGetTypeInfoCount) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  Init(root);

  ComPtr<IDispatch> dispatch = QueryInterfaceFromNode<IDispatch>(GetRoot());
  ASSERT_NE(nullptr, dispatch.Get());

  UINT type_info_count = 0;
  EXPECT_HRESULT_SUCCEEDED(dispatch->GetTypeInfoCount(&type_info_count));
  EXPECT_EQ(1u, type_info_count);
}

TEST_F(AXPlatformNodeWinTest, IDispatchGetTypeInfo) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  Init(root);

  ComPtr<IDispatch> dispatch = QueryInterfaceFromNode<IDispatch>(GetRoot());
  ASSERT_NE(nullptr, dispatch.Get());

  ComPtr<ITypeInfo> type_info;
  HRESULT hr = dispatch->GetTypeInfo(0, LOCALE_USER_DEFAULT, &type_info);
  if (hr == TYPE_E_LIBNOTREGISTERED) {
    // IAccessible2 type library is not registered on this machine; skip.
    GTEST_SKIP() << "IAccessible2 type library not registered";
  }
  EXPECT_HRESULT_SUCCEEDED(hr);
  ASSERT_NE(nullptr, type_info.Get());

  // Verify the type info is for the IAccessible2 interface.
  TYPEATTR* type_attr = nullptr;
  EXPECT_HRESULT_SUCCEEDED(type_info->GetTypeAttr(&type_attr));
  ASSERT_NE(nullptr, type_attr);
  type_info->ReleaseTypeAttr(type_attr);

  // GetTypeInfo with invalid index should fail.
  ComPtr<ITypeInfo> type_info_invalid;
  EXPECT_EQ(DISP_E_BADINDEX,
            dispatch->GetTypeInfo(1, LOCALE_USER_DEFAULT, &type_info_invalid));
}

TEST_F(AXPlatformNodeWinTest, IDispatchGetIDsOfNames) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;

  Init(root);

  ComPtr<IDispatch> dispatch = QueryInterfaceFromNode<IDispatch>(GetRoot());
  ASSERT_NE(nullptr, dispatch.Get());

  // Look up the DISPID for "accName" (IAccessible::get_accName).
  LPOLESTR name = const_cast<LPOLESTR>(L"accName");
  DISPID dispid = 0;
  HRESULT hr =
      dispatch->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dispid);
  if (hr == TYPE_E_LIBNOTREGISTERED) {
    // IAccessible2 type library is not registered on this machine; skip.
    GTEST_SKIP() << "IAccessible2 type library not registered";
  }
  EXPECT_HRESULT_SUCCEEDED(hr);
  EXPECT_EQ(DISPID_ACC_NAME, dispid);
}

//
// IChromeAccessible tests
//

class TestIChromeAccessibleDelegate
    : public Microsoft::WRL::RuntimeClass<
          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
          IChromeAccessibleDelegate> {
 public:
  TestIChromeAccessibleDelegate() = default;
  ~TestIChromeAccessibleDelegate() override = default;

  std::string WaitForBulkFetchResult(LONG expected_request_id) {
    if (bulk_fetch_result_.empty())
      WaitUsingRunLoop();
    CHECK_EQ(expected_request_id, request_id_);
    return bulk_fetch_result_;
  }

  IUnknown* WaitForHitTestResult(LONG expected_request_id) {
    if (!hit_test_result_)
      WaitUsingRunLoop();
    CHECK_EQ(expected_request_id, request_id_);
    return hit_test_result_.Get();
  }

 private:
  void WaitUsingRunLoop() {
    base::RunLoop run_loop;
    run_loop_quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  IFACEMETHODIMP put_bulkFetchResult(LONG request_id, BSTR result) override {
    bulk_fetch_result_ = base::WideToUTF8(result);
    request_id_ = request_id;
    if (run_loop_quit_closure_)
      run_loop_quit_closure_.Run();
    return S_OK;
  }

  IFACEMETHODIMP put_hitTestResult(LONG request_id, IUnknown* result) override {
    hit_test_result_ = result;
    request_id_ = request_id;
    if (run_loop_quit_closure_)
      run_loop_quit_closure_.Run();
    return S_OK;
  }

  std::string bulk_fetch_result_;
  ComPtr<IUnknown> hit_test_result_;
  LONG request_id_ = 0;
  base::RepeatingClosure run_loop_quit_closure_;
};

TEST_F(AXPlatformNodeWinTest, BulkFetch) {
  base::test::SingleThreadTaskEnvironment task_environment;
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kScrollBar;

  Init(root);

  ComPtr<IChromeAccessible> chrome_accessible =
      QueryInterfaceFromNode<IChromeAccessible>(GetRoot());

  auto delegate = Microsoft::WRL::Make<TestIChromeAccessibleDelegate>();
  ScopedBstr input_bstr(L"Potato");
  chrome_accessible->get_bulkFetch(input_bstr.Get(), 99, delegate.Get());
  std::string response = delegate->WaitForBulkFetchResult(99);

  // Note: base::JSONReader is fine for unit tests, but production code
  // that parses untrusted JSON should always use DataDecoder instead.
  std::optional<base::Value> result_val =
      base::JSONReader::Read(response, base::JSON_ALLOW_TRAILING_COMMAS);
  ASSERT_TRUE(result_val);
  const base::DictValue& result = result_val->GetDict();
  ASSERT_TRUE(result.contains("role"));
  ASSERT_EQ("scrollBar", CHECK_DEREF(result.FindString("role")));
}

TEST_F(AXPlatformNodeWinTest, AsyncHitTest) {
  base::test::SingleThreadTaskEnvironment task_environment;
  AXNodeData root;
  root.id = 50;
  root.role = ax::mojom::Role::kArticle;
  root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);

  Init(root);

  ComPtr<IChromeAccessible> chrome_accessible =
      QueryInterfaceFromNode<IChromeAccessible>(GetRoot());

  auto delegate = Microsoft::WRL::Make<TestIChromeAccessibleDelegate>();
  ScopedBstr input_bstr(L"Potato");
  chrome_accessible->get_hitTest(400, 300, 12345, delegate.Get());
  ComPtr<IUnknown> result = delegate->WaitForHitTestResult(12345);
  ComPtr<IAccessible2> accessible = ToIAccessible2(result);
  LONG result_unique_id = 0;
  ASSERT_HRESULT_SUCCEEDED(accessible->get_uniqueID(&result_unique_id));
  ComPtr<IAccessible2> root_accessible =
      QueryInterfaceFromNode<IAccessible2>(GetRoot());
  LONG root_unique_id = 0;
  ASSERT_HRESULT_SUCCEEDED(root_accessible->get_uniqueID(&root_unique_id));
  ASSERT_EQ(root_unique_id, result_unique_id);
}

namespace {

class FakeAxPlatformNodeDelegate : public AXPlatformNodeDelegate {
 public:
  AXPlatformNodeId GetUniqueId() const override { return unique_id_; }

 private:
  const AXUniqueId unique_id_{AXUniqueId::Create()};
};

}  // namespace

// Tests lifecycle accounting for dormant -> destroyed.
TEST_F(AXPlatformNodeWinTest, DormantDestroyed) {
  AXPlatformNodeDelegate test_delegate;

  // All zeros to start with.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));

  AXPlatformNode::Pointer node = AXPlatformNode::Create(test_delegate);

  // One instance and one dormant node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));

  node.reset();

  // Zero instances and no ghost nodes.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));
}

// Tests lifecycle accounting for dormant -> live -> dormant -> destroyed.
TEST_F(AXPlatformNodeWinTest, DormantLiveDormantDestroyed) {
  AXPlatformNodeDelegate test_delegate;

  // All zeros to start with.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));

  AXPlatformNode::Pointer node = AXPlatformNode::Create(test_delegate);

  // One instance and one dormant node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));

  Microsoft::WRL::ComPtr<IAccessible> a_ref;
  ASSERT_HRESULT_SUCCEEDED(
      static_cast<AXPlatformNodeWin&>(*node).QueryInterface(
          IID_PPV_ARGS(&a_ref)));

  // One instance and one live node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 0U, 1U, 0U}));

  a_ref.Reset();

  // One instance and one dormant node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));

  node.reset();

  // Zero instances and no ghost nodes.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));
}

// Tests lifecycle accounting for dormant -> live -> ghost -> destroyed.
TEST_F(AXPlatformNodeWinTest, DormantLiveGhostDestroyed) {
  AXPlatformNodeDelegate test_delegate;

  // All zeros to start with.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));

  AXPlatformNode::Pointer node = AXPlatformNode::Create(test_delegate);

  // One instance and one dormant node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));

  Microsoft::WRL::ComPtr<IAccessible> a_ref;
  ASSERT_HRESULT_SUCCEEDED(
      static_cast<AXPlatformNodeWin&>(*node).QueryInterface(
          IID_PPV_ARGS(&a_ref)));

  // One instance and one live node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 0U, 1U, 0U}));

  Microsoft::WRL::ComPtr<IAccessible> a_second_ref;
  ASSERT_HRESULT_SUCCEEDED(a_ref.CopyTo(&a_second_ref));

  // Still one instance and one live node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 0U, 1U, 0U}));

  node.reset();

  // Zero instances and one ghost node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 1U}));

  a_ref.Reset();

  // Still zero instances and one ghost node.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 1U}));

  a_second_ref.Reset();

  // Zero instances and no ghost nodes.
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));
}

// Regression test for crbug.com/532828233.
TEST_F(AXPlatformNodeWinTest, OwnedNodeSurvivesUnexpectedReleases) {
  AXPlatformNodeDelegate test_delegate;

  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));

  AXPlatformNode::Pointer node = AXPlatformNode::Create(test_delegate);
  auto* win_node = static_cast<AXPlatformNodeWin*>(node.get());

  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));
  ASSERT_EQ(win_node->ref_count_for_testing(), 1U);

  ASSERT_EQ(win_node->AddRef(), 2U);
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 0U, 1U, 0U}));
  ASSERT_EQ(win_node->Release(), 1U);
  ASSERT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));

  // Avoid retaining a dangling owner if the regression reappears.
  node.release();
  for (int i = 0; i < 2; ++i) {
    const ULONG ref_count = win_node->Release();
    if (ref_count != 1U) {
      EXPECT_EQ(ref_count, 1U);
      return;
    }
  }
  EXPECT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 1U, 0U, 0U}));
  EXPECT_EQ(win_node->ref_count_for_testing(), 1U);

  Microsoft::WRL::ComPtr<IAccessible> reacquired;
  EXPECT_HRESULT_SUCCEEDED(win_node->QueryInterface(IID_PPV_ARGS(&reacquired)));
  EXPECT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{1U, 0U, 1U, 0U}));
  reacquired.Reset();

  win_node->Destroy();
  EXPECT_EQ(AXPlatformNodeWin::GetCounts(),
            (AXPlatformNodeWin::Counts{0U, 0U, 0U, 0U}));
}

// Test for UIA's MathML Implementation.
TEST_F(AXPlatformNodeWinTest, UiaMathMlFeatureFlag) {
  // Verify flag is disabled by default.
  EXPECT_TRUE(base::FeatureList::IsEnabled(features::kUiaMathMlSupport));

  // Verify flag can be explicitly disabled.
  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(features::kUiaMathMlSupport);
    EXPECT_FALSE(base::FeatureList::IsEnabled(features::kUiaMathMlSupport));
  }
}

// Regression test for crbug.com/503419515: a node destroyed mid-event must
// not be inserted into the global alert targets set.
TEST_F(AXPlatformNodeWinTest, DestroyedNodeNotAddedToAlertTargets) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids = {2};

  AXNodeData alert;
  alert.id = 2;
  alert.role = ax::mojom::Role::kAlert;

  Init(root, alert);
  AXNode* alert_ax_node = GetRoot()->children()[0];

  auto* alert_node = static_cast<AXPlatformNodeWin*>(
      AXPlatformNodeFromNode(alert_ax_node));
  ASSERT_TRUE(alert_node);

  const size_t initial_count =
      AXPlatformNodeWin::GetAlertTargetCountForTesting();

  // Put the node in the IsDestroyed() state without actually destroying it,
  // so the wrapper can still tear down cleanly at the end of the test.
  AXPlatformNodeDelegate* original_delegate =
      alert_node->SetDelegateForTesting(nullptr);
  ASSERT_TRUE(alert_node->IsDestroyed());

  alert_node->AddAlertTargetForTesting();
  EXPECT_EQ(initial_count,
            AXPlatformNodeWin::GetAlertTargetCountForTesting());

  alert_node->SetDelegateForTesting(original_delegate);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_NoneByDefault) {
  EXPECT_FALSE(AXPlatform::GetInstance().GetActiveClientApi().has_value());
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_MsaaOnly) {
  AXPlatform::GetInstance().SetMsaaActive();
  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kMsaaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_UiaOnly) {
  AXPlatform::GetInstance().SetUiaActive();
  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kUiaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_Both) {
  AXPlatform::GetInstance().SetMsaaActive();
  AXPlatform::GetInstance().SetUiaActive();
  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kBoth, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_SetMsaaActiveIdempotent) {
  AXPlatform::GetInstance().SetMsaaActive();
  AXPlatform::GetInstance().SetMsaaActive();
  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kMsaaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_MsaaSetViaIAccessible) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  Init(root);

  ComPtr<IAccessible> root_accessible = GetRootIAccessible();
  ASSERT_NE(nullptr, root_accessible.Get());

  EXPECT_FALSE(AXPlatform::GetInstance().GetActiveClientApi().has_value());

  ScopedBstr name;
  root_accessible->get_accName(SELF, name.Receive());

  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kMsaaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_UiaSetViaProvider) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  Init(root);

  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();
  ASSERT_NE(nullptr, root_node.Get());

  EXPECT_FALSE(AXPlatform::GetInstance().GetActiveClientApi().has_value());

  ScopedVariant property_value;
  root_node->GetPropertyValue(UIA_NamePropertyId, property_value.Receive());

  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kUiaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, ActiveClientApi_BothSetViaMixedCalls) {
  AXNodeData root;
  root.id = 1;
  root.role = ax::mojom::Role::kRootWebArea;
  root.SetName("root");
  Init(root);

  TestAXNodeWrapper::SetGlobalIsWebContent(true);

  ComPtr<IAccessible> root_accessible = GetRootIAccessible();
  ComPtr<IRawElementProviderSimple> root_node =
      GetRootIRawElementProviderSimple();

  ScopedBstr name;
  root_accessible->get_accName(SELF, name.Receive());

  ScopedVariant property_value;
  root_node->GetPropertyValue(UIA_NamePropertyId, property_value.Receive());

  auto result = AXPlatform::GetInstance().GetActiveClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kBoth, *result);
}

TEST_F(AXPlatformNodeWinTest, RequestedClientApi_NoneByDefault) {
  EXPECT_FALSE(AXPlatform::GetInstance().GetRequestedClientApi().has_value());
}

TEST_F(AXPlatformNodeWinTest, RequestedClientApi_MsaaOnly) {
  AXPlatform::GetInstance().SetMsaaRequested();
  auto result = AXPlatform::GetInstance().GetRequestedClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kMsaaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, RequestedClientApi_UiaOnly) {
  AXPlatform::GetInstance().SetUiaRequested();
  auto result = AXPlatform::GetInstance().GetRequestedClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kUiaOnly, *result);
}

TEST_F(AXPlatformNodeWinTest, RequestedClientApi_Both) {
  AXPlatform::GetInstance().SetMsaaRequested();
  AXPlatform::GetInstance().SetUiaRequested();
  auto result = AXPlatform::GetInstance().GetRequestedClientApi();
  ASSERT_TRUE(result.has_value());
  EXPECT_EQ(AXPlatform::ActiveClientApi::kBoth, *result);
}

}  // namespace ui
