// Copyright 2012 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/views/controls/tree/tree_view.h"

#include <numeric>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_node_id_forward.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/models/tree_node_model.h"
#include "ui/compositor/canvas_painter.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/tree/tree_view_controller.h"
#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

using ui::TreeModel;
using ui::TreeModelNode;
using ui::TreeNode;

using base::ASCIIToUTF16;

namespace views {

namespace {

std::string AccessibilityViewAsString(const AXVirtualView& view) {
  std::string result =
      view.GetData().GetStringAttribute(ax::mojom::StringAttribute::kName);
  if (!view.GetChildCount() ||
      view.GetData().HasState(ax::mojom::State::kCollapsed)) {
    // We don't descend into collapsed nodes because they are invisible.
    return result;
  }

  result += " [";
  for (const auto& child_view : view.children()) {
    result += AccessibilityViewAsString(*child_view) + " ";
  }
  result.pop_back();
  result += "]";

  return result;
}

}  // namespace

class TestNode : public TreeNode<TestNode> {
 public:
  TestNode() = default;

  TestNode(const TestNode&) = delete;
  TestNode& operator=(const TestNode&) = delete;

  ~TestNode() override = default;
};

// Creates the following structure:
// 'root'
//   'a'
//   'b'
//     'b1'
//   'c'
class TreeViewTest : public ViewsTestBase {
 public:
  TreeViewTest() : model_(std::make_unique<TestNode>()) {
    static_cast<TestNode*>(model_.GetRoot())->SetTitle(u"root");
    Add(model_.GetRoot(), 0, "a");
    Add(Add(model_.GetRoot(), 1, "b"), 0, "b1");
    Add(model_.GetRoot(), 2, "c");
  }

  TreeViewTest(const TreeViewTest&) = delete;
  TreeViewTest& operator=(const TreeViewTest&) = delete;

  // ViewsTestBase
  void SetUp() override;
  void TearDown() override;

 protected:
  using AccessibilityEventsSet = std::set<
      std::pair<const ui::AXPlatformNodeDelegate*, const ax::mojom::Event>>;

  const AccessibilityEventsSet accessibility_events() const {
    return accessibility_events_;
  }

  bool FiredAccessibilityEvent(std::pair<const ui::AXPlatformNodeDelegate*,
                                         const ax::mojom::Event> event) const;
  void ClearAccessibilityEvents();

  TestNode* Add(TestNode* parent, size_t index, const std::string& title);

  std::string TreeViewContentsAsString();

  std::string TreeViewAccessibilityContentsAsString() const;

  // Gets the selected node from the tree view. The result can be compared with
  // GetSelectedAccessibilityViewName() to check consistency between the tree
  // view state and the accessibility data.
  std::string GetSelectedNodeTitle();

  // Finds the selected node via iterative depth first search over the internal
  // accessibility tree, examining both ignored and unignored nodes. The result
  // can be compared with GetSelectedNodeTitle() to check consistency between
  // the tree view state and the accessibility data.
  std::string GetSelectedAccessibilityViewName() const;

  // Gets the active node from the tree view. The result can be compared with
  // GetSelectedAccessibilityViewName() to check consistency between the tree
  // view state and the accessibility data.
  std::string GetActiveNodeTitle();

  // Gets the active node from the tree view's |ViewAccessibility|. The result
  // can be compared with GetSelectedNodeTitle() to check consistency between
  // the tree view internal state and the accessibility data.
  std::string GetActiveAccessibilityViewName() const;

  std::string GetEditingNodeTitle();

  AXVirtualView* GetRootAccessibilityView() const;

  ViewAXPlatformNodeDelegate* GetTreeAccessibilityView() const;

  TestNode* GetNodeByTitle(const std::string& title);

  const AXVirtualView* GetAccessibilityViewByName(
      const std::string& name) const;

  void IncrementSelection(bool next);
  void CollapseOrSelectParent();
  void ExpandOrSelectChild();
  size_t GetRowCount();
  PrefixSelector* selector() { return tree()->GetPrefixSelector(); }
  TreeView* tree() {
    return const_cast<TreeView*>(std::as_const(*this).tree());
  }
  const TreeView* tree() const {
    return static_cast<const TreeView*>(widget_->GetContentsView());
  }

  ui::TreeNodeModel<TestNode> model_;
  UniqueWidgetPtr widget_;

 private:
  std::string InternalNodeAsString(TreeView::InternalNode* node);

  TestNode* GetNodeByTitleImpl(TestNode* node, const std::u16string& title);

