bookmark_bubble_controller_unittest.mm revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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#import <Cocoa/Cocoa.h>
6
7#include "base/basictypes.h"
8#include "base/mac/scoped_nsobject.h"
9#include "base/strings/string16.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/bookmarks/bookmark_model_factory.h"
12#include "chrome/browser/signin/signin_manager.h"
13#include "chrome/browser/signin/signin_manager_factory.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_window.h"
16#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_controller.h"
17#include "chrome/browser/ui/cocoa/browser_window_controller.h"
18#include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
19#import "chrome/browser/ui/cocoa/info_bubble_window.h"
20#include "content/public/browser/notification_service.h"
21#include "testing/gtest/include/gtest/gtest.h"
22#import "testing/gtest_mac.h"
23#include "testing/platform_test.h"
24
25using content::WebContents;
26
27// Watch for bookmark pulse notifications so we can confirm they were sent.
28@interface BookmarkPulseObserver : NSObject {
29  int notifications_;
30}
31@property (assign, nonatomic) int notifications;
32@end
33
34
35@implementation BookmarkPulseObserver
36
37@synthesize notifications = notifications_;
38
39- (id)init {
40  if ((self = [super init])) {
41    [[NSNotificationCenter defaultCenter]
42      addObserver:self
43         selector:@selector(pulseBookmarkNotification:)
44             name:bookmark_button::kPulseBookmarkButtonNotification
45           object:nil];
46  }
47  return self;
48}
49
50- (void)pulseBookmarkNotification:(NSNotificationCenter *)notification {
51  notifications_++;
52}
53
54- (void)dealloc {
55  [[NSNotificationCenter defaultCenter] removeObserver:self];
56  [super dealloc];
57}
58
59@end
60
61
62namespace {
63
64// URL of the test bookmark.
65const char kTestBookmarkURL[] = "http://www.google.com";
66
67class BookmarkBubbleControllerTest : public CocoaProfileTest {
68 public:
69  static int edits_;
70  BookmarkBubbleController* controller_;
71
72  BookmarkBubbleControllerTest() : controller_(nil) {
73    edits_ = 0;
74  }
75
76  virtual void TearDown() OVERRIDE {
77    [controller_ close];
78    CocoaProfileTest::TearDown();
79  }
80
81  // Returns a controller but ownership not transferred.
82  // Only one of these will be valid at a time.
83  BookmarkBubbleController* ControllerForNode(const BookmarkNode* node) {
84    if (controller_ && !IsWindowClosing()) {
85      [controller_ close];
86      controller_ = nil;
87    }
88    controller_ = [[BookmarkBubbleController alloc]
89        initWithParentWindow:browser()->window()->GetNativeWindow()
90                       model:BookmarkModelFactory::GetForProfile(profile())
91                        node:node
92           alreadyBookmarked:YES];
93    EXPECT_TRUE([controller_ window]);
94    // The window must be gone or we'll fail a unit test with windows left open.
95    [static_cast<InfoBubbleWindow*>([controller_ window])
96        setAllowedAnimations:info_bubble::kAnimateNone];
97    [controller_ showWindow:nil];
98    return controller_;
99  }
100
101  BookmarkModel* GetBookmarkModel() {
102    return BookmarkModelFactory::GetForProfile(profile());
103  }
104
105  const BookmarkNode* CreateTestBookmark() {
106    BookmarkModel* model = GetBookmarkModel();
107    return model->AddURL(model->bookmark_bar_node(),
108                         0,
109                         ASCIIToUTF16("Bookie markie title"),
110                         GURL(kTestBookmarkURL));
111  }
112
113  bool IsWindowClosing() {
114    return [static_cast<InfoBubbleWindow*>([controller_ window]) isClosing];
115  }
116};
117
118// static
119int BookmarkBubbleControllerTest::edits_;
120
121// Confirm basics about the bubble window (e.g. that it is inside the
122// parent window)
123TEST_F(BookmarkBubbleControllerTest, TestBubbleWindow) {
124  const BookmarkNode* node = CreateTestBookmark();
125  BookmarkBubbleController* controller = ControllerForNode(node);
126  EXPECT_TRUE(controller);
127  NSWindow* window = [controller window];
128  EXPECT_TRUE(window);
129  EXPECT_TRUE(NSContainsRect([browser()->window()->GetNativeWindow() frame],
130                             [window frame]));
131}
132
133// Test that we can handle closing the parent window
134TEST_F(BookmarkBubbleControllerTest, TestClosingParentWindow) {
135  const BookmarkNode* node = CreateTestBookmark();
136  BookmarkBubbleController* controller = ControllerForNode(node);
137  EXPECT_TRUE(controller);
138  NSWindow* window = [controller window];
139  EXPECT_TRUE(window);
140  base::mac::ScopedNSAutoreleasePool pool;
141  [browser()->window()->GetNativeWindow() performClose:NSApp];
142}
143
144
145// Confirm population of folder list
146TEST_F(BookmarkBubbleControllerTest, TestFillInFolder) {
147  // Create some folders, including a nested folder
148  BookmarkModel* model = GetBookmarkModel();
149  EXPECT_TRUE(model);
150  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
151  EXPECT_TRUE(bookmarkBarNode);
152  const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
153                                               ASCIIToUTF16("one"));
154  EXPECT_TRUE(node1);
155  const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
156                                               ASCIIToUTF16("two"));
157  EXPECT_TRUE(node2);
158  const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
159                                               ASCIIToUTF16("three"));
160  EXPECT_TRUE(node3);
161  const BookmarkNode* node4 = model->AddFolder(node2, 0, ASCIIToUTF16("sub"));
162  EXPECT_TRUE(node4);
163  const BookmarkNode* node5 = model->AddURL(node1, 0, ASCIIToUTF16("title1"),
164                                            GURL(kTestBookmarkURL));
165  EXPECT_TRUE(node5);
166  const BookmarkNode* node6 = model->AddURL(node3, 0, ASCIIToUTF16("title2"),
167                                            GURL(kTestBookmarkURL));
168  EXPECT_TRUE(node6);
169  const BookmarkNode* node7 = model->AddURL(
170      node4, 0, ASCIIToUTF16("title3"), GURL("http://www.google.com/reader"));
171  EXPECT_TRUE(node7);
172
173  BookmarkBubbleController* controller = ControllerForNode(node4);
174  EXPECT_TRUE(controller);
175
176  NSArray* titles =
177      [[[controller folderPopUpButton] itemArray] valueForKey:@"title"];
178  EXPECT_TRUE([titles containsObject:@"one"]);
179  EXPECT_TRUE([titles containsObject:@"two"]);
180  EXPECT_TRUE([titles containsObject:@"three"]);
181  EXPECT_TRUE([titles containsObject:@"sub"]);
182  EXPECT_FALSE([titles containsObject:@"title1"]);
183  EXPECT_FALSE([titles containsObject:@"title2"]);
184
185
186  // Verify that the top level folders are displayed correctly.
187  EXPECT_TRUE([titles containsObject:@"Other Bookmarks"]);
188  EXPECT_TRUE([titles containsObject:@"Bookmarks Bar"]);
189  if (model->mobile_node()->IsVisible()) {
190    EXPECT_TRUE([titles containsObject:@"Mobile Bookmarks"]);
191  } else {
192    EXPECT_FALSE([titles containsObject:@"Mobile Bookmarks"]);
193  }
194}
195
196// Confirm ability to handle folders with blank name.
197TEST_F(BookmarkBubbleControllerTest, TestFolderWithBlankName) {
198  // Create some folders, including a nested folder
199  BookmarkModel* model = GetBookmarkModel();
200  EXPECT_TRUE(model);
201  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
202  EXPECT_TRUE(bookmarkBarNode);
203  const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
204                                               ASCIIToUTF16("one"));
205  EXPECT_TRUE(node1);
206  const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
207                                               string16());
208  EXPECT_TRUE(node2);
209  const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
210                                               ASCIIToUTF16("three"));
211  EXPECT_TRUE(node3);
212  const BookmarkNode* node2_1 = model->AddURL(node2, 0, ASCIIToUTF16("title1"),
213                                              GURL(kTestBookmarkURL));
214  EXPECT_TRUE(node2_1);
215
216  BookmarkBubbleController* controller = ControllerForNode(node1);
217  EXPECT_TRUE(controller);
218
219  // One of the items should be blank and its node should be node2.
220  NSArray* items = [[controller folderPopUpButton] itemArray];
221  EXPECT_GT([items count], 4U);
222  BOOL blankFolderFound = NO;
223  for (NSMenuItem* item in [[controller folderPopUpButton] itemArray]) {
224    if ([[item title] length] == 0 &&
225        static_cast<const BookmarkNode*>([[item representedObject]
226                                          pointerValue]) == node2) {
227      blankFolderFound = YES;
228      break;
229    }
230  }
231  EXPECT_TRUE(blankFolderFound);
232}
233
234
235// Click on edit; bubble gets closed.
236TEST_F(BookmarkBubbleControllerTest, TestEdit) {
237  const BookmarkNode* node = CreateTestBookmark();
238  BookmarkBubbleController* controller = ControllerForNode(node);
239  EXPECT_TRUE(controller);
240
241  EXPECT_EQ(edits_, 0);
242  EXPECT_FALSE(IsWindowClosing());
243  [controller edit:controller];
244  EXPECT_EQ(edits_, 1);
245  EXPECT_TRUE(IsWindowClosing());
246}
247
248// CallClose; bubble gets closed.
249// Also confirm pulse notifications get sent.
250TEST_F(BookmarkBubbleControllerTest, TestClose) {
251  const BookmarkNode* node = CreateTestBookmark();
252  EXPECT_EQ(edits_, 0);
253
254  base::scoped_nsobject<BookmarkPulseObserver> observer(
255      [[BookmarkPulseObserver alloc] init]);
256  EXPECT_EQ([observer notifications], 0);
257  BookmarkBubbleController* controller = ControllerForNode(node);
258  EXPECT_TRUE(controller);
259  EXPECT_FALSE(IsWindowClosing());
260  EXPECT_EQ([observer notifications], 1);
261  [controller ok:controller];
262  EXPECT_EQ(edits_, 0);
263  EXPECT_TRUE(IsWindowClosing());
264  EXPECT_EQ([observer notifications], 2);
265}
266
267// User changes title and parent folder in the UI
268TEST_F(BookmarkBubbleControllerTest, TestUserEdit) {
269  BookmarkModel* model = GetBookmarkModel();
270  EXPECT_TRUE(model);
271  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
272  EXPECT_TRUE(bookmarkBarNode);
273  const BookmarkNode* node = model->AddURL(bookmarkBarNode,
274                                           0,
275                                           ASCIIToUTF16("short-title"),
276                                           GURL(kTestBookmarkURL));
277  const BookmarkNode* grandma = model->AddFolder(bookmarkBarNode, 0,
278                                                 ASCIIToUTF16("grandma"));
279  EXPECT_TRUE(grandma);
280  const BookmarkNode* grandpa = model->AddFolder(bookmarkBarNode, 0,
281                                                 ASCIIToUTF16("grandpa"));
282  EXPECT_TRUE(grandpa);
283
284  BookmarkBubbleController* controller = ControllerForNode(node);
285  EXPECT_TRUE(controller);
286
287  // simulate a user edit
288  [controller setTitle:@"oops" parentFolder:grandma];
289  [controller edit:controller];
290
291  // Make sure bookmark has changed
292  EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("oops"));
293  EXPECT_EQ(node->parent()->GetTitle(), ASCIIToUTF16("grandma"));
294}
295
296// Confirm happiness with parent nodes that have the same name.
297TEST_F(BookmarkBubbleControllerTest, TestNewParentSameName) {
298  BookmarkModel* model = GetBookmarkModel();
299  EXPECT_TRUE(model);
300  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
301  EXPECT_TRUE(bookmarkBarNode);
302  for (int i=0; i<2; i++) {
303    const BookmarkNode* node = model->AddURL(bookmarkBarNode,
304                                             0,
305                                             ASCIIToUTF16("short-title"),
306                                             GURL(kTestBookmarkURL));
307    EXPECT_TRUE(node);
308    const BookmarkNode* folder = model->AddFolder(bookmarkBarNode, 0,
309                                                 ASCIIToUTF16("NAME"));
310    EXPECT_TRUE(folder);
311    folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
312    EXPECT_TRUE(folder);
313    folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
314    EXPECT_TRUE(folder);
315    BookmarkBubbleController* controller = ControllerForNode(node);
316    EXPECT_TRUE(controller);
317
318    // simulate a user edit
319    [controller setParentFolderSelection:bookmarkBarNode->GetChild(i)];
320    [controller edit:controller];
321
322    // Make sure bookmark has changed, and that the parent is what we
323    // expect.  This proves nobody did searching based on name.
324    EXPECT_EQ(node->parent(), bookmarkBarNode->GetChild(i));
325  }
326}
327
328// Confirm happiness with nodes with the same Name
329TEST_F(BookmarkBubbleControllerTest, TestDuplicateNodeNames) {
330  BookmarkModel* model = GetBookmarkModel();
331  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
332  EXPECT_TRUE(bookmarkBarNode);
333  const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
334                                               ASCIIToUTF16("NAME"));
335  EXPECT_TRUE(node1);
336  const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 0,
337                                               ASCIIToUTF16("NAME"));
338  EXPECT_TRUE(node2);
339  BookmarkBubbleController* controller = ControllerForNode(bookmarkBarNode);
340  EXPECT_TRUE(controller);
341
342  NSPopUpButton* button = [controller folderPopUpButton];
343  [controller setParentFolderSelection:node1];
344  NSMenuItem* item = [button selectedItem];
345  id itemObject = [item representedObject];
346  EXPECT_NSEQ([NSValue valueWithPointer:node1], itemObject);
347  [controller setParentFolderSelection:node2];
348  item = [button selectedItem];
349  itemObject = [item representedObject];
350  EXPECT_NSEQ([NSValue valueWithPointer:node2], itemObject);
351}
352
353// Click the "remove" button
354TEST_F(BookmarkBubbleControllerTest, TestRemove) {
355  const BookmarkNode* node = CreateTestBookmark();
356  BookmarkBubbleController* controller = ControllerForNode(node);
357  EXPECT_TRUE(controller);
358
359  BookmarkModel* model = GetBookmarkModel();
360  EXPECT_TRUE(model->IsBookmarked(GURL(kTestBookmarkURL)));
361
362  [controller remove:controller];
363  EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
364  EXPECT_TRUE(IsWindowClosing());
365}
366
367// Confirm picking "choose another folder" caused edit: to be called.
368TEST_F(BookmarkBubbleControllerTest, PopUpSelectionChanged) {
369  BookmarkModel* model = GetBookmarkModel();
370  const BookmarkNode* node = model->AddURL(model->bookmark_bar_node(),
371                                           0, ASCIIToUTF16("super-title"),
372                                           GURL(kTestBookmarkURL));
373  BookmarkBubbleController* controller = ControllerForNode(node);
374  EXPECT_TRUE(controller);
375
376  NSPopUpButton* button = [controller folderPopUpButton];
377  [button selectItemWithTitle:[[controller class] chooseAnotherFolderString]];
378  EXPECT_EQ(edits_, 0);
379  [button sendAction:[button action] to:[button target]];
380  EXPECT_EQ(edits_, 1);
381}
382
383// Create a controller that simulates the bookmark just now being created by
384// the user clicking the star, then sending the "cancel" command to represent
385// them pressing escape. The bookmark should not be there.
386TEST_F(BookmarkBubbleControllerTest, EscapeRemovesNewBookmark) {
387  BookmarkModel* model = GetBookmarkModel();
388  const BookmarkNode* node = CreateTestBookmark();
389  BookmarkBubbleController* controller =
390      [[BookmarkBubbleController alloc]
391          initWithParentWindow:browser()->window()->GetNativeWindow()
392                         model:model
393                          node:node
394             alreadyBookmarked:NO];  // The last param is the key difference.
395  EXPECT_TRUE([controller window]);
396  // Calls release on controller.
397  [controller cancel:nil];
398  EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
399}
400
401// Create a controller where the bookmark already existed prior to clicking
402// the star and test that sending a cancel command doesn't change the state
403// of the bookmark.
404TEST_F(BookmarkBubbleControllerTest, EscapeDoesntTouchExistingBookmark) {
405  const BookmarkNode* node = CreateTestBookmark();
406  BookmarkBubbleController* controller = ControllerForNode(node);
407  EXPECT_TRUE(controller);
408
409  [(id)controller cancel:nil];
410  EXPECT_TRUE(GetBookmarkModel()->IsBookmarked(GURL(kTestBookmarkURL)));
411}
412
413// Confirm indentation of items in pop-up menu
414TEST_F(BookmarkBubbleControllerTest, TestMenuIndentation) {
415  // Create some folders, including a nested folder
416  BookmarkModel* model = GetBookmarkModel();
417  EXPECT_TRUE(model);
418  const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
419  EXPECT_TRUE(bookmarkBarNode);
420  const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
421                                               ASCIIToUTF16("one"));
422  EXPECT_TRUE(node1);
423  const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
424                                               ASCIIToUTF16("two"));
425  EXPECT_TRUE(node2);
426  const BookmarkNode* node2_1 = model->AddFolder(node2, 0,
427                                                 ASCIIToUTF16("two dot one"));
428  EXPECT_TRUE(node2_1);
429  const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
430                                               ASCIIToUTF16("three"));
431  EXPECT_TRUE(node3);
432
433  BookmarkBubbleController* controller = ControllerForNode(node1);
434  EXPECT_TRUE(controller);
435
436  // Compare the menu item indents against expectations.
437  static const int kExpectedIndent[] = {0, 1, 1, 2, 1, 0};
438  NSArray* items = [[controller folderPopUpButton] itemArray];
439  ASSERT_GE([items count], 6U);
440  for(int itemNo = 0; itemNo < 6; itemNo++) {
441    NSMenuItem* item = [items objectAtIndex:itemNo];
442    EXPECT_EQ(kExpectedIndent[itemNo], [item indentationLevel])
443        << "Unexpected indent for menu item #" << itemNo;
444  }
445}
446
447// Confirm that the sync promo is displayed when the user is not signed in.
448TEST_F(BookmarkBubbleControllerTest, SyncPromoNotSignedIn) {
449  const BookmarkNode* node = CreateTestBookmark();
450  BookmarkBubbleController* controller = ControllerForNode(node);
451
452  EXPECT_EQ(1u, [[controller.syncPromoPlaceholder subviews] count]);
453}
454
455// Confirm that the sync promo is not displayed when the user is signed in.
456TEST_F(BookmarkBubbleControllerTest, SyncPromoSignedIn) {
457  SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
458  signin->SetAuthenticatedUsername("fake_username");
459
460  const BookmarkNode* node = CreateTestBookmark();
461  BookmarkBubbleController* controller = ControllerForNode(node);
462
463  EXPECT_EQ(0u, [[controller.syncPromoPlaceholder subviews] count]);
464}
465
466}  // namespace
467
468@implementation NSApplication (BookmarkBubbleUnitTest)
469// Add handler for the editBookmarkNode: action to NSApp for testing purposes.
470// Normally this would be sent up the responder tree correctly, but since
471// tests run in the background, key window and main window are never set on
472// NSApplication. Adding it to NSApplication directly removes the need for
473// worrying about what the current window with focus is.
474- (void)editBookmarkNode:(id)sender {
475  EXPECT_TRUE([sender respondsToSelector:@selector(node)]);
476  BookmarkBubbleControllerTest::edits_++;
477}
478
479@end
480