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