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