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