| // 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 <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/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 AccessibilityEventsVector = std::vector< |
| std::pair<const ui::AXPlatformNodeDelegate*, const ax::mojom::Event>>; |
| |
| const AccessibilityEventsVector accessibility_events() const { |
| return accessibility_events_; |
| } |
| |
| 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(); } |
| |
| ui::TreeNodeModel<TestNode> model_; |
| raw_ptr<TreeView> tree_; |
| 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. |
| AccessibilityEventsVector accessibility_events_; |
| }; |
| |
| void TreeViewTest::SetUp() { |
| ViewsTestBase::SetUp(); |
| widget_ = std::make_unique<Widget>(); |
| Widget::InitParams params = |
| CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| params.bounds = gfx::Rect(0, 0, 200, 200); |
| widget_->Init(std::move(params)); |
| tree_ = widget_->SetContentsView(std::make_unique<TreeView>()); |
| tree_->RequestFocus(); |
| |
| ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback = |
| base::BindRepeating( |
| [](std::vector<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->push_back({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_); |
| } |
| |
| 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 AXVirtualView* ax_view = |
| tree_->GetViewAccessibility().FocusedVirtualChild(); |
| return ax_view ? ax_view->GetData().GetStringAttribute( |
| ax::mojom::StringAttribute::kName) |
| : 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_INTERNAL_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) { |
| std::string ax_view_name; |
| if (ax_view->GetData().GetStringAttribute(ax::mojom::StringAttribute::kName, |
| &ax_view_name) && |
| 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)); |
| } |
| |
| // 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_EQ( |
| (AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetRootAccessibilityView(), ax::mojom::Event::kFocus), |
| std::make_pair(GetRootAccessibilityView(), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| } |
| |
| // 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_EQ( |
| (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("c"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("c"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kStateChanged), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kRowExpanded), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged), |
| std::make_pair(GetAccessibilityViewByName("b1"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("b1"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| } |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetRootAccessibilityView(), |
| ax::mojom::Event::kStateChanged)}), |
| accessibility_events()); |
| } |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("b1"), |
| ax::mojom::Event::kStateChanged), |
| std::make_pair(GetAccessibilityViewByName("b1"), |
| ax::mojom::Event::kRowExpanded), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| } |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kStateChanged), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kRowCollapsed), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| } |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| |
| // 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()); |
| // Added node is not visible, hence no accessibility event needed. |
| EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); |
| |
| // 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()); |
| // Added node is not visible, hence no accessibility event needed. |
| EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kStateChanged), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kRowExpanded), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| } |
| |
| // 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_); |
| |
| // 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()); |
| |
| // Expand b1, then collapse it and remove its only child, b1. This shouldn't |
| // effect the tree. |
| tree_->Expand(GetNodeByTitle("b")); |
| tree_->Collapse(GetNodeByTitle("b")); |
| 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_EQ( |
| (AccessibilityEventsVector{std::make_pair( |
| GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)}), |
| accessibility_events()); |
| |
| // Remove 'b'. |
| 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_EQ( |
| (AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| |
| // 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()); |
| // Node "c11" is not visible, hence no accessibility event needed. |
| EXPECT_EQ(AccessibilityEventsVector(), 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_EQ( |
| (AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| |
| // 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_EQ( |
| (AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("c1"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("c1"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| |
| // 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_EQ( |
| (AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kRowCountChanged)}), |
| accessibility_events()); |
| |
| 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(AccessibilityEventsVector(), 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(AccessibilityEventsVector(), 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetAccessibilityViewByName("b.new"), |
| ax::mojom::Event::kLocationChanged)}), |
| accessibility_events()); |
| } |
| |
| // Verifies that IncrementSelection() works. |
| TEST_F(TreeViewTest, IncrementSelection) { |
| tree_->SetModel(&model_); |
| ClearAccessibilityEvents(); |
| |
| IncrementSelection(true); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| EXPECT_EQ("a", GetSelectedAccessibilityViewName()); |
| EXPECT_EQ( |
| (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| |
| 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 = -1; |
| 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 = -1; |
| 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetRootAccessibilityView(), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| |
| // 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((AccessibilityEventsVector{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_EQ( |
| (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus)}), |
| accessibility_events()); |
| |
| // 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_EQ( |
| (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetAccessibilityViewByName(name), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetAccessibilityViewByName(name), |
| ax::mojom::Event::kFocus)}), |
| accessibility_events()); |
| } |
| |
| // 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 = -1; |
| 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_EQ( |
| (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| |
| // 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 = -1; |
| 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetRootAccessibilityView(), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kSelection), |
| std::make_pair(GetRootAccessibilityView(), |
| ax::mojom::Event::kStateChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kFocus)}), |
| accessibility_events()); |
| |
| // 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_EQ((AccessibilityEventsVector{ |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetTreeAccessibilityView(), |
| ax::mojom::Event::kChildrenChanged), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kFocus), |
| std::make_pair(GetAccessibilityViewByName("a"), |
| ax::mojom::Event::kSelection)}), |
| accessibility_events()); |
| } |
| |
| } // namespace views |