  // Keeps a record of all accessibility events that have been fired on the tree
  // view.
  AccessibilityEventsSet accessibility_events_;
};

void TreeViewTest::SetUp() {
  ViewsTestBase::SetUp();
  widget_ = std::make_unique<Widget>();
  Widget::InitParams params =
      CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
                   Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.bounds = gfx::Rect(0, 0, 200, 200);
  widget_->Init(std::move(params));
  widget_->SetContentsView(std::make_unique<TreeView>());
  tree()->RequestFocus();

  ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
      base::BindRepeating(
          [](std::set<std::pair<const ui::AXPlatformNodeDelegate*,
                                const ax::mojom::Event>>* accessibility_events,
             const ui::AXPlatformNodeDelegate* delegate,
             const ax::mojom::Event event_type) {
            DCHECK(accessibility_events);
            accessibility_events->insert(std::make_pair(delegate, event_type));
          },
          &accessibility_events_);
  tree()->GetViewAccessibility().set_accessibility_events_callback(
      std::move(accessibility_events_callback));
}

void TreeViewTest::TearDown() {
  widget_.reset();
  ViewsTestBase::TearDown();
}

void TreeViewTest::ClearAccessibilityEvents() {
  accessibility_events_.clear();
}

TestNode* TreeViewTest::Add(TestNode* parent,
                            size_t index,
                            const std::string& title) {
  std::unique_ptr<TestNode> new_node = std::make_unique<TestNode>();
  new_node->SetTitle(ASCIIToUTF16(title));
  return model_.Add(parent, std::move(new_node), index);
}

std::string TreeViewTest::TreeViewContentsAsString() {
  return InternalNodeAsString(&tree()->root_);
}

bool TreeViewTest::FiredAccessibilityEvent(
    std::pair<const ui::AXPlatformNodeDelegate*, const ax::mojom::Event> event)
    const {
  return accessibility_events_.find(event) != accessibility_events_.end();
}

std::string TreeViewTest::TreeViewAccessibilityContentsAsString() const {
  AXVirtualView* ax_view = GetRootAccessibilityView();
  if (!ax_view) {
    return "Empty";
  }
  return AccessibilityViewAsString(*ax_view);
}

std::string TreeViewTest::GetSelectedNodeTitle() {
  TreeModelNode* model_node = tree()->GetSelectedNode();
  return model_node ? base::UTF16ToASCII(model_node->GetTitle())
                    : std::string();
}

std::string TreeViewTest::GetSelectedAccessibilityViewName() const {
  const AXVirtualView* ax_view = GetRootAccessibilityView();

  while (ax_view) {
    if (ax_view->GetData().GetBoolAttribute(
            ax::mojom::BoolAttribute::kSelected)) {
      return ax_view->GetData().GetStringAttribute(
          ax::mojom::StringAttribute::kName);
    }

    if (ax_view->children().size()) {
      ax_view = ax_view->children()[0].get();
      continue;
    }

    const AXVirtualView* parent_view = ax_view->virtual_parent_view();
    while (parent_view) {
      size_t sibling_index_in_parent =
          parent_view->GetIndexOf(ax_view).value() + 1;
      if (sibling_index_in_parent < parent_view->children().size()) {
        ax_view = parent_view->children()[sibling_index_in_parent].get();
        break;
      }

      ax_view = parent_view;
      parent_view = parent_view->virtual_parent_view();
    }

    if (!parent_view) {
      break;
    }
  }

  return {};
}

std::string TreeViewTest::GetActiveNodeTitle() {
  TreeModelNode* model_node = tree()->GetActiveNode();
  return model_node ? base::UTF16ToASCII(model_node->GetTitle())
                    : std::string();
}

std::string TreeViewTest::GetActiveAccessibilityViewName() const {
  const ViewAccessibility* ax_view =
      tree()->GetViewAccessibility().GetActiveDescendantView();
  return ax_view ? base::UTF16ToASCII(ax_view->GetCachedName()) : std::string();
}

std::string TreeViewTest::GetEditingNodeTitle() {
  TreeModelNode* model_node = tree()->GetEditingNode();
  return model_node ? base::UTF16ToASCII(model_node->GetTitle())
                    : std::string();
}

AXVirtualView* TreeViewTest::GetRootAccessibilityView() const {
  return tree()->root_.accessibility_view();
}

ViewAXPlatformNodeDelegate* TreeViewTest::GetTreeAccessibilityView() const {
#if !BUILDFLAG(HAS_NATIVE_ACCESSIBILITY)
  return nullptr;  // ViewAXPlatformNodeDelegate is not used on this platform.
#else
  return static_cast<ViewAXPlatformNodeDelegate*>(
      &(tree()->GetViewAccessibility()));
#endif
}

TestNode* TreeViewTest::GetNodeByTitle(const std::string& title) {
  return GetNodeByTitleImpl(model_.GetRoot(), ASCIIToUTF16(title));
}

const AXVirtualView* TreeViewTest::GetAccessibilityViewByName(
    const std::string& name) const {
  const AXVirtualView* ax_view = GetRootAccessibilityView();

  while (ax_view) {
    if (ax_view->GetData().HasStringAttribute(
            ax::mojom::StringAttribute::kName)) {
      const std::string& ax_view_name = ax_view->GetData().GetStringAttribute(
          ax::mojom::StringAttribute::kName);
      if (ax_view_name == name) {
        return ax_view;
      }
    }

    if (ax_view->children().size()) {
      ax_view = ax_view->children()[0].get();
      continue;
    }

    const AXVirtualView* parent_view = ax_view->virtual_parent_view();
    while (parent_view) {
      size_t sibling_index_in_parent =
          parent_view->GetIndexOf(ax_view).value() + 1;
      if (sibling_index_in_parent < parent_view->children().size()) {
        ax_view = parent_view->children()[sibling_index_in_parent].get();
        break;
      }

      ax_view = parent_view;
      parent_view = parent_view->virtual_parent_view();
    }

    if (!parent_view) {
      break;
    }
  }

  return nullptr;
}

void TreeViewTest::IncrementSelection(bool next) {
  tree()->IncrementSelection(next ? TreeView::IncrementType::kNext
                                  : TreeView::IncrementType::kPrevious);
}

void TreeViewTest::CollapseOrSelectParent() {
  tree()->CollapseOrSelectParent();
}

void TreeViewTest::ExpandOrSelectChild() {
  tree()->ExpandOrSelectChild();
}

size_t TreeViewTest::GetRowCount() {
  return tree()->GetRowCount();
}

TestNode* TreeViewTest::GetNodeByTitleImpl(TestNode* node,
                                           const std::u16string& title) {
  if (node->GetTitle() == title) {
    return node;
  }
  for (auto& child : node->children()) {
    TestNode* matching_node = GetNodeByTitleImpl(child.get(), title);
    if (matching_node) {
      return matching_node;
    }
  }
  return nullptr;
}

std::string TreeViewTest::InternalNodeAsString(TreeView::InternalNode* node) {
  std::string result = base::UTF16ToASCII(node->model_node()->GetTitle());
  if (node->is_expanded() && !node->children().empty()) {
    result += std::accumulate(
                  node->children().cbegin() + 1, node->children().cend(),
                  " [" + InternalNodeAsString(node->children().front().get()),
                  [this](const std::string& str, const auto& child) {
                    return str + " " + InternalNodeAsString(child.get());
                  }) +
              "]";
  }
  return result;
}

// Verify properties are accessible via metadata.
TEST_F(TreeViewTest, MetadataTest) {
  tree()->SetModel(&model_);
  test::TestViewMetadata(tree());
}

TEST_F(TreeViewTest, TreeViewPaintCoverage) {
  tree()->SetModel(&model_);
  SkBitmap bitmap;
  gfx::Size size = tree()->size();
  ui::CanvasPainter canvas_painter(&bitmap, size, 1.f, SK_ColorTRANSPARENT,
                                   false);
  widget_->GetRootView()->Paint(
      PaintInfo::CreateRootPaintInfo(canvas_painter.context(), size));
}

TEST_F(TreeViewTest, InitialAccessibilityProperties) {
  tree()->SetModel(&model_);
  ui::AXNodeData data;
  tree()->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kTree);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kVertical));
  EXPECT_EQ(data.GetDefaultActionVerb(),
            ax::mojom::DefaultActionVerb::kActivate);
  EXPECT_EQ(data.GetRestriction(), ax::mojom::Restriction::kReadOnly);
  EXPECT_EQ(data.GetStringAttribute(ax::mojom::StringAttribute::kName), "");
  EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
}

