chromium/services/accessibility/android/accessibility_node_info_data_wrapper_unittest.cc

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

#include "services/accessibility/android/accessibility_node_info_data_wrapper.h"

#include <map>
#include <memory>
#include <utility>

#include "chrome/grit/generated_resources.h"
#include "services/accessibility/android/accessibility_window_info_data_wrapper.h"
#include "services/accessibility/android/android_accessibility_util.h"
#include "services/accessibility/android/ax_tree_source_android.h"
#include "services/accessibility/android/public/mojom/accessibility_helper.mojom.h"
#include "services/accessibility/android/test/android_accessibility_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom-shared.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_android_constants.h"
#include "ui/base/l10n/l10n_util.h"

namespace ax::android {

using AXActionType = mojom::AccessibilityActionType;
using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXCollectionInfoData = mojom::AccessibilityCollectionInfoData;
using AXCollectionItemInfoData = mojom::AccessibilityCollectionItemInfoData;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
using AXIntProperty = mojom::AccessibilityIntProperty;
using AXNodeInfoData = mojom::AccessibilityNodeInfoData;
using AXRangeInfoData = mojom::AccessibilityRangeInfoData;
using AXStringProperty = mojom::AccessibilityStringProperty;

class AccessibilityNodeInfoDataWrapperTest
    : public testing::Test,
      public AXTreeSourceAndroid::Delegate {
 public:
  class TestSerializationDelegate
      : public AXTreeSourceAndroid::SerializationDelegate {
    // AXTreeSourceAndroid::SerializationDelegate overrides.
    void PopulateBounds(const AccessibilityInfoDataWrapper& node,
                        ui::AXNodeData& out_data) const override {}
  };

  class TestAXTreeSourceAndroid : public AXTreeSourceAndroid {
   public:
    explicit TestAXTreeSourceAndroid(AXTreeSourceAndroid::Delegate* delegate)
        : AXTreeSourceAndroid(delegate,
                              std::make_unique<TestSerializationDelegate>(),
                              /*window=*/nullptr) {}

    // AXTreeSourceAndroid overrides.
    bool IsRootOfNodeTree(int32_t id) const override {
      return id == node_root_id_;
    }

    AccessibilityInfoDataWrapper* GetFromId(int32_t id) const override {
      auto itr = wrapper_map_.find(id);
      if (itr == wrapper_map_.end()) {
        return nullptr;
      }
      return itr->second;
    }

    AccessibilityInfoDataWrapper* GetParent(
        AccessibilityInfoDataWrapper* info_data) const override {
      auto itr = parent_map_.find(info_data->GetId());
      if (itr == parent_map_.end()) {
        return nullptr;
      }
      return GetFromId(itr->second);
    }

    void set_node_root_id(int32_t id) { node_root_id_ = id; }

    void SetIdToWrapper(AccessibilityInfoDataWrapper* wrapper) {
      wrapper_map_[wrapper->GetId()] = wrapper;
    }
    void SetParentId(int32_t child_id, int32_t parent_id) {
      parent_map_[child_id] = parent_id;
    }

   private:
    int32_t node_root_id_ = -1;
    std::map<int32_t, AccessibilityInfoDataWrapper*> wrapper_map_;
    std::map<int32_t, int32_t> parent_map_;
  };

  AccessibilityNodeInfoDataWrapperTest()
      : tree_source_(new TestAXTreeSourceAndroid(this)) {}

  ui::AXNodeData CallSerialize(
      const AccessibilityInfoDataWrapper& wrapper) const {
    ui::AXNodeData data;
    wrapper.Serialize(&data);
    return data;
  }

  void SetNodeRootId(int32_t id) { tree_source_->set_node_root_id(id); }
  void SetIdToWrapper(AccessibilityInfoDataWrapper* wrapper) {
    tree_source_->SetIdToWrapper(wrapper);
  }
  void SetParentId(int32_t child_id, int32_t parent_id) {
    tree_source_->SetParentId(child_id, parent_id);
  }

  // AXTreeSourceAndroid::Delegate overrides.
  bool UseFullFocusMode() const override { return full_focus_mode_; }
  void OnAction(const ui::AXActionData& data) const override {}

  void set_full_focus_mode(bool enabled) { full_focus_mode_ = enabled; }

  AXTreeSourceAndroid* tree_source() { return tree_source_.get(); }

 private:
  const std::unique_ptr<TestAXTreeSourceAndroid> tree_source_;
  bool full_focus_mode_ = true;
};

TEST_F(AccessibilityNodeInfoDataWrapperTest, Name) {
  AXNodeInfoData node;
  SetProperty(node, AXStringProperty::CLASS_NAME, "");

  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);

