blob: fa4c7bf7b0cf43fe1e21e70dd4d103d7a401a136 [file] [log] [blame]
// 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