// Verifies setting model correctly updates internal state.
TEST_F(TreeViewTest, SetModel) {
  tree()->SetModel(&model_);
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());

  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetRootAccessibilityView(), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetRootAccessibilityView(), ax::mojom::Event::kSelection)));
}

// Verifies that SetSelectedNode works.
TEST_F(TreeViewTest, SetSelectedNode) {
  tree()->SetModel(&model_);
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());

  // NULL should clear the selection.
  tree()->SetSelectedNode(nullptr);
  EXPECT_EQ(std::string(), GetSelectedNodeTitle());
  EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());

  // Select 'c'.
  ClearAccessibilityEvents();
  tree()->SetSelectedNode(GetNodeByTitle("c"));
  EXPECT_EQ("c", GetSelectedNodeTitle());
  EXPECT_EQ("c", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("c"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("c"), ax::mojom::Event::kSelection)));

  // Select 'b1', which should expand 'b'.
  ClearAccessibilityEvents();
  tree()->SetSelectedNode(GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
  // Node "b" must have been expanded.
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kExpandedChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kRowExpanded)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b1"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b1"), ax::mojom::Event::kSelection)));
}

// Makes sure SetRootShown doesn't blow up.
TEST_F(TreeViewTest, HideRoot) {
  tree()->SetModel(&model_);
  ClearAccessibilityEvents();
  tree()->SetRootShown(false);
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_EQ(3u, GetRowCount());

  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetRootAccessibilityView(), ax::mojom::Event::kTreeChanged)));

  // Root should be ignored when hidden.
  ui::AXNodeData data = GetRootAccessibilityView()->GetData();
  EXPECT_TRUE(data.HasState(ax::mojom::State::kIgnored));
}

