| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/tree/tree_view.h" |
| |
| #include <string> |
| |
| #include "base/macros.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/base/models/tree_node_model.h" |
| #include "ui/views/controls/prefix_selector.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/test/views_test_base.h" |
| |
| using ui::TreeModel; |
| using ui::TreeModelNode; |
| using ui::TreeNode; |
| |
| using base::ASCIIToUTF16; |
| |
| namespace views { |
| |
| class TestNode : public TreeNode<TestNode> { |
| public: |
| TestNode() {} |
| ~TestNode() override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestNode); |
| }; |
| |
| // 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(ASCIIToUTF16("root")); |
| Add(model_.GetRoot(), 0, "a"); |
| Add(Add(model_.GetRoot(), 1, "b"), 0, "b1"); |
| Add(model_.GetRoot(), 2, "c"); |
| } |
| |
| protected: |
| TestNode* Add(TestNode* parent, |
| int index, |
| const std::string& title); |
| |
| std::string TreeViewContentsAsString(); |
| |
| std::string GetSelectedNodeTitle(); |
| |
| std::string GetEditingNodeTitle(); |
| |
| TestNode* GetNodeByTitle(const std::string& title); |
| |
| void IncrementSelection(bool next); |
| void CollapseOrSelectParent(); |
| void ExpandOrSelectChild(); |
| int GetRowCount(); |
| PrefixSelector* selector() { return tree_.GetPrefixSelector(); } |
| |
| ui::TreeNodeModel<TestNode> model_; |
| TreeView tree_; |
| |
| private: |
| std::string InternalNodeAsString(TreeView::InternalNode* node); |
| |
| TestNode* GetNodeByTitleImpl(TestNode* node, const base::string16& title); |
| |
| DISALLOW_COPY_AND_ASSIGN(TreeViewTest); |
| }; |
| |
| TestNode* TreeViewTest::Add(TestNode* parent, |
| int 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::GetSelectedNodeTitle() { |
| TreeModelNode* model_node = tree_.GetSelectedNode(); |
| return model_node ? base::UTF16ToASCII(model_node->GetTitle()) |
| : std::string(); |
| } |
| |
| std::string TreeViewTest::GetEditingNodeTitle() { |
| TreeModelNode* model_node = tree_.GetEditingNode(); |
| return model_node ? base::UTF16ToASCII(model_node->GetTitle()) |
| : std::string(); |
| } |
| |
| TestNode* TreeViewTest::GetNodeByTitle(const std::string& title) { |
| return GetNodeByTitleImpl(model_.GetRoot(), ASCIIToUTF16(title)); |
| } |
| |
| void TreeViewTest::IncrementSelection(bool next) { |
| tree_.IncrementSelection(next ? TreeView::INCREMENT_NEXT : |
| TreeView::INCREMENT_PREVIOUS); |
| } |
| |
| void TreeViewTest::CollapseOrSelectParent() { |
| tree_.CollapseOrSelectParent(); |
| } |
| |
| void TreeViewTest::ExpandOrSelectChild() { |
| tree_.ExpandOrSelectChild(); |
| } |
| |
| int TreeViewTest::GetRowCount() { |
| return tree_.GetRowCount(); |
| } |
| |
| TestNode* TreeViewTest::GetNodeByTitleImpl(TestNode* node, |
| const base::string16& title) { |
| if (node->GetTitle() == title) |
| return node; |
| for (int i = 0; i < node->child_count(); ++i) { |
| TestNode* child = GetNodeByTitleImpl(node->GetChild(i), title); |
| if (child) |
| return child; |
| } |
| return NULL; |
| } |
| |
| std::string TreeViewTest::InternalNodeAsString( |
| TreeView::InternalNode* node) { |
| std::string result = base::UTF16ToASCII(node->model_node()->GetTitle()); |
| if (node->is_expanded() && node->child_count()) { |
| result += " ["; |
| for (int i = 0; i < node->child_count(); ++i) { |
| if (i > 0) |
| result += " "; |
| result += InternalNodeAsString(node->GetChild(i)); |
| } |
| result += "]"; |
| } |
| return result; |
| } |
| |
| // Verifies setting model correctly updates internal state. |
| TEST_F(TreeViewTest, SetModel) { |
| tree_.SetModel(&model_); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| } |
| |
| // Verifies SetSelectedNode works. |
| TEST_F(TreeViewTest, SetSelectedNode) { |
| tree_.SetModel(&model_); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| |
| // NULL should clear the selection. |
| tree_.SetSelectedNode(NULL); |
| EXPECT_EQ(std::string(), GetSelectedNodeTitle()); |
| |
| // Select 'c'. |
| tree_.SetSelectedNode(GetNodeByTitle("c")); |
| EXPECT_EQ("c", GetSelectedNodeTitle()); |
| |
| // Select 'b1', which should expand 'b'. |
| tree_.SetSelectedNode(GetNodeByTitle("b1")); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| } |
| |
| // Makes sure SetRootShown doesn't blow up. |
| TEST_F(TreeViewTest, HideRoot) { |
| tree_.SetModel(&model_); |
| tree_.SetRootShown(false); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| EXPECT_EQ(3, GetRowCount()); |
| } |
| |
| // Expands a node and verifies the children are loaded correctly. |
| TEST_F(TreeViewTest, Expand) { |
| tree_.SetModel(&model_); |
| tree_.Expand(GetNodeByTitle("b1")); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root",GetSelectedNodeTitle()); |
| EXPECT_EQ(5, GetRowCount()); |
| } |
| |
| // Collapes 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(5, GetRowCount()); |
| tree_.SetSelectedNode(GetNodeByTitle("b1")); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| tree_.Collapse(GetNodeByTitle("b")); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| // Selected node should have moved to 'b' |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| } |
| |
| // Verifies adding nodes works. |
| TEST_F(TreeViewTest, TreeNodesAdded) { |
| tree_.SetModel(&model_); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| // Add a node between b and c. |
| Add(model_.GetRoot(), 2, "B"); |
| EXPECT_EQ("root [a b B c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(5, GetRowCount()); |
| |
| // Add a child of b1, which hasn't been loaded and shouldn't do anything. |
| Add(GetNodeByTitle("b1"), 0, "b11"); |
| EXPECT_EQ("root [a b B c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(5, GetRowCount()); |
| |
| // Add a child of b, which isn't expanded yet, so it shouldn't effect |
| // anything. |
| Add(GetNodeByTitle("b"), 1, "b2"); |
| EXPECT_EQ("root [a b B c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(5, GetRowCount()); |
| |
| // Expand b and make sure b2 is there. |
| tree_.Expand(GetNodeByTitle("b")); |
| EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root",GetSelectedNodeTitle()); |
| EXPECT_EQ(7, GetRowCount()); |
| } |
| |
| // Verifies 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", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, 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")); |
| model_.Remove(GetNodeByTitle("b1")->parent(), GetNodeByTitle("b1")); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| |
| // Remove 'b'. |
| model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b")); |
| EXPECT_EQ("root [a c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(3, GetRowCount()); |
| |
| // Remove 'c11', shouldn't visually change anything. |
| model_.Remove(GetNodeByTitle("c11")->parent(), GetNodeByTitle("c11")); |
| EXPECT_EQ("root [a c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(3, GetRowCount()); |
| |
| // Select 'c1', remove 'c' and make sure selection changes. |
| tree_.SetSelectedNode(GetNodeByTitle("c1")); |
| EXPECT_EQ("c1", GetSelectedNodeTitle()); |
| model_.Remove(GetNodeByTitle("c")->parent(), GetNodeByTitle("c")); |
| EXPECT_EQ("root [a]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(2, GetRowCount()); |
| |
| tree_.SetRootShown(false); |
| // Add 'b' select it and remove it. Because we're not showing the root |
| // selection should change to 'a'. |
| Add(GetNodeByTitle("root"), 1, "b"); |
| tree_.SetSelectedNode(GetNodeByTitle("b")); |
| model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b")); |
| EXPECT_EQ("root [a]", TreeViewContentsAsString()); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| EXPECT_EQ(1, GetRowCount()); |
| } |
| |
| // Verifies 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_); |
| |
| // Change c11, shouldn't do anything. |
| model_.SetTitle(GetNodeByTitle("c11"), ASCIIToUTF16("c11.new")); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| |
| // Change 'b1', shouldn't do anything. |
| model_.SetTitle(GetNodeByTitle("b1"), ASCIIToUTF16("b1.new")); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| |
| // Change 'b'. |
| model_.SetTitle(GetNodeByTitle("b"), ASCIIToUTF16("b.new")); |
| EXPECT_EQ("root [a b.new c]", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| EXPECT_EQ(4, GetRowCount()); |
| } |
| |
| // Verifies IncrementSelection() works. |
| TEST_F(TreeViewTest, IncrementSelection) { |
| tree_.SetModel(&model_); |
| |
| IncrementSelection(true); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| IncrementSelection(true); |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| IncrementSelection(true); |
| tree_.Expand(GetNodeByTitle("b")); |
| IncrementSelection(false); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| IncrementSelection(true); |
| EXPECT_EQ("c", GetSelectedNodeTitle()); |
| IncrementSelection(true); |
| EXPECT_EQ("c", GetSelectedNodeTitle()); |
| |
| tree_.SetRootShown(false); |
| tree_.SetSelectedNode(GetNodeByTitle("a")); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| IncrementSelection(false); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| } |
| |
| // Verifies CollapseOrSelectParent works. |
| TEST_F(TreeViewTest, CollapseOrSelectParent) { |
| tree_.SetModel(&model_); |
| |
| tree_.SetSelectedNode(GetNodeByTitle("root")); |
| CollapseOrSelectParent(); |
| EXPECT_EQ("root", TreeViewContentsAsString()); |
| EXPECT_EQ("root", GetSelectedNodeTitle()); |
| |
| // Hide the root, which should implicitly expand the root. |
| tree_.SetRootShown(false); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| |
| tree_.SetSelectedNode(GetNodeByTitle("b1")); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| CollapseOrSelectParent(); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| CollapseOrSelectParent(); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| } |
| |
| // Verifies ExpandOrSelectChild works. |
| TEST_F(TreeViewTest, ExpandOrSelectChild) { |
| tree_.SetModel(&model_); |
| |
| tree_.SetSelectedNode(GetNodeByTitle("root")); |
| ExpandOrSelectChild(); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| |
| ExpandOrSelectChild(); |
| EXPECT_EQ("root [a b c]", TreeViewContentsAsString()); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| |
| tree_.SetSelectedNode(GetNodeByTitle("b")); |
| ExpandOrSelectChild(); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| ExpandOrSelectChild(); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| ExpandOrSelectChild(); |
| EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| } |
| |
| // Verify selection is properly updated on each keystroke. |
| TEST_F(TreeViewTest, SelectOnKeyStroke) { |
| tree_.SetModel(&model_); |
| tree_.ExpandAll(model_.GetRoot()); |
| selector()->InsertText(ASCIIToUTF16("b")); |
| EXPECT_EQ("b", GetSelectedNodeTitle()); |
| selector()->InsertText(ASCIIToUTF16("1")); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| |
| // Invoke OnViewBlur() to reset time. |
| selector()->OnViewBlur(); |
| selector()->InsertText(ASCIIToUTF16("z")); |
| EXPECT_EQ("b1", GetSelectedNodeTitle()); |
| |
| selector()->OnViewBlur(); |
| selector()->InsertText(ASCIIToUTF16("a")); |
| EXPECT_EQ("a", GetSelectedNodeTitle()); |
| } |
| |
| // Verifies 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(ASCIIToUTF16("a changed")); |
| tree_.OnDidChangeFocus(NULL, NULL); |
| EXPECT_TRUE(GetNodeByTitle("a changed") != NULL); |
| } |
| |
| } // namespace views |