  // No attributes.
  ui::AXNodeData data = CallSerialize(wrapper);
  std::string name;
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));

  // Text (empty).
  SetProperty(node, AXStringProperty::TEXT, "");

  data = CallSerialize(wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));

  // Text (non-empty).
  SetProperty(node, AXStringProperty::TEXT, "label text");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("label text", name);

  // Content description (empty), text (non-empty).
  SetProperty(node, AXStringProperty::CONTENT_DESCRIPTION, "");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("label text", name);

  // Content description (non-empty), text (empty).
  SetProperty(node, AXStringProperty::TEXT, "");
  SetProperty(node, AXStringProperty::CONTENT_DESCRIPTION,
              "label content description");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("label content description", name);

  // Content description (non-empty), text (non-empty).
  SetProperty(node, AXStringProperty::TEXT, "label text");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("label content description label text", name);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, NameFromDescendants) {
  AXNodeInfoData root;
  root.id = 10;
  AccessibilityNodeInfoDataWrapper root_wrapper(tree_source(), &root);
  SetIdToWrapper(&root_wrapper);
  SetProperty(root, AXStringProperty::CLASS_NAME, "");
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(root, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({1, 2}));

  AXNodeInfoData child1;
  child1.id = 1;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &child1);
  SetIdToWrapper(&child1_wrapper);
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, true);

  AXNodeInfoData child2;
  child2.id = 2;
  AccessibilityNodeInfoDataWrapper child2_wrapper(tree_source(), &child2);
  SetIdToWrapper(&child2_wrapper);
  SetProperty(child2, AXBooleanProperty::IMPORTANCE, true);

  SetParentId(child1.id, root.id);
  SetParentId(child2.id, root.id);

  // Root node has no name, but has descendants with name.
  // Name from contents can happen if a node is focusable.
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(child1, AXStringProperty::TEXT, "child1 label text");
  SetProperty(child2, AXStringProperty::TEXT, "child2 label text");

  // If the screen reader mode is off, do not compute from descendants.
  set_full_focus_mode(false);

  ui::AXNodeData data = CallSerialize(root_wrapper);
  std::string name;
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));

  data = CallSerialize(child1_wrapper);
  ASSERT_FALSE(data.IsIgnored());
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child1 label text", name);

  data = CallSerialize(child2_wrapper);
  ASSERT_FALSE(data.IsIgnored());
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child2 label text", name);

  // Enable screen reader.
  // Compute the name of the clickable node from descendants, and ignore them.
  set_full_focus_mode(true);

  data = CallSerialize(root_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child1 label text child2 label text", name);

  data = CallSerialize(child1_wrapper);
  ASSERT_TRUE(data.IsIgnored());
  data = CallSerialize(child2_wrapper);
  ASSERT_TRUE(data.IsIgnored());

  // Don't compute name from descendants for scrollable, e.g. ScrollView.
  SetProperty(root, AXBooleanProperty::SCROLLABLE, true);

  data = CallSerialize(root_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));

  SetProperty(root, AXBooleanProperty::SCROLLABLE, false);

  // If one child is clickable, do not use clickable child.
  SetProperty(child1, AXBooleanProperty::CLICKABLE, true);

  data = CallSerialize(root_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child2 label text", name);

  data = CallSerialize(child1_wrapper);
  ASSERT_FALSE(data.IsIgnored());
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child1 label text", name);

  data = CallSerialize(child2_wrapper);
  ASSERT_TRUE(data.IsIgnored());

  // If both children are also clickable, do not use child properties.
  SetProperty(child2, AXBooleanProperty::CLICKABLE, true);

  data = CallSerialize(root_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));

  // If the node has a name, it should override the contents.
  child1.boolean_properties->clear();
  child2.boolean_properties->clear();
  SetProperty(root, AXStringProperty::TEXT, "root label text");

  data = CallSerialize(root_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("root label text", name);

  // Clearing both clickable and name from root, the name should not be
  // populated.
  root.boolean_properties->clear();
  root.string_properties->clear();
  data = CallSerialize(root_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest,
       NameFromDescendants_ignoreWebView) {
  AXNodeInfoData root;
  root.id = 10;
  AccessibilityNodeInfoDataWrapper root_wrapper(tree_source(), &root);
  SetIdToWrapper(&root_wrapper);
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(root, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({1, 2}));
  SetProperty(root, AXStringProperty::CHROME_ROLE, "webView");

  AXNodeInfoData child1;
  child1.id = 1;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &child1);
  SetIdToWrapper(&child1_wrapper);
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, true);
  child1.is_virtual_node = true;

  AXNodeInfoData child2;
  child2.id = 2;
  AccessibilityNodeInfoDataWrapper child2_wrapper(tree_source(), &child2);
  SetIdToWrapper(&child2_wrapper);
  SetProperty(child2, AXBooleanProperty::IMPORTANCE, true);
  child2.is_virtual_node = true;

  SetParentId(child1.id, root.id);
  SetParentId(child2.id, root.id);

  // Root node has no name, but has descendants with name.
  // Name from contents can happen if a node is focusable.
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(child1, AXStringProperty::TEXT, "child1 label text");
  SetProperty(child2, AXStringProperty::TEXT, "child2 label text");

  set_full_focus_mode(true);

  ui::AXNodeData data = CallSerialize(root_wrapper);
  data = CallSerialize(root_wrapper);
  std::string name;
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest,
       NameFromDescendants_fromRoleDescription) {
  AXNodeInfoData root;
  root.id = 10;
  AccessibilityNodeInfoDataWrapper root_wrapper(tree_source(), &root);
  SetIdToWrapper(&root_wrapper);
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(root, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({1, 2}));
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(root, AXStringProperty::ROLE_DESCRIPTION, "rootRole");

  AXNodeInfoData child1;
  child1.id = 1;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &child1);
  SetIdToWrapper(&child1_wrapper);
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(child1, AXStringProperty::TEXT, "child1");

  AXNodeInfoData child2;
  child2.id = 2;
  AccessibilityNodeInfoDataWrapper child2_wrapper(tree_source(), &child2);
  SetIdToWrapper(&child2_wrapper);
  SetProperty(child2, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(child2, AXStringProperty::ROLE_DESCRIPTION, "child2Role");

  SetParentId(child1.id, root.id);
  SetParentId(child2.id, root.id);

  set_full_focus_mode(true);

  ui::AXNodeData data = CallSerialize(root_wrapper);
  data = CallSerialize(root_wrapper);
  std::string name;
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("child1 child2Role", name);  // rootRole is not used.
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, NameFromTextProperties) {
  AXNodeInfoData root;
  root.id = 10;
  AccessibilityNodeInfoDataWrapper root_wrapper(tree_source(), &root);
  SetIdToWrapper(&root_wrapper);
  SetProperty(root, AXStringProperty::CLASS_NAME, "");
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(root, AXBooleanProperty::CLICKABLE, true);
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(root, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({1}));

  AXNodeInfoData child1;
  child1.id = 1;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &child1);
  SetIdToWrapper(&child1_wrapper);

  // Set all properties that will be used in name computation.
  SetProperty(child1, AXStringProperty::TEXT, "text");
  SetProperty(child1, AXStringProperty::CONTENT_DESCRIPTION,
              "content_description");
  SetProperty(child1, AXStringProperty::STATE_DESCRIPTION, "state_description");
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, true);

  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);

  ui::AXNodeData data = CallSerialize(wrapper);

  std::string name;

  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("text", name);

  // Unset TEXT property, and confirm that CONTENT_DESCRIPTION is used as name.
  SetProperty(child1, AXStringProperty::TEXT, "");
  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("content_description", name);

  // Unset CONTENT_DESCRIPTION property, and confirm that STATE_DESCRIPTION is
  // used as name.
  SetProperty(child1, AXStringProperty::CONTENT_DESCRIPTION, "");
  data = CallSerialize(wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("state_description", name);

  // Don't use any text if the node doesn't have importance.
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, false);
  SetProperty(child1, AXStringProperty::TEXT, "text");
  SetProperty(child1, AXStringProperty::CONTENT_DESCRIPTION,
              "content_description");
  data = CallSerialize(wrapper);
  ASSERT_FALSE(data.HasStringAttribute(ax::mojom::StringAttribute::kName));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, TextFieldNameAndValue) {
  AXNodeInfoData node;
  SetProperty(node, AXStringProperty::CLASS_NAME, "");
  SetProperty(node, AXBooleanProperty::EDITABLE, true);

  struct AndroidState {
    std::string content_description, text, hint_text;
    bool showingHint = false;
  };
  struct ChromeState {
    std::string name, value;
  };

  std::vector<std::pair<AndroidState, ChromeState>> test_cases = {
      {
          {"email", "editing_text", "", false},
          {"email", "editing_text"},
      },
      {
          {"email", "", "", false},
          {"email", ""},
      },
      {
          {"", "editing_text", "", false},
          {"", "editing_text"},
      },
      {
          // User input and hint text.
          {"", "editing_text", "[email protected]", false},
          {"[email protected]", "editing_text"},
      },
      {
          // No user input. Hint text is non-empty.
          {"", "[email protected]", "[email protected]", true},
          {"[email protected]", ""},
      },
      {
          // User input is the same as hint text.
          {"", "[email protected]", "[email protected]", false},
          {"[email protected]", "[email protected]"},
      },
      {
          // No user input. Content description and hint tex are non-empty.
          {"email", "[email protected]", "[email protected]", true},
          {"email [email protected]", ""},
      },
      {
          {"email", "editing_text", "[email protected]", false},
          {"email [email protected]", "editing_text"},
      },
      {
          {"", "", "", false},
          {"", ""},
      },
  };

  for (const auto& test_case : test_cases) {
    SetProperty(node, AXStringProperty::CONTENT_DESCRIPTION,
                test_case.first.content_description);
    SetProperty(node, AXStringProperty::TEXT, test_case.first.text);
    SetProperty(node, AXStringProperty::HINT_TEXT, test_case.first.hint_text);
    SetProperty(node, AXBooleanProperty::SHOWING_HINT_TEXT,
                test_case.first.showingHint);

    AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);
    ui::AXNodeData data = CallSerialize(wrapper);

    std::string prop;
    ASSERT_EQ(
        !test_case.second.name.empty(),
        data.GetStringAttribute(ax::mojom::StringAttribute::kName, &prop));
    if (!test_case.second.name.empty()) {
      EXPECT_EQ(test_case.second.name, prop);
    }

    ASSERT_EQ(
        !test_case.second.value.empty(),
        data.GetStringAttribute(ax::mojom::StringAttribute::kValue, &prop));
    if (!test_case.second.value.empty()) {
      EXPECT_EQ(test_case.second.value, prop);
    }
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, StringProperties) {
  AXNodeInfoData node;
  node.id = 10;
  SetProperty(node, AXStringProperty::CLASS_NAME, "");
  SetProperty(node, AXStringProperty::PACKAGE_NAME, "com.android.vending");
  SetProperty(node, AXStringProperty::TOOLTIP, "tooltip text");

  SetNodeRootId(node.id);

  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);

  ui::AXNodeData data = CallSerialize(wrapper);

  std::string prop;
  // Url includes AXTreeId, which is unguessable. Just verifies the prefix.
  ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kUrl, &prop));
  EXPECT_EQ(0U, prop.find("com.android.vending/"));

  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kTooltip, &prop));
  ASSERT_EQ("tooltip text", prop);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, States) {
  AXNodeInfoData node;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);

  // Node is checkable, but not checked.
  SetProperty(node, AXBooleanProperty::CHECKABLE, true);
  SetProperty(node, AXBooleanProperty::CHECKED, false);

  ui::AXNodeData data = CallSerialize(wrapper);
  EXPECT_EQ(ax::mojom::CheckedState::kFalse, data.GetCheckedState());

  // Make the node checked.
  SetProperty(node, AXBooleanProperty::CHECKED, true);

  data = CallSerialize(wrapper);
  EXPECT_EQ(ax::mojom::CheckedState::kTrue, data.GetCheckedState());

  // Make the node expandable (i.e. collapsed).
  AddStandardAction(&node, AXActionType::EXPAND);

  data = CallSerialize(wrapper);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed));
  EXPECT_FALSE(data.HasState(ax::mojom::State::kExpanded));

  // Make the node collapsible (i.e. expanded).
  node.standard_actions = std::nullopt;
  AddStandardAction(&node, AXActionType::COLLAPSE);

  data = CallSerialize(wrapper);
  EXPECT_FALSE(data.HasState(ax::mojom::State::kCollapsed));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kExpanded));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, GridRole) {
  AXNodeInfoData grid;
  grid.id = 1;
  SetProperty(grid, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  grid.collection_info = AXCollectionInfoData::New();
  grid.collection_info->row_count = 2;
  grid.collection_info->column_count = 2;

  AccessibilityNodeInfoDataWrapper grid_wrapper(tree_source(), &grid);
  SetIdToWrapper(&grid_wrapper);

  std::vector<AXNodeInfoData> cells(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = cells[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = i % 2;
    node.collection_item_info->column_index = i / 2;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, grid.id);
  }

  ui::AXNodeData data = CallSerialize(grid_wrapper);
  ASSERT_EQ(ax::mojom::Role::kGrid, data.role);

  // Verify that the cells has role kGridCell
  for (int i = 0; i < 4; i++) {
    auto& cell = cells[i];
    AccessibilityNodeInfoDataWrapper cell_wrapper(tree_source(), &cell);
    ui::AXNodeData cell_data = CallSerialize(cell_wrapper);
    ASSERT_EQ(ax::mojom::Role::kGridCell, cell_data.role);
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, CellIndexes) {
  AXNodeInfoData grid;
  grid.id = 1;
  SetProperty(grid, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  grid.collection_info = AXCollectionInfoData::New();
  grid.collection_info->row_count = 2;
  grid.collection_info->column_count = 2;

  AccessibilityNodeInfoDataWrapper grid_wrapper(tree_source(), &grid);
  SetIdToWrapper(&grid_wrapper);

  std::vector<AXNodeInfoData> cells(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = cells[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = i % 2;
    node.collection_item_info->column_index = i / 2;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, grid.id);
  }

  // Verify that the cells get indexes and aria indexes set.
  for (int i = 0; i < 4; i++) {
    auto& cell = cells[i];
    AccessibilityNodeInfoDataWrapper cell_wrapper(tree_source(), &cell);
    ui::AXNodeData data = CallSerialize(cell_wrapper);
    int expected_row_index = i % 2;
    int expected_col_index = i / 2;
    ASSERT_EQ(
        expected_row_index,
        data.GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex));
    ASSERT_EQ(
        expected_col_index,
        data.GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex));
    ASSERT_EQ(expected_row_index + 1,
              data.GetIntAttribute(ax::mojom::IntAttribute::kAriaCellRowIndex));
    ASSERT_EQ(
        expected_col_index + 1,
        data.GetIntAttribute(ax::mojom::IntAttribute::kAriaCellColumnIndex));
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, SelectedState) {
  AXNodeInfoData grid;
  grid.id = 1;
  SetProperty(grid, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  grid.collection_info = AXCollectionInfoData::New();
  grid.collection_info->row_count = 2;
  grid.collection_info->column_count = 2;

  AccessibilityNodeInfoDataWrapper grid_wrapper(tree_source(), &grid);
  SetIdToWrapper(&grid_wrapper);

  // Make child of grid have cell role, which supports selected state.
  std::vector<AXNodeInfoData> cells(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = cells[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = i % 2;
    node.collection_item_info->column_index = i / 2;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, grid.id);
  }

  AccessibilityNodeInfoDataWrapper cell_wrapper(tree_source(), &cells[0]);

  ui::AXNodeData data = CallSerialize(cell_wrapper);
  ASSERT_EQ(ax::mojom::Role::kGridCell, data.role);
  ASSERT_EQ(0,
            data.GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex));
  ASSERT_EQ(
      0, data.GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex));
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
  ASSERT_FALSE(
      data.HasStringAttribute(ax::mojom::StringAttribute::kDescription));

  // text_node is simple static text, which doesn't supports selected state in
  // the web.
  AXNodeInfoData text_node;
  SetProperty(text_node, AXStringProperty::TEXT, "text.");
  SetProperty(text_node, AXBooleanProperty::SELECTED, true);

  AccessibilityNodeInfoDataWrapper text_wrapper(tree_source(), &text_node);

  std::string description;
  data = CallSerialize(text_wrapper);
  ASSERT_EQ(ax::mojom::Role::kStaticText, data.role);
  ASSERT_FALSE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
  ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                      &description));
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ARC_ACCESSIBILITY_SELECTED_STATUS),
            description);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, HorizontalList) {
  AXNodeInfoData list;
  list.id = 1;
  SetProperty(list, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  list.collection_info = AXCollectionInfoData::New();
  list.collection_info->row_count = 4;
  list.collection_info->column_count = 1;

  AccessibilityNodeInfoDataWrapper list_wrapper(tree_source(), &list);
  SetIdToWrapper(&list_wrapper);

  std::vector<AXNodeInfoData> items(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = items[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = i;
    node.collection_item_info->column_index = 0;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, list.id);
  }

  ui::AXNodeData data = CallSerialize(list_wrapper);
  ASSERT_EQ(ax::mojom::Role::kList, data.role);
  ASSERT_EQ(4, data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));

  // Verify that the items has role kListItem and index
  for (int i = 0; i < 4; i++) {
    auto& item = items[i];
    AccessibilityNodeInfoDataWrapper item_wrapper(tree_source(), &item);
    ui::AXNodeData item_data = CallSerialize(item_wrapper);
    ASSERT_EQ(ax::mojom::Role::kListItem, item_data.role);
    ASSERT_EQ(i, item_data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, VerticalList) {
  AXNodeInfoData list;
  list.id = 1;
  SetProperty(list, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  list.collection_info = AXCollectionInfoData::New();
  list.collection_info->row_count = 1;
  list.collection_info->column_count = 4;

  AccessibilityNodeInfoDataWrapper list_wrapper(tree_source(), &list);
  SetIdToWrapper(&list_wrapper);

  std::vector<AXNodeInfoData> items(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = items[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = 0;
    node.collection_item_info->column_index = i;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, list.id);
  }

  ui::AXNodeData data = CallSerialize(list_wrapper);
  ASSERT_EQ(ax::mojom::Role::kList, data.role);
  ASSERT_EQ(4, data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));

  // Verify that the items has role kListItem and index
  for (int i = 0; i < 4; i++) {
    auto& item = items[i];
    AccessibilityNodeInfoDataWrapper item_wrapper(tree_source(), &item);
    ui::AXNodeData item_data = CallSerialize(item_wrapper);
    ASSERT_EQ(ax::mojom::Role::kListItem, item_data.role);
    ASSERT_EQ(i, item_data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, ListWithOneItem) {
  AXNodeInfoData list;
  list.id = 1;
  SetProperty(list, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({10}));
  list.collection_info = AXCollectionInfoData::New();
  list.collection_info->row_count = 1;
  list.collection_info->column_count = 1;

  AccessibilityNodeInfoDataWrapper list_wrapper(tree_source(), &list);
  SetIdToWrapper(&list_wrapper);

  AXNodeInfoData item;
  item.id = 10;
  item.collection_item_info = AXCollectionItemInfoData::New();
  item.collection_item_info->row_index = 0;
  item.collection_item_info->column_index = 0;
  SetProperty(item, AXBooleanProperty::SELECTED, true);
  SetParentId(item.id, list.id);

  ui::AXNodeData data = CallSerialize(list_wrapper);
  ASSERT_EQ(ax::mojom::Role::kList, data.role);
  ASSERT_EQ(1, data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));

  // Verify that the items has role kListItem and kPosInSet
  AccessibilityNodeInfoDataWrapper item_wrapper(tree_source(), &item);
  ui::AXNodeData item_data = CallSerialize(item_wrapper);
  ASSERT_EQ(ax::mojom::Role::kListItem, item_data.role);
  ASSERT_EQ(0, data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, ListWithoutCount) {
  AXNodeInfoData list;
  list.id = 1;
  SetProperty(list, AXIntListProperty::CHILD_NODE_IDS,
              std::vector<int>({10, 11, 12, 13}));
  list.collection_info = AXCollectionInfoData::New();
  list.collection_info->row_count = -1;
  list.collection_info->column_count = 1;

  AccessibilityNodeInfoDataWrapper list_wrapper(tree_source(), &list);
  SetIdToWrapper(&list_wrapper);

  std::vector<AXNodeInfoData> items(4);
  for (int i = 0; i < 4; i++) {
    AXNodeInfoData& node = items[i];
    node.id = 10 + i;
    node.collection_item_info = AXCollectionItemInfoData::New();
    node.collection_item_info->row_index = i;
    node.collection_item_info->column_index = 1;
    SetProperty(node, AXBooleanProperty::SELECTED, true);

    SetParentId(node.id, list.id);
  }

  ui::AXNodeData data = CallSerialize(list_wrapper);
  ASSERT_EQ(ax::mojom::Role::kList, data.role);
  int setSize;
  ASSERT_FALSE(
      data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize, &setSize));

  // Verify that the items has role kListItem without index
  for (int i = 0; i < 4; i++) {
    auto& item = items[i];
    AccessibilityNodeInfoDataWrapper item_wrapper(tree_source(), &item);
    ui::AXNodeData item_data = CallSerialize(item_wrapper);
    ASSERT_EQ(ax::mojom::Role::kListItem, item_data.role);
    int pos;
    ASSERT_FALSE(
        data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet, &pos));
  }
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, EditTextRole) {
  AXNodeInfoData node;
  node.id = 1;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);

  // Editable node is textField.
  SetProperty(node, AXStringProperty::CLASS_NAME, ui::kAXEditTextClassname);
  SetProperty(node, AXBooleanProperty::EDITABLE, true);

  ui::AXNodeData data = CallSerialize(wrapper);
  EXPECT_EQ(ax::mojom::Role::kTextField, data.role);

  // Non-editable node is not textField even if it has EditTextClassname.
  // When it has text and no children, it is staticText. Otherwise, it's
  // genericContainer.
  SetProperty(node, AXBooleanProperty::EDITABLE, false);
  SetProperty(node, AXStringProperty::TEXT, "text");

  data = CallSerialize(wrapper);
  EXPECT_EQ(ax::mojom::Role::kStaticText, data.role);

  // Add a child.
  SetProperty(node, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({2}));
  AXNodeInfoData child;
  child.id = 2;
  AccessibilityNodeInfoDataWrapper child_wrapper(tree_source(), &child);
  SetIdToWrapper(&child_wrapper);
  SetParentId(child.id, node.id);

  data = CallSerialize(wrapper);
  EXPECT_EQ(ax::mojom::Role::kGenericContainer, data.role);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, StateDescription) {
  AXNodeInfoData node;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);
  node.id = 10;

  // No attributes.
  ui::AXNodeData data = CallSerialize(wrapper);
  std::string description;
  ASSERT_FALSE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                       &description));
  std::string value;
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kValue, &value));

  std::string checked_state_description;
  ASSERT_FALSE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kCheckedStateDescription,
      &checked_state_description));

  // State Description without Range Value should be stored as kDescription
  SetProperty(node, AXStringProperty::STATE_DESCRIPTION, "state description");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                      &description));
  EXPECT_EQ("state description", description);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kValue, &value));
  ASSERT_FALSE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kCheckedStateDescription,
      &checked_state_description));

  // State Description with Range Value should be stored as kValue
  node.range_info = AXRangeInfoData::New();

  data = CallSerialize(wrapper);
  ASSERT_FALSE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                       &description));
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kValue, &value));
  EXPECT_EQ("state description", value);
  ASSERT_FALSE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kCheckedStateDescription,
      &checked_state_description));

  // State Description for compound button should be stores as
  // checkedDescription.
  node.range_info.reset();
  SetProperty(node, AXBooleanProperty::CHECKABLE, true);

  data = CallSerialize(wrapper);
  ASSERT_FALSE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                       &description));
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kValue, &value));
  ASSERT_TRUE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kCheckedStateDescription,
      &checked_state_description));
  EXPECT_EQ("state description", checked_state_description);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, LabeledByLoop) {
  AXNodeInfoData root;
  root.id = 1;
  SetProperty(root, AXIntProperty::LABELED_BY, 2);
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);
  SetIdToWrapper(&wrapper);

  AXNodeInfoData node2;
  node2.id = 2;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &node2);
  SetIdToWrapper(&child1_wrapper);
  SetProperty(node2, AXStringProperty::CONTENT_DESCRIPTION, "node2");
  SetProperty(node2, AXIntProperty::LABELED_BY, 1);

  ui::AXNodeData data = CallSerialize(wrapper);
  std::string name;
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("node2", name);

  data = CallSerialize(child1_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  EXPECT_EQ("node2", name);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, AppendkDescription) {
  AXNodeInfoData node;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);
  node.id = 10;

  // No attributes.
  ui::AXNodeData data = CallSerialize(wrapper);
  std::string description;
  ASSERT_FALSE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                       &description));

  SetProperty(node, AXStringProperty::STATE_DESCRIPTION, "state description");
  SetProperty(node, AXBooleanProperty::SELECTED, true);
  SetProperty(node, AXStringProperty::TEXT, "text");

  data = CallSerialize(wrapper);
  ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
                                      &description));
  EXPECT_EQ("state description " +
                l10n_util::GetStringUTF8(IDS_ARC_ACCESSIBILITY_SELECTED_STATUS),
            description);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, ControlIsFocusable) {
  AXNodeInfoData root;
  root.id = 1;
  SetProperty(root, AXStringProperty::CLASS_NAME, ui::kAXSeekBarClassname);
  SetProperty(root, AXStringProperty::TEXT, "");
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);

  // Check the pre conditions required, before checking whether this
  // control is focusable.
  ui::AXNodeData data = CallSerialize(wrapper);
  std::string name;
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ(ax::mojom::Role::kSlider, data.role);

  ASSERT_TRUE(wrapper.IsFocusableInFullFocusMode());
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, FocusAndClickAction) {
  AXNodeInfoData root;
  root.id = 10;
  AccessibilityNodeInfoDataWrapper root_wrapper(tree_source(), &root);
  SetIdToWrapper(&root_wrapper);
  SetProperty(root, AXStringProperty::CLASS_NAME, "");
  SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(root, AXBooleanProperty::FOCUSABLE, true);
  SetProperty(root, AXBooleanProperty::CLICKABLE, true);
  SetProperty(root, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({1}));

  AXNodeInfoData child1;
  child1.id = 1;
  AccessibilityNodeInfoDataWrapper child1_wrapper(tree_source(), &child1);
  SetIdToWrapper(&child1_wrapper);
  SetProperty(child1, AXBooleanProperty::IMPORTANCE, true);
  SetProperty(child1, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({2}));
  SetParentId(child1.id, root.id);

  AXNodeInfoData child2;
  child2.id = 2;
  AccessibilityNodeInfoDataWrapper child2_wrapper(tree_source(), &child2);
  SetIdToWrapper(&child2_wrapper);
  SetProperty(child2, AXBooleanProperty::IMPORTANCE, true);
  SetParentId(child2.id, child1.id);

  SetProperty(child2, AXStringProperty::CONTENT_DESCRIPTION, "test text");

  set_full_focus_mode(true);

  ui::AXNodeData data = CallSerialize(root_wrapper);
  std::string name;
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("test text", name);
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));

  data = CallSerialize(child1_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_FALSE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_FALSE(data.HasState(ax::mojom::State::kFocusable));

  // Set click and focus action to child1. child1 will be clickable and
  // focusable, and gets ax name from descendants.
  AddStandardAction(&child1, AXActionType::CLICK);
  AddStandardAction(&child1, AXActionType::FOCUS);

  data = CallSerialize(root_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));

  data = CallSerialize(child1_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("test text", name);
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));

  // Same for clear_focus action instead of focus action.
  child1.standard_actions = std::nullopt;
  AddStandardAction(&child1, AXActionType::CLICK);
  AddStandardAction(&child1, AXActionType::CLEAR_FOCUS);

  data = CallSerialize(root_wrapper);
  ASSERT_FALSE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));

  data = CallSerialize(child1_wrapper);
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
  ASSERT_EQ("test text", name);
  ASSERT_TRUE(data.GetBoolAttribute(ax::mojom::BoolAttribute::kClickable));
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, LiveRegionStatus) {
  AXNodeInfoData root;
  root.id = 1;
  mojom::AccessibilityLiveRegionType politeLiveRegion =
      mojom::AccessibilityLiveRegionType::POLITE;
  SetProperty(root, AXIntProperty::LIVE_REGION,
              static_cast<int32_t>(politeLiveRegion));
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);

  // Check that live region status was set on node.
  ui::AXNodeData data = CallSerialize(wrapper);
  std::string val;
  ASSERT_TRUE(
      data.GetStringAttribute(ax::mojom::StringAttribute::kLiveStatus, &val));
  ASSERT_EQ("polite", val);

  ASSERT_TRUE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kContainerLiveStatus, &val));
  ASSERT_EQ("polite", val);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, CustomActions) {
  AXNodeInfoData node;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &node);

  // Check if a custom action is properly serialized.
  AddCustomAction(&node, 300, "This is label");

  ui::AXNodeData data = CallSerialize(wrapper);
  std::vector<int> result_ids;
  std::vector<std::string> result_labels;
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kCustomAction));
  EXPECT_TRUE(data.GetIntListAttribute(
      ax::mojom::IntListAttribute::kCustomActionIds, &result_ids));
  EXPECT_EQ(std::vector<int>({300}), result_ids);
  EXPECT_TRUE(data.GetStringListAttribute(
      ax::mojom::StringListAttribute::kCustomActionDescriptions,
      &result_labels));
  EXPECT_EQ(std::vector<std::string>({"This is label"}), result_labels);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, ActionLabel) {
  AXNodeInfoData root;
  root.id = 1;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);

  // Check if labels for click and long click are serialized as kDoDefaultLabel
  // and kLongClickLabel.
  AddStandardAction(&root, AXActionType::CLICK, "click label");
  AddStandardAction(&root, AXActionType::LONG_CLICK, "long click label");

  ui::AXNodeData data = CallSerialize(wrapper);
  std::string val;
  EXPECT_TRUE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kDoDefaultLabel, &val));
  EXPECT_EQ("click label", val);
  EXPECT_TRUE(data.GetStringAttribute(
      ax::mojom::StringAttribute::kLongClickLabel, &val));
  EXPECT_EQ("long click label", val);
}

TEST_F(AccessibilityNodeInfoDataWrapperTest, InvalidChromeRole) {
  AXNodeInfoData root;
  root.id = 1;
  AccessibilityNodeInfoDataWrapper wrapper(tree_source(), &root);

  SetProperty(root, AXStringProperty::CHROME_ROLE, "ThisRoleDoesNotExist");

  ui::AXNodeData data = CallSerialize(wrapper);
  // This test makes sure that an invalid role name won't make Chrome crash.
}
}  // namespace ax::android