// Expands a node and verifies the children are loaded correctly.
TEST_F(TreeViewTest, Expand) {
  tree()->SetModel(&model_);
  ClearAccessibilityEvents();
  tree()->Expand(GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(5u, GetRowCount());

  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b1"), ax::mojom::Event::kExpandedChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b1"), ax::mojom::Event::kRowExpanded)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));
}

// Collapse a node and verifies state.
TEST_F(TreeViewTest, Collapse) {
  tree()->SetModel(&model_);
  tree()->Expand(GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ(5u, GetRowCount());
  tree()->SetSelectedNode(GetNodeByTitle("b1"));
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());

  ClearAccessibilityEvents();
  tree()->Collapse(GetNodeByTitle("b"));
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  // Selected node should have moved to 'b'
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());

  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kExpandedChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kRowCollapsed)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));
}

// Verifies that adding nodes works.
TEST_F(TreeViewTest, TreeNodesAdded) {
  tree()->SetModel(&model_);
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());

  // Add a node between b and c.
  ClearAccessibilityEvents();
  Add(model_.GetRoot(), 2, "B");
  EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(5u, GetRowCount());

  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));

  // Add a child of b1, which hasn't been loaded and shouldn't do anything.
  ClearAccessibilityEvents();
  Add(GetNodeByTitle("b1"), 0, "b11");
  EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(5u, GetRowCount());

  // Add a child of b, which isn't expanded yet, so it shouldn't effect
  // anything.
  ClearAccessibilityEvents();
  Add(GetNodeByTitle("b"), 1, "b2");
  EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(5u, GetRowCount());

  // Expand b and make sure b2 is there.
  ClearAccessibilityEvents();
  tree()->Expand(GetNodeByTitle("b"));
  EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(7u, GetRowCount());
  // Since the added node was not visible when it was added, no extra events
  // other than the ones for expanding a node are needed.
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kExpandedChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kRowExpanded)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));
}

