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, &gtk_other_node));
206  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
207  editor.ApplyEdits(&gtk_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, &gtk_other_node));
228  ASSERT_TRUE(gtk_tree_model_iter_next(store, &gtk_other_node));
229  editor.ApplyEdits(&gtk_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