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