// Verifies that removing nodes works.
TEST_F(TreeViewTest, TreeNodesRemoved) {
  // Add c1 as a child of c and c11 as a child of c1.
  Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
  tree()->SetModel(&model_);

  int root_children_set_size = 3;
  const int root_pos_in_set = 1;
  const int root_set_size = 1;
  int a_pos_in_set = 1;
  int b_pos_in_set = 2;
  int c_pos_in_set = 3;

  ui::AXNodeData data;

  // Remove c11, which shouldn't have any effect on the tree.
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  data = GetAccessibilityViewByName("root")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel),
            1);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            root_pos_in_set);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kInvisible));

  data = GetAccessibilityViewByName("a")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            a_pos_in_set);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kFocus));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kScrollToMakeVisible));
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel),
            2);

  data = GetAccessibilityViewByName("b")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            b_pos_in_set);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kFocus));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kScrollToMakeVisible));
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel),
            2);

  data = GetAccessibilityViewByName("c")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            c_pos_in_set);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kFocus));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kScrollToMakeVisible));
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel),
            2);

  // Expand b1, then collapse it and remove its only child, b1. This shouldn't
  // effect the tree.
  tree()->Expand(GetNodeByTitle("b"));
  data = GetAccessibilityViewByName("b1")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize), 1);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet), 1);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kFocus));
  EXPECT_TRUE(data.HasAction(ax::mojom::Action::kScrollToMakeVisible));
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel),
            3);

  tree()->Collapse(GetNodeByTitle("b"));
  data = GetAccessibilityViewByName("b1")->GetData();
  EXPECT_TRUE(data.HasState(ax::mojom::State::kInvisible));
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("b1")->parent(), GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));

  // Remove 'b'.
  root_children_set_size = 2;
  c_pos_in_set = 2;
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
  EXPECT_EQ("root [a c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(3u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kActiveDescendantChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));
  data = GetAccessibilityViewByName("root")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            root_pos_in_set);

  data = GetAccessibilityViewByName("a")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            a_pos_in_set);

  data = GetAccessibilityViewByName("c")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            c_pos_in_set);

  // Remove 'c11', shouldn't visually change anything.
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("c11")->parent(), GetNodeByTitle("c11"));
  EXPECT_EQ("root [a c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(3u, GetRowCount());
  data = GetAccessibilityViewByName("root")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            root_pos_in_set);

  data = GetAccessibilityViewByName("a")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            a_pos_in_set);

  data = GetAccessibilityViewByName("c")->GetData();
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize),
            root_children_set_size);
  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet),
            c_pos_in_set);

  // Node "c11" is not visible, hence no accessibility event needed.
  EXPECT_EQ(AccessibilityEventsSet(), accessibility_events());

  // Select 'c1', remove 'c' and make sure selection changes.
  tree()->SetSelectedNode(GetNodeByTitle("c1"));
  EXPECT_EQ("c1", GetSelectedNodeTitle());
  EXPECT_EQ("c1", GetSelectedAccessibilityViewName());
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("c")->parent(), GetNodeByTitle("c"));
  EXPECT_EQ("root [a]", TreeViewContentsAsString());
  EXPECT_EQ("root [a]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_EQ(2u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kActiveDescendantChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));

  // Add 'c1', 'c2', 'c3', select 'c2', remove it and 'c3" should be selected.
  Add(GetNodeByTitle("a"), 0, "c1");
  Add(GetNodeByTitle("a"), 1, "c2");
  Add(GetNodeByTitle("a"), 2, "c3");
  tree()->SetSelectedNode(GetNodeByTitle("c2"));
  model_.Remove(GetNodeByTitle("c2")->parent(), GetNodeByTitle("c2"));
  EXPECT_EQ("root [a [c1 c3]]", TreeViewContentsAsString());
  EXPECT_EQ("root [a [c1 c3]]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("c3", GetSelectedNodeTitle());
  EXPECT_EQ("c3", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());

  // Now delete 'c3' and then 'c1' should be selected.
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("c3")->parent(), GetNodeByTitle("c3"));
  EXPECT_EQ("root [a [c1]]", TreeViewContentsAsString());
  EXPECT_EQ("root [a [c1]]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("c1", GetSelectedNodeTitle());
  EXPECT_EQ("c1", GetSelectedAccessibilityViewName());
  EXPECT_EQ(3u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kActiveDescendantChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("c1"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("c1"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));

  // Finally delete 'c1' and then 'a' should be selected.
  ClearAccessibilityEvents();
  model_.Remove(GetNodeByTitle("c1")->parent(), GetNodeByTitle("c1"));
  EXPECT_EQ("root [a]", TreeViewContentsAsString());
  EXPECT_EQ("root [a]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_EQ(2u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kActiveDescendantChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kRowCountChanged)));

  tree()->SetRootShown(false);
  // Add 'b' and 'c', select 'b' and remove it. Selection should change to 'c'.
  Add(GetNodeByTitle("root"), 1, "b");
  Add(GetNodeByTitle("root"), 2, "c");
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
  EXPECT_EQ("root [a c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("c", GetSelectedNodeTitle());
  EXPECT_EQ("c", GetSelectedAccessibilityViewName());
  EXPECT_EQ(2u, GetRowCount());
}

class TestController : public TreeViewController {
 public:
  void OnTreeViewSelectionChanged(TreeView* tree_view) override {
    call_count_++;
  }

  bool CanEdit(TreeView* tree_view, ui::TreeModelNode* node) override {
    return true;
  }

  int selection_change_count() const { return call_count_; }

 private:
  int call_count_ = 0;
};

TEST_F(TreeViewTest, RemovingLastNodeNotifiesSelectionChanged) {
  TestController controller;
  tree()->SetController(&controller);
  tree()->SetRootShown(false);
  tree()->SetModel(&model_);

  // Remove all but one node.
  model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
  model_.Remove(GetNodeByTitle("c")->parent(), GetNodeByTitle("c"));
  tree()->SetSelectedNode(GetNodeByTitle("a"));
  EXPECT_EQ("root [a]", TreeViewContentsAsString());
  EXPECT_EQ("root [a]", TreeViewAccessibilityContentsAsString());

  const int prior_call_count = controller.selection_change_count();
  // Remove the final node and expect
  // |TestController::OnTreeViewSelectionChanged| to be called.
  model_.Remove(GetNodeByTitle("a")->parent(), GetNodeByTitle("a"));
  EXPECT_EQ(prior_call_count + 1, controller.selection_change_count());
}

// Verifies that changing a node title works.
TEST_F(TreeViewTest, TreeNodeChanged) {
  // Add c1 as a child of c and c11 as a child of c1.
  Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
  tree()->SetModel(&model_);
  ClearAccessibilityEvents();

  // Change c11, shouldn't do anything.
  model_.SetTitle(GetNodeByTitle("c11"), u"c11.new");
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  EXPECT_EQ(AccessibilityEventsSet(), accessibility_events());

  // Change 'b1', shouldn't do anything.
  ClearAccessibilityEvents();
  model_.SetTitle(GetNodeByTitle("b1"), u"b1.new");
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  EXPECT_EQ(AccessibilityEventsSet(), accessibility_events());

  // Change 'b'.
  ClearAccessibilityEvents();
  model_.SetTitle(GetNodeByTitle("b"), u"b.new");
  EXPECT_EQ("root [a b.new c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b.new c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetAccessibilityViewByName("b.new"),
                     ax::mojom::Event::kLocationChanged)));
}

// Verifies that IncrementSelection() works.
TEST_F(TreeViewTest, IncrementSelection) {
  tree()->SetModel(&model_);
  ClearAccessibilityEvents();

  IncrementSelection(true);
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));

  IncrementSelection(true);
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  IncrementSelection(true);
  tree()->Expand(GetNodeByTitle("b"));
  IncrementSelection(false);
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
  IncrementSelection(true);
  EXPECT_EQ("c", GetSelectedNodeTitle());
  EXPECT_EQ("c", GetSelectedAccessibilityViewName());
  IncrementSelection(true);
  EXPECT_EQ("c", GetSelectedNodeTitle());
  EXPECT_EQ("c", GetSelectedAccessibilityViewName());

  tree()->SetRootShown(false);
  tree()->SetSelectedNode(GetNodeByTitle("a"));
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  IncrementSelection(false);
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
}

