1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/bookmarks/bookmark_editor_view.h" 6 7#include <string> 8 9#include "base/message_loop/message_loop.h" 10#include "base/strings/string_util.h" 11#include "base/strings/utf_string_conversions.h" 12#include "chrome/browser/bookmarks/bookmark_model_factory.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/test/base/testing_profile.h" 15#include "components/bookmarks/browser/bookmark_model.h" 16#include "components/bookmarks/test/bookmark_test_helpers.h" 17#include "content/public/test/test_browser_thread.h" 18#include "testing/gtest/include/gtest/gtest.h" 19#include "ui/views/controls/textfield/textfield.h" 20#include "ui/views/controls/tree/tree_view.h" 21 22using base::ASCIIToUTF16; 23using base::UTF8ToUTF16; 24using base::Time; 25using base::TimeDelta; 26using content::BrowserThread; 27 28// Base class for bookmark editor tests. Creates a BookmarkModel and populates 29// it with test data. 30class BookmarkEditorViewTest : public testing::Test { 31 public: 32 BookmarkEditorViewTest() 33 : ui_thread_(BrowserThread::UI, &message_loop_), 34 file_thread_(BrowserThread::FILE, &message_loop_), 35 model_(NULL) { 36 } 37 38 virtual void SetUp() { 39 profile_.reset(new TestingProfile()); 40 profile_->CreateBookmarkModel(true); 41 42 model_ = BookmarkModelFactory::GetForProfile(profile_.get()); 43 test::WaitForBookmarkModelToLoad(model_); 44 45 AddTestData(); 46 } 47 48 virtual void TearDown() { 49 } 50 51 protected: 52 std::string base_path() const { return "file:///c:/tmp/"; } 53 54 const BookmarkNode* GetNode(const std::string& name) { 55 return model_->GetMostRecentlyAddedUserNodeForURL(GURL(base_path() + name)); 56 } 57 58 BookmarkNode* GetMutableNode(const std::string& name) { 59 return const_cast<BookmarkNode*>(GetNode(name)); 60 } 61 62 BookmarkEditorView::EditorTreeModel* editor_tree_model() { 63 return editor_->tree_model_.get(); 64 } 65 66 void CreateEditor(Profile* profile, 67 const BookmarkNode* parent, 68 const BookmarkEditor::EditDetails& details, 69 BookmarkEditor::Configuration configuration) { 70 editor_.reset(new BookmarkEditorView(profile, parent, details, 71 configuration)); 72 } 73 74 void SetTitleText(const base::string16& title) { 75 editor_->title_tf_->SetText(title); 76 } 77 78 void SetURLText(const base::string16& text) { 79 if (editor_->details_.type != BookmarkEditor::EditDetails::NEW_FOLDER) 80 editor_->url_tf_->SetText(text); 81 } 82 83 void ApplyEdits() { 84 editor_->ApplyEdits(); 85 } 86 87 void ApplyEdits(BookmarkEditorView::EditorNode* node) { 88 editor_->ApplyEdits(node); 89 } 90 91 BookmarkEditorView::EditorNode* AddNewFolder( 92 BookmarkEditorView::EditorNode* parent) { 93 return editor_->AddNewFolder(parent); 94 } 95 96 void NewFolder() { 97 return editor_->NewFolder(); 98 } 99 100 bool URLTFHasParent() { 101 if (editor_->details_.type == BookmarkEditor::EditDetails::NEW_FOLDER) 102 return false; 103 return editor_->url_tf_->parent(); 104 } 105 106 void ExpandAndSelect() { 107 editor_->ExpandAndSelect(); 108 } 109 110 views::TreeView* tree_view() { return editor_->tree_view_; } 111 112 base::MessageLoopForUI message_loop_; 113 content::TestBrowserThread ui_thread_; 114 content::TestBrowserThread file_thread_; 115 116 BookmarkModel* model_; 117 scoped_ptr<TestingProfile> profile_; 118 119 private: 120 // Creates the following structure: 121 // bookmark bar node 122 // a 123 // F1 124 // f1a 125 // F11 126 // f11a 127 // F2 128 // other node 129 // oa 130 // OF1 131 // of1a 132 void AddTestData() { 133 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 134 std::string test_base = base_path(); 135 model_->AddURL(bb_node, 0, ASCIIToUTF16("a"), GURL(test_base + "a")); 136 const BookmarkNode* f1 = model_->AddFolder(bb_node, 1, ASCIIToUTF16("F1")); 137 model_->AddURL(f1, 0, ASCIIToUTF16("f1a"), GURL(test_base + "f1a")); 138 const BookmarkNode* f11 = model_->AddFolder(f1, 1, ASCIIToUTF16("F11")); 139 model_->AddURL(f11, 0, ASCIIToUTF16("f11a"), GURL(test_base + "f11a")); 140 model_->AddFolder(bb_node, 2, ASCIIToUTF16("F2")); 141 142 // Children of the other node. 143 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("oa"), 144 GURL(test_base + "oa")); 145 const BookmarkNode* of1 = 146 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("OF1")); 147 model_->AddURL(of1, 0, ASCIIToUTF16("of1a"), GURL(test_base + "of1a")); 148 } 149 150 scoped_ptr<BookmarkEditorView> editor_; 151}; 152 153// Makes sure the tree model matches that of the bookmark bar model. 154TEST_F(BookmarkEditorViewTest, ModelsMatch) { 155 CreateEditor(profile_.get(), NULL, 156 BookmarkEditor::EditDetails::AddNodeInFolder( 157 NULL, -1, GURL(), base::string16()), 158 BookmarkEditorView::SHOW_TREE); 159 BookmarkEditorView::EditorNode* editor_root = editor_tree_model()->GetRoot(); 160 // The root should have two or three children: bookmark bar, other bookmarks 161 // and conditionally mobile bookmarks. 162 if (model_->mobile_node()->IsVisible()) { 163 ASSERT_EQ(3, editor_root->child_count()); 164 } else { 165 ASSERT_EQ(2, editor_root->child_count()); 166 } 167 168 BookmarkEditorView::EditorNode* bb_node = editor_root->GetChild(0); 169 // The root should have 2 nodes: folder F1 and F2. 170 ASSERT_EQ(2, bb_node->child_count()); 171 ASSERT_EQ(ASCIIToUTF16("F1"), bb_node->GetChild(0)->GetTitle()); 172 ASSERT_EQ(ASCIIToUTF16("F2"), bb_node->GetChild(1)->GetTitle()); 173 174 // F1 should have one child, F11 175 ASSERT_EQ(1, bb_node->GetChild(0)->child_count()); 176 ASSERT_EQ(ASCIIToUTF16("F11"), bb_node->GetChild(0)->GetChild(0)->GetTitle()); 177 178 BookmarkEditorView::EditorNode* other_node = editor_root->GetChild(1); 179 // Other node should have one child (OF1). 180 ASSERT_EQ(1, other_node->child_count()); 181 ASSERT_EQ(ASCIIToUTF16("OF1"), other_node->GetChild(0)->GetTitle()); 182} 183 184// Changes the title and makes sure parent/visual order doesn't change. 185TEST_F(BookmarkEditorViewTest, EditTitleKeepsPosition) { 186 CreateEditor(profile_.get(), NULL, 187 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 188 BookmarkEditorView::SHOW_TREE); 189 SetTitleText(ASCIIToUTF16("new_a")); 190 191 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(0)); 192 193 const BookmarkNode* bb_node = 194 BookmarkModelFactory::GetForProfile(profile_.get())->bookmark_bar_node(); 195 ASSERT_EQ(ASCIIToUTF16("new_a"), bb_node->GetChild(0)->GetTitle()); 196 // The URL shouldn't have changed. 197 ASSERT_TRUE(GURL(base_path() + "a") == bb_node->GetChild(0)->url()); 198} 199 200// Changes the url and makes sure parent/visual order doesn't change. 201TEST_F(BookmarkEditorViewTest, EditURLKeepsPosition) { 202 Time node_time = Time::Now() + TimeDelta::FromDays(2); 203 GetMutableNode("a")->set_date_added(node_time); 204 CreateEditor(profile_.get(), NULL, 205 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 206 BookmarkEditorView::SHOW_TREE); 207 208 SetURLText(UTF8ToUTF16(GURL(base_path() + "new_a").spec())); 209 210 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(0)); 211 212 const BookmarkNode* bb_node = 213 BookmarkModelFactory::GetForProfile(profile_.get())->bookmark_bar_node(); 214 ASSERT_EQ(ASCIIToUTF16("a"), bb_node->GetChild(0)->GetTitle()); 215 // The URL should have changed. 216 ASSERT_TRUE(GURL(base_path() + "new_a") == bb_node->GetChild(0)->url()); 217 ASSERT_TRUE(node_time == bb_node->GetChild(0)->date_added()); 218} 219 220// Moves 'a' to be a child of the other node. 221TEST_F(BookmarkEditorViewTest, ChangeParent) { 222 CreateEditor(profile_.get(), NULL, 223 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 224 BookmarkEditorView::SHOW_TREE); 225 226 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(1)); 227 228 const BookmarkNode* other_node = 229 BookmarkModelFactory::GetForProfile(profile_.get())->other_node(); 230 ASSERT_EQ(ASCIIToUTF16("a"), other_node->GetChild(2)->GetTitle()); 231 ASSERT_TRUE(GURL(base_path() + "a") == other_node->GetChild(2)->url()); 232} 233 234// Moves 'a' to be a child of the other node and changes its url to new_a. 235TEST_F(BookmarkEditorViewTest, ChangeParentAndURL) { 236 Time node_time = Time::Now() + TimeDelta::FromDays(2); 237 GetMutableNode("a")->set_date_added(node_time); 238 CreateEditor(profile_.get(), NULL, 239 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 240 BookmarkEditorView::SHOW_TREE); 241 242 SetURLText(UTF8ToUTF16(GURL(base_path() + "new_a").spec())); 243 244 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(1)); 245 246 const BookmarkNode* other_node = 247 BookmarkModelFactory::GetForProfile(profile_.get())->other_node(); 248 ASSERT_EQ(ASCIIToUTF16("a"), other_node->GetChild(2)->GetTitle()); 249 ASSERT_TRUE(GURL(base_path() + "new_a") == other_node->GetChild(2)->url()); 250 ASSERT_TRUE(node_time == other_node->GetChild(2)->date_added()); 251} 252 253// Creates a new folder and moves a node to it. 254TEST_F(BookmarkEditorViewTest, MoveToNewParent) { 255 CreateEditor(profile_.get(), NULL, 256 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 257 BookmarkEditorView::SHOW_TREE); 258 259 // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21". 260 BookmarkEditorView::EditorNode* f2 = 261 editor_tree_model()->GetRoot()->GetChild(0)->GetChild(1); 262 BookmarkEditorView::EditorNode* f21 = AddNewFolder(f2); 263 f21->SetTitle(ASCIIToUTF16("F21")); 264 BookmarkEditorView::EditorNode* f211 = AddNewFolder(f21); 265 f211->SetTitle(ASCIIToUTF16("F211")); 266 267 // Parent the node to "F21". 268 ApplyEdits(f2); 269 270 const BookmarkNode* bb_node = 271 BookmarkModelFactory::GetForProfile(profile_.get())->bookmark_bar_node(); 272 const BookmarkNode* mf2 = bb_node->GetChild(1); 273 274 // F2 in the model should have two children now: F21 and the node edited. 275 ASSERT_EQ(2, mf2->child_count()); 276 // F21 should be first. 277 ASSERT_EQ(ASCIIToUTF16("F21"), mf2->GetChild(0)->GetTitle()); 278 // Then a. 279 ASSERT_EQ(ASCIIToUTF16("a"), mf2->GetChild(1)->GetTitle()); 280 281 // F21 should have one child, F211. 282 const BookmarkNode* mf21 = mf2->GetChild(0); 283 ASSERT_EQ(1, mf21->child_count()); 284 ASSERT_EQ(ASCIIToUTF16("F211"), mf21->GetChild(0)->GetTitle()); 285} 286 287// Brings up the editor, creating a new URL on the bookmark bar. 288TEST_F(BookmarkEditorViewTest, NewURL) { 289 const BookmarkNode* bb_node = 290 BookmarkModelFactory::GetForProfile(profile_.get())->bookmark_bar_node(); 291 292 CreateEditor(profile_.get(), bb_node, 293 BookmarkEditor::EditDetails::AddNodeInFolder( 294 bb_node, 1, GURL(), base::string16()), 295 BookmarkEditorView::SHOW_TREE); 296 297 SetURLText(UTF8ToUTF16(GURL(base_path() + "a").spec())); 298 SetTitleText(ASCIIToUTF16("new_a")); 299 300 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(0)); 301 302 ASSERT_EQ(4, bb_node->child_count()); 303 304 const BookmarkNode* new_node = bb_node->GetChild(1); 305 306 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 307 EXPECT_TRUE(GURL(base_path() + "a") == new_node->url()); 308} 309 310// Brings up the editor with no tree and modifies the url. 311TEST_F(BookmarkEditorViewTest, ChangeURLNoTree) { 312 CreateEditor(profile_.get(), NULL, 313 BookmarkEditor::EditDetails::EditNode( 314 model_->other_node()->GetChild(0)), 315 BookmarkEditorView::NO_TREE); 316 317 SetURLText(UTF8ToUTF16(GURL(base_path() + "a").spec())); 318 SetTitleText(ASCIIToUTF16("new_a")); 319 320 ApplyEdits(NULL); 321 322 const BookmarkNode* other_node = 323 BookmarkModelFactory::GetForProfile(profile_.get())->other_node(); 324 ASSERT_EQ(2, other_node->child_count()); 325 326 const BookmarkNode* new_node = other_node->GetChild(0); 327 328 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 329 EXPECT_TRUE(GURL(base_path() + "a") == new_node->url()); 330} 331 332// Brings up the editor with no tree and modifies only the title. 333TEST_F(BookmarkEditorViewTest, ChangeTitleNoTree) { 334 CreateEditor(profile_.get(), NULL, 335 BookmarkEditor::EditDetails::EditNode( 336 model_->other_node()->GetChild(0)), 337 BookmarkEditorView::NO_TREE); 338 339 SetTitleText(ASCIIToUTF16("new_a")); 340 341 ApplyEdits(NULL); 342 343 const BookmarkNode* other_node = 344 BookmarkModelFactory::GetForProfile(profile_.get())->other_node(); 345 ASSERT_EQ(2, other_node->child_count()); 346 347 const BookmarkNode* new_node = other_node->GetChild(0); 348 349 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 350} 351 352// Creates a new folder. 353TEST_F(BookmarkEditorViewTest, NewFolder) { 354 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 355 BookmarkEditor::EditDetails details = 356 BookmarkEditor::EditDetails::AddFolder(bb_node, 1); 357 details.urls.push_back(std::make_pair(GURL(base_path() + "x"), 358 ASCIIToUTF16("z"))); 359 CreateEditor(profile_.get(), bb_node, details, BookmarkEditorView::SHOW_TREE); 360 361 // The url field shouldn't be visible. 362 EXPECT_FALSE(URLTFHasParent()); 363 SetTitleText(ASCIIToUTF16("new_F")); 364 365 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(0)); 366 367 // Make sure the folder was created. 368 ASSERT_EQ(4, bb_node->child_count()); 369 const BookmarkNode* new_node = bb_node->GetChild(1); 370 EXPECT_EQ(BookmarkNode::FOLDER, new_node->type()); 371 EXPECT_EQ(ASCIIToUTF16("new_F"), new_node->GetTitle()); 372 // The node should have one child. 373 ASSERT_EQ(1, new_node->child_count()); 374 const BookmarkNode* new_child = new_node->GetChild(0); 375 // Make sure the child url/title match. 376 EXPECT_EQ(BookmarkNode::URL, new_child->type()); 377 EXPECT_EQ(details.urls[0].second, new_child->GetTitle()); 378 EXPECT_EQ(details.urls[0].first, new_child->url()); 379} 380 381// Creates a new folder and selects a different folder for the folder to appear 382// in then the editor is initially created showing. 383TEST_F(BookmarkEditorViewTest, MoveFolder) { 384 BookmarkEditor::EditDetails details = BookmarkEditor::EditDetails::AddFolder( 385 model_->bookmark_bar_node(), -1); 386 details.urls.push_back(std::make_pair(GURL(base_path() + "x"), 387 ASCIIToUTF16("z"))); 388 CreateEditor(profile_.get(), model_->bookmark_bar_node(), 389 details, BookmarkEditorView::SHOW_TREE); 390 391 SetTitleText(ASCIIToUTF16("new_F")); 392 393 // Create the folder in the 'other' folder. 394 ApplyEdits(editor_tree_model()->GetRoot()->GetChild(1)); 395 396 // Make sure the folder we edited is still there. 397 ASSERT_EQ(3, model_->other_node()->child_count()); 398 const BookmarkNode* new_node = model_->other_node()->GetChild(2); 399 EXPECT_EQ(BookmarkNode::FOLDER, new_node->type()); 400 EXPECT_EQ(ASCIIToUTF16("new_F"), new_node->GetTitle()); 401 // The node should have one child. 402 ASSERT_EQ(1, new_node->child_count()); 403 const BookmarkNode* new_child = new_node->GetChild(0); 404 // Make sure the child url/title match. 405 EXPECT_EQ(BookmarkNode::URL, new_child->type()); 406 EXPECT_EQ(details.urls[0].second, new_child->GetTitle()); 407 EXPECT_EQ(details.urls[0].first, new_child->url()); 408} 409 410// Verifies the title of a new folder is updated correctly if ApplyEdits() is 411// is invoked while focus is still on the text field. 412TEST_F(BookmarkEditorViewTest, NewFolderTitleUpdatedOnCommit) { 413 const BookmarkNode* parent = 414 BookmarkModelFactory::GetForProfile(profile_.get())-> 415 bookmark_bar_node() ->GetChild(2); 416 417 CreateEditor(profile_.get(), parent, 418 BookmarkEditor::EditDetails::AddNodeInFolder( 419 parent, 1, GURL(), base::string16()), 420 BookmarkEditorView::SHOW_TREE); 421 ExpandAndSelect(); 422 423 SetURLText(UTF8ToUTF16(GURL(base_path() + "a").spec())); 424 SetTitleText(ASCIIToUTF16("new_a")); 425 426 NewFolder(); 427 ASSERT_TRUE(tree_view()->editor() != NULL); 428 tree_view()->editor()->SetText(ASCIIToUTF16("modified")); 429 ApplyEdits(); 430 431 // Verify the new folder was added and title set appropriately. 432 ASSERT_EQ(1, parent->child_count()); 433 const BookmarkNode* new_folder = parent->GetChild(0); 434 ASSERT_TRUE(new_folder->is_folder()); 435 EXPECT_EQ("modified", base::UTF16ToASCII(new_folder->GetTitle())); 436} 437