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/gtk/bookmarks/bookmark_editor_gtk.h" 6 7#include <gtk/gtk.h> 8 9#include <string> 10 11#include "base/compiler_specific.h" 12#include "base/strings/string_util.h" 13#include "base/strings/utf_string_conversions.h" 14#include "chrome/browser/bookmarks/bookmark_model.h" 15#include "chrome/browser/bookmarks/bookmark_model_factory.h" 16#include "chrome/browser/ui/gtk/bookmarks/bookmark_tree_model.h" 17#include "chrome/test/base/testing_profile.h" 18#include "chrome/test/base/ui_test_utils.h" 19#include "content/public/test/test_browser_thread.h" 20#include "testing/gtest/include/gtest/gtest.h" 21 22using base::Time; 23using base::TimeDelta; 24using bookmark_utils::GetTitleFromTreeIter; 25using content::BrowserThread; 26 27// Base class for bookmark editor tests. This class is a copy from 28// bookmark_editor_view_unittest.cc, and all the tests in this file are 29// GTK-ifications of the corresponding views tests. Testing here is really 30// important because on Linux, we make round trip copies from chrome's 31// BookmarkModel class to GTK's native GtkTreeStore. 32class BookmarkEditorGtkTest : public testing::Test { 33 public: 34 BookmarkEditorGtkTest() 35 : model_(NULL), 36 ui_thread_(BrowserThread::UI, &message_loop_), 37 file_thread_(BrowserThread::FILE, &message_loop_) { 38 } 39 40 virtual void SetUp() OVERRIDE { 41 profile_.reset(new TestingProfile()); 42 profile_->CreateBookmarkModel(true); 43 44 model_ = BookmarkModelFactory::GetForProfile(profile_.get()); 45 ui_test_utils::WaitForBookmarkModelToLoad(model_); 46 47 AddTestData(); 48 } 49 50 virtual void TearDown() OVERRIDE { 51 } 52 53 protected: 54 std::string base_path() const { return "file:///c:/tmp/"; } 55 56 const BookmarkNode* GetNode(const std::string& name) { 57 return model_->GetMostRecentlyAddedNodeForURL(GURL(base_path() + name)); 58 } 59 60 BookmarkModel* model_; 61 scoped_ptr<TestingProfile> profile_; 62 63 private: 64 // Creates the following structure: 65 // bookmark bar node 66 // a 67 // F1 68 // f1a 69 // F11 70 // f11a 71 // F2 72 // other node 73 // oa 74 // OF1 75 // of1a 76 // mobile node 77 // sa 78 void AddTestData() { 79 std::string test_base = base_path(); 80 81 model_->AddURL(model_->bookmark_bar_node(), 0, ASCIIToUTF16("a"), 82 GURL(test_base + "a")); 83 const BookmarkNode* f1 = 84 model_->AddFolder(model_->bookmark_bar_node(), 1, ASCIIToUTF16("F1")); 85 model_->AddURL(f1, 0, ASCIIToUTF16("f1a"), GURL(test_base + "f1a")); 86 const BookmarkNode* f11 = model_->AddFolder(f1, 1, ASCIIToUTF16("F11")); 87 model_->AddURL(f11, 0, ASCIIToUTF16("f11a"), GURL(test_base + "f11a")); 88 model_->AddFolder(model_->bookmark_bar_node(), 2, ASCIIToUTF16("F2")); 89 90 // Children of the other node. 91 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("oa"), 92 GURL(test_base + "oa")); 93 const BookmarkNode* of1 = 94 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("OF1")); 95 model_->AddURL(of1, 0, ASCIIToUTF16("of1a"), GURL(test_base + "of1a")); 96 97 // Children of the mobile node. 98 model_->AddURL(model_->mobile_node(), 0, ASCIIToUTF16("sa"), 99 GURL(test_base + "sa")); 100 } 101 102 base::MessageLoopForUI message_loop_; 103 content::TestBrowserThread ui_thread_; 104 content::TestBrowserThread file_thread_; 105}; 106 107// Makes sure the tree model matches that of the bookmark bar model. 108TEST_F(BookmarkEditorGtkTest, ModelsMatch) { 109 BookmarkEditorGtk editor( 110 NULL, 111 profile_.get(), 112 NULL, 113 BookmarkEditor::EditDetails::AddNodeInFolder( 114 NULL, -1, GURL(), string16()), 115 BookmarkEditor::SHOW_TREE); 116 117 // The root should have two or three children, one for the bookmark bar node, 118 // another for the 'other bookmarks' folder, and depending on the visib 119 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 120 GtkTreeIter toplevel; 121 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &toplevel)); 122 GtkTreeIter bookmark_bar_node = toplevel; 123 ASSERT_TRUE(gtk_tree_model_iter_next(store, &toplevel)); 124 GtkTreeIter other_node = toplevel; 125 if (model_->mobile_node()->IsVisible()) { 126 // If we have a mobile node, then the iterator should find one element after 127 // "other bookmarks" 128 ASSERT_TRUE(gtk_tree_model_iter_next(store, &toplevel)); 129 ASSERT_FALSE(gtk_tree_model_iter_next(store, &toplevel)); 130 } else { 131 ASSERT_FALSE(gtk_tree_model_iter_next(store, &toplevel)); 132 } 133 134 // The bookmark bar should have 2 nodes: folder F1 and F2. 135 GtkTreeIter f1_iter; 136 GtkTreeIter child; 137 ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node)); 138 ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &bookmark_bar_node)); 139 f1_iter = child; 140 ASSERT_EQ("F1", UTF16ToUTF8(GetTitleFromTreeIter(store, &child))); 141 ASSERT_TRUE(gtk_tree_model_iter_next(store, &child)); 142 ASSERT_EQ("F2", UTF16ToUTF8(GetTitleFromTreeIter(store, &child))); 143 ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); 144 145 // F1 should have one child, F11 146 ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f1_iter)); 147 ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &f1_iter)); 148 ASSERT_EQ("F11", UTF16ToUTF8(GetTitleFromTreeIter(store, &child))); 149 ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); 150 151 // Other node should have one child (OF1). 152 ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &other_node)); 153 ASSERT_TRUE(gtk_tree_model_iter_children(store, &child, &other_node)); 154 ASSERT_EQ("OF1", UTF16ToUTF8(GetTitleFromTreeIter(store, &child))); 155 ASSERT_FALSE(gtk_tree_model_iter_next(store, &child)); 156} 157 158// Changes the title and makes sure parent/visual order doesn't change. 159TEST_F(BookmarkEditorGtkTest, EditTitleKeepsPosition) { 160 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 161 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 162 BookmarkEditor::SHOW_TREE); 163 gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); 164 165 GtkTreeIter bookmark_bar_node; 166 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 167 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); 168 editor.ApplyEdits(&bookmark_bar_node); 169 170 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 171 ASSERT_EQ(ASCIIToUTF16("new_a"), bb_node->GetChild(0)->GetTitle()); 172 // The URL shouldn't have changed. 173 ASSERT_TRUE(GURL(base_path() + "a") == bb_node->GetChild(0)->url()); 174} 175 176// Changes the url and makes sure parent/visual order doesn't change. 177TEST_F(BookmarkEditorGtkTest, EditURLKeepsPosition) { 178 Time node_time = GetNode("a")->date_added(); 179 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 180 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 181 BookmarkEditor::SHOW_TREE); 182 gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), 183 GURL(base_path() + "new_a").spec().c_str()); 184 185 GtkTreeIter bookmark_bar_node; 186 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 187 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); 188 editor.ApplyEdits(&bookmark_bar_node); 189 190 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 191 ASSERT_EQ(ASCIIToUTF16("a"), bb_node->GetChild(0)->GetTitle()); 192 // The URL should have changed. 193 ASSERT_TRUE(GURL(base_path() + "new_a") == bb_node->GetChild(0)->url()); 194 ASSERT_TRUE(node_time == bb_node->GetChild(0)->date_added()); 195} 196 197// Moves 'a' to be a child of the other node. 198TEST_F(BookmarkEditorGtkTest, ChangeParent) { 199 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 200 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 201 BookmarkEditor::SHOW_TREE); 202 203 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 204 GtkTreeIter gtk_other_node; 205 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, >k_other_node)); 206 ASSERT_TRUE(gtk_tree_model_iter_next(store, >k_other_node)); 207 editor.ApplyEdits(>k_other_node); 208 209 const BookmarkNode* other_node = model_->other_node(); 210 ASSERT_EQ(ASCIIToUTF16("a"), other_node->GetChild(2)->GetTitle()); 211 ASSERT_TRUE(GURL(base_path() + "a") == other_node->GetChild(2)->url()); 212} 213 214// Moves 'a' to be a child of the other node. 215// Moves 'a' to be a child of the other node and changes its url to new_a. 216TEST_F(BookmarkEditorGtkTest, ChangeParentAndURL) { 217 Time node_time = GetNode("a")->date_added(); 218 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 219 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 220 BookmarkEditor::SHOW_TREE); 221 222 gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), 223 GURL(base_path() + "new_a").spec().c_str()); 224 225 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 226 GtkTreeIter gtk_other_node; 227 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, >k_other_node)); 228 ASSERT_TRUE(gtk_tree_model_iter_next(store, >k_other_node)); 229 editor.ApplyEdits(>k_other_node); 230 231 const BookmarkNode* other_node = model_->other_node(); 232 ASSERT_EQ(ASCIIToUTF16("a"), other_node->GetChild(2)->GetTitle()); 233 ASSERT_TRUE(GURL(base_path() + "new_a") == other_node->GetChild(2)->url()); 234 ASSERT_TRUE(node_time == other_node->GetChild(2)->date_added()); 235} 236 237// Creates a new folder and moves a node to it. 238TEST_F(BookmarkEditorGtkTest, MoveToNewParent) { 239 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 240 BookmarkEditor::EditDetails::EditNode(GetNode("a")), 241 BookmarkEditor::SHOW_TREE); 242 243 GtkTreeIter bookmark_bar_node; 244 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 245 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); 246 247 // The bookmark bar should have 2 nodes: folder F1 and F2. 248 GtkTreeIter f2_iter; 249 ASSERT_EQ(2, gtk_tree_model_iter_n_children(store, &bookmark_bar_node)); 250 ASSERT_TRUE(gtk_tree_model_iter_children(store, &f2_iter, 251 &bookmark_bar_node)); 252 ASSERT_TRUE(gtk_tree_model_iter_next(store, &f2_iter)); 253 254 // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21". 255 GtkTreeIter f21_iter; 256 editor.AddNewFolder(&f2_iter, &f21_iter); 257 gtk_tree_store_set(editor.tree_store_, &f21_iter, 258 bookmark_utils::FOLDER_NAME, "F21", -1); 259 GtkTreeIter f211_iter; 260 editor.AddNewFolder(&f21_iter, &f211_iter); 261 gtk_tree_store_set(editor.tree_store_, &f211_iter, 262 bookmark_utils::FOLDER_NAME, "F211", -1); 263 264 ASSERT_EQ(1, gtk_tree_model_iter_n_children(store, &f2_iter)); 265 266 editor.ApplyEdits(&f2_iter); 267 268 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 269 const BookmarkNode* mf2 = bb_node->GetChild(1); 270 271 // F2 in the model should have two children now: F21 and the node edited. 272 ASSERT_EQ(2, mf2->child_count()); 273 // F21 should be first. 274 ASSERT_EQ(ASCIIToUTF16("F21"), mf2->GetChild(0)->GetTitle()); 275 // Then a. 276 ASSERT_EQ(ASCIIToUTF16("a"), mf2->GetChild(1)->GetTitle()); 277 278 // F21 should have one child, F211. 279 const BookmarkNode* mf21 = mf2->GetChild(0); 280 ASSERT_EQ(1, mf21->child_count()); 281 ASSERT_EQ(ASCIIToUTF16("F211"), mf21->GetChild(0)->GetTitle()); 282} 283 284// Brings up the editor, creating a new URL on the bookmark bar. 285TEST_F(BookmarkEditorGtkTest, NewURL) { 286 BookmarkEditorGtk editor( 287 NULL, 288 profile_.get(), 289 NULL, 290 BookmarkEditor::EditDetails::AddNodeInFolder( 291 NULL, -1, GURL(), string16()), 292 BookmarkEditor::SHOW_TREE); 293 294 gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), 295 GURL(base_path() + "a").spec().c_str()); 296 gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); 297 298 GtkTreeIter bookmark_bar_node; 299 GtkTreeModel* store = GTK_TREE_MODEL(editor.tree_store_); 300 ASSERT_TRUE(gtk_tree_model_get_iter_first(store, &bookmark_bar_node)); 301 editor.ApplyEdits(&bookmark_bar_node); 302 303 const BookmarkNode* bb_node = model_->bookmark_bar_node(); 304 ASSERT_EQ(4, bb_node->child_count()); 305 306 const BookmarkNode* new_node = bb_node->GetChild(3); 307 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 308 EXPECT_TRUE(GURL(base_path() + "a") == new_node->url()); 309} 310 311// Brings up the editor with no tree and modifies the url. 312TEST_F(BookmarkEditorGtkTest, ChangeURLNoTree) { 313 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 314 BookmarkEditor::EditDetails::EditNode( 315 model_->other_node()->GetChild(0)), 316 BookmarkEditor::NO_TREE); 317 318 gtk_entry_set_text(GTK_ENTRY(editor.url_entry_), 319 GURL(base_path() + "a").spec().c_str()); 320 gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); 321 322 editor.ApplyEdits(NULL); 323 324 const BookmarkNode* other_node = model_->other_node(); 325 ASSERT_EQ(2, other_node->child_count()); 326 327 const BookmarkNode* new_node = other_node->GetChild(0); 328 329 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 330 EXPECT_TRUE(GURL(base_path() + "a") == new_node->url()); 331} 332 333// Brings up the editor with no tree and modifies only the title. 334TEST_F(BookmarkEditorGtkTest, ChangeTitleNoTree) { 335 BookmarkEditorGtk editor(NULL, profile_.get(), NULL, 336 BookmarkEditor::EditDetails::EditNode( 337 model_->other_node()->GetChild(0)), 338 BookmarkEditor::NO_TREE); 339 gtk_entry_set_text(GTK_ENTRY(editor.name_entry_), "new_a"); 340 341 editor.ApplyEdits(); 342 343 const BookmarkNode* other_node = model_->other_node(); 344 ASSERT_EQ(2, other_node->child_count()); 345 346 const BookmarkNode* new_node = other_node->GetChild(0); 347 EXPECT_EQ(ASCIIToUTF16("new_a"), new_node->GetTitle()); 348} 349