// Verifies that CollapseOrSelectParent works.
TEST_F(TreeViewTest, CollapseOrSelectParent) {
  tree()->SetModel(&model_);

  tree()->SetSelectedNode(GetNodeByTitle("root"));
  CollapseOrSelectParent();
  EXPECT_EQ("root", TreeViewContentsAsString());
  EXPECT_EQ("root", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());

  // Hide the root, which should implicitly expand the root.
  tree()->SetRootShown(false);
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());

  tree()->SetSelectedNode(GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
  CollapseOrSelectParent();
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  CollapseOrSelectParent();
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
}

// Verifies that ExpandOrSelectChild works.
TEST_F(TreeViewTest, ExpandOrSelectChild) {
  tree()->SetModel(&model_);

  tree()->SetSelectedNode(GetNodeByTitle("root"));
  ExpandOrSelectChild();
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());

  ExpandOrSelectChild();
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());

  tree()->SetSelectedNode(GetNodeByTitle("b"));
  ExpandOrSelectChild();
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());

  ExpandOrSelectChild();
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());

  ExpandOrSelectChild();
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
}

// Verify that selection is properly updated on each keystroke.
TEST_F(TreeViewTest, SelectOnKeyStroke) {
  tree()->SetModel(&model_);
  tree()->ExpandAll(model_.GetRoot());
  selector()->InsertText(
      u"b",
      ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  selector()->InsertText(
      u"1",
      ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());

  // Invoke OnViewBlur() to reset time.
  selector()->OnViewBlur();
  selector()->InsertText(
      u"z",
      ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  EXPECT_EQ("b1", GetSelectedNodeTitle());
  EXPECT_EQ("b1", GetSelectedAccessibilityViewName());

  selector()->OnViewBlur();
  selector()->InsertText(
      u"a",
      ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
}

// Verifies that edits are committed when focus is lost.
TEST_F(TreeViewTest, CommitOnFocusLost) {
  tree()->SetModel(&model_);

  tree()->SetSelectedNode(GetNodeByTitle("root"));
  ExpandOrSelectChild();
  tree()->SetEditable(true);
  tree()->StartEditing(GetNodeByTitle("a"));
  tree()->editor()->SetText(u"a changed");
  tree()->OnDidChangeFocus(nullptr, nullptr);
  EXPECT_TRUE(GetNodeByTitle("a changed") != nullptr);

  ASSERT_NE(nullptr, GetRootAccessibilityView());
  ASSERT_LE(1u, GetRootAccessibilityView()->children().size());
  EXPECT_EQ(
      "a changed",
      GetRootAccessibilityView()->children()[0]->GetData().GetStringAttribute(
          ax::mojom::StringAttribute::kName));
}

// Verifies that virtual accessible actions go to virtual view targets.
TEST_F(TreeViewTest, VirtualAccessibleAction) {
  tree()->SetModel(&model_);
  tree()->Expand(GetNodeByTitle("b1"));
  EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ(5u, GetRowCount());

  // Set to nullptr should clear the selection.
  tree()->SetSelectedNode(nullptr);
  EXPECT_EQ(std::string(), GetActiveNodeTitle());
  EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
  EXPECT_EQ(std::string(), GetSelectedNodeTitle());
  EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());

  // Test using each virtual view as target.
  ui::AXActionData data;
  const std::string test_cases[] = {"root", "a", "b", "b1", "c"};
  for (const std::string& name : test_cases) {
    data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
    data.action = ax::mojom::Action::kDoDefault;
    EXPECT_TRUE(tree()->HandleAccessibleAction(data));
    EXPECT_EQ(name, GetActiveNodeTitle());
    EXPECT_EQ(name, GetActiveAccessibilityViewName());
    EXPECT_EQ(name, GetSelectedNodeTitle());
    EXPECT_EQ(name, GetSelectedAccessibilityViewName());
  }

  // Do nothing when a valid node id is not provided. This can happen if the
  // actions target the owner view itself.
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  data.target_node_id = ui::kInvalidAXNodeID;
  data.action = ax::mojom::Action::kDoDefault;
  EXPECT_FALSE(tree()->HandleAccessibleAction(data));
  EXPECT_EQ("b", GetActiveNodeTitle());
  EXPECT_EQ("b", GetActiveAccessibilityViewName());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());

  // Check that the active node is set if assistive technologies set focus.
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  data.target_node_id = GetAccessibilityViewByName("a")->GetData().id;
  data.action = ax::mojom::Action::kFocus;
  EXPECT_TRUE(tree()->HandleAccessibleAction(data));
  EXPECT_EQ("a", GetActiveNodeTitle());
  EXPECT_EQ("a", GetActiveAccessibilityViewName());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());

  // Do not handle accessible actions when no node is selected.
  tree()->SetSelectedNode(nullptr);
  data.target_node_id = ui::kInvalidAXNodeID;
  data.action = ax::mojom::Action::kDoDefault;
  EXPECT_FALSE(tree()->HandleAccessibleAction(data));
  EXPECT_EQ(std::string(), GetActiveNodeTitle());
  EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
  EXPECT_EQ(std::string(), GetSelectedNodeTitle());
  EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
}

