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