// Verifies that accessibility focus events get fired for the correct nodes when
// the tree view is given focus.
TEST_F(TreeViewTest, OnFocusAccessibilityEvents) {
  // Without keyboard focus, model changes should not fire focus events.
  tree()->GetFocusManager()->ClearFocus();
  EXPECT_FALSE(tree()->HasFocus());
  tree()->SetModel(&model_);
  EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
  EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
  EXPECT_EQ("root", GetSelectedNodeTitle());
  EXPECT_EQ("root", GetSelectedAccessibilityViewName());
  EXPECT_EQ(4u, GetRowCount());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetRootAccessibilityView(), ax::mojom::Event::kSelection)));

  // The initial focus should fire a focus event for the active node
  // (in this case, the root node).
  ClearAccessibilityEvents();
  tree()->RequestFocus();
  EXPECT_TRUE(tree()->HasFocus());
  EXPECT_EQ((AccessibilityEventsSet{std::make_pair(GetRootAccessibilityView(),
                                                   ax::mojom::Event::kFocus)}),
            accessibility_events());

  // Focus clear and restore should fire a focus event for the active node.
  ClearAccessibilityEvents();
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  tree()->SetActiveNode(GetNodeByTitle("a"));
  EXPECT_EQ("a", GetActiveNodeTitle());
  EXPECT_EQ("a", GetActiveAccessibilityViewName());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  tree()->GetFocusManager()->ClearFocus();
  EXPECT_FALSE(tree()->HasFocus());
  tree()->GetFocusManager()->RestoreFocusedView();
  EXPECT_TRUE(tree()->HasFocus());
  EXPECT_EQ("a", GetActiveNodeTitle());
  EXPECT_EQ("a", GetActiveAccessibilityViewName());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  // Without keyboard focus, selection should not fire focus events.
  ClearAccessibilityEvents();
  tree()->GetFocusManager()->ClearFocus();
  tree()->SetSelectedNode(GetNodeByTitle("a"));
  EXPECT_FALSE(tree()->HasFocus());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kSelectedChildrenChanged)));

  // A direct focus action on a tree item should give focus to the tree view but
  // only fire a focus event for the target node.
  ui::AXActionData data;
  const std::string test_cases[] = {"root", "a", "b", "c"};
  for (const std::string& name : test_cases) {
    ClearAccessibilityEvents();
    tree()->GetFocusManager()->ClearFocus();
    EXPECT_FALSE(tree()->HasFocus());
    data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
    data.action = ax::mojom::Action::kFocus;
    EXPECT_TRUE(tree()->HandleAccessibleAction(data));
    EXPECT_TRUE(tree()->HasFocus());
    EXPECT_EQ(name, GetActiveNodeTitle());
    EXPECT_EQ(name, GetActiveAccessibilityViewName());
    EXPECT_EQ(name, GetSelectedNodeTitle());
    EXPECT_EQ(name, GetSelectedAccessibilityViewName());
    EXPECT_TRUE(FiredAccessibilityEvent(
        std::make_pair(GetTreeAccessibilityView(),
                       ax::mojom::Event::kActiveDescendantChanged)));
    EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
        GetAccessibilityViewByName(name), ax::mojom::Event::kSelection)));
    EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
        GetAccessibilityViewByName(name), ax::mojom::Event::kFocus)));
    EXPECT_TRUE(FiredAccessibilityEvent(
        std::make_pair(GetTreeAccessibilityView(),
                       ax::mojom::Event::kSelectedChildrenChanged)));
  }

  // A direct focus action on the tree view itself with an active node should
  // have no effect.
  ClearAccessibilityEvents();
  tree()->GetFocusManager()->ClearFocus();
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  data.target_node_id = ui::kInvalidAXNodeID;
  data.action = ax::mojom::Action::kFocus;
  EXPECT_FALSE(tree()->HandleAccessibleAction(data));
  EXPECT_FALSE(tree()->HasFocus());
  EXPECT_EQ("b", GetActiveNodeTitle());
  EXPECT_EQ("b", GetActiveAccessibilityViewName());
  EXPECT_EQ("b", GetSelectedNodeTitle());
  EXPECT_EQ("b", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kActiveDescendantChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("b"), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kSelectedChildrenChanged)));

  // A direct focus action on a tree view without an active node (i.e. empty
  // tree) should fire a focus event for the tree view.
  ClearAccessibilityEvents();
  tree()->GetFocusManager()->ClearFocus();
  ui::TreeNodeModel<TestNode> empty_model(std::make_unique<TestNode>());
  static_cast<TestNode*>(empty_model.GetRoot())->SetTitle(u"root");
  tree()->SetModel(&empty_model);
  tree()->SetRootShown(false);
  data.target_node_id = ui::kInvalidAXNodeID;
  data.action = ax::mojom::Action::kFocus;
  EXPECT_TRUE(tree()->HandleAccessibleAction(data));
  EXPECT_TRUE(tree()->HasFocus());
  EXPECT_EQ(std::string(), GetActiveNodeTitle());
  EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
  EXPECT_EQ(std::string(), GetSelectedNodeTitle());
  EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetRootAccessibilityView(), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kSelection)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetRootAccessibilityView(), ax::mojom::Event::kTreeChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus)));

  // When a focused empty tree is populated with nodes, it should immediately
  // hand off focus to one of them and select it.
  ClearAccessibilityEvents();
  tree()->SetModel(&model_);
  EXPECT_EQ("a", GetActiveNodeTitle());
  EXPECT_EQ("a", GetActiveAccessibilityViewName());
  EXPECT_EQ("a", GetSelectedNodeTitle());
  EXPECT_EQ("a", GetSelectedAccessibilityViewName());
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus)));
  EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair(
      GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection)));

  // On node selection, ensure the focus event is fired before the selection
  // event.
  ClearAccessibilityEvents();
  tree()->SetModel(&model_);
  tree()->SetSelectedNode(GetNodeByTitle("b"));
  std::vector<ax::mojom::Event> events;
  for (const auto& event : accessibility_events()) {
    if (event.first == GetAccessibilityViewByName("b")) {
      events.push_back(event.second);
    }
  }

  auto focus_it =
      std::find(events.begin(), events.end(), ax::mojom::Event::kFocus);
  auto selection_it =
      std::find(events.begin(), events.end(), ax::mojom::Event::kSelection);
  ASSERT_NE(focus_it, events.end());
  ASSERT_NE(selection_it, events.end());
  EXPECT_LT(std::distance(events.begin(), focus_it),
            std::distance(events.begin(), selection_it));
}

TEST_F(TreeViewTest, ExpandSetsAccessibilityExpandedState) {
  tree()->SetModel(&model_);
  ClearAccessibilityEvents();

  tree()->Expand(GetNodeByTitle("b1"));

  const AXVirtualView* ax_view = GetAccessibilityViewByName("b1");
  ASSERT_TRUE(ax_view);
  EXPECT_TRUE(ax_view->GetData().HasState(ax::mojom::State::kExpanded));
  EXPECT_FALSE(ax_view->GetData().HasState(ax::mojom::State::kCollapsed));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(ax_view, ax::mojom::Event::kExpandedChanged)));
}

TEST_F(TreeViewTest, CollapseSetsAccessibilityCollapsedState) {
  tree()->SetModel(&model_);
  tree()->Expand(GetNodeByTitle("b1"));
  ClearAccessibilityEvents();

  tree()->Collapse(GetNodeByTitle("b"));

  const AXVirtualView* ax_view = GetAccessibilityViewByName("b");
  ASSERT_TRUE(ax_view);
  EXPECT_TRUE(ax_view->GetData().HasState(ax::mojom::State::kCollapsed));
  EXPECT_FALSE(ax_view->GetData().HasState(ax::mojom::State::kExpanded));
  EXPECT_TRUE(FiredAccessibilityEvent(
      std::make_pair(ax_view, ax::mojom::Event::kExpandedChanged)));
}

TEST_F(TreeViewTest, InitialAccessibilityExpandedState) {
  tree()->SetModel(&model_);
  tree()->Expand(GetNodeByTitle("b1"));

  const AXVirtualView* b1_ax = GetAccessibilityViewByName("b1");
  ASSERT_TRUE(b1_ax);
  EXPECT_TRUE(b1_ax->GetData().HasState(ax::mojom::State::kExpanded));
  EXPECT_FALSE(b1_ax->GetData().HasState(ax::mojom::State::kCollapsed));

  const AXVirtualView* a_ax = GetAccessibilityViewByName("a");
  ASSERT_TRUE(a_ax);
  EXPECT_FALSE(a_ax->GetData().HasState(ax::mojom::State::kExpanded));
  EXPECT_TRUE(a_ax->GetData().HasState(ax::mojom::State::kCollapsed));
}

}